howcode 0.1.0 → 0.1.2

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 +32 -3
  2. package/lib/howcode.js +43 -22
  3. package/package.json +6 -3
package/README.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # howcode
2
2
 
3
- Launch the Howcode desktop app from npm.
3
+ Howcode is a desktop app for coding with Pi.
4
4
 
5
- ## Use
5
+ It gives you:
6
+
7
+ - threaded Pi chats tied to your projects
8
+ - a built-in terminal
9
+ - project and inbox sidebars
10
+ - git and diff workflows in the app, with some early-release actions still partial
11
+ - local desktop performance instead of a browser tab
12
+
13
+ ## Install / run
6
14
 
7
15
  ```bash
8
16
  npx howcode
@@ -11,7 +19,28 @@ npm i -g howcode
11
19
  howcode
12
20
  ```
13
21
 
14
- On first run, the launcher downloads the matching desktop build from GitHub Releases and caches it locally.
22
+ This npm package is a small launcher.
23
+
24
+ On first run, it downloads the matching desktop app for your platform from GitHub Releases and caches it locally.
25
+ After the first successful download, it can fall back to the cached app if release metadata is temporarily unavailable.
26
+
27
+ ## What you actually get
28
+
29
+ - macOS, Linux, and Windows desktop builds
30
+ - Linux AppImage artifacts for direct installs
31
+ - local cached installs after first download
32
+ - desktop builds that bundle Electron/Chromium for a more consistent renderer
33
+
34
+ ## Project
35
+
36
+ - App repo: https://github.com/IgorWarzocha/howcode
37
+ - Issues: https://github.com/IgorWarzocha/howcode/issues
38
+
39
+ ## Renderer note
40
+
41
+ Release builds now bundle Electron/Chromium on macOS, Linux, and Windows. The launcher no longer needs to inject the old Linux `WEBKIT_DISABLE_DMABUF_RENDERER` workaround.
42
+
43
+ Expect downloads to be larger than the native-webview builds in exchange for more consistent rendering behavior.
15
44
 
16
45
  ## Cache location
17
46
 
package/lib/howcode.js CHANGED
@@ -1,46 +1,49 @@
1
1
  const fs = require("node:fs");
2
2
  const fsp = require("node:fs/promises");
3
+ const crypto = require("node:crypto");
3
4
  const os = require("node:os");
4
5
  const path = require("node:path");
5
- const { spawn, spawnSync } = require("node:child_process");
6
+ const { spawn } = require("node:child_process");
6
7
  const { pipeline } = require("node:stream/promises");
7
8
  const { Readable } = require("node:stream");
9
+ const tar = require("tar");
8
10
 
9
11
  const packageJson = require("../package.json");
10
12
 
11
13
  const APP_NAME = packageJson.howcode.appName;
12
14
  const RELEASE_BASE_URL = process.env.HOWCODE_BASE_URL || packageJson.howcode.releaseBaseUrl;
15
+ const DOWNLOAD_TIMEOUT_MS = 5 * 60_000;
13
16
 
14
17
  const TARGETS = {
15
18
  "darwin:arm64": {
16
19
  os: "macos",
17
20
  arch: "arm64",
18
- executable: `${APP_NAME}.app/Contents/MacOS/launcher`,
21
+ executable: `${APP_NAME}.app/Contents/MacOS/${APP_NAME}`,
19
22
  },
20
23
  "darwin:x64": {
21
24
  os: "macos",
22
25
  arch: "x64",
23
- executable: `${APP_NAME}.app/Contents/MacOS/launcher`,
26
+ executable: `${APP_NAME}.app/Contents/MacOS/${APP_NAME}`,
24
27
  },
25
28
  "linux:arm64": {
26
29
  os: "linux",
27
30
  arch: "arm64",
28
- executable: `${APP_NAME}/bin/launcher`,
31
+ executable: `${APP_NAME}/${APP_NAME}`,
29
32
  },
30
33
  "linux:x64": {
31
34
  os: "linux",
32
35
  arch: "x64",
33
- executable: `${APP_NAME}/bin/launcher`,
36
+ executable: `${APP_NAME}/${APP_NAME}`,
34
37
  },
35
38
  "win32:arm64": {
36
39
  os: "win",
37
- arch: "x64",
38
- executable: `${APP_NAME}/bin/launcher.exe`,
40
+ arch: "arm64",
41
+ executable: `${APP_NAME}/${APP_NAME}.exe`,
39
42
  },
40
43
  "win32:x64": {
41
44
  os: "win",
42
45
  arch: "x64",
43
- executable: `${APP_NAME}/bin/launcher.exe`,
46
+ executable: `${APP_NAME}/${APP_NAME}.exe`,
44
47
  },
45
48
  };
46
49
 
@@ -108,9 +111,9 @@ async function fetchJson(url) {
108
111
  }
109
112
  }
110
113
 
