howcode 0.1.5 → 0.1.6

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/bin/howcode.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- require("../lib/howcode.js").main();
3
+ require('../lib/howcode.js').main()
package/lib/howcode.js CHANGED
@@ -1,305 +1,305 @@
1
- const fs = require("node:fs");
2
- const fsp = require("node:fs/promises");
3
- const crypto = require("node:crypto");
4
- const os = require("node:os");
5
- const path = require("node:path");
6
- const { spawn } = require("node:child_process");
7
- const { pipeline } = require("node:stream/promises");
8
- const { Readable } = require("node:stream");
9
- const tar = require("tar");
10
-
11
- const packageJson = require("../package.json");
12
-
13
- const APP_NAME = packageJson.howcode.appName;
14
- const RELEASE_BASE_URL = process.env.HOWCODE_BASE_URL || packageJson.howcode.releaseBaseUrl;
15
- const DOWNLOAD_TIMEOUT_MS = 5 * 60_000;
1
+ const fs = require('node:fs')
2
+ const fsp = require('node:fs/promises')
3
+ const crypto = require('node:crypto')
4
+ const os = require('node:os')
5
+ const path = require('node:path')
6
+ const { spawn } = require('node:child_process')
7
+ const { pipeline } = require('node:stream/promises')
8
+ const { Readable } = require('node:stream')
9
+ const tar = require('tar')
10
+
11
+ const packageJson = require('../package.json')
12
+
13
+ const APP_NAME = packageJson.howcode.appName
14
+ const RELEASE_BASE_URL = process.env.HOWCODE_BASE_URL || packageJson.howcode.releaseBaseUrl
15
+ const DOWNLOAD_TIMEOUT_MS = 5 * 60_000
16
16
 
