@typed-assistant/builder 0.0.11 → 0.0.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typed-assistant/builder",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "exports": {
5
5
  "./appProcess": "./src/appProcess.tsx",
6
6
  "./bunInstall": "./src/bunInstall.tsx",
@@ -11,7 +11,9 @@
11
11
  "ansi-to-html": "^0.7.2",
12
12
  "elysia": "^0.8.9",
13
13
  "@mdi/svg": "^7.3.67",
14
- "ignore": "^5.3.0"
14
+ "ignore": "^5.3.0",
15
+ "react": "^18",
16
+ "react-dom": "^18"
15
17
  },
16
18
  "devDependencies": {
17
19
  "@types/node": "^20.10.6",
@@ -20,9 +22,9 @@
20
22
  "home-assistant-js-websocket": "^8.2.0",
21
23
  "typescript": "^5.3.3",
22
24
  "@typed-assistant/eslint-config": "0.0.4",
23
- "@typed-assistant/logger": "0.0.5",
24
25
  "@typed-assistant/typescript-config": "0.0.4",
25
- "@typed-assistant/utils": "0.0.7"
26
+ "@typed-assistant/utils": "0.0.7",
27
+ "@typed-assistant/logger": "0.0.5"
26
28
  },
27
29
  "peerDependencies": {
28
30
  "home-assistant-js-websocket": "^8.2.0"
@@ -60,6 +60,7 @@ export async function setupWatcher(
60
60
  ...args: Parameters<typeof buildAndStartAppProcess>
61
61
  ) {
62
62
  const { data: addonInfo, error: addonInfoError } = await getAddonInfo()
63
+ console.log("😅😅😅 ~ addonInfo:", addonInfo)
63
64
  if (addonInfoError) {
64
65
  log(`🚨 Failed to get addon info: ${addonInfoError}`)
65
66
  }
@@ -81,12 +82,14 @@ export async function setupWatcher(
81
82
  },
82
83
  )
83
84
 
84
- startWebappServer({getSubprocesses: () => subprocesses})
85
+ startWebappServer({
86
+ basePath: addonInfo?.ingress_entry ?? "",
87
+ getSubprocesses: () => subprocesses,
88
+ })
85
89
 
86
90
  return subprocesses
87
91
  }
88
92
 
