@typed-assistant/builder 0.0.83 → 0.0.85

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @typed-assistant/builder
2
2
 
3
+ ## 0.0.85
4
+
5
+ ### Patch Changes
6
+
7
+ - Restart app when empty string bug is detected.
8
+
9
+ ## 0.0.84
10
+
11
+ ### Patch Changes
12
+
13
+ - Improve logging for random crashes.
14
+
3
15
  ## 0.0.83
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typed-assistant/builder",
3
- "version": "0.0.83",
3
+ "version": "0.0.85",
4
4
  "exports": {
5
5
  "./appProcess": "./src/appProcess.tsx",
6
6
  "./bunInstall": "./src/bunInstall.tsx",
@@ -30,7 +30,7 @@
30
30
  "typescript": "^5.4.0",
31
31
  "@typed-assistant/eslint-config": "0.0.10",
32
32
  "@typed-assistant/logger": "0.0.22",
33
- "@typed-assistant/typescript-config": "0.0.10",
33
+ "@typed-assistant/typescript-config": "0.0.11",
34
34
  "@typed-assistant/utils": "0.0.19"
35
35
  },
36
36
  "publishConfig": {
@@ -45,6 +45,16 @@ export async function setup({
45
45
  subprocesses,
46
46
  )
47
47
  },
48
+ onProcessError: async (message) => {
49
+ const messageAll = `${message}. Restarting app...`
50
+ logger.fatal({ emoji: "🚨" }, messageAll)
51
+ onProcessError?.(messageAll, addonUrl)
52
+ subprocesses = await killAndRestartApp(
53
+ entryFile,
54
+ { mdiPaths },
55
+ subprocesses,
56
+ )
57
+ },
48
58
  })