17
17
  const TARGETS = {
18
- "darwin:arm64": {
19
- os: "macos",
20
- arch: "arm64",
18
+ 'darwin:arm64': {
19
+ os: 'macos',
20
+ arch: 'arm64',
21
21
  executable: `${APP_NAME}.app/Contents/MacOS/${APP_NAME}`,
22
22
  },
23
- "darwin:x64": {
24
- os: "macos",
25
- arch: "x64",
23
+ 'darwin:x64': {
24
+ os: 'macos',
25
+ arch: 'x64',
26
26
  executable: `${APP_NAME}.app/Contents/MacOS/${APP_NAME}`,
27
27
  },
28
- "linux:arm64": {
29
- os: "linux",
30
- arch: "arm64",
28
+ 'linux:arm64': {
29
+ os: 'linux',
30
+ arch: 'arm64',
31
31
  executable: `${APP_NAME}/${APP_NAME}`,
32
32
  },
33
- "linux:x64": {
34
- os: "linux",
35
- arch: "x64",
33
+ 'linux:x64': {
34
+ os: 'linux',
35
+ arch: 'x64',
36
36
  executable: `${APP_NAME}/${APP_NAME}`,
37
37
  },
38
- "win32:arm64": {
39
- os: "win",
40
- arch: "arm64",
38
+ 'win32:arm64': {
39
+ os: 'win',
40
+ arch: 'arm64',
41
41
  executable: `${APP_NAME}/${APP_NAME}.exe`,
42
42
  },
43
- "win32:x64": {
44
- os: "win",
45
- arch: "x64",
43
+ 'win32:x64': {
44
+ os: 'win',
45
+ arch: 'x64',
46
46
  executable: `${APP_NAME}/${APP_NAME}.exe`,
47
47
  },
48
- };
48
+ }
49
49
 
50
50
  function readJsonIfPresent(filePath) {
51
51
  try {
52
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
52
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
53
53
  } catch {
54
- return null;
54
+ return null
55
55
  }
56
56
  }
57
57
 
58
58
  function getTarget() {
59
- const key = `${process.platform}:${process.arch}`;
60
- const target = TARGETS[key];
59
+ const key = `${process.platform}:${process.arch}`
60
+ const target = TARGETS[key]
61
61
  if (!target) {
62
- throw new Error(`Unsupported platform: ${process.platform} ${process.arch}`);
62
+ throw new Error(`Unsupported platform: ${process.platform} ${process.arch}`)
63
63
  }
64
- return target;
64
+ return target
65
65
  }
66
66
 
67
67
  function getCacheRoot() {
68
68
  if (process.env.HOWCODE_CACHE_DIR) {
69
- return process.env.HOWCODE_CACHE_DIR;
69
+ return process.env.HOWCODE_CACHE_DIR
70
70
  }
71
71
 
72
- if (process.platform === "win32") {
72
+ if (process.platform === 'win32') {
73
73
  return path.join(
74
- process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"),
74
+ process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
75
75
  APP_NAME,
76
- );
76
+ )
77
77
  }
78
78
 
79
- if (process.platform === "darwin") {
80
- return path.join(os.homedir(), "Library", "Caches", APP_NAME);
79
+ if (process.platform === 'darwin') {
80
+ return path.join(os.homedir(), 'Library', 'Caches', APP_NAME)
81
81
  }
82
82
 
83
- return path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"), APP_NAME);
83
+ return path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'), APP_NAME)
84
84
  }
85
85
 
86
86
  function getPaths(target, releaseInfo) {
87
- const cacheRoot = getCacheRoot();
88
- const versionsRoot = path.join(cacheRoot, "versions");
89
- const releaseKey = `${releaseInfo.version}-${releaseInfo.hash}`;
90
- const installDir = path.join(versionsRoot, releaseKey);
91
- const launcherWorkingDirectory = path.dirname(path.join(installDir, target.executable));
87
+ const cacheRoot = getCacheRoot()
88
+ const versionsRoot = path.join(cacheRoot, 'versions')
89
+ const releaseKey = `${releaseInfo.version}-${releaseInfo.hash}`
90
+ const installDir = path.join(versionsRoot, releaseKey)
91
+ const launcherWorkingDirectory = path.dirname(path.join(installDir, target.executable))
92
92
  return {
93
93
  cacheRoot,
94
- currentFile: path.join(cacheRoot, "current.json"),
94
+ currentFile: path.join(cacheRoot, 'current.json'),
95
95
  windowsCommandFile: path.join(cacheRoot, `${APP_NAME}.cmd`),
96
96
  launcherWorkingDirectory,
97
97
  installDir,
98
98
  executablePath: path.join(installDir, target.executable),
99
- };
99
+ }
100
100
  }
101
101
 
102
102
  function getWindowsStartMenuShortcutPath() {
103
- const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
104
- return path.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", `${APP_NAME}.lnk`);
103
+ const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
104
+ return path.join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', `${APP_NAME}.lnk`)
105
105
  }
106
106
 
107
107
  function escapeWindowsCommandValue(value) {
108
- return value.replace(/%/g, "%%");
108
+ return value.replace(/%/g, '%%')
109
109
  }
110
110
 
111
111
  function getWindowsScriptHostPath(executableName) {
112
- const systemRoot = process.env.SystemRoot || process.env.SYSTEMROOT;
112
+ const systemRoot = process.env.SystemRoot || process.env.SYSTEMROOT
113
113
  if (systemRoot) {
114
- return path.join(systemRoot, "System32", executableName);
114
+ return path.join(systemRoot, 'System32', executableName)
115
115
  }
116
116
 
117
- return path.join("C:", "Windows", "System32", executableName);
117
+ return path.join('C:', 'Windows', 'System32', executableName)
118
118
  }
119
119
 
120
120
  async function writeWindowsCommandLauncher(paths) {
121
121
  const commandContents = [
122
- "@echo off",
123
- "chcp 65001 >nul",
124
- "setlocal",
125
- "set NODE_TLS_REJECT_UNAUTHORIZED=",
126
- `set \"HOWCODE_EXE=${escapeWindowsCommandValue(paths.executablePath)}\"`,
127
- `set \"HOWCODE_REPO_ROOT=${escapeWindowsCommandValue(paths.launcherWorkingDirectory)}\"`,
122
+ '@echo off',
123
+ 'chcp 65001 >nul',
124
+ 'setlocal',
125
+ 'set NODE_TLS_REJECT_UNAUTHORIZED=',
126
+ `set "HOWCODE_EXE=${escapeWindowsCommandValue(paths.executablePath)}"`,
127
+ `set "HOWCODE_REPO_ROOT=${escapeWindowsCommandValue(paths.launcherWorkingDirectory)}"`,
128
128
  'if not exist "%HOWCODE_EXE%" (',
129
129
  ` echo ${APP_NAME}: installed app executable was not found.`,
130
130
  ` echo Run npx ${APP_NAME} to repair the local install.`,
131
- " exit /b 1",
132
- ")",
131
+ ' exit /b 1',
132
+ ')',
133
133
  'start "" /D "%HOWCODE_REPO_ROOT%" "%HOWCODE_EXE%"',
134
- "endlocal",
135
- "",
136
- ].join("\r\n");
134
+ 'endlocal',
135
+ '',
136
+ ].join('\r\n')
137
137
 
138
- await fsp.writeFile(paths.windowsCommandFile, commandContents, "utf8");
138
+ await fsp.writeFile(paths.windowsCommandFile, commandContents, 'utf8')
139
139
  }
140
140
 
141
141
  async function createWindowsStartMenuShortcut(paths) {
142
- const shortcutPath = getWindowsStartMenuShortcutPath();
142
+ const shortcutPath = getWindowsStartMenuShortcutPath()
143
143
  const shortcutScriptPath = path.join(
144
144
  paths.cacheRoot,
145
145
  `.create-${APP_NAME}-shortcut-${process.pid}.js`,
146
- );
147
- await fsp.mkdir(path.dirname(shortcutPath), { recursive: true });
146
+ )
147
+ await fsp.mkdir(path.dirname(shortcutPath), { recursive: true })
148
148
  await fsp.writeFile(
149
149
  shortcutScriptPath,
150
150
  [
151
151
  "var shell = WScript.CreateObject('WScript.Shell');",
152
- "var shortcut = shell.CreateShortcut(WScript.Arguments.Item(0));",
153
- "shortcut.TargetPath = WScript.Arguments.Item(1);",
154
- "shortcut.WorkingDirectory = WScript.Arguments.Item(2);",
155
- "shortcut.IconLocation = WScript.Arguments.Item(3);",
156
- "shortcut.Description = WScript.Arguments.Item(4);",
157
- "shortcut.Save();",
158
- "",
159
- ].join("\r\n"),
160
- "utf8",
161
- );
152
+ 'var shortcut = shell.CreateShortcut(WScript.Arguments.Item(0));',
153
+ 'shortcut.TargetPath = WScript.Arguments.Item(1);',
154
+ 'shortcut.WorkingDirectory = WScript.Arguments.Item(2);',
155
+ 'shortcut.IconLocation = WScript.Arguments.Item(3);',
156
+ 'shortcut.Description = WScript.Arguments.Item(4);',
157
+ 'shortcut.Save();',
158
+ '',
159
+ ].join('\r\n'),
160
+ 'utf8',
161
+ )
162
162
 
163
163
  try {
164
164
  await new Promise((resolve, reject) => {
165
165
  const child = spawn(
166
- getWindowsScriptHostPath("cscript.exe"),
166
+ getWindowsScriptHostPath('cscript.exe'),
167
167
  [
168
- "//NoLogo",
168
+ '//NoLogo',
169
169
  shortcutScriptPath,
170
170
  shortcutPath,
171
171
  paths.windowsCommandFile,
172
172
  paths.launcherWorkingDirectory,
173
173
  `${paths.executablePath},0`,
174
- "howcode",
174
+ 'howcode',
175
175
  ],
176
- { stdio: "ignore", windowsHide: true },
177
- );
178
- child.on("error", reject);
179
- child.on("exit", (code) => {
176
+ { stdio: 'ignore', windowsHide: true },
177
+ )
178
+ child.on('error', reject)
179
+ child.on('exit', (code) => {
180
180
  if (code === 0) {
181
- resolve();
181
+ resolve()
182
182
  } else {
183
- reject(new Error(`cscript exited with code ${code} while creating Start Menu shortcut.`));
183
+ reject(new Error(`cscript exited with code ${code} while creating Start Menu shortcut.`))
184
184
  }
185
- });
186
- });
185
+ })
186
+ })
187
187
  } finally {
188
- await fsp.rm(shortcutScriptPath, { force: true });
188
+ await fsp.rm(shortcutScriptPath, { force: true })
189
189
  }
