feat(项目): 添加项目路径支持并更新相关功能

添加项目路径配置功能,支持在创建项目时指定路径前缀
更新静态服务器和URL生成逻辑以支持路径前缀
在项目详情页显示项目路径信息
添加http-proxy-middleware依赖
清理测试数据文件
This commit is contained in:
MaeLucia 2026-02-27 12:31:28 +08:00
parent 3c562fae2a
commit 5e59f350b6
12 changed files with 298 additions and 197 deletions

View File

@ -34,6 +34,18 @@
></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">项目路径</label>
<input
v-model="form.path"
type="text"
class="input"
placeholder="例如:/demo/lot-demo"
/>
<p class="text-xs text-gray-500 mt-1">"/"开头的相对路径用于生成完整访问URL</p>
<p v-if="form.path && pathPreview" class="text-xs text-primary-600 mt-1">预览: {{ pathPreview }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">项目文件 *</label>
@ -137,7 +149,7 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, computed } from 'vue'
import { useProjectStore } from '../stores/project'
const emit = defineEmits(['close', 'success'])
@ -152,9 +164,20 @@ const uploadProgress = ref(0)
const form = reactive({
name: '',
description: '',
path: '',
files: []
})
const pathPreview = computed(() => {
if (!form.path) return ''
let path = form.path.trim()
if (!path.startsWith('/')) {
path = '/' + path
}
const baseUrl = window.location.origin
return `${baseUrl}${path}`
})
const triggerFileInput = () => {
fileInput.value?.click()
}
@ -188,6 +211,11 @@ const handleSubmit = async () => {
return
}
let projectPath = form.path.trim()
if (projectPath && !projectPath.startsWith('/')) {
projectPath = '/' + projectPath
}
loading.value = true
error.value = ''
uploadProgress.value = 0
@ -196,6 +224,7 @@ const handleSubmit = async () => {
const formData = new FormData()
formData.append('name', form.name)
formData.append('description', form.description)
formData.append('path', projectPath)
form.files.forEach(file => {
formData.append('files', file)

View File

@ -150,6 +150,10 @@
<dt class="text-sm text-gray-500">更新时间</dt>
<dd class="text-sm font-medium text-gray-900 mt-1">{{ formatDateTime(project.updatedAt) }}</dd>
</div>
<div v-if="project.path">
<dt class="text-sm text-gray-500">项目路径</dt>
<dd class="text-sm font-medium text-gray-900 mt-1 font-mono">{{ project.path }}</dd>
</div>
<div v-if="project.lastDeployed">
<dt class="text-sm text-gray-500">最后部署</dt>
<dd class="text-sm font-medium text-gray-900 mt-1">{{ formatDateTime(project.lastDeployed) }}</dd>

View File

@ -1,52 +0,0 @@
[
{
"timestamp": "2026-02-23T04:58:11.547Z",
"message": "Extracted dist.zip",
"type": "success"
},
{
"timestamp": "2026-02-23T04:58:11.965Z",
"message": "Project \"loT-demo\" created with 4 file(s)",
"type": "success"
},
{
"timestamp": "2026-02-23T04:58:19.431Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T04:58:36.076Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:04:00.097Z",
"message": "Project started on port 9001",
"type": "success"
},
{
"timestamp": "2026-02-23T05:04:56.824Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:43:59.981Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:48:32.620Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:48:33.171Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:57:37.014Z",
"message": "Project stopped",
"type": "info"
}
]

View File

@ -1,62 +0,0 @@
[
{
"timestamp": "2026-02-23T05:03:36.023Z",
"message": "Extracted dist.zip",
"type": "success"
},
{
"timestamp": "2026-02-23T05:03:36.120Z",
"message": "Project \"rushe-demo\" created with 3 file(s)",
"type": "success"
},
{
"timestamp": "2026-02-23T05:03:44.138Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:05:11.825Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:09:33.720Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:28:22.481Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:28:23.286Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:28:28.136Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:44:04.885Z",
"message": "Project started on port 9001",
"type": "success"
},
{
"timestamp": "2026-02-23T05:48:30.005Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:48:37.640Z",
"message": "Project started on port 9001",
"type": "success"
},
{
"timestamp": "2026-02-23T05:57:34.667Z",
"message": "Project stopped",
"type": "info"
}
]

View File

@ -1,63 +1 @@
[
{
"id": "1771822691510",
"name": "loT-demo",
"description": "智慧楼宇demo演示",
"files": [
{
"name": "assets\\index-csziwVZn.css",
"size": 28375,
"hash": "6e9e99cb0161d7c6ca05aa04d421c424"
},
{
"name": "assets\\index-Ii3hNxW3.js",
"size": 2046014,
"hash": "3cd425f9f0c4e0540ac6c2d63280f821"
},
{
"name": "index.html",
"size": 787,
"hash": "907b8098cc7855605955cddd567f7c74"
},
{
"name": "vite.svg",
"size": 633,
"hash": "3208e1714d3d4aff82769f80952b2960"
}
],
"status": "stopped",
"port": null,
"url": null,
"createdAt": "2026-02-23T04:58:11.965Z",
"updatedAt": "2026-02-23T04:58:11.965Z",
"lastDeployed": "2026-02-23T05:48:33.170Z"
},
{
"id": "1771823016002",
"name": "rushe-demo",
"description": "入舍家装改造demo",
"files": [
{
"name": "assets\\index-DtArR4F6.js",
"size": 416147,
"hash": "9c55c3f179e1bc5bf03baf01ccaa7479"
},
{
"name": "assets\\index-gvDC5mON.css",
"size": 25321,
"hash": "7da89693bde15e541205dbc19aee46a5"
},
{
"name": "index.html",
"size": 572,
"hash": "831e97d4a3f00617843c3c797098842b"
}
],
"status": "stopped",
"port": null,
"url": null,
"createdAt": "2026-02-23T05:03:36.120Z",
"updatedAt": "2026-02-23T05:03:36.120Z",
"lastDeployed": "2026-02-23T05:48:37.640Z"
}
]
[]

211
package-lock.json generated
View File

@ -16,6 +16,7 @@
"crypto": "^1.0.1",
"dotenv": "^17.3.1",
"express": "^4.18.2",
"http-proxy-middleware": "^3.0.5",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1"
},
@ -32,6 +33,24 @@
"node": ">=6.9.0"
}
},
"node_modules/@types/http-proxy": {
"version": "1.17.17",
"resolved": "https://registry.npmmirror.com/@types/http-proxy/-/http-proxy-1.17.17.tgz",
"integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "25.3.2",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.3.2.tgz",
"integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -191,6 +210,18 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@ -620,6 +651,12 @@
"node": ">= 0.6"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
},
"node_modules/events-universal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
@ -678,6 +715,18 @@
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
@ -695,6 +744,26 @@
"node": ">= 0.8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -853,6 +922,60 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"license": "MIT",
"dependencies": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-proxy-middleware": {
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz",
"integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==",
"license": "MIT",
"dependencies": {
"@types/http-proxy": "^1.17.15",
"debug": "^4.3.6",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.3",
"is-plain-object": "^5.0.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/http-proxy-middleware/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/http-proxy-middleware/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -887,6 +1010,15 @@
"node": ">= 0.10"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@ -896,6 +1028,36 @@
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@ -1056,6 +1218,19 @@
"node": ">= 0.6"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@ -1206,6 +1381,18 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -1289,6 +1476,12 @@
"node": ">=0.10.0"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
@ -1559,6 +1752,18 @@
"b4a": "^1.6.4"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -1599,6 +1804,12 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@ -25,6 +25,7 @@
"crypto": "^1.0.1",
"dotenv": "^17.3.1",
"express": "^4.18.2",
"http-proxy-middleware": "^3.0.5",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1"
},