49
59
  setupWatcher({
50
60
  directoryToWatch,
@@ -101,12 +101,14 @@ export const startWebappServer = async ({
101
101
  basePath,
102
102
  getSubprocesses,
103
103
  onRestartAppRequest,
104
+ onProcessError,
104
105
  }: {
105
106
  basePath: string
106
107
  getSubprocesses: () => {
107
108
  app: Subprocess<"ignore", "pipe", "pipe">
108
109
  }
109
110
  onRestartAppRequest: () => void
111
+ onProcessError: (message: string) => void
110
112
  }) => {
111
113
  const buildResult = await Bun.build({
112
114
  entrypoints: [tsEntryPoint],
@@ -344,29 +346,53 @@ export const startWebappServer = async ({
344
346
  await server.stop()
345
347
  })
346
348
 
349
+ let emptyStringCount = 0
350
+
347
351
  // eslint-disable-next-line no-constant-condition
348
352
  while (true) {
349
- getSubprocesses().app.exitCode
350
- const stdoutReader = getReader("stdout", getSubprocesses().app.stdout)
351
- const stderrReader = getReader("stderr", getSubprocesses().app.stderr)
352
- const { value } = await stdoutReader.read()
353
- const { value: stderrValue } = value
354
- ? { value: undefined }
355
- : await stderrReader.read()
356
-
357
- const decodedString = stderrValue
358
- ? decoder.decode(stderrValue)
359
- : decoder.decode(value)
353
+ const app = getSubprocesses().app
354
+ const stdoutReader = getReader("stdout", app.stdout)
355
+ const stderrReader = getReader("stderr", app.stderr)
356
+ const stdoutResult = await stdoutReader.read()
357
+ const stderrResult =
358
+ stdoutResult.value === undefined
359
+ ? await stderrReader.read()
360
+ : ({
361
+ value: undefined,
362
+ done: true,
363
+ } satisfies ReadableStreamDefaultReadDoneResult)
364
+
365
+ const streamEnded =
366
+ stdoutResult.done &&
367
+ (stderrResult.done || stderrResult.value === undefined)
368
+ if (streamEnded) {
369
+ logger.warn(
370
+ {
371
+ emoji: "😴",
372
+ additionalDetails: JSON.stringify({ exitCode: app.exitCode }),
373
+ },
374
+ "Subprocess output streams ended; waiting for restart or new output",
375
+ )
376
+ await new Promise((resolve) => setTimeout(resolve, 1000))
377
+ continue
378
+ }
379
+
380
+ const chunk = stdoutResult.value ?? stderrResult.value
381
+ const decodedString = chunk ? decoder.decode(chunk) : ""
360
382
  const convertedMessage = convert.toHtml(decodedString)
361
383
  if (convertedMessage !== "") {
362
384
  lastMessage = convertedMessage
363
385
  }
364
386
  if (convertedMessage === "") {
387
+ emptyStringCount += 1
388
+ const emptyStringMessage =
389
+ "Process is returning an empty string"
390
+ if (emptyStringCount === 10) {
391
+ onProcessError(emptyStringMessage)
392
+ }
365
393
  subscribers.forEach((send) =>
366
- send(
367
- "Process is returning an empty string. This was the last non-empty message:\n\n" +
368
- lastMessage,
369
- ),
394
+ send("Process is returning an empty string. This was the last non-empty message:\n\n" +
395
+ lastMessage),
370
396
  )
371
397
  logger.fatal(
372
398
  {
@@ -375,11 +401,12 @@ export const startWebappServer = async ({
375
401
  exitCode: getSubprocesses().app.exitCode,
376
402
  }),
377
403
  },
378
- "Process is returning an empty string",
404
+ emptyStringMessage,
379
405
  )
380
406
  await new Promise((resolve) => setTimeout(resolve, 1000))
381
407
  continue
382
408
  }
409
+ emptyStringCount = 0
383
410
  subscribers.forEach((send) => send(convertedMessage))
384
411
  }
385
412
 
@@ -1,174 +0,0 @@
1
- import { logger } from "@typed-assistant/logger"
2
- import { handleFetchError } from "@typed-assistant/utils/getHassAPI"
3
- import { withErrorHandling } from "@typed-assistant/utils/withErrorHandling"
4
- import { z } from "zod"
5
-
6
- const commonOptions = {
7
- headers: {
8
- Accept: "application/vnd.github+json",
9
- Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
10
- "X-GitHub-Api-Version": "2022-11-28",
11
- },
12
- }
13
-
14
- const webhookIsSetup = async (webhookUrl: string) => {
15
- const { error } = await fetch(webhookUrl, {
16
- ...commonOptions,
17
- body: JSON.stringify({ check: "true" }),
18
- })
19
- .then(handleFetchError)
20
- .then((d) => d.json())
21
-
22
- if (error) {
23
- logger.error(
24
- { additionalDetails: error.message, emoji: "🚨" },
25
- `Failed to reach webhook "${webhookUrl}"`,
26
- )
27
- return false
28
- }
29
-
30
- logger.debug({ emoji: "🪝" }, "Webhook reached successfully: ")
31
- return true
32
- }
33
-
34
- const listRepoWebhooks = async () =>
35
- withErrorHandling(() =>
36
- fetch(
37
- `https://api.github.com/repos/${process.env.GITHUB_USERNAME}/${process.env.GITHUB_REPO}/hooks`,
38
- { ...commonOptions },
39
- )
40
- .then(handleFetchError)
41
- .then((d) => d?.json())
42
- .then(z.array(Webhook).parse),
43
- )()
44
-
45
- // const deleteRepoWebhook = async (id: number) =>
46
- // withErrorHandling(() =>
47
- // fetch(
48
- // `https://api.github.com/repos/${process.env.GITHUB_USERNAME}/${process.env.GITHUB_REPO}/hooks/${id}`,
49
- // { ...commonOptions, method: "DELETE" },
50
- // ),
51
- // )()
52
-
53
- // const deleteAllRepoWebhooks = async () => {
54
- // const { data: webhooks, error } = await listRepoWebhooks()
55
-
56
- // if (error) {
57
- // logger.error("🚨 Failed fetching webhooks")
58
- // logger.error(` ${error.message}`)
59
- // return
60
- // }
61
-
62
- // await Promise.all(
63
- // webhooks.map(async (webhook) => {
64
- // await deleteRepoWebhook(webhook.id)
65
- // logger.info("🚮 Webhook deleted: " + webhook.config.url)
66
- // }),
67
- // )
68
- // }
69
-
70
- const createRepoWebhook = async (webhookUrl: string) =>
71
- withErrorHandling(() =>
72
- fetch(
73
- `https://api.github.com/repos/${process.env.GITHUB_USERNAME}/${process.env.GITHUB_REPO}/hooks`,
74
- {
75
- ...commonOptions,
76
- method: "POST",
77
- body: JSON.stringify({
78
- name: "web",
79
- active: true,
80
- config: {
81
- url: webhookUrl,
82
- content_type: "json",
83
- insecure_ssl: "0",
84
- },
85
- events: ["push"],
86
- }),
87
- },
88
- )
89
- .then(handleFetchError)
90
- .then((d) => d.json())
91
- .then(Webhook.parse),
92
- )()
93
-
94
- const Webhook = z.object({
95
- type: z.literal("Repository"),
96
- id: z.number(),
97
- name: z.literal("web"),
98
- active: z.boolean(),
99
- events: z.array(z.string()),
100
- config: z.object({
101
- content_type: z.string(),
102
- insecure_ssl: z.enum(["0", "1"]),
103
- url: z.string(),
104
- }),
105
- updated_at: z.string(),
106
- created_at: z.string(),
107
- url: z.string(),
108
- test_url: z.string(),
109
- ping_url: z.string(),
110
- deliveries_url: z.string(),
111
- last_response: z.object({
112
- code: z.number().nullable(),
113
- status: z.string().nullable(),
114
- message: z.string().nullable(),
115
- }),
116
- })
117
-
118
- type Webhook = z.infer<typeof Webhook>
119
-
120
- const retryTimeout = 2000
121
- let retries = 0
122
- export const setupWebhook = async (webhookUrl: string): Promise<void> => {
123
- const { data: webhooks, error } = await listRepoWebhooks()
124
-
125
- if (error) {
126
- if (retries < 5) {
127
- retries++
128
- logger.error(
129
- { emoji: "🔁" },
130
- `Failed fetching webhooks. Retrying setup in ${retryTimeout / 1000}s...`,
131
- )
132
- setTimeout(setupWebhook, retryTimeout)
133
- return
134
- }
135
- logger.error(
136
- { additionalDetails: error.message, emoji: "🚨" },
137
- "Failed fetching webhooks. Giving up.",
138
- )
139
- return
140
- }
141
-
142
- const webhookAlreadyExists = webhooks.some(
143
- async (webhook) => webhook.config.url === webhookUrl,
144
- )
145
-
146
- if (webhookAlreadyExists) {
147
- logger.info({ emoji: "🪝" }, "Webhook already set up")
148
- return
149
- }
150
-
151
- await webhookIsSetup(webhookUrl)
152
-
153
- const { data: webhook, error: createError } =
154
- await createRepoWebhook(webhookUrl)
155
-
156
- if (createError) {
157
- if (retries < 5) {
158
- retries++
159
- logger.error(
160
- { emoji: "🔁" },
161
- `Failed creating webhook. Retrying setup in ${retryTimeout / 1000}s...`,
162
- )
163
- setTimeout(setupWebhook, retryTimeout)
164
- return
165
- }
166
- logger.error(
167
- { additionalDetails: createError.message, emoji: "🚨" },
168
- "Failed creating webhook. Giving up.",
169
- )
170
- return
171
- }
172
-
173
- logger.info({ emoji: "🪝" }, "Webhook created: " + webhook.config.url)
174
- }