190
190
 
191
- return shortcutPath;
191
+ return shortcutPath
192
192
  }
193
193
 
194
194
  async function ensureWindowsLaunchIntegration(target, paths) {
195
- if (target.os !== "win") {
196
- return true;
195
+ if (target.os !== 'win') {
196
+ return true
197
197
  }
198
198
 
199
199
  try {
200
- await writeWindowsCommandLauncher(paths);
200
+ await writeWindowsCommandLauncher(paths)
201
201
  } catch (error) {
202
- const message = error instanceof Error ? error.message : String(error);
203
- console.warn(`${APP_NAME}: could not create command launcher: ${message}`);
204
- console.warn(`${APP_NAME}: Start Menu shortcut was not updated.`);
205
- return false;
202
+ const message = error instanceof Error ? error.message : String(error)
203
+ console.warn(`${APP_NAME}: could not create command launcher: ${message}`)
204
+ console.warn(`${APP_NAME}: Start Menu shortcut was not updated.`)
205
+ return false
206
206
  }
207
207
 
208
208
  try {
209
- await createWindowsStartMenuShortcut(paths);
210
- return true;
209
+ await createWindowsStartMenuShortcut(paths)
210
+ return true
211
211
  } catch (error) {
212
- const message = error instanceof Error ? error.message : String(error);
213
- console.warn(`${APP_NAME}: could not create Start Menu shortcut: ${message}`);
214
- console.warn(`${APP_NAME}: you can still relaunch with ${paths.windowsCommandFile}`);
215
- return false;
212
+ const message = error instanceof Error ? error.message : String(error)
213
+ console.warn(`${APP_NAME}: could not create Start Menu shortcut: ${message}`)
214
+ console.warn(`${APP_NAME}: you can still relaunch with ${paths.windowsCommandFile}`)
215
+ return false
216
216
  }
217
217
  }
218
218
 
219
219
  async function fetchJson(url) {
220
- const controller = new AbortController();
221
- const timeout = setTimeout(() => controller.abort(), 5000);
220
+ const controller = new AbortController()
221
+ const timeout = setTimeout(() => controller.abort(), 5000)
222
222
 
223
223
  try {
224
- const response = await fetch(url, { signal: controller.signal });
224
+ const response = await fetch(url, { signal: controller.signal })
225
225
  if (!response.ok) {
226
- throw new Error(`HTTP ${response.status} while fetching ${url}`);
226
+ throw new Error(`HTTP ${response.status} while fetching ${url}`)
227
227
  }
228
- return await response.json();
228
+ return await response.json()
229
229
  } finally {
230
- clearTimeout(timeout);
230
+ clearTimeout(timeout)
231
231
  }
232
232
  }
233
233
 
234
234
  async function downloadFile(url, filePath, timeoutMs = DOWNLOAD_TIMEOUT_MS) {
235
- const controller = new AbortController();
236
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
235
+ const controller = new AbortController()
236
+ const timeout = setTimeout(() => controller.abort(), timeoutMs)
237
237
 
238
238
  try {
239
- const response = await fetch(url, { signal: controller.signal });
240
- if (!response.ok || !response.body) {
241
- throw new Error(`HTTP ${response.status} while downloading ${url}`);
239
+ const response = await fetch(url, { signal: controller.signal })
240
+ if (!(response.ok && response.body)) {
241
+ throw new Error(`HTTP ${response.status} while downloading ${url}`)
242
242
  }
243
243
 
244
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
245
- await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(filePath));
244
+ await fsp.mkdir(path.dirname(filePath), { recursive: true })
245
+ await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(filePath))
246
246
  } finally {
247
- clearTimeout(timeout);
247
+ clearTimeout(timeout)
248
248
  }
249
249
  }