View File

@ -6,11 +6,18 @@ const config = {
projectPortStart: parseInt(process.env.PROJECT_PORT_START, 10) || 9000,
projectPortEnd: parseInt(process.env.PROJECT_PORT_END, 10) || 9100,
getProjectUrl(port) {
getProjectUrl(port, projectPath = '') {
let baseUrl;
if (this.baseDomain) {
return `http://${this.baseDomain}:${port}`;
baseUrl = `http://${this.baseDomain}:${port}`;
} else {
baseUrl = `http://localhost:${port}`;
}
return `http://localhost:${port}`;
if (projectPath) {
return `${baseUrl}${projectPath}`;
}
return baseUrl;
},
getBaseUrl() {

View File

@ -2,6 +2,7 @@ const express = require('express');
const { authMiddleware } = require('../middleware/auth');
const projectService = require('../services/projectService');
const processManager = require('../services/processManager');
const config = require('../config');
const router = express.Router();
@ -18,11 +19,13 @@ router.post('/:id/start', authMiddleware, async (req, res) => {
try {
const result = await processManager.startProject(project);
projectService.updateProjectStatus(project.id, 'running', result.port, result.url);
const url = config.getProjectUrl(result.port, project.path);
projectService.updateProjectStatus(project.id, 'running', result.port, url);
res.json({
message: 'Project started successfully',
url: result.url,
url: url,
port: result.port
});
} catch (error) {

View File

@ -40,7 +40,7 @@ router.get('/:id', authMiddleware, (req, res) => {
});
router.post('/', authMiddleware, upload.array('files'), (req, res) => {
const { name, description } = req.body;
const { name, description, path: projectPath } = req.body;
const files = req.files;
if (!name) {
@ -51,9 +51,15 @@ router.post('/', authMiddleware, upload.array('files'), (req, res) => {
return res.status(400).json({ error: 'At least one file is required' });
}
let normalizedPath = projectPath ? projectPath.trim() : '';
if (normalizedPath && !normalizedPath.startsWith('/')) {
normalizedPath = '/' + normalizedPath;
}
const project = projectService.createProject({
name,
description: description || '',
path: normalizedPath,
files
});

View File

@ -73,19 +73,33 @@ const detectProjectType = (projectDir) => {
return 'static';
};
const startStaticServer = (projectDir, port) => {
const startStaticServer = (projectDir, port, projectPath = '') => {
return new Promise((resolve, reject) => {
const app = express();
app.use(express.static(projectDir));
app.get('*', (req, res) => {
const indexPath = path.join(projectDir, 'index.html');
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} else {
res.status(404).send('index.html not found');
}
});
if (projectPath) {
app.use(projectPath, express.static(projectDir));
app.get(`${projectPath}/*`, (req, res) => {
const indexPath = path.join(projectDir, 'index.html');
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} else {
res.status(404).send('index.html not found');
}
});
} else {
app.use(express.static(projectDir));
app.get('*', (req, res) => {
const indexPath = path.join(projectDir, 'index.html');
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} else {
res.status(404).send('index.html not found');
}
});
}
const server = app.listen(port, () => {
resolve({ server, port });
@ -110,12 +124,13 @@ const startProject = async (project) => {
const port = await findAvailablePort();
const projectType = detectProjectType(projectDir);
const projectPath = project.path || '';
let processInfo = null;
switch (projectType) {
case 'static':
const { server } = await startStaticServer(projectDir, port);
const { server } = await startStaticServer(projectDir, port, projectPath);
processInfo = {
type: 'static',
server,
@ -162,7 +177,7 @@ const startProject = async (project) => {
runningProcesses.set(project.id, processInfo);
const url = config.getProjectUrl(port);
const url = config.getProjectUrl(port, projectPath);
return { port, url };
};

View File

@ -62,7 +62,7 @@ const getProjectById = (id) => {
return projects.find(p => p.id === id);
};
const createProject = ({ name, description, files }) => {
const createProject = ({ name, description, path: projectPath, files }) => {
const projects = getProjects();
const id = Date.now().toString();
@ -100,6 +100,7 @@ const createProject = ({ name, description, files }) => {
id,
name,
description,
path: projectPath || '',
files: allFiles.map(f => ({
name: path.relative(projectDir, f),
size: fs.statSync(f).size,