usight 0.1.4 → 0.1.5

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/usight.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { install, uninstall, list, setPath } from '../lib/widgets.js';
4
4
  import { getConfig } from '../lib/config.js';
5
5
  import { getWidgetsDir } from '../lib/ubersicht.js';
6
+ import { detectRuntime } from '../lib/runtime.js';
6
7
 
7
8
  const [,, cmd, ...args] = process.argv;
8
9
 
@@ -22,9 +23,11 @@ const commands = {
22
23
  },
23
24
  config: () => {
24
25
  const cfg = getConfig();
26
+ const runtime = detectRuntime();
25
27
  console.log(`Cache path: ${cfg.cachePath}`);
26
28
  console.log(`Widgets dir: ${getWidgetsDir()}`);
27
29
  console.log(`Installed: ${Object.keys(cfg.widgets).length} widget(s)`);
30
+ if (runtime) console.log(`Runtime hint: ${runtime} (informational only)`);
28
31
  },
29
32
  help: () => help(),
30
33
  };
package/lib/github.js CHANGED
@@ -1,12 +1,11 @@
1
- import https from 'https';
2
- import { mkdirSync } from 'fs';
3
- import { spawn } from 'child_process';
1
+ import { gunzipSync } from 'zlib';
2
+ import { mkdirSync, writeFileSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
4
 
5
5
  // Parse "owner/repo" or full GitHub URL into { owner, repo }
6
6
  export function parseRepo(input) {
7
7
  if (!input) throw new Error('No repository specified');
8
8
 
9
- // strip trailing .git and leading https://github.com/
10
9
  const clean = input
11
10
  .replace(/^https?:\/\/github\.com\//, '')
12
11
  .replace(/\.git$/, '')
@@ -20,39 +19,58 @@ export function parseRepo(input) {
20
19
  return { owner: parts[0], repo: parts[1] };
21
20
  }
22
21
 
23
- // Download GitHub tarball and extract into destDir (streaming, no temp file).
24
- export function downloadAndExtract(owner, repo, destDir) {
22
+ // Download GitHub tarball and extract into destDir.
23
+ // Uses native fetch (Node ≥18) + built-in zlib — no external tools, no git.
24
+ export async function downloadAndExtract(owner, repo, destDir) {
25
25
  mkdirSync(destDir, { recursive: true });
26
26
 
27
27
  const url = `https://api.github.com/repos/${owner}/${repo}/tarball/HEAD`;
28
-
29
- return new Promise((resolve, reject) => {
30
- const get = (url) => {
31
- https.get(url, { headers: { 'User-Agent': 'usight-cli' } }, (res) => {
32
- if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
33
- get(res.headers.location);
34
- return;
35
- }
36
- if (res.statusCode !== 200) {
37
- reject(new Error(`GitHub returned HTTP ${res.statusCode} for ${owner}/${repo}`));
38
- return;
39
- }
40
-
41
- // pipe into tar — strips the top-level GitHub-generated directory
42
- const tar = spawn('tar', ['-xzf', '-', '-C', destDir, '--strip-components=1'], {
43
- stdio: ['pipe', 'inherit', 'inherit'],
44
- });
45
-
46
- res.pipe(tar.stdin);
47
- res.on('error', reject);
48
- tar.on('error', reject);
49
- tar.on('close', (code) => {
50
- if (code === 0) resolve();
51
- else reject(new Error(`tar exited with code ${code}`));
52
- });
53
- }).on('error', reject);
54
- };
55
-
56
- get(url);
28
+ const res = await fetch(url, {
29
+ redirect: 'follow',
30
+ headers: { 'User-Agent': 'usight-cli' },
57
31
  });
32
+
33
+ if (!res.ok) {
34
+ throw new Error(`GitHub returned HTTP ${res.status} for ${owner}/${repo}`);
35
+ }
36
+
37
+ const compressed = Buffer.from(await res.arrayBuffer());
38
+ extractTar(gunzipSync(compressed), destDir);
39
+ }
40
+
41
+ // Minimal tar extractor — enough for GitHub repo archives.
42
+ // ponytail: skips symlinks/hardlinks (rare in widget repos); add if you hit a case
43
+ export function extractTar(data, destDir) {
44
+ let offset = 0;
45
+
46
+ while (offset + 512 <= data.length) {
47
+ const header = data.subarray(offset, offset + 512);
48
+ offset += 512;
49
+
50
+ if (header.every(b => b === 0)) break; // end-of-archive sentinel
51
+
52
+ const name = nullStr(header.subarray(0, 100));
53
+ const size = parseInt(nullStr(header.subarray(124, 136)).trim(), 8) || 0;
54
+ const type = String.fromCharCode(header[156]);
55
+
56
+ // GitHub archives wrap everything in a top-level "owner-repo-sha/" dir — strip it
57
+ const stripped = name.split('/').slice(1).join('/');
58
+
59
+ if (stripped) {
60
+ const dest = join(destDir, stripped);
61
+ if (type === '5' || stripped.endsWith('/')) {
62
+ mkdirSync(dest, { recursive: true });
63
+ } else if (type === '0' || type === '\0') {
64
+ mkdirSync(dirname(dest), { recursive: true });
65
+ writeFileSync(dest, data.subarray(offset, offset + size));
66
+ }
67
+ }
68
+
69
+ offset += Math.ceil(size / 512) * 512;
70
+ }
71
+ }
72
+
73
+ function nullStr(buf) {
74
+ const end = buf.indexOf(0);
75
+ return end === -1 ? buf.toString('utf8') : buf.subarray(0, end).toString('utf8');
58
76
  }
package/lib/runtime.js ADDED
@@ -0,0 +1,13 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ // Detect available JS runtimes in preference order.
4
+ // Informational only — usight's install logic never delegates to any of these.
5
+ export function detectRuntime() {
6
+ for (const tool of ['bun', 'pnpm', 'npm']) {
7
+ try {
8
+ execSync(`${tool} --version`, { stdio: 'pipe' });
9
+ return tool;
10
+ } catch {}
11
+ }
12
+ return null;
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usight",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Manage installs for Übersicht widgets from GitHub — no git required",
5
5
  "type": "module",
6
6
  "bin": {