89
-
90
93
  const setupGitSync = async ({
91
94
  gitPullPollDuration,
92
95
  }: {
@@ -123,6 +126,6 @@ const getAddonInfo = async () => {
123
126
  log("🔍 Getting addon info...")
124
127
 
125
128
  return withErrorHandling(getSupervisorAPI)<{
126
- data: { ingress_entry: string }
129
+ ingress_entry: string
127
130
  }>("/addons/self/info")
128
131
  }
@@ -1,8 +1,17 @@
1
+ import { log } from "@typed-assistant/logger"
1
2
  import Convert from "ansi-to-html"
2
- import type { Context } from "elysia"
3
- import { Elysia, t } from "elysia"
4
3
  import type { Subprocess } from "bun"
5
- import terminalHtmlUrl from "./webserver/terminal.html" with { type: "url" }
4
+ import { $ } from "bun"
5
+ import type { Context } from "elysia"
6
+ import { Elysia } from "elysia"
7
+ import { basename, join } from "path"
8
+
9
+ const indexHtmlFilePath = `${import.meta.dir}/webserver/index.html`
10
+ const cssFile = `${import.meta.dir}/webserver/input.css`
11
+ const terminalHtmlUrl = `${import.meta.dir}/webserver/terminal.html`
12
+ const tsEntryPoint = `${import.meta.dir}/webserver/index.tsx`
13
+ const tailwindConfig = `${import.meta.dir}/webserver/tailwind.config.js`
14
+ const cssOutputFile = join(process.cwd(), `./build/output.css`)
6
15
 
7
16
  const convert = new Convert()
8
17
  const decoder = new TextDecoder()
@@ -32,52 +41,73 @@ const subscribers = new Map<number, (message: string) => void>()
32
41
  let lastMessage = ""
33
42
 
34
43
  export const startWebappServer = async ({
44
+ basePath,
35
45
  getSubprocesses,
36
46
  }: {
47
+ basePath: string
37
48
  getSubprocesses: () => {
38
49
  app: Subprocess<"ignore", "pipe", "pipe">
39
50
  }
40
51
  }) => {
41
- new Elysia()
42
- .get("/", ({ request }) => {
43
- getIngressPath(request)
44
- return new Response(
45
- `
46
- <html>
47
- <meta charset="UTF-8">
48
- <body>
49
- <a href="${getIngressPath(request)}/log.txt">Logs</a>
50
- <a href="${getIngressPath(request)}/terminal">Terminal</a>
51
- </body>
52
- </html>
53
- `,
54
- { headers: { "content-type": "text/html" } },
55
- )
56
- })
52
+ const buildResult = await Bun.build({
53
+ entrypoints: [tsEntryPoint],
54
+ outdir: "./build",
55
+ define: {
56
+ "process.env.BASE_PATH": `"${basePath}"`,
57
+ },
58
+ })
59
+ if (!buildResult.success) {
60
+ for (const message of buildResult.logs) {
61
+ // Bun will pretty print the message object
62
+ console.error(message)
63
+ }
64
+ throw new Error("Build failed")
65
+ }
66
+ log("🛠️ Web server built successfully")
67
+
68
+ await $`bunx tailwindcss -c ${tailwindConfig} -i ${cssFile} -o ${cssOutputFile}`.quiet()
69
+ log("💄 Tailwind built successfully")
70
+
71
+ const indexHtml = (await Bun.file(indexHtmlFilePath).text())
72
+ .replace("{{ STYLESHEET }}", `/assets/${basename(cssOutputFile)}`)
73
+ .replace(
74
+ "{{ SCRIPTS }}",
75
+ buildResult.outputs
76
+ .map(
77
+ (output) =>
78
+ `<script type="module" src="/assets/${basename(output.path)}"></script>`,
79
+ )
80
+ .join("\n"),
81
+ )
82
+
83
+ const server = new Elysia()
84
+ .get(
85
+ "/",
86
+ () =>
87
+ new Response(indexHtml, {
88
+ headers: { "content-type": "text/html" },
89
+ }),
90
+ )
57
91
  .get("/terminal", Bun.file(terminalHtmlUrl))
58
92
  .get("/log.txt", Bun.file("./log.txt"))
59
- .get("/*", ({ request }) => {
60
- getIngressPath(request)
61
- return "Hello Elysia"
62
- })
63
93
  .ws("/ws", {
64
- body: t.Object({
65
- id: t.String(),
66
- }),
67
94
  async open(ws) {
68
95
  ws.send("Connected successfully. Awaiting messages...")
69
96
  subscribers.set(ws.id, (message) => {
70
97
  ws.send(message)
71
98
  })
72
- console.log("😅😅😅 ~ ws.id:", ws.id)
73
99
  },
74
- close(ws, code, message) {
75
- console.log("ws closed", code, message, ws.id)
76
- console.log("😅😅😅 ~ ws.id:", ws.id)
100
+ close(ws) {
77
101
  subscribers.delete(ws.id)
78
102
  },
79
103
  })
80
- .listen(8099)
104
+
105
+ server.get(`/assets/${basename(cssOutputFile)}`, Bun.file(cssOutputFile))
106
+ buildResult.outputs.forEach((output) => {
107
+ server.get(`/assets/${basename(output.path)}`, Bun.file(output.path))
108
+ })
109
+
110
+ server.listen(8099)
81
111
 
82
112
  // eslint-disable-next-line no-constant-condition
83
113
  while (true) {
@@ -0,0 +1,57 @@
1
+ import { useEffect, useState } from "react"
2
+
3
+ const getWS = () => {
4
+ const url = new URL(window.location.href)
5
+ url.pathname = `${process.env.BASE_PATH}/ws`
6
+ url.protocol = "ws:"
7
+ const ws = new WebSocket(url)
8
+
9
+ return ws
10
+ }
11
+
12
+ export function Terminal() {
13
+ const [content, setContent] = useState("")
14
+ const [ws, setWS] = useState<WebSocket>(() => getWS())
15
+
16
+ useEffect(() => {
17
+ let timeout: NodeJS.Timeout
18
+
19
+ ws.onclose = function () {
20
+ timeout = setTimeout(() => {
21
+ if (ws.readyState === WebSocket.OPEN) return
22
+ setWS(getWS())
23
+ }, 1000)
24
+ }
25
+
26
+ ws.onmessage = function (event) {
27
+ setContent(event.data)
28
+ }
29
+
30
+ return () => {
31
+ clearTimeout(timeout)
32
+ ws.close()
33
+ }
34
+ }, [ws])
35
+
36
+ return (
37
+ <>
38
+ <h1 className="text-white text-2xl">
39
+ Terminal{" "}
40
+ {ws.readyState === WebSocket.OPEN ? (
41
+ <span className="py-1 px-2 rounded-sm bg-emerald-300 text-emerald-800 text-xs uppercase">
42
+ Connected
43
+ </span>
44
+ ) : (
45
+ <span className="py-1 px-2 rounded-sm bg-rose-300 text-rose-800 text-xs uppercase">
46
+ Disconnected
47
+ </span>
48
+ )}
49
+ </h1>
50
+ <p>
51
+ Logs are also available at <a href="/log.txt">/log.txt</a>
52
+ </p>
53
+
54
+ <pre className="" dangerouslySetInnerHTML={{ __html: content }} />
55
+ </>
56
+ )
57
+ }
@@ -0,0 +1,9 @@
1
+ <html class="h-full">
2
+ <head>
3
+ <link rel="stylesheet" href="{{ STYLESHEET }}" />
4
+ </head>
5
+ <body class="bg-slate-950 text-white h-full">
6
+ <div id="root"></div>
7
+ {{ SCRIPTS }}
8
+ </body>
9
+ </html>
@@ -0,0 +1,7 @@
1
+ import * as ReactDOM from "react-dom/client"
2
+ import { Terminal } from "./Terminal"
3
+
4
+ const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
5
+ root.render(<Terminal />)
6
+
7
+ export default {}
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,13 @@
1
+ import { join } from "path"
2
+
3
+ // eslint-disable-next-line no-undef
4
+ const content = [join(__dirname, "./**/*.tsx"), join(__dirname, "./**/*.html")]
5
+
6
+ /** @type {import('tailwindcss').Config} */
7
+ export default {
8
+ content,
9
+ theme: {
10
+ extend: {},
11
+ },
12
+ plugins: [],
13
+ }
@@ -7,7 +7,6 @@
7
7
  color: #fff;
8
8
  }
9
9
  </style>
10
- <script src="https://cdnjs.cloudflare.com/ajax/libs/ansi-to-html/0.7.2/ansi_to_html.js"></script>
11
10
  <script>
12
11
  function log(msg) {
13
12
  document.getElementById("log").innerHTML = msg + "\n"