Get many parts working
This commit is contained in:
parent
5ff96febac
commit
f65d45260e
@ -1,4 +1,7 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::pattern::Pattern;
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
ClearLights,
|
||||
ChangePattern(Box<dyn Pattern + Send + Sync>),
|
||||
@ -6,3 +9,15 @@ pub enum Message {
|
||||
SetTickTime(u64),
|
||||
Quit,
|
||||
}
|
||||
|
||||
// impl Debug for Message {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// match self {
|
||||
// Message::ClearLights => write!(f, "Message::ClearLights"),
|
||||
// Message::ChangePattern(_) => write!(f, "Message::ChangePattern(_)"),
|
||||
// Message::SetNumLights(n) => write!(f, "Message::SetNumLights({n})"),
|
||||
// Message::SetTickTime(n) => write!(f, "Message::SetTickTime({n})"),
|
||||
// Message::Quit => write!(f, "Message::Quit"),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
@ -5,6 +5,21 @@ use serde::{Deserialize, Serialize};
|
||||
// TODO:
|
||||
pub type Icon = ();
|
||||
|
||||
pub mod homeassistant {
|
||||
pub const STATUS_ONLINE: &[u8] = b"online";
|
||||
pub const STATUS_OFFLINE: &[u8] = b"offline";
|
||||
|
||||
// use super::*;
|
||||
|
||||
// #[derive(Debug, PartialEq, Clone)]
|
||||
// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
// #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||
// pub enum Status {
|
||||
// Offline,
|
||||
// Online,
|
||||
// }
|
||||
}
|
||||
|
||||
pub mod alarm_control_panel {
|
||||
use super::*;
|
||||
|
||||
|
@ -11,3 +11,4 @@ serde_json = "1.0.115"
|
||||
tracing = "0.1.37"
|
||||
homeassistant-mqtt-discovery = { path = "../homeassistant-mqtt-discovery/" }
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
anyhow = "1.0.82"
|
||||
|
462
mqtt/src/lib.rs
462
mqtt/src/lib.rs
@ -1,119 +1,362 @@
|
||||
#![feature(let_chains)]
|
||||
use common::MqttConfig;
|
||||
use homeassistant_mqtt_discovery::integrations::light;
|
||||
use rumqttc::{Client, Event, Incoming, MqttOptions, Publish, QoS};
|
||||
use anyhow::{Context as _, Result};
|
||||
use common::{
|
||||
color::Rgb,
|
||||
error::ProgramError,
|
||||
pattern::{MovingRainbow, Solid, SolidParams},
|
||||
MqttConfig,
|
||||
};
|
||||
use homeassistant_mqtt_discovery::{
|
||||
integrations::{
|
||||
homeassistant,
|
||||
light::{self, JsonIncoming},
|
||||
},
|
||||
Common,
|
||||
};
|
||||
use rumqttc::{
|
||||
Client, Connection, ConnectionError, Event, Incoming, MqttOptions, Packet, PingReq, Publish,
|
||||
QoS,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use tracing::info;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use common::{error::ProgramResult, strip};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
const MQTT_PREFIX: &str = "aw_lights";
|
||||
const FRIENDLY_NAME: &str = "AW Lights Light";
|
||||
const HOMEASSISTANT_TOPIC: &str = "homeassistant/status";
|
||||
const LIGHT_ON: &str = "ON";
|
||||
const LIGHT_OFF: &str = "OFF";
|
||||
|
||||
pub fn start(_strip_tx: Sender<strip::Message>, config: MqttConfig) -> ProgramResult<()> {
|
||||
info!("Starting mqtt with config {config:?}");
|
||||
|
||||
x(&config);
|
||||
|
||||
Ok(())
|
||||
pub struct MqttBuilder {
|
||||
topics: Topics,
|
||||
config: MqttConfig,
|
||||
}
|
||||
|
||||
fn x(config: &MqttConfig) {
|
||||
let mut mqttoptions = MqttOptions::new(&config.mqtt_id, &config.mqtt_broker, config.mqtt_port);
|
||||
impl MqttBuilder {
|
||||
pub fn new(config: MqttConfig) -> Self {
|
||||
let topics = Topics::new(&config);
|
||||
|
||||
mqttoptions.set_keep_alive(Duration::from_secs(5));
|
||||
|
||||
if let Some(mqtt_username) = config.mqtt_username.as_ref()
|
||||
&& let Some(mqtt_password) = config.mqtt_password.as_ref()
|
||||
{
|
||||
info!("Using authentication with mqtt");
|
||||
mqttoptions.set_credentials(mqtt_username, mqtt_password);
|
||||
Self { config, topics }
|
||||
}
|
||||
|
||||
info!("Starting mqtt client");
|
||||
let (client, mut connection) = Client::new(mqttoptions, 10);
|
||||
info!("Subscribing to homeassistant");
|
||||
pub fn start(self, strip_tx: Sender<strip::Message>) -> ProgramResult<()> {
|
||||
let (client, connection) = self.create_conection();
|
||||
|
||||
let (autodiscovery_topic_name, discovery) = gen_discovery_message(&config);
|
||||
let mut mqtt = Mqtt {
|
||||
topics: self.topics,
|
||||
config: self.config,
|
||||
client,
|
||||
};
|
||||
|
||||
info!(
|
||||
"discovery_message: {:#?} (topic: {autodiscovery_topic_name:?})",
|
||||
discovery
|
||||
);
|
||||
mqtt.start(connection, strip_tx)
|
||||
}
|
||||
|
||||
// client.publish(, QoS::AtLeastOnce, false, payload)
|
||||
fn create_conection(&self) -> (Client, Connection) {
|
||||
info!("Creating mqtt client");
|
||||
let mut mqttoptions = MqttOptions::new(
|
||||
&self.config.mqtt_id,
|
||||
&self.config.mqtt_broker,
|
||||
self.config.mqtt_port,
|
||||
);
|
||||
|
||||
client
|
||||
.subscribe(HOMEASSISTANT_TOPIC, QoS::AtMostOnce)
|
||||
.unwrap();
|
||||
mqttoptions.set_keep_alive(Duration::from_secs(5));
|
||||
|
||||
client
|
||||
.subscribe(&discovery.command_topic, QoS::AtMostOnce)
|
||||
.unwrap();
|
||||
if let Some(mqtt_username) = self.config.mqtt_username.as_ref()
|
||||
&& let Some(mqtt_password) = self.config.mqtt_password.as_ref()
|
||||
{
|
||||
info!("Using authentication with mqtt");
|
||||
mqttoptions.set_credentials(mqtt_username, mqtt_password);
|
||||
}
|
||||
Client::new(mqttoptions, 10)
|
||||
}
|
||||
}
|
||||
|
||||
client
|
||||
.publish(
|
||||
autodiscovery_topic_name,
|
||||
QoS::AtLeastOnce,
|
||||
false,
|
||||
serde_json::to_vec(&discovery).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
pub struct Mqtt {
|
||||
topics: Topics,
|
||||
config: MqttConfig,
|
||||
client: rumqttc::Client,
|
||||
// connection: rumqttc::Connection,
|
||||
}
|
||||
|
||||
// thread::spawn(move || {
|
||||
// for i in 0..10 {
|
||||
// client
|
||||
// .publish("hello/rumqtt", QoS::AtLeastOnce, false, vec![i; i as usize])
|
||||
// .unwrap();
|
||||
// thread::sleep(Duration::from_millis(100));
|
||||
impl Mqtt {
|
||||
// pub fn new(config: MqttConfig) -> Self {
|
||||
// let topics = Topics::new(&config);
|
||||
|
||||
// Self {
|
||||
// config,
|
||||
// topics,
|
||||
// // client,
|
||||
// // connection,
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// Iterate to poll the eventloop for connection progress
|
||||
for (i, notification) in connection.iter().enumerate() {
|
||||
info!("Notification #{i} = {:?}", notification);
|
||||
pub fn start(
|
||||
&mut self,
|
||||
mut connection: Connection,
|
||||
strip_tx: Sender<strip::Message>,
|
||||
) -> ProgramResult<()> {
|
||||
info!("Starting mqtt");
|
||||
self.init().map_err(|e| ProgramError::Boxed(Box::new(e)))?;
|
||||
|
||||
if let Ok(Event::Incoming(Incoming::Publish(p))) = notification {
|
||||
info!(
|
||||
"Got publish notification. Trying to deserialize: {:#?}",
|
||||
serde_json::from_slice::<light::JsonIncoming>(&p.payload)
|
||||
// Iterate to poll the eventloop for connection progress
|
||||
for (i, message) in connection.iter().enumerate() {
|
||||
// info!("Notification #{i} = {:?}", message);
|
||||
|
||||
match message {
|
||||
Ok(Event::Incoming(Incoming::Publish(p))) => {
|
||||
if let Err(e) = self.handle_incoming_message(p) {
|
||||
info!("Got error: {e:?}");
|
||||
}
|
||||
// info!(
|
||||
// "Got publish notification. Trying to deserialize: {:#?}",
|
||||
// serde_json::from_slice::<light::JsonIncoming>(&p.payload)
|
||||
// )
|
||||
}
|
||||
Ok(Event::Outgoing(_))
|
||||
| Ok(Event::Incoming(Packet::PingResp))
|
||||
| Ok(Event::Incoming(Incoming::SubAck(_)))
|
||||
| Ok(Event::Incoming(Incoming::ConnAck(_)))
|
||||
| Ok(Event::Incoming(Incoming::PubAck(_)))
|
||||
| Ok(Event::Incoming(Incoming::PubRec(_)))
|
||||
| Ok(Event::Incoming(Packet::PingReq)) => {}
|
||||
Ok(m) => info!("Got unhandled message: {m:?}"),
|
||||
Err(e) => {
|
||||
error!("Connection error to mqtt: {e:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let (client, mut connection) = self.create_conection();
|
||||
|
||||
// self.handle_loop(&mut connection, strip_tx)
|
||||
// .map_err(|e| ProgramError::Boxed(Box::new(e)))?;
|
||||
|
||||
// Iterate to poll the eventloop for connection progress
|
||||
// for (i, message) in connection.iter().enumerate() {
|
||||
// info!("Notification #{i} = {:?}", message);
|
||||
|
||||
// match message {
|
||||
// Ok(Event::Incoming(Incoming::Publish(p))) => {
|
||||
// info!(
|
||||
// "Got publish notification. Trying to deserialize: {:#?}",
|
||||
// serde_json::from_slice::<light::JsonIncoming>(&p.payload)
|
||||
// )
|
||||
// }
|
||||
// Ok(_) => todo!(),
|
||||
// Err(e) => {
|
||||
// error!("Connection error to mqtt: {e:?}")
|
||||
// }
|
||||
// }
|
||||
|
||||
// // if let Ok(Event::Incoming(Incoming::Publish(p))) = message {
|
||||
// // info!(
|
||||
// // "Got publish notification. Trying to deserialize: {:#?}",
|
||||
// // serde_json::from_slice::<light::JsonIncoming>(&p.payload)
|
||||
// // )
|
||||
// // }
|
||||
// }
|
||||
|
||||
info!("Done with mqtt");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_incoming_message(&self, publish: Publish) -> Result<()> {
|
||||
info!("Got incoming message: {publish:?}");
|
||||
|
||||
if publish.topic == self.topics.command_topic {
|
||||
info!("Got command topic");
|
||||
|
||||
let command = serde_json::from_slice::<light::JsonIncoming>(&publish.payload)
|
||||
.context("Deserializing command message")?;
|
||||
|
||||
let translated_command = build_strip_tx_msg(&command);
|
||||
info!("Setting light to state: {:?}", translated_command);
|
||||
} else if publish.topic == HOMEASSISTANT_TOPIC {
|
||||
if &publish.payload == homeassistant::STATUS_ONLINE {
|
||||
info!("Homeassistant is online");
|
||||
self.send_discovery()?;
|
||||
} else if &publish.payload == homeassistant::STATUS_OFFLINE {
|
||||
warn!("Homeassistant is offline");
|
||||
} else {
|
||||
anyhow::bail!("Homeassistant status topic {:?} unknown", &publish.payload);
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("Incoming message has unknown topic: {:?}", publish.topic);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// fn handle_loop(
|
||||
// &mut self,
|
||||
// // client: &Client,
|
||||
// connection: &mut Connection,
|
||||
// strip_tx: Sender<strip::Message>,
|
||||
// ) -> Result<()> {
|
||||
// // Iterate to poll the eventloop for connection progress
|
||||
// for (i, message) in connection.iter().enumerate() {
|
||||
// info!("Notification #{i} = {:?}", message);
|
||||
|
||||
// match message {
|
||||
// Ok(Event::Incoming(Incoming::Publish(p))) => {
|
||||
// info!(
|
||||
// "Got publish notification. Trying to deserialize: {:#?}",
|
||||
// serde_json::from_slice::<light::JsonIncoming>(&p.payload)
|
||||
// )
|
||||
// }
|
||||
// Ok(_) => todo!(),
|
||||
// Err(e) => {
|
||||
// error!("Connection error to mqtt: {e:?}")
|
||||
// }
|
||||
// }
|
||||
|
||||
// todo![]
|
||||
// }
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
/// Called after the initial connection to mqtt
|
||||
fn init(&mut self) -> Result<()> {
|
||||
// let topics = Topics::new(self.config);
|
||||
|
||||
info!("Subscribing to homeassistant");
|
||||
|
||||
// Check if homeassistant is starting or not
|
||||
self.client
|
||||
.subscribe(HOMEASSISTANT_TOPIC, QoS::AtMostOnce)
|
||||
.context("Subscribing to homeassistant status topic")?;
|
||||
|
||||
// Check for commands
|
||||
self.client
|
||||
.subscribe(&self.topics.command_topic, QoS::AtMostOnce)
|
||||
.context("Subscribing to command topic")?;
|
||||
|
||||
self.send_discovery()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_discovery(&self) -> Result<()> {
|
||||
let discovery = self.gen_discovery_message();
|
||||
|
||||
info!(
|
||||
"Sending discovery_message: {:?} (topic: {:?})",
|
||||
discovery, self.topics.autodiscovery
|
||||
);
|
||||
|
||||
// Send initial autodiscovery
|
||||
self.client
|
||||
.publish(
|
||||
&self.topics.autodiscovery,
|
||||
QoS::AtLeastOnce,
|
||||
false,
|
||||
serde_json::to_vec(&discovery).unwrap(),
|
||||
)
|
||||
.context("Sending initial autodiscovery")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// /// Called for each message
|
||||
// fn handle_incoming_event(
|
||||
// &self,
|
||||
// idx: usize,
|
||||
// message: Result<Event, ConnectionError>,
|
||||
// ) -> Result<()> {
|
||||
// }
|
||||
|
||||
/// Generates a discovery message
|
||||
fn gen_discovery_message(&self) -> light::JsonDiscovery {
|
||||
// "<discovery_prefix>/device_trigger/[<node_id>/]<object_id>/config",
|
||||
// homeassistant/device_trigger/0x90fd9ffffedf1266/action_arrow_left_click/config
|
||||
let discovery = light::JsonDiscovery {
|
||||
common: Common {
|
||||
unique_id: Some(self.config.mqtt_id.to_string()),
|
||||
..Common::default()
|
||||
},
|
||||
name: Some(FRIENDLY_NAME.to_string()),
|
||||
effect_list: Some(vec!["Rainbow".into()]),
|
||||
brightness: Some(false),
|
||||
effect: Some(true),
|
||||
supported_color_modes: Some(vec![light::ColorMode::Rgb]),
|
||||
state_topic: Some(self.topics.state_topic.clone()),
|
||||
..light::JsonDiscovery::new(self.topics.command_topic.clone())
|
||||
};
|
||||
discovery
|
||||
}
|
||||
}
|
||||
|
||||
fn build_strip_tx_msg(command: &JsonIncoming) -> Option<strip::Message> {
|
||||
use strip::Message;
|
||||
|
||||
if let Some(state) = &command.state
|
||||
&& state == LIGHT_OFF
|
||||
{
|
||||
return Some(Message::ClearLights);
|
||||
}
|
||||
|
||||
if let Some(effect) = &command.effect
|
||||
&& effect == "Rainbow"
|
||||
{
|
||||
return Some(Message::ChangePattern(Box::new(MovingRainbow::default())));
|
||||
}
|
||||
|
||||
if command.effect.is_none()
|
||||
&& let Some(color) = &command.color
|
||||
&& let Some(r) = color.r
|
||||
&& let Some(g) = color.g
|
||||
&& let Some(b) = color.b
|
||||
{
|
||||
return Some(Message::ChangePattern(Box::new(Solid::new(&SolidParams {
|
||||
color: Rgb(r as u8, g as u8, b as u8),
|
||||
}))));
|
||||
}
|
||||
|
||||
error!("Not able to parse input as a command: {command:?}");
|
||||
None
|
||||
|
||||
// ClearLights
|
||||
// ChangePattern(Box<dyn Pattern + Send + Sync>)
|
||||
// SetNumLights(u16)
|
||||
// SetTickTime(u64)
|
||||
// Quit
|
||||
}
|
||||
|
||||
// enum Status {
|
||||
// Alive,
|
||||
// Dead,
|
||||
// }
|
||||
|
||||
// impl ToString for Status {
|
||||
// fn to_string(&self) -> String {
|
||||
// match self {
|
||||
// Self::Alive => String::from("alive"),
|
||||
// Self::Dead => String::from("dead"),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
struct Topics {
|
||||
autodiscovery: String,
|
||||
state_topic: String,
|
||||
command_topic: String,
|
||||
status_topic: String,
|
||||
}
|
||||
|
||||
impl Topics {
|
||||
pub fn new(config: &MqttConfig) -> Self {
|
||||
let mqtt_id = &config.mqtt_id;
|
||||
Self {
|
||||
autodiscovery: format!("{}/light/{mqtt_id}/config", config.mqtt_discovery_prefix),
|
||||
state_topic: format!("{MQTT_PREFIX}/{mqtt_id}/state"),
|
||||
command_topic: format!("{MQTT_PREFIX}/{mqtt_id}/set"),
|
||||
status_topic: format!("{MQTT_PREFIX}/{mqtt_id}/status"),
|
||||
}
|
||||
}
|
||||
info!("Done with mqtt");
|
||||
}
|
||||
|
||||
fn gen_discovery_message(config: &MqttConfig) -> (String, light::JsonDiscovery) {
|
||||
// "<discovery_prefix>/light/[<node_id>/]<object_id>/config",
|
||||
// homeassistant/light/0x90fd9ffffedf1266/action_arrow_left_click/config
|
||||
let autodiscovery_topic_name = format!(
|
||||
"{}/light/{}/config",
|
||||
config.mqtt_discovery_prefix, config.mqtt_id
|
||||
);
|
||||
let discovery = light::JsonDiscovery {
|
||||
effect: Some(true),
|
||||
effect_list: Some(vec!["rainbow".into()]),
|
||||
supported_color_modes: Some(vec![light::ColorMode::Rgb]),
|
||||
state_topic: Some(format!("aw_lights/{}", config.mqtt_id)),
|
||||
..light::JsonDiscovery::new(format!("aw_lights/{}/set", config.mqtt_id))
|
||||
};
|
||||
// {
|
||||
// common: Common {
|
||||
// device: Some(Device {
|
||||
// identifiers: Some(vec![format!("aw_lights_{}", config.mqtt_id)]),
|
||||
// ..Device::default()
|
||||
// }),
|
||||
// ..Common::default()
|
||||
// },
|
||||
// topic: format!("aw_lights/{}/action", config.mqtt_id),
|
||||
// r#type: "action".to_string(),
|
||||
// subtype: format!("_click"),
|
||||
// automation_type: "trigger".to_string(),
|
||||
// payload: None,
|
||||
// qos: None,
|
||||
// value_template: None,
|
||||
// };
|
||||
(autodiscovery_topic_name, discovery)
|
||||
}
|
||||
|
||||
// unique_id: bedroom_switch
|
||||
@ -129,3 +372,42 @@ fn gen_discovery_message(config: &MqttConfig) -> (String, light::JsonDiscovery)
|
||||
// optimistic: false
|
||||
// qos: 0
|
||||
// retain: true
|
||||
|
||||
// https://github.com/smrtnt/Open-Home-Automation/blob/master/ha_mqtt_rgbw_light_with_discovery/ha_mqtt_rgbw_light_with_discovery.ino
|
||||
|
||||
// On connect:
|
||||
// JsonObject& root = staticJsonBuffer.createObject();
|
||||
// root["name"] = FRIENDLY_NAME;
|
||||
// root["platform"] = "mqtt_json";
|
||||
// root["state_topic"] = MQTT_STATE_TOPIC;
|
||||
// root["command_topic"] = MQTT_COMMAND_TOPIC;
|
||||
// root["brightness"] = true;
|
||||
// root["rgb"] = true;
|
||||
// root["white_value"] = true;
|
||||
// root["color_temp"] = true;
|
||||
// root["effect"] = true;
|
||||
// root["effect_list"] = EFFECT_LIST;
|
||||
|
||||
// On update
|
||||
// cmd = CMD_NOT_DEFINED;
|
||||
// DynamicJsonBuffer dynamicJsonBuffer;
|
||||
// JsonObject& root = dynamicJsonBuffer.createObject();
|
||||
// root["state"] = bulb.getState() ? MQTT_STATE_ON_PAYLOAD : MQTT_STATE_OFF_PAYLOAD;
|
||||
// root["brightness"] = bulb.getBrightness();
|
||||
// JsonObject& color = root.createNestedObject("color");
|
||||
// color["r"] = bulb.getColor().red;
|
||||
// color["g"] = bulb.getColor().green;
|
||||
// color["b"] = bulb.getColor().blue;
|
||||
// root["white_value"] = bulb.getColor().white;
|
||||
// root["color_temp"] = bulb.getColorTemperature();
|
||||
|
||||
// Status topic (/status)
|
||||
// "alive" or "dead"
|
||||
|
||||
//#define MQTT_STATE_TOPIC_TEMPLATE "%s/rgbw/state"
|
||||
// #define MQTT_COMMAND_TOPIC_TEMPLATE "%s/rgbw/set"
|
||||
// #define MQTT_STATUS_TOPIC_TEMPLATE "%s/rgbw/status" // MQTT connection: alive/dead
|
||||
|
||||
// #define MQTT_HOME_ASSISTANT_DISCOVERY_PREFIX "homeassistant"
|
||||
// #define MQTT_STATE_ON_PAYLOAD "ON"
|
||||
// #define MQTT_STATE_OFF_PAYLOAD "OFF"
|
||||
|
@ -77,7 +77,10 @@ fn main() -> ProgramResult<()> {
|
||||
// Mqtt user-interface
|
||||
make_child(
|
||||
message_tx.clone(),
|
||||
move |_message_tx| -> ProgramResult<()> { mqtt::start(mqtt_strip_tx, config.mqtt.clone()) },
|
||||
move |_message_tx| -> ProgramResult<()> {
|
||||
mqtt::MqttBuilder::new(config.mqtt.clone()).start(mqtt_strip_tx)
|
||||
// mqtt::start(mqtt_strip_tx, config.mqtt.clone())
|
||||
},
|
||||
);
|
||||
|
||||
std::mem::drop(message_tx);
|
||||
|
@ -15,7 +15,7 @@ fn main() {
|
||||
},
|
||||
)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.expect("Could not spawn npm")
|
||||
.wait()
|
||||
.unwrap()
|
||||
.success();
|
||||
|
@ -23,7 +23,7 @@
|
||||
"morphdom": "git+https://github.com/austenadler/morphdom#fix/input-value-type-change"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-cli": "^4.6.0",
|
||||
"webpack": "^5.28.0"
|
||||
"webpack": "^5.28.0",
|
||||
"webpack-cli": "^4.6.0"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user