250
250
 
251
251
  async function sha256File(filePath) {
252
- const hash = crypto.createHash("sha256");
253
- await pipeline(fs.createReadStream(filePath), hash);
254
- return hash.digest("hex");
252
+ const hash = crypto.createHash('sha256')
253
+ await pipeline(fs.createReadStream(filePath), hash)
254
+ return hash.digest('hex')
255
255
  }
256
256
 
257
257
  async function resolveLatestRelease(target) {
258
- const updateUrl = `${RELEASE_BASE_URL}/stable-${target.os}-${target.arch}-update.json`;
259
- const metadata = await fetchJson(updateUrl);
260
- if (!metadata || typeof metadata.version !== "string" || typeof metadata.hash !== "string") {
261
- throw new Error(`Invalid release metadata from ${updateUrl}`);
258
+ const updateUrl = `${RELEASE_BASE_URL}/stable-${target.os}-${target.arch}-update.json`
259
+ const metadata = await fetchJson(updateUrl)
260
+ if (!metadata || typeof metadata.version !== 'string' || typeof metadata.hash !== 'string') {
261
+ throw new Error(`Invalid release metadata from ${updateUrl}`)
262
262
  }
263
263
 
264
264
  return {
265
265
  version: metadata.version,
266
266
  hash: metadata.hash,
267
267
  assetUrl: `${RELEASE_BASE_URL}/${APP_NAME}-${target.os}-${target.arch}.tar.gz`,
268
- };
268
+ }
269
269
  }
