@typed-assistant/builder 0.0.1
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/.eslintrc.js +6 -0
- package/.turbo/turbo-lint.log +1 -0
- package/CHANGELOG.md +7 -0
- package/README.md +9 -0
- package/package.json +35 -0
- package/src/appProcess.tsx +102 -0
- package/src/bunInstall.tsx +21 -0
- package/src/getSpawnText.tsx +14 -0
- package/src/pullChanges.tsx +31 -0
- package/tsconfig.json +5 -0
- package/turbo.json +9 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ eslint . --max-warnings 0
|
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Home Assistant Configuration
|
|
2
|
+
|
|
3
|
+
This application requires two environment variables to be set: `HASS_SERVER` and `HASS_TOKEN`.
|
|
4
|
+
|
|
5
|
+
- `HASS_SERVER`: This should be the URL of your Home Assistant server. For example, `http://192.168.1.99:8123`.
|
|
6
|
+
|
|
7
|
+
- `HASS_TOKEN`: This is the API token generated from your Home Assistant instance. You can generate this token from your Home Assistant profile page. Please ensure to keep this token secure as it provides full access to your Home Assistant instance.
|
|
8
|
+
|
|
9
|
+
Please ensure these environment variables are set before running the application.
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@typed-assistant/builder",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"exports": {
|
|
5
|
+
"./appProcess": "./src/appProcess.tsx",
|
|
6
|
+
"./bunInstall": "./src/bunInstall.tsx",
|
|
7
|
+
"./getSpawnText": "./src/getSpawnText.tsx",
|
|
8
|
+
"./pullChanges": "./src/pullChanges.tsx"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"lint": "eslint . --max-warnings 0"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@mdi/svg": "^7.3.67",
|
|
15
|
+
"ignore": "^5.3.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@typed-assistant/eslint-config": "workspace:*",
|
|
19
|
+
"@typed-assistant/logger": "workspace:*",
|
|
20
|
+
"@typed-assistant/typescript-config": "workspace:*",
|
|
21
|
+
"@typed-assistant/utils": "workspace:*",
|
|
22
|
+
"@types/node": "^20.10.6",
|
|
23
|
+
"@types/eslint": "^8.56.1",
|
|
24
|
+
"eslint": "^8.56.0",
|
|
25
|
+
"home-assistant-js-websocket": "^8.2.0",
|
|
26
|
+
"typescript": "^5.3.3"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"home-assistant-js-websocket": "^8.2.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"registry": "https://registry.npmjs.org/"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { generateTypes } from "@typed-assistant/types/generateTypes"
|
|
2
|
+
import { log } from "@typed-assistant/logger"
|
|
3
|
+
import type { Subprocess } from "bun"
|
|
4
|
+
import { readFileSync, watch } from "fs"
|
|
5
|
+
import { join, relative } from "path"
|
|
6
|
+
import ignore from "ignore"
|
|
7
|
+
import { ONE_SECOND } from "@typed-assistant/utils/durations"
|
|
8
|
+
import { getHassAPI } from "@typed-assistant/utils/getHassAPI"
|
|
9
|
+
import { pullChanges } from "./pullChanges"
|
|
10
|
+
|
|
11
|
+
type Processes = Awaited<ReturnType<typeof buildAndStartAppProcess>>
|
|
12
|
+
|
|
13
|
+
async function buildAndStartAppProcess(
|
|
14
|
+
appSourceFile?: string,
|
|
15
|
+
options?: Parameters<typeof generateTypes>[0],
|
|
16
|
+
) {
|
|
17
|
+
await generateTypes({ mdiPaths: options?.mdiPaths })
|
|
18
|
+
return { app: await startApp(appSourceFile) }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function startApp(appSourceFile: string = "src/entry.tsx") {
|
|
22
|
+
log("🚀 Starting app...")
|
|
23
|
+
const path = join(process.cwd(), appSourceFile)
|
|
24
|
+
return Bun.spawn(["bun", path], { stdout: "inherit" })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function kill(process: Subprocess) {
|
|
28
|
+
log(`💀 Killing process: ${process.pid}`)
|
|
29
|
+
process.kill()
|
|
30
|
+
await process.exited
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let settingUp = { current: false }
|
|
34
|
+
async function killAndRestartApp(subprocesses: Processes) {
|
|
35
|
+
if (settingUp.current) return subprocesses
|
|
36
|
+
log("♻️ Restarting app...")
|
|
37
|
+
settingUp.current = true
|
|
38
|
+
if (subprocesses.app) await kill(subprocesses.app)
|
|
39
|
+
const newSubprocesses = await buildAndStartAppProcess()
|
|
40
|
+
settingUp.current = false
|
|
41
|
+
return newSubprocesses
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setupWatcherInternal(...args: Parameters<typeof watch>) {
|
|
45
|
+
const [directory, callback] = args
|
|
46
|
+
if (typeof directory !== "string") throw new Error("Directory must be string")
|
|
47
|
+
|
|
48
|
+
log("👀 Watching directory:", directory)
|
|
49
|
+
const watcher = watch(directory, { recursive: true }, callback)
|
|
50
|
+
|
|
51
|
+
return watcher
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function setupWatcher(
|
|
55
|
+
...args: Parameters<typeof buildAndStartAppProcess>
|
|
56
|
+
) {
|
|
57
|
+
await setupGitSync()
|
|
58
|
+
|
|
59
|
+
let subprocesses = await buildAndStartAppProcess(...args)
|
|
60
|
+
|
|
61
|
+
setupWatcherInternal(
|
|
62
|
+
join(process.cwd(), "src"),
|
|
63
|
+
async function onFileChange(event, filename) {
|
|
64
|
+
if (!filename) return
|
|
65
|
+
if (shouldIgnoreFileOrFolder(filename)) return
|
|
66
|
+
log(`⚠️ Change to ${filename} detected.`)
|
|
67
|
+
if (filename.endsWith("process.tsx")) {
|
|
68
|
+
await restartAddon()
|
|
69
|
+
} else {
|
|
70
|
+
subprocesses = await killAndRestartApp(subprocesses)
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return subprocesses
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const setupGitSync = async ({
|
|
79
|
+
gitPullPollDuration,
|
|
80
|
+
}: {
|
|
81
|
+
/** Duration in seconds */
|
|
82
|
+
gitPullPollDuration?: number
|
|
83
|
+
} = {}) => {
|
|
84
|
+
const duration = gitPullPollDuration ?? 5
|
|
85
|
+
await pullChanges()
|
|
86
|
+
log(` ⏳ Pulling changes again in ${duration} seconds...`)
|
|
87
|
+
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
setupGitSync({ gitPullPollDuration })
|
|
90
|
+
}, duration * ONE_SECOND)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const ig = ignore().add(
|
|
94
|
+
`${readFileSync(join(process.cwd(), ".gitignore")).toString()}\n.git`,
|
|
95
|
+
)
|
|
96
|
+
const shouldIgnoreFileOrFolder = (filename: string) =>
|
|
97
|
+
ig.ignores(relative(process.cwd(), filename))
|
|
98
|
+
|
|
99
|
+
const restartAddon = async () => {
|
|
100
|
+
log("♻️ Restarting addon...")
|
|
101
|
+
await getHassAPI(`http://supervisor/addons/self/restart`, { method: "POST" })
|
|
102
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { log } from "@typed-assistant/logger"
|
|
2
|
+
|
|
3
|
+
export async function bunInstall() {
|
|
4
|
+
log("🏗️ Running bun install...")
|
|
5
|
+
const proc = Bun.spawn([
|
|
6
|
+
"bun",
|
|
7
|
+
"install",
|
|
8
|
+
"--frozen-lockfile",
|
|
9
|
+
"--cache-dir=.bun-cache",
|
|
10
|
+
])
|
|
11
|
+
await proc.exited
|
|
12
|
+
const bunInstallText = await Bun.readableStreamToText(proc.stdout)
|
|
13
|
+
if (proc.exitCode === 0) return { error: null }
|
|
14
|
+
return {
|
|
15
|
+
error: {
|
|
16
|
+
signalCode: proc.signalCode,
|
|
17
|
+
exitCode: proc.exitCode,
|
|
18
|
+
text: bunInstallText,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function getSpawnText(...args: Parameters<typeof Bun.spawn>) {
|
|
2
|
+
const { exitCode, exited, stderr, stdout } = Bun.spawn(...args)
|
|
3
|
+
await exited
|
|
4
|
+
if (exitCode === 0 || exitCode === null)
|
|
5
|
+
return await Bun.readableStreamToText(stdout)
|
|
6
|
+
if (!exitCode) return ""
|
|
7
|
+
throw new Error(
|
|
8
|
+
`Failed to run command: "${args.join(
|
|
9
|
+
" ",
|
|
10
|
+
)}". Exit code: ${exitCode}. Stderr: ${
|
|
11
|
+
stderr ? await Bun.readableStreamToText(stderr) : "None"
|
|
12
|
+
}`,
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { log } from "@typed-assistant/logger"
|
|
2
|
+
import { getSpawnText } from "./getSpawnText"
|
|
3
|
+
import { bunInstall } from "./bunInstall"
|
|
4
|
+
|
|
5
|
+
export const pullChanges = async () => {
|
|
6
|
+
if (
|
|
7
|
+
!process.env.GITHUB_TOKEN ||
|
|
8
|
+
!process.env.GITHUB_USERNAME ||
|
|
9
|
+
!process.env.GITHUB_REPO
|
|
10
|
+
) {
|
|
11
|
+
log(
|
|
12
|
+
"⚠️ Cannot pull changes without GITHUB_TOKEN, GITHUB_USERNAME, and GITHUB_REPO environment variables.",
|
|
13
|
+
)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
log("⬇️ Pulling changes...")
|
|
17
|
+
const gitPullText = await getSpawnText(["git", "pull"])
|
|
18
|
+
const packageJSONUpdated = /package.json/.test(gitPullText)
|
|
19
|
+
const nothingNew = /Already up to date./.test(gitPullText)
|
|
20
|
+
if (nothingNew) {
|
|
21
|
+
log(" 👌 No new changes.")
|
|
22
|
+
return
|
|
23
|
+
} else {
|
|
24
|
+
log(" 👍 Changes pulled.")
|
|
25
|
+
}
|
|
26
|
+
if (packageJSONUpdated) {
|
|
27
|
+
log(" 📦 package.json updated.")
|
|
28
|
+
const { error } = await bunInstall()
|
|
29
|
+
if (error) throw new Error(error.text)
|
|
30
|
+
}
|
|
31
|
+
}
|
package/tsconfig.json
ADDED