howcode 0.1.0 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/lib/howcode.js +127 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -13,6 +13,16 @@ howcode
13
13
 
14
14
  On first run, the launcher downloads the matching desktop build from GitHub Releases and caches it locally.
15
15
 
16
+ ## Linux note
17
+
18
+ If the Linux build hits a WebKit/GBM white-screen issue, the launcher retries with `WEBKIT_DISABLE_DMABUF_RENDERER=1` automatically.
19
+
20
+ If you are launching a downloaded Linux release asset manually, use:
21
+
22
+ ```bash
23
+ WEBKIT_DISABLE_DMABUF_RENDERER=1 ./howcode/bin/launcher
24
+ ```
25
+
16
26
  ## Cache location
17
27
 
18
28
  - macOS: `~/Library/Caches/howcode`
package/lib/howcode.js CHANGED
@@ -44,6 +44,8 @@ const TARGETS = {
44
44
  },
45
45
  };
46
46
 
47
+ const LINUX_DMABUF_FAILURE_PATTERNS = [/Failed to create GBM buffer/i, /GLXBadWindow/i, /dmabuf/i];
48
+
47
49
  function readJsonIfPresent(filePath) {
48
50
  try {
49
51
  return JSON.parse(fs.readFileSync(filePath, "utf8"));
@@ -203,15 +205,135 @@ async function pruneOldVersions(cacheRoot, keepDir) {
203
205
  );
204
206
  }
205
207
 
206
- function launch(executablePath) {
207
- const child = spawn(executablePath, [], {
208
+ function spawnLauncherProcess(executablePath, options = {}) {
209
+ return spawn(executablePath, [], {
208
210
  detached: true,
209
- stdio: "ignore",
211
+ stdio: options.stdio || "ignore",
210
212
  windowsHide: true,
211
213
  cwd: path.dirname(executablePath),
214
+ env: {
215
+ ...process.env,
216
+ ...(options.env || {}),
217
+ },
218
+ });
219
+ }
220
+
221
+ function killDetachedProcess(child) {
222
+ if (!child.pid) {
223
+ return;
224
+ }
225
+
226
+ try {
227
+ if (process.platform !== "win32") {
228
+ process.kill(-child.pid, "SIGTERM");
229
+ return;
230
+ }
231
+ } catch {}
232
+
233
+ try {
234
+ child.kill("SIGTERM");
235
+ } catch {}
236
+ }
237
+
238
+ function hasLinuxDmabufFailure(output) {
239
+ return LINUX_DMABUF_FAILURE_PATTERNS.some((pattern) => pattern.test(output));
240
+ }
241
+
242
+ function trimLauncherLog(output) {
243
+ return output.trim().split(/\r?\n/).slice(-10).join("\n");
244
+ }
245
+
246
+ async function launch(executablePath) {
247
+ if (process.platform !== "linux" || process.env.WEBKIT_DISABLE_DMABUF_RENDERER === "1") {
248
+ const child = spawnLauncherProcess(executablePath);
249
+ child.unref();
250
+ return;
251
+ }
252
+
253
+ const child = spawnLauncherProcess(executablePath, {
254
+ stdio: ["ignore", "pipe", "pipe"],
212
255
  });
213
256
 
257
+ let output = "";
258
+ const appendOutput = (chunk) => {
259
+ output += chunk.toString();
260
+ if (output.length > 32_000) {
261
+ output = output.slice(-32_000);
262
+ }
263
+ };
264
+
265
+ child.stdout?.on("data", appendOutput);
266
+ child.stderr?.on("data", appendOutput);
267
+
268
+ const outcome = await new Promise((resolve) => {
269
+ let settled = false;
270
+
271
+ const finish = (value) => {
272
+ if (settled) {
273
+ return;
274
+ }
275
+
276
+ settled = true;
277
+ clearTimeout(timer);
278
+ resolve(value);
279
+ };
280
+
281
+ const checkForFallback = () => {
282
+ if (hasLinuxDmabufFailure(output)) {
283
+ finish({ type: "fallback" });
284
+ }
285
+ };
286
+
287
+ const timer = setTimeout(() => finish({ type: "ok" }), 4_000);
288
+
289
+ child.stdout?.on("data", checkForFallback);
290
+ child.stderr?.on("data", checkForFallback);
291
+ child.once("error", (error) => finish({ type: "error", error }));
292
+ child.once("exit", (code, signal) => {
293
+ if (hasLinuxDmabufFailure(output)) {
294
+ finish({ type: "fallback" });
295
+ return;
296
+ }
297
+
298
+ finish({ type: "exit", code, signal });
299
+ });
300
+ });
301
+
302
+ child.stdout?.destroy();
303
+ child.stderr?.destroy();
214
304
  child.unref();
305
+
306
+ if (outcome.type === "fallback") {
307
+ killDetachedProcess(child);
308
+
309
+ const fallbackChild = spawnLauncherProcess(executablePath, {
310
+ env: {
311
+ WEBKIT_DISABLE_DMABUF_RENDERER: "1",
312
+ },
313
+ });
314
+
315
+ fallbackChild.unref();
316
+ return;
317
+ }
318
+
319
+ if (outcome.type === "ok") {
320
+ return;
321
+ }
322
+
323
+ if (outcome.type === "error") {
324
+ throw outcome.error;
325
+ }
326
+
327
+ if (outcome.code === 0) {
328
+ return;
329
+ }
330
+
331
+ const logTail = trimLauncherLog(output);
332
+ throw new Error(
333
+ logTail
334
+ ? `Desktop launcher exited early.\n${logTail}`
335
+ : `Desktop launcher exited early with code ${outcome.code ?? "unknown"}.`,
336
+ );
215
337
  }
216
338
 
217
339
  async function main() {
@@ -226,7 +348,7 @@ async function main() {
226
348
  releaseInfo = await resolveLatestRelease(target);
227
349
  } catch (error) {
228
350
  if (current?.executablePath && fs.existsSync(current.executablePath)) {
229
- launch(current.executablePath);
351
+ await launch(current.executablePath);
230
352
  return;
231
353
  }
232
354
 
@@ -239,7 +361,7 @@ async function main() {
239
361
  }
240
362
 
241
363
  await pruneOldVersions(cacheRoot, paths.installDir);
242
- launch(paths.executablePath);
364
+ await launch(paths.executablePath);
243
365
  }
244
366
 
245
367
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "howcode",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Launch the Howcode desktop app from npm or npx.",
5
5
  "license": "MIT",
6
6
  "author": "Igor Warzocha",