@typed-assistant/builder 0.0.51 → 0.0.53
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.
- package/package.json +6 -5
- package/src/appProcess.tsx +34 -14
- package/src/setupWebserver.tsx +6 -0
- package/src/webserver/App.tsx +2 -2
- package/src/webserver/Logs.tsx +7 -1
- package/src/webserver/Terminal.tsx +132 -9
- package/src/webserver/styles.tsx +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typed-assistant/builder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.53",
|
|
4
4
|
"exports": {
|
|
5
5
|
"./appProcess": "./src/appProcess.tsx",
|
|
6
6
|
"./bunInstall": "./src/bunInstall.tsx",
|
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
"eslint-plugin-html": "^7.1.0",
|
|
23
23
|
"eslint": "^8.56.0",
|
|
24
24
|
"home-assistant-js-websocket": "^8.2.0",
|
|
25
|
+
"tailwind-merge": "^2.2.1",
|
|
25
26
|
"ts-toolbelt": "^9.6.0",
|
|
26
27
|
"typescript": "^5.3.3",
|
|
27
|
-
"@typed-assistant/eslint-config": "0.0.
|
|
28
|
-
"@typed-assistant/logger": "0.0.
|
|
29
|
-
"@typed-assistant/typescript-config": "0.0.
|
|
30
|
-
"@typed-assistant/utils": "0.0.
|
|
28
|
+
"@typed-assistant/eslint-config": "0.0.9",
|
|
29
|
+
"@typed-assistant/logger": "0.0.17",
|
|
30
|
+
"@typed-assistant/typescript-config": "0.0.9",
|
|
31
|
+
"@typed-assistant/utils": "0.0.15"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"home-assistant-js-websocket": "^8.2.0"
|
package/src/appProcess.tsx
CHANGED
|
@@ -24,8 +24,8 @@ export async function setup({
|
|
|
24
24
|
onProcessError,
|
|
25
25
|
}: {
|
|
26
26
|
entryFile: string
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
onProcessError: (message: string, addonUrl: string) => void
|
|
28
|
+
} & Parameters<typeof generateTypes>[0]) {
|
|
29
29
|
const addonInfo = await getAddonInfo()
|
|
30
30
|
const basePath = addonInfo?.data.ingress_entry ?? ""
|
|
31
31
|
const slug = addonInfo?.data.slug ?? ""
|
|
@@ -40,6 +40,13 @@ export async function setup({
|
|
|
40
40
|
startWebappServer({
|
|
41
41
|
basePath,
|
|
42
42
|
getSubprocesses: () => subprocesses,
|
|
43
|
+
onRestartAppRequest: async () => {
|
|
44
|
+
subprocesses = await killAndRestartApp(
|
|
45
|
+
entryFile,
|
|
46
|
+
{ mdiPaths },
|
|
47
|
+
subprocesses,
|
|
48
|
+
)
|
|
49
|
+
},
|
|
43
50
|
})
|
|
44
51
|
setupWatcher({
|
|
45
52
|
directoryToWatch,
|
|
@@ -51,7 +58,24 @@ export async function setup({
|
|
|
51
58
|
getSubprocesses: () => subprocesses,
|
|
52
59
|
})
|
|
53
60
|
|
|
54
|
-
checkProcesses(entryFile, {
|
|
61
|
+
checkProcesses(entryFile, {
|
|
62
|
+
onMultiProcessError: (ps) => {
|
|
63
|
+
const message = `Multiple processes detected. Restarting addon...`
|
|
64
|
+
logger.fatal({ additionalDetails: ps, emoji: "🚨" }, message)
|
|
65
|
+
onProcessError?.(message, addonUrl)
|
|
66
|
+
restartAddon()
|
|
67
|
+
},
|
|
68
|
+
onNoProcessError: async (ps) => {
|
|
69
|
+
const message = `No processes detected. Restarting app...`
|
|
70
|
+
logger.fatal({ additionalDetails: ps, emoji: "🚨" }, message)
|
|
71
|
+
onProcessError?.(message, addonUrl)
|
|
72
|
+
subprocesses = await killAndRestartApp(
|
|
73
|
+
entryFile,
|
|
74
|
+
{ mdiPaths },
|
|
75
|
+
subprocesses,
|
|
76
|
+
)
|
|
77
|
+
},
|
|
78
|
+
})
|
|
55
79
|
await setupGitSync(webhookUrl)
|
|
56
80
|
|
|
57
81
|
return subprocesses
|
|
@@ -96,11 +120,11 @@ let noProcessesErrorCount = 0
|
|
|
96
120
|
const checkProcesses = async (
|
|
97
121
|
entryFile: string,
|
|
98
122
|
{
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
onMultiProcessError,
|
|
124
|
+
onNoProcessError,
|
|
101
125
|
}: {
|
|
102
|
-
|
|
103
|
-
|
|
126
|
+
onMultiProcessError?: (psOutput: string) => void | Promise<void>
|
|
127
|
+
onNoProcessError?: (psOutput: string) => void | Promise<void>
|
|
104
128
|
},
|
|
105
129
|
) => {
|
|
106
130
|
const ps = await $`ps -f`.text()
|
|
@@ -109,9 +133,7 @@ const checkProcesses = async (
|
|
|
109
133
|
if (matches.length > 1) {
|
|
110
134
|
multipleProcessesErrorCount++
|
|
111
135
|
if (multipleProcessesErrorCount > 5) {
|
|
112
|
-
|
|
113
|
-
logger.fatal({ additionalDetails: ps, emoji: "🚨" }, message)
|
|
114
|
-
onProcessError?.(message, addonUrl)
|
|
136
|
+
await onMultiProcessError?.(ps)
|
|
115
137
|
return
|
|
116
138
|
}
|
|
117
139
|
} else {
|
|
@@ -121,9 +143,7 @@ const checkProcesses = async (
|
|
|
121
143
|
if (matches.length === 0) {
|
|
122
144
|
noProcessesErrorCount++
|
|
123
145
|
if (noProcessesErrorCount > 5) {
|
|
124
|
-
|
|
125
|
-
logger.fatal({ additionalDetails: ps, emoji: "🚨" }, message)
|
|
126
|
-
onProcessError?.(message, addonUrl)
|
|
146
|
+
await onNoProcessError?.(ps)
|
|
127
147
|
return
|
|
128
148
|
}
|
|
129
149
|
} else {
|
|
@@ -131,7 +151,7 @@ const checkProcesses = async (
|
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
setTimeout(
|
|
134
|
-
() => checkProcesses(entryFile, {
|
|
154
|
+
() => checkProcesses(entryFile, { onMultiProcessError, onNoProcessError }),
|
|
135
155
|
5000,
|
|
136
156
|
)
|
|
137
157
|
}
|
package/src/setupWebserver.tsx
CHANGED
|
@@ -57,11 +57,13 @@ let lastMessage = ""
|
|
|
57
57
|
export const startWebappServer = async ({
|
|
58
58
|
basePath,
|
|
59
59
|
getSubprocesses,
|
|
60
|
+
onRestartAppRequest,
|
|
60
61
|
}: {
|
|
61
62
|
basePath: string
|
|
62
63
|
getSubprocesses: () => {
|
|
63
64
|
app: Subprocess<"ignore", "pipe", "pipe">
|
|
64
65
|
}
|
|
66
|
+
onRestartAppRequest: () => void
|
|
65
67
|
}) => {
|
|
66
68
|
const buildResult = await Bun.build({
|
|
67
69
|
entrypoints: [tsEntryPoint],
|
|
@@ -105,6 +107,10 @@ export const startWebappServer = async ({
|
|
|
105
107
|
headers: { "content-type": "text/html" },
|
|
106
108
|
}),
|
|
107
109
|
)
|
|
110
|
+
.get("/restart-app", async () => {
|
|
111
|
+
onRestartAppRequest()
|
|
112
|
+
return { message: "Restarting app..." }
|
|
113
|
+
})
|
|
108
114
|
.get("/restart-addon", async () => {
|
|
109
115
|
await killSubprocess(getSubprocesses().app)
|
|
110
116
|
restartAddon()
|
package/src/webserver/App.tsx
CHANGED
|
@@ -7,10 +7,10 @@ const App = () => {
|
|
|
7
7
|
return (
|
|
8
8
|
<div className="grid md:grid-cols-3">
|
|
9
9
|
<div className="col-span-2">
|
|
10
|
-
<Terminal
|
|
10
|
+
<Terminal />
|
|
11
11
|
</div>
|
|
12
12
|
<div className="col-span-1">
|
|
13
|
-
<Logs />
|
|
13
|
+
<Logs basePath={basePath} />
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
16
16
|
)
|
package/src/webserver/Logs.tsx
CHANGED
|
@@ -7,8 +7,9 @@ import { useWS } from "./useWS"
|
|
|
7
7
|
import { getPrettyTimestamp } from "@typed-assistant/utils/getPrettyTimestamp"
|
|
8
8
|
import { levels } from "@typed-assistant/logger/levels"
|
|
9
9
|
import type { LogSchema } from "@typed-assistant/logger"
|
|
10
|
+
import { buttonStyle } from "./styles"
|
|
10
11
|
|
|
11
|
-
export const Logs = () => {
|
|
12
|
+
export const Logs = ({ basePath }: { basePath: string }) => {
|
|
12
13
|
const [limit, setLimit] = useState(200)
|
|
13
14
|
const [level, setLevel] = useState<
|
|
14
15
|
"trace" | "debug" | "info" | "warn" | "error" | "fatal"
|
|
@@ -39,6 +40,11 @@ export const Logs = () => {
|
|
|
39
40
|
<h2 className="mb-2 text-2xl flex items-baseline gap-3">
|
|
40
41
|
Logs <WSIndicator ws={ws.ws} />
|
|
41
42
|
</h2>
|
|
43
|
+
|
|
44
|
+
<a className={buttonStyle} href={`${basePath}/log.txt?limit=500`}>
|
|
45
|
+
View raw log.txt
|
|
46
|
+
</a>
|
|
47
|
+
|
|
42
48
|
<div className="flex flex-wrap gap-2">
|
|
43
49
|
<div className="flex gap-2">
|
|
44
50
|
<label htmlFor="dateTimeVisibility">Date/Time</label>
|
|
@@ -1,10 +1,75 @@
|
|
|
1
|
-
import { useCallback, useState } from "react"
|
|
1
|
+
import { useCallback, useEffect, useState } from "react"
|
|
2
2
|
import { app } from "./api"
|
|
3
3
|
import { useWS } from "./useWS"
|
|
4
4
|
import { WSIndicator } from "./WSIndicator"
|
|
5
5
|
import { AppSection } from "./AppSection"
|
|
6
|
+
import { buttonStyle } from "./styles"
|
|
7
|
+
import { twMerge } from "tailwind-merge"
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
type ButtonAsyncProps = {
|
|
10
|
+
onClick: () => Promise<{ error?: unknown }>
|
|
11
|
+
stateLabels: {
|
|
12
|
+
idle: string
|
|
13
|
+
loading?: string
|
|
14
|
+
error?: string
|
|
15
|
+
success?: string
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ButtonAsync = ({
|
|
20
|
+
onClick,
|
|
21
|
+
stateLabels: stateLabelsProp,
|
|
22
|
+
}: ButtonAsyncProps) => {
|
|
23
|
+
const stateLabels = {
|
|
24
|
+
error: "Error",
|
|
25
|
+
loading: "Loading...",
|
|
26
|
+
success: "Success",
|
|
27
|
+
...stateLabelsProp,
|
|
28
|
+
}
|
|
29
|
+
const [state, setState] = useState<"idle" | "loading" | "error" | "success">(
|
|
30
|
+
"idle",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (state !== "loading" && state !== "idle") {
|
|
35
|
+
const timeout = setTimeout(() => setState("idle"), 2000)
|
|
36
|
+
|
|
37
|
+
return () => {
|
|
38
|
+
clearTimeout(timeout)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, [state])
|
|
42
|
+
|
|
43
|
+
const handleClick = async () => {
|
|
44
|
+
if (state !== "idle") return
|
|
45
|
+
setState("loading")
|
|
46
|
+
|
|
47
|
+
const { error } = await onClick()
|
|
48
|
+
|
|
49
|
+
if (error) {
|
|
50
|
+
setState("error")
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setState("success")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
className={twMerge(
|
|
60
|
+
buttonStyle,
|
|
61
|
+
state === "loading" && "bg-blue-300 text-blue-800",
|
|
62
|
+
state === "error" && "bg-red-300 text-red-800",
|
|
63
|
+
state === "success" && "bg-green-300 text-green-800",
|
|
64
|
+
)}
|
|
65
|
+
onClick={handleClick}
|
|
66
|
+
>
|
|
67
|
+
{stateLabels[state]}
|
|
68
|
+
</button>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function Terminal() {
|
|
8
73
|
const [content, setContent] = useState("")
|
|
9
74
|
const ws = useWS({
|
|
10
75
|
subscribe: useCallback(() => app.ws.subscribe(), []),
|
|
@@ -19,12 +84,22 @@ export function Terminal({ basePath }: { basePath: string }) {
|
|
|
19
84
|
TypedAssistant <WSIndicator ws={ws.ws} />
|
|
20
85
|
</h1>
|
|
21
86
|
<div className="flex flex-wrap gap-2">
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
87
|
+
<ButtonAsync
|
|
88
|
+
onClick={() => app["restart-app"].get()}
|
|
89
|
+
stateLabels={{
|
|
90
|
+
idle: "Restart app",
|
|
91
|
+
loading: "Restarting app...",
|
|
92
|
+
success: "App restarted",
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
<ButtonAsync
|
|
96
|
+
onClick={() => app["restart-addon"].get()}
|
|
97
|
+
stateLabels={{
|
|
98
|
+
idle: "Restart addon",
|
|
99
|
+
loading: "Restarting addon...",
|
|
100
|
+
success: "Addon restarted",
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
28
103
|
</div>
|
|
29
104
|
</>
|
|
30
105
|
)}
|
|
@@ -34,4 +109,52 @@ export function Terminal({ basePath }: { basePath: string }) {
|
|
|
34
109
|
)
|
|
35
110
|
}
|
|
36
111
|
|
|
37
|
-
const
|
|
112
|
+
const RestartAppButton = () => {
|
|
113
|
+
const [state, setState] = useState<"idle" | "loading" | "error" | "success">(
|
|
114
|
+
"idle",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (state !== "loading" && state !== "idle") {
|
|
119
|
+
const timeout = setTimeout(() => setState("idle"), 2000)
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
clearTimeout(timeout)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [state])
|
|
126
|
+
|
|
127
|
+
const onClick = async () => {
|
|
128
|
+
if (state !== "idle") return
|
|
129
|
+
setState("loading")
|
|
130
|
+
|
|
131
|
+
const { data, error } = await app["restart-app"].get()
|
|
132
|
+
|
|
133
|
+
if (error) {
|
|
134
|
+
setState("error")
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setState("success")
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<button
|
|
143
|
+
className={twMerge(
|
|
144
|
+
buttonStyle,
|
|
145
|
+
state === "loading" && "bg-blue-500",
|
|
146
|
+
state === "error" && "bg-red-500",
|
|
147
|
+
state === "success" && "bg-green-500",
|
|
148
|
+
)}
|
|
149
|
+
onClick={onClick}
|
|
150
|
+
>
|
|
151
|
+
{state === "loading"
|
|
152
|
+
? "Restarting app..."
|
|
153
|
+
: state === "error"
|
|
154
|
+
? "Error restarting app"
|
|
155
|
+
: state === "success"
|
|
156
|
+
? "App restarted"
|
|
157
|
+
: "Restart app"}
|
|
158
|
+
</button>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const buttonStyle = "bg-slate-800 text-white px-3 py-1 rounded-md"
|