@fatdoge/wtree 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.
package/README.en.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # wtree
2
2
 
3
- English | [简体中文](./README.md)
3
+ English | [简体中文](https://github.com/FatDoge/wtree/blob/main/README.md)
4
4
 
5
5
  `wtree` is a local tool for managing git worktrees. It runs in an interactive command-line mode by default, and also supports a one-click local UI (TreeLab) for visual management.
6
6
 
@@ -15,14 +15,16 @@ English | [简体中文](./README.md)
15
15
 
16
16
  ## Installation
17
17
 
18
+ Install globally via npm (specify the public registry if you are using a private one):
19
+
18
20
  ```bash
19
- npm install -g wtree
21
+ npm install -g @fatdoge/wtree --registry=https://registry.npmjs.org/
20
22
  ```
21
23
 
22
24
  Or run directly using `npx`:
23
25
 
24
26
  ```bash
25
- npx wtree
27
+ npx --registry=https://registry.npmjs.org/ @fatdoge/wtree
26
28
  ```
27
29
 
28
30
  ## Usage
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # wtree
2
2
 
3
- [English](./README.en.md) | 简体中文
3
+ [English](https://github.com/FatDoge/wtree/blob/main/README.en.md) | 简体中文
4
4
 
5
5
  `wtree` 是一个本地工具,用于管理 git worktree。默认是交互式命令行模式,也支持一键启动 UI 页面 (TreeLab) 进行可视化操作。
6
6
 
@@ -15,16 +15,19 @@
15
15
 
16
16
  ## 安装
17
17
 
18
- 需要本机已安装 `git`,建议 Node.js `>= 18`。
18
+ 可以通过 npm 全局安装(如果使用了私有源,请指定官方源):
19
19
 
20
- 本地软链安装:
21
20
  ```bash
22
- pnpm install
23
- pnpm run build
24
- npm link
21
+ npm install -g @fatdoge/wtree --registry=https://registry.npmjs.org/
22
+ ```
23
+
24
+ 或者直接使用 `npx` 运行:
25
+
26
+ ```bash
27
+ npx --registry=https://registry.npmjs.org/ @fatdoge/wtree
25
28
  ```
26
29
 
27
- ## 快速开始
30
+ ## 运行与使用
28
31
 
29
32
  ### 交互式 CLI
30
33
 
@@ -1,29 +1,10 @@
1
- import os from 'node:os'
2
1
  import path from 'node:path'
3
- import fs from 'node:fs'
4
2
  import { fileURLToPath } from 'node:url'
5
- import { createServer } from 'vite'
6
- import type { ViteDevServer } from 'vite'
3
+ import express from 'express'
4
+ import serveStatic from 'serve-static'
7
5
  import { createApiApp } from '../createApiApp.js'
8
6
  import { openPath } from '../core/open.js'
9
7
 
10
- function findUiRoot(fromDir: string) {
11
- let cur = fromDir
12
- for (let i = 0; i < 10; i += 1) {
13
- const indexHtml = path.join(cur, 'index.html')
14
- const viteConfig = path.join(cur, 'vite.config.ts')
15
- if (exists(indexHtml) && exists(viteConfig)) return cur
16
- const parent = path.dirname(cur)
17
- if (parent === cur) break
18
- cur = parent
19
- }
20
- return fromDir
21
- }
22
-
23
- function exists(p: string) {
24
- return fs.existsSync(p)
25
- }
26
-
27
8
  export type UiDevHandle = {
28
9
  uiUrl: string
29
10
  close: () => Promise<void>
@@ -43,29 +24,62 @@ export async function startUiDevServer(options: {
43
24
  await new Promise<void>((resolve) => apiServer.once('listening', () => resolve()))
44
25
  const apiAddress = apiServer.address()
45
26
  const apiPort = typeof apiAddress === 'object' && apiAddress ? apiAddress.port : 0
27
+
28
+ // Set API URL for UI to consume
46
29
  process.env.WTUI_API_URL = `http://127.0.0.1:${apiPort}`
47
- process.env.VITE_WTUI_API_URL = process.env.WTUI_API_URL
48
30
 
31
+ // Setup static file serving for UI
49
32
  const here = path.dirname(fileURLToPath(import.meta.url))
50
- const uiRoot = findUiRoot(path.resolve(here, '..', '..'))
51
- const prevCwd = process.cwd()
52
- process.chdir(uiRoot)
33
+ // In production (dist-node/api/ui), UI assets are in ../../dist
34
+ const distPath = path.resolve(here, '..', '..', 'dist')
35
+
36
+ const uiApp = express()
37
+
38
+ // Proxy API requests to the API server
39
+ uiApp.use('/api', (req, res) => {
40
+ // Basic proxy implementation since we are in the same process
41
+ // But since we have createApiApp, we can just mount it?
42
+ // Actually createApiApp returns an express app, we can mount it directly.
43
+ // However, createApiApp includes cors and body parsers which might conflict if mounted twice?
44
+ // Let's just use the apiApp directly for API requests if possible,
45
+ // but here we are starting a separate UI server.
46
+ // A better approach: merge them into one server.
47
+ res.redirect(`http://127.0.0.1:${apiPort}/api${req.url}`)
48
+ })
53
49
 
54
- const vite: ViteDevServer = await createServer({
55
- root: uiRoot,
56
- configFile: path.join(uiRoot, 'vite.config.ts'),
57
- cacheDir: path.join(os.tmpdir(), 'wtui-vite-cache'),
58
- server: {
59
- host: '127.0.0.1',
60
- port: uiPort,
61
- strictPort: false,
62
- },
63
- clearScreen: false,
64
- appType: 'spa',
50
+ // Serve static files
51
+ uiApp.use(serveStatic(distPath))
52
+
53
+ // SPA fallback
54
+ uiApp.get('*', (req, res) => {
55
+ res.sendFile(path.join(distPath, 'index.html'))
65
56
  })
66
57
 
67
- await vite.listen()
68
- const url = vite.resolvedUrls?.local?.[0] || `http://127.0.0.1:${uiPort}/`
58
+ // Start UI server
59
+ // Note: We need to inject the API URL into the HTML or provide it via an endpoint
60
+ // The current UI implementation uses relative paths /api/..., so we need to proxy /api
61
+ // Let's rewrite the logic to use a single server for both API and UI
62
+
63
+ const combinedApp = express()
64
+
65
+ // 1. API routes
66
+ combinedApp.use('/api', createApiApp(() => repoRoot))
67
+
68
+ // 2. Static files
69
+ combinedApp.use(serveStatic(distPath))
70
+
71
+ // 3. SPA fallback
72
+ combinedApp.get('*', (req, res) => {
73
+ res.sendFile(path.join(distPath, 'index.html'))
74
+ })
75
+
76
+ // Close the temporary apiServer we created earlier since we are using combinedApp now
77
+ apiServer.close()
78
+
79
+ const combinedServer = combinedApp.listen(uiPort, '127.0.0.1')
80
+ await new Promise<void>((resolve) => combinedServer.once('listening', () => resolve()))
81
+
82
+ const url = `http://127.0.0.1:${uiPort}/`
69
83
 
70
84
  if (open) {
71
85
  openPath(url)
@@ -74,9 +88,7 @@ export async function startUiDevServer(options: {
74
88
  return {
75
89
  uiUrl: url,
76
90
  close: async () => {
77
- await vite.close()
78
- await new Promise<void>((resolve) => apiServer.close(() => resolve()))
79
- process.chdir(prevCwd)
91
+ await new Promise<void>((resolve) => combinedServer.close(() => resolve()))
80
92
  },
81
93
  }
82
94
  }
@@ -1,27 +1,9 @@
1
- import os from 'node:os';
2
1
  import path from 'node:path';
3
- import fs from 'node:fs';
4
2
  import { fileURLToPath } from 'node:url';
5
- import { createServer } from 'vite';
3
+ import express from 'express';
4
+ import serveStatic from 'serve-static';
6
5
  import { createApiApp } from '../createApiApp.js';
7
6
  import { openPath } from '../core/open.js';
8
- function findUiRoot(fromDir) {
9
- let cur = fromDir;
10
- for (let i = 0; i < 10; i += 1) {
11
- const indexHtml = path.join(cur, 'index.html');
12
- const viteConfig = path.join(cur, 'vite.config.ts');
13
- if (exists(indexHtml) && exists(viteConfig))
14
- return cur;
15
- const parent = path.dirname(cur);
16
- if (parent === cur)
17
- break;
18
- cur = parent;
19
- }
20
- return fromDir;
21
- }
22
- function exists(p) {
23
- return fs.existsSync(p);
24
- }
25
7
  export async function startUiDevServer(options) {
26
8
  const repoRoot = options.repoRoot;
27
9
  const open = options.open !== false;
@@ -31,35 +13,55 @@ export async function startUiDevServer(options) {
31
13
  await new Promise((resolve) => apiServer.once('listening', () => resolve()));
32
14
  const apiAddress = apiServer.address();
33
15
  const apiPort = typeof apiAddress === 'object' && apiAddress ? apiAddress.port : 0;
16
+ // Set API URL for UI to consume
34
17
  process.env.WTUI_API_URL = `http://127.0.0.1:${apiPort}`;
35
- process.env.VITE_WTUI_API_URL = process.env.WTUI_API_URL;
18
+ // Setup static file serving for UI
36
19
  const here = path.dirname(fileURLToPath(import.meta.url));
37
- const uiRoot = findUiRoot(path.resolve(here, '..', '..'));
38
- const prevCwd = process.cwd();
39
- process.chdir(uiRoot);
40
- const vite = await createServer({
41
- root: uiRoot,
42
- configFile: path.join(uiRoot, 'vite.config.ts'),
43
- cacheDir: path.join(os.tmpdir(), 'wtui-vite-cache'),
44
- server: {
45
- host: '127.0.0.1',
46
- port: uiPort,
47
- strictPort: false,
48
- },
49
- clearScreen: false,
50
- appType: 'spa',
20
+ // In production (dist-node/api/ui), UI assets are in ../../dist
21
+ const distPath = path.resolve(here, '..', '..', 'dist');
22
+ const uiApp = express();
23
+ // Proxy API requests to the API server
24
+ uiApp.use('/api', (req, res) => {
25
+ // Basic proxy implementation since we are in the same process
26
+ // But since we have createApiApp, we can just mount it?
27
+ // Actually createApiApp returns an express app, we can mount it directly.
28
+ // However, createApiApp includes cors and body parsers which might conflict if mounted twice?
29
+ // Let's just use the apiApp directly for API requests if possible,
30
+ // but here we are starting a separate UI server.
31
+ // A better approach: merge them into one server.
32
+ res.redirect(`http://127.0.0.1:${apiPort}/api${req.url}`);
33
+ });
34
+ // Serve static files
35
+ uiApp.use(serveStatic(distPath));
36
+ // SPA fallback
37
+ uiApp.get('*', (req, res) => {
38
+ res.sendFile(path.join(distPath, 'index.html'));
39
+ });
40
+ // Start UI server
41
+ // Note: We need to inject the API URL into the HTML or provide it via an endpoint
42
+ // The current UI implementation uses relative paths /api/..., so we need to proxy /api
43
+ // Let's rewrite the logic to use a single server for both API and UI
44
+ const combinedApp = express();
45
+ // 1. API routes
46
+ combinedApp.use('/api', createApiApp(() => repoRoot));
47
+ // 2. Static files
48
+ combinedApp.use(serveStatic(distPath));
49
+ // 3. SPA fallback
50
+ combinedApp.get('*', (req, res) => {
51
+ res.sendFile(path.join(distPath, 'index.html'));
51
52
  });
52
- await vite.listen();
53
- const url = vite.resolvedUrls?.local?.[0] || `http://127.0.0.1:${uiPort}/`;
53
+ // Close the temporary apiServer we created earlier since we are using combinedApp now
54
+ apiServer.close();
55
+ const combinedServer = combinedApp.listen(uiPort, '127.0.0.1');
56
+ await new Promise((resolve) => combinedServer.once('listening', () => resolve()));
57
+ const url = `http://127.0.0.1:${uiPort}/`;
54
58
  if (open) {
55
59
  openPath(url);
56
60
  }
57
61
  return {
58
62
  uiUrl: url,
59
63
  close: async () => {
60
- await vite.close();
61
- await new Promise((resolve) => apiServer.close(() => resolve()));
62
- process.chdir(prevCwd);
64
+ await new Promise((resolve) => combinedServer.close(() => resolve()));
63
65
  },
64
66
  };
65
67
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fatdoge/wtree",
3
3
  "private": false,
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "description": "CLI + UI tool for managing git worktrees",
6
6
  "keywords": [
7
7
  "git",
@@ -12,6 +12,14 @@
12
12
  ],
13
13
  "author": "fatdoge",
14
14
  "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/FatDoge/wtree.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/FatDoge/wtree/issues"
21
+ },
22
+ "homepage": "https://github.com/FatDoge/wtree#readme",
15
23
  "type": "module",
16
24
  "bin": {
17
25
  "wtree": "dist-node/api/cli/wtree.js"
@@ -59,6 +67,7 @@
59
67
  "react-dom": "^18.3.1",
60
68
  "react-i18next": "^16.5.7",
61
69
  "react-router-dom": "^7.3.0",
70
+ "serve-static": "^2.2.1",
62
71
  "tailwind-merge": "^3.0.2",
63
72
  "tailwindcss": "^3.4.17",
64
73
  "vite": "^6.3.5",
@@ -73,6 +82,7 @@
73
82
  "@types/node": "^22.15.30",
74
83
  "@types/react": "^18.3.12",
75
84
  "@types/react-dom": "^18.3.1",
85
+ "@types/serve-static": "^2.2.0",
76
86
  "@vercel/node": "^5.3.6",
77
87
  "babel-plugin-react-dev-locator": "^1.0.0",
78
88
  "concurrently": "^9.2.0",