111
- async function downloadFile(url, filePath) {
114
+ async function downloadFile(url, filePath, timeoutMs = DOWNLOAD_TIMEOUT_MS) {
112
115
  const controller = new AbortController();
113
- const timeout = setTimeout(() => controller.abort(), 60_000);
116
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
114
117
 
115
118
  try {
116
119
  const response = await fetch(url, { signal: controller.signal });
@@ -125,6 +128,12 @@ async function downloadFile(url, filePath) {
125
128
  }
126
129
  }
127
130
 
131
+ async function sha256File(filePath) {
132
+ const hash = crypto.createHash("sha256");
133
+ await pipeline(fs.createReadStream(filePath), hash);
134
+ return hash.digest("hex");
135
+ }
136
+
128
137
  async function resolveLatestRelease(target) {
129
138
  const updateUrl = `${RELEASE_BASE_URL}/stable-${target.os}-${target.arch}-update.json`;
130
139
  const metadata = await fetchJson(updateUrl);
@@ -151,16 +160,19 @@ async function installRelease(target, releaseInfo, paths) {
151
160
  await fsp.mkdir(tempRoot, { recursive: true });
152
161
  await fsp.mkdir(path.dirname(paths.installDir), { recursive: true });
153
162
  await downloadFile(releaseInfo.assetUrl, archivePath);
154
- await fsp.mkdir(tempInstallDir, { recursive: true });
155
-
156
- const extract = spawnSync("tar", ["-xzf", archivePath, "-C", tempInstallDir], {
157
- stdio: "inherit",
158
- });
159
163
 
160
- if (extract.status !== 0) {
161
- throw new Error("Failed to extract downloaded archive with `tar -xzf`.");
164
+ const archiveHash = await sha256File(archivePath);
165
+ if (archiveHash !== releaseInfo.hash) {
166
+ await fsp.rm(tempRoot, { recursive: true, force: true });
167
+ throw new Error(
168
+ `Downloaded archive hash mismatch. Expected ${releaseInfo.hash}, got ${archiveHash}.`,
169
+ );
162
170
  }
163
171
 
172
+ await fsp.mkdir(tempInstallDir, { recursive: true });
173
+
174
+ await tar.x({ file: archivePath, cwd: tempInstallDir });
175
+
164
176
  if (!fs.existsSync(path.join(tempInstallDir, target.executable))) {
165
177
  throw new Error(`Downloaded archive did not contain ${target.executable}.`);
166
178
  }
@@ -203,13 +215,22 @@ async function pruneOldVersions(cacheRoot, keepDir) {
203
215
  );
204
216
  }
205
217
 
206
- function launch(executablePath) {
207
- const child = spawn(executablePath, [], {
218
+ function spawnLauncherProcess(executablePath, options = {}) {
219
+ return spawn(executablePath, [], {
208
220
  detached: true,
209
- stdio: "ignore",
221
+ stdio: options.stdio || "ignore",
210
222
  windowsHide: true,
211
223
  cwd: path.dirname(executablePath),
224
+ env: {
225
+ ...process.env,
226
+ HOWCODE_REPO_ROOT: process.env.HOWCODE_REPO_ROOT || process.cwd(),
227
+ ...(options.env || {}),
228
+ },
212
229
  });
230
+ }
231
+
232
+ async function launch(executablePath) {
233
+ const child = spawnLauncherProcess(executablePath);
213
234
 
214
235
  child.unref();
215
236
  }
@@ -226,7 +247,7 @@ async function main() {
226
247
  releaseInfo = await resolveLatestRelease(target);
227
248
  } catch (error) {
228
249
  if (current?.executablePath && fs.existsSync(current.executablePath)) {
229
- launch(current.executablePath);
250
+ await launch(current.executablePath);
230
251
  return;
231
252
  }
232
253
 
@@ -239,7 +260,7 @@ async function main() {
239
260
  }
240
261
 
241
262
  await pruneOldVersions(cacheRoot, paths.installDir);
242
- launch(paths.executablePath);
263
+ await launch(paths.executablePath);
243
264
  }
244
265
 
245
266
  module.exports = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "howcode",
3
- "version": "0.1.0",
4
- "description": "Launch the Howcode desktop app from npm or npx.",
3
+ "version": "0.1.2",
4
+ "description": "Desktop coding app for Pi with projects, terminal, git, and diff workflows.",
5
5
  "license": "MIT",
6
6
  "author": "Igor Warzocha",
7
7
  "homepage": "https://github.com/IgorWarzocha/howcode",
@@ -16,10 +16,13 @@
16
16
  "howcode": "bin/howcode.js"
17
17
  },
18
18
  "files": ["bin", "lib", "README.md", "LICENSE"],
19
+ "dependencies": {
20
+ "tar": "^7.5.1"
21
+ },
19
22
  "engines": {
20
23
  "node": ">=18"
21
24
  },
22
- "keywords": ["desktop", "launcher", "pi", "coding"],
25
+ "keywords": ["desktop", "coding", "ai", "terminal", "git", "diff", "assistant"],
23
26
  "howcode": {
24
27
  "appName": "howcode",
25
28
  "releaseBaseUrl": "https://github.com/IgorWarzocha/howcode/releases/latest/download"