270
270
 
271
271
  async function installRelease(target, releaseInfo, paths) {
272
- const tempRoot = path.join(paths.cacheRoot, `.tmp-${Date.now()}-${process.pid}`);
273
- const tempInstallDir = `${paths.installDir}.partial`;
274
- const archivePath = path.join(tempRoot, `${APP_NAME}-${target.os}-${target.arch}.tar.gz`);
272
+ const tempRoot = path.join(paths.cacheRoot, `.tmp-${Date.now()}-${process.pid}`)
273
+ const tempInstallDir = `${paths.installDir}.partial`
274
+ const archivePath = path.join(tempRoot, `${APP_NAME}-${target.os}-${target.arch}.tar.gz`)
275
275
 
276
- console.log(`Downloading ${APP_NAME} ${releaseInfo.version} for ${target.os}-${target.arch}...`);
276
+ console.log(`Downloading ${APP_NAME} ${releaseInfo.version} for ${target.os}-${target.arch}...`)
277
277
 
278
- await fsp.rm(tempRoot, { recursive: true, force: true });
279
- await fsp.rm(tempInstallDir, { recursive: true, force: true });
280
- await fsp.mkdir(tempRoot, { recursive: true });
281
- await fsp.mkdir(path.dirname(paths.installDir), { recursive: true });
282
- await downloadFile(releaseInfo.assetUrl, archivePath);
278
+ await fsp.rm(tempRoot, { recursive: true, force: true })
279
+ await fsp.rm(tempInstallDir, { recursive: true, force: true })
280
+ await fsp.mkdir(tempRoot, { recursive: true })
281
+ await fsp.mkdir(path.dirname(paths.installDir), { recursive: true })
282
+ await downloadFile(releaseInfo.assetUrl, archivePath)
283
283
 
284
- const archiveHash = await sha256File(archivePath);
284
+ const archiveHash = await sha256File(archivePath)
285
285
  if (archiveHash !== releaseInfo.hash) {
286
- await fsp.rm(tempRoot, { recursive: true, force: true });
286
+ await fsp.rm(tempRoot, { recursive: true, force: true })
287
287
  throw new Error(
288
288
  `Downloaded archive hash mismatch. Expected ${releaseInfo.hash}, got ${archiveHash}.`,
289
- );
289
+ )
290
290
  }
291
291
 
292
- await fsp.mkdir(tempInstallDir, { recursive: true });
292
+ await fsp.mkdir(tempInstallDir, { recursive: true })
293
293
 
294
- await tar.x({ file: archivePath, cwd: tempInstallDir });
294
+ await tar.x({ file: archivePath, cwd: tempInstallDir })
295
295
 
296
296
  if (!fs.existsSync(path.join(tempInstallDir, target.executable))) {
297
- throw new Error(`Downloaded archive did not contain ${target.executable}.`);
297
+ throw new Error(`Downloaded archive did not contain ${target.executable}.`)
298
298
  }
299
299
 
300
- await fsp.rm(paths.installDir, { recursive: true, force: true });
301
- await fsp.rename(tempInstallDir, paths.installDir);
302
- await fsp.rm(tempRoot, { recursive: true, force: true });
300
+ await fsp.rm(paths.installDir, { recursive: true, force: true })
301
+ await fsp.rename(tempInstallDir, paths.installDir)
302
+ await fsp.rm(tempRoot, { recursive: true, force: true })
303
303
 
304
304
  await fsp.writeFile(
305
305
  paths.currentFile,
@@ -313,17 +313,17 @@ async function installRelease(target, releaseInfo, paths) {
313
313
  null,
314
314
  2,
315
315
  ),
316
- );
316
+ )
317
317
  }
318
318
 
319
319
  async function pruneOldVersions(cacheRoot, keepDir) {
320
- const versionsRoot = path.join(cacheRoot, "versions");
321
- let entries = [];
320
+ const versionsRoot = path.join(cacheRoot, 'versions')
321
+ let entries = []
322
322
 
323
323
  try {
324
- entries = await fsp.readdir(versionsRoot, { withFileTypes: true });
324
+ entries = await fsp.readdir(versionsRoot, { withFileTypes: true })
325
325
  } catch {
326
- return;
326
+ return
327
327
  }
328
328
 
329
329
  await Promise.all(
@@ -332,7 +332,7 @@ async function pruneOldVersions(cacheRoot, keepDir) {
332
332
  .map((entry) => path.join(versionsRoot, entry.name))
333
333
  .filter((dirPath) => dirPath !== keepDir)
334
334
  .map((dirPath) => fsp.rm(dirPath, { recursive: true, force: true })),
335
- );
335
+ )
336
336
  }
337
337
 
338
338
  function spawnLauncherProcess(executablePath, options = {}) {
@@ -340,77 +340,77 @@ function spawnLauncherProcess(executablePath, options = {}) {
340
340
  ...process.env,
341
341
  HOWCODE_REPO_ROOT: process.env.HOWCODE_REPO_ROOT || process.cwd(),
342
342
  ...(options.env || {}),
343
- };
344
- Reflect.deleteProperty(env, "NODE_TLS_REJECT_UNAUTHORIZED");
343
+ }
344
+ Reflect.deleteProperty(env, 'NODE_TLS_REJECT_UNAUTHORIZED')
345
345
 
346
346
  return spawn(executablePath, [], {
347
347
  detached: true,
348
- stdio: options.stdio || "ignore",
348
+ stdio: options.stdio || 'ignore',
349
349
  windowsHide: true,
350
350
  cwd: path.dirname(executablePath),
351
351
  env,
352
- });
352
+ })
353
353
  }
354
354
 
355
355
  async function launch(executablePath) {
356
- const child = spawnLauncherProcess(executablePath);
356
+ const child = spawnLauncherProcess(executablePath)
357
357
 
358
- child.unref();
358
+ child.unref()
359
359
  }
360
360
 
361
361
  async function main() {
362
- const target = getTarget();
363
- const cacheRoot = getCacheRoot();
364
- await fsp.mkdir(cacheRoot, { recursive: true });
362
+ const target = getTarget()
363
+ const cacheRoot = getCacheRoot()
364
+ await fsp.mkdir(cacheRoot, { recursive: true })
365
365
 
366
- const current = readJsonIfPresent(path.join(cacheRoot, "current.json"));
366
+ const current = readJsonIfPresent(path.join(cacheRoot, 'current.json'))
367
367
 
368
- let releaseInfo = null;
368
+ let releaseInfo = null
369
369
  try {
370
- releaseInfo = await resolveLatestRelease(target);
370
+ releaseInfo = await resolveLatestRelease(target)
371
371
  } catch (error) {
372
372
  if (current?.executablePath && fs.existsSync(current.executablePath)) {
373
373
  await ensureWindowsLaunchIntegration(target, {
374
374
  cacheRoot,
375
- currentFile: path.join(cacheRoot, "current.json"),
375
+ currentFile: path.join(cacheRoot, 'current.json'),
376
376
  windowsCommandFile: path.join(cacheRoot, `${APP_NAME}.cmd`),
377
377
  installDir: current.installDir || path.dirname(path.dirname(current.executablePath)),
378
378
  launcherWorkingDirectory: path.dirname(current.executablePath),
379
379
  executablePath: current.executablePath,
380
- });
381
- await launch(current.executablePath);
382
- return;
380
+ })
381
+ await launch(current.executablePath)
382
+ return
383
383
  }
384
384
 
385
- throw error;
385
+ throw error
386
386
  }
387
387
 
388
- const paths = getPaths(target, releaseInfo);
389
- const didInstall = !fs.existsSync(paths.executablePath);
388
+ const paths = getPaths(target, releaseInfo)
389
+ const didInstall = !fs.existsSync(paths.executablePath)
390
390
  if (!fs.existsSync(paths.executablePath)) {
391
- await installRelease(target, releaseInfo, paths);
391
+ await installRelease(target, releaseInfo, paths)
392
392
  }
393
393
 
394
- const launchIntegrationReady = await ensureWindowsLaunchIntegration(target, paths);
395
- if (target.os === "win" && didInstall && launchIntegrationReady) {
396
- console.log(`${APP_NAME}: installed. You can relaunch it from the Windows Start Menu.`);
394
+ const launchIntegrationReady = await ensureWindowsLaunchIntegration(target, paths)
395
+ if (target.os === 'win' && didInstall && launchIntegrationReady) {
396
+ console.log(`${APP_NAME}: installed. You can relaunch it from the Windows Start Menu.`)
397
397
  }
398
- await pruneOldVersions(cacheRoot, paths.installDir);
399
- await launch(paths.executablePath);
398
+ await pruneOldVersions(cacheRoot, paths.installDir)
399
+ await launch(paths.executablePath)
400
400
  }
401
401
 
402
402
  module.exports = {
403
403
  main: async () => {
404
404
  try {
405
- await main();
405
+ await main()
406
406
  } catch (error) {
407
- const message = error instanceof Error ? error.message : String(error);
408
- console.error(`howcode: ${message}`);
409
- process.exit(1);
407
+ const message = error instanceof Error ? error.message : String(error)
408
+ console.error(`howcode: ${message}`)
409
+ process.exit(1)
410
410
  }
411
411
  },
412
- };
412
+ }
413
413
 
414
414
  if (require.main === module) {
415
- module.exports.main();
415
+ module.exports.main()
416
416
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "howcode",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Desktop coding app for Pi with projects, terminal, git, and diff workflows.",
5
5
  "license": "MIT",
6
6
  "author": "Igor Warzocha",
@@ -15,14 +15,27 @@
15
15
  "bin": {
16
16
  "howcode": "bin/howcode.js"
17
17
  },
18
- "files": ["bin", "lib", "README.md", "LICENSE"],
18
+ "files": [
19
+ "bin",
20
+ "lib",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
19
24
  "dependencies": {
20
25
  "tar": "^7.5.1"
21
26
  },
22
27
  "engines": {
23
28
  "node": ">=18"
24
29
  },
25
- "keywords": ["desktop", "coding", "ai", "terminal", "git", "diff", "assistant"],
30
+ "keywords": [
31
+ "desktop",
32
+ "coding",
33
+ "ai",
34
+ "terminal",
35
+ "git",
36
+ "diff",
37
+ "assistant"
38
+ ],
26
39
  "howcode": {
27
40
  "appName": "howcode",
28
41
  "releaseBaseUrl": "https://github.com/IgorWarzocha/howcode/releases/latest/download"