@fatdoge/wtree 0.1.1 → 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/api/ui/startUiDev.ts +53 -41
- package/dist-node/api/ui/startUiDev.js +42 -40
- package/package.json +3 -1
package/api/ui/startUiDev.ts
CHANGED
|
@@ -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
|
|
6
|
-
import
|
|
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
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
18
|
+
// Setup static file serving for UI
|
|
36
19
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
|
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.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "CLI + UI tool for managing git worktrees",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"git",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"react-dom": "^18.3.1",
|
|
68
68
|
"react-i18next": "^16.5.7",
|
|
69
69
|
"react-router-dom": "^7.3.0",
|
|
70
|
+
"serve-static": "^2.2.1",
|
|
70
71
|
"tailwind-merge": "^3.0.2",
|
|
71
72
|
"tailwindcss": "^3.4.17",
|
|
72
73
|
"vite": "^6.3.5",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"@types/node": "^22.15.30",
|
|
82
83
|
"@types/react": "^18.3.12",
|
|
83
84
|
"@types/react-dom": "^18.3.1",
|
|
85
|
+
"@types/serve-static": "^2.2.0",
|
|
84
86
|
"@vercel/node": "^5.3.6",
|
|
85
87
|
"babel-plugin-react-dev-locator": "^1.0.0",
|
|
86
88
|
"concurrently": "^9.2.0",
|