build: ship compiled lib so the GitHub tarball installs without a build step
This commit is contained in:
+2
-1
@@ -1,6 +1,7 @@
|
||||
node_modules/
|
||||
lib/
|
||||
/lib/
|
||||
*.log
|
||||
*.map
|
||||
03_local/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
/* node_modules/xterm/css/xterm.css */
|
||||
.xterm {
|
||||
cursor: text;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.xterm.focus,
|
||||
.xterm:focus {
|
||||
outline: none;
|
||||
}
|
||||
.xterm .xterm-helpers {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
.xterm .xterm-helper-textarea {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -5;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
.xterm .composition-view {
|
||||
background: #000;
|
||||
color: #FFF;
|
||||
display: none;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
||||
.xterm .composition-view.active {
|
||||
display: block;
|
||||
}
|
||||
.xterm .xterm-viewport {
|
||||
background-color: #000;
|
||||
overflow-y: scroll;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.xterm .xterm-screen {
|
||||
position: relative;
|
||||
}
|
||||
.xterm .xterm-screen canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.xterm .xterm-scroll-area {
|
||||
visibility: hidden;
|
||||
}
|
||||
.xterm-char-measure-element {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -9999em;
|
||||
line-height: normal;
|
||||
}
|
||||
.xterm.enable-mouse-events {
|
||||
cursor: default;
|
||||
}
|
||||
.xterm.xterm-cursor-pointer,
|
||||
.xterm .xterm-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.xterm.column-select.focus {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.xterm .xterm-accessibility,
|
||||
.xterm .xterm-message {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.xterm-dim {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.xterm-underline-1 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.xterm-underline-2 {
|
||||
text-decoration: double underline;
|
||||
}
|
||||
.xterm-underline-3 {
|
||||
text-decoration: wavy underline;
|
||||
}
|
||||
.xterm-underline-4 {
|
||||
text-decoration: dotted underline;
|
||||
}
|
||||
.xterm-underline-5 {
|
||||
text-decoration: dashed underline;
|
||||
}
|
||||
.xterm-overline {
|
||||
text-decoration: overline;
|
||||
}
|
||||
.xterm-overline.xterm-underline-1 {
|
||||
text-decoration: overline underline;
|
||||
}
|
||||
.xterm-overline.xterm-underline-2 {
|
||||
text-decoration: overline double underline;
|
||||
}
|
||||
.xterm-overline.xterm-underline-3 {
|
||||
text-decoration: overline wavy underline;
|
||||
}
|
||||
.xterm-overline.xterm-underline-4 {
|
||||
text-decoration: overline dotted underline;
|
||||
}
|
||||
.xterm-overline.xterm-underline-5 {
|
||||
text-decoration: overline dashed underline;
|
||||
}
|
||||
.xterm-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.xterm-screen .xterm-decoration-container .xterm-decoration {
|
||||
z-index: 6;
|
||||
position: absolute;
|
||||
}
|
||||
.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
|
||||
z-index: 7;
|
||||
}
|
||||
.xterm-decoration-overview-ruler {
|
||||
z-index: 8;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.xterm-decoration-top {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
/*! Bundled license information:
|
||||
|
||||
xterm/css/xterm.css:
|
||||
(**
|
||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* https://github.com/chjj/term.js
|
||||
* @license MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* Originally forked from (with the author's permission):
|
||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||
* http://bellard.org/jslinux/
|
||||
* Copyright (c) 2011 Fabrice Bellard
|
||||
* The original design remains. The terminal itself
|
||||
* has been extended to include xterm CSI codes, among
|
||||
* other features.
|
||||
*)
|
||||
*/
|
||||
/*# sourceMappingURL=client.css.map */
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,535 @@
|
||||
// src/index.ts
|
||||
import { execFile } from "node:child_process";
|
||||
import { readdirSync, realpathSync } from "node:fs";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import { dirname, resolve as pathResolve, sep } from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
import { homedir } from "node:os";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
// template/dshell.css
|
||||
var dshell_default = `/* dsh-worktable \u539F\u751F\u76AE\u80A4 \xB7 DSH \u8BBE\u8BA1\u7CFB\u7EDF\u7EC4\u4EF6\u5E93
|
||||
\u7528\u6CD5\uFF1A<link rel="stylesheet" href="/api/worktable/template/dshell.css">
|
||||
\u6240\u6709\u989C\u8272\u8D70 DSH \u4E3B\u9898\u53D8\u91CF\uFF08--dsw-alias-*\uFF09\uFF0C\u81EA\u52A8\u9002\u914D\u660E\u6697\u4E3B\u9898\u3002 */
|
||||
:root { color-scheme: dark; }
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
body {
|
||||
background: var(--dsw-alias-bg-base, #0b0e14);
|
||||
color: var(--dsw-alias-label-primary, #e6e8eb);
|
||||
font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.dshell { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px; min-height: 100%; }
|
||||
/* \u6587\u5B57\u5C42\u7EA7 */
|
||||
.dshell-title { margin: 0; font-size: 16px; font-weight: 600; color: var(--dsw-alias-label-primary, #e6e8eb); }
|
||||
.dshell-sub { margin: 0; font-size: 12px; color: var(--dsw-alias-label-secondary, #9aa4b2); }
|
||||
.dshell-muted { color: var(--dsw-alias-label-tertiary, #6b7280); font-size: 11.5px; }
|
||||
/* \u5361\u7247 */
|
||||
.dshell-card { border: 1px solid var(--dsw-alias-border-l1, #262b36); border-radius: 10px; background: var(--dsw-alias-fill-l1, rgba(255,255,255,.02)); padding: 12px 14px; }
|
||||
.dshell-card + .dshell-card { margin-top: 10px; }
|
||||
/* \u6309\u94AE\uFF08\u7EFF\u8272\u4E3B\u6309\u94AE / \u5E7D\u7075\u6309\u94AE / \u5371\u9669\uFF09 */
|
||||
.dshell-btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; border-radius: 999px; border: 1px solid transparent; background: #3fb950; color: #0b0e14; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; }
|
||||
.dshell-btn:hover { filter: brightness(1.08); }
|
||||
.dshell-btnGhost { background: transparent; border-color: var(--dsw-alias-border-l1, #262b36); color: var(--dsw-alias-label-secondary, #9aa4b2); }
|
||||
.dshell-btnGhost:hover { color: var(--dsw-alias-label-primary, #e6e8eb); border-color: var(--dsw-alias-border-l2, #3a4150); }
|
||||
.dshell-btnDanger { background: transparent; border-color: #f85149; color: #f85149; }
|
||||
/* \u72B6\u6001\u5FBD\u6807\uFF08\u5706\u70B9 + \u6587\u5B57\uFF1B\u7EFF=\u5DF2\u5B8C\u6210 \u9EC4=\u5F85\u529E/\u5F85\u53D1\u5E03 \u7070=\u672A\u5F00\u59CB\uFF09 */
|
||||
.dshell-badge { display: inline-flex; align-items: center; gap: 6px; padding: 2px 10px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l1, #262b36); font-size: 11.5px; color: var(--dsw-alias-label-secondary, #9aa4b2); background: var(--dsw-alias-fill-l1, rgba(255,255,255,.03)); }
|
||||
.dshell-badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #6b7280); }
|
||||
.dshell-badgeDone { color: #3fb950; border-color: rgba(63,185,80,.4); }
|
||||
.dshell-badgeDone::before { background: #3fb950; box-shadow: 0 0 5px #3fb950; }
|
||||
.dshell-badgeWait { color: #d29922; border-color: rgba(210,153,34,.4); }
|
||||
.dshell-badgeWait::before { background: #d29922; box-shadow: 0 0 5px #d29922; }
|
||||
.dshell-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #6b7280); }
|
||||
.dshell-dotDone { background: #3fb950; box-shadow: 0 0 5px #3fb950; }
|
||||
.dshell-dotWait { background: #d29922; box-shadow: 0 0 5px #d29922; }
|
||||
/* \u6807\u7B7E\u9875 */
|
||||
.dshell-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #262b36); }
|
||||
.dshell-tab { padding: 7px 12px; font-size: 12.5px; color: var(--dsw-alias-label-secondary, #9aa4b2); cursor: pointer; border: none; background: none; font: inherit; border-bottom: 2px solid transparent; margin-bottom: -1px; }
|
||||
.dshell-tabOn { color: var(--dsw-alias-label-primary, #e6e8eb); border-bottom-color: var(--dsw-alias-state-accent-primary, #4f8ef7); }
|
||||
/* \u5217\u8868 */
|
||||
.dshell-list { display: flex; flex-direction: column; gap: 6px; }
|
||||
.dshell-listItem { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 12px; border: 1px solid var(--dsw-alias-border-l1, #262b36); border-radius: 8px; background: var(--dsw-alias-fill-l1, rgba(255,255,255,.02)); cursor: pointer; }
|
||||
.dshell-listItem:hover { border-color: var(--dsw-alias-border-l2, #3a4150); background: var(--dsw-alias-fill-l1, rgba(255,255,255,.05)); }
|
||||
.dshell-listItemTitle { font-size: 12.5px; color: var(--dsw-alias-label-primary, #e6e8eb); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.dshell-listItemMeta { flex: none; font-size: 11px; color: var(--dsw-alias-label-tertiary, #6b7280); }
|
||||
/* \u7F51\u683C / \u7EDF\u8BA1 */
|
||||
.dshell-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
|
||||
.dshell-stat { padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l1, #262b36); border-radius: 10px; background: var(--dsw-alias-fill-l1, rgba(255,255,255,.02)); }
|
||||
.dshell-statLabel { font-size: 11px; color: var(--dsw-alias-label-secondary, #9aa4b2); }
|
||||
.dshell-statValue { font-size: 20px; font-weight: 600; color: var(--dsw-alias-label-primary, #e6e8eb); }
|
||||
.dshell-statDelta { font-size: 11px; color: #3fb950; }
|
||||
/* \u8FDB\u5EA6\u6761 */
|
||||
.dshell-progress { height: 6px; border-radius: 3px; background: var(--dsw-alias-fill-l1, rgba(255,255,255,.06)); overflow: hidden; }
|
||||
.dshell-progressBar { height: 100%; border-radius: 3px; background: #3fb950; }
|
||||
/* \u8F93\u5165 */
|
||||
.dshell-input, .dshell-textarea { width: 100%; padding: 7px 10px; border: 1px solid var(--dsw-alias-border-l1, #262b36); border-radius: 8px; background: var(--dsw-alias-fill-l1, rgba(255,255,255,.03)); color: var(--dsw-alias-label-primary, #e6e8eb); font: inherit; font-size: 12.5px; outline: none; }
|
||||
.dshell-input:focus, .dshell-textarea:focus { border-color: var(--dsw-alias-state-accent-primary, #4f8ef7); }
|
||||
/* \u8868\u683C */
|
||||
.dshell-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||
.dshell-table th, .dshell-table td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--dsw-alias-border-l1, #262b36); }
|
||||
.dshell-table th { color: var(--dsw-alias-label-secondary, #9aa4b2); font-weight: 500; }
|
||||
/* \u952E\u503C\u5BF9 */
|
||||
.dshell-kv { display: flex; flex-direction: column; gap: 6px; }
|
||||
.dshell-kvRow { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }
|
||||
.dshell-kvKey { color: var(--dsw-alias-label-secondary, #9aa4b2); }
|
||||
.dshell-kvValue { color: var(--dsw-alias-label-primary, #e6e8eb); text-align: right; }
|
||||
/* \u5206\u5272\u7EBF */
|
||||
.dshell-divider { height: 1px; background: var(--dsw-alias-border-l1, #262b36); margin: 6px 0; }
|
||||
/* \u6EDA\u52A8\u6761 */
|
||||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,.14); border-radius: 5px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
`;
|
||||
|
||||
// template/dshell.html
|
||||
var dshell_default2 = '<!doctype html>\n<!-- dsh-worktable \u539F\u751F\u76AE\u80A4\u6A21\u677F\uFF1A\u65B0\u9875\u9762\u4EE5\u6B64\u4E3A\u57FA\u7840\uFF0C\u66FF\u6362\u4E0B\u9762\u793A\u4F8B\u5185\u5BB9\u5373\u53EF\u3002\n \u6837\u5F0F\u8868\u7531\u63D2\u4EF6\u63D0\u4F9B\uFF08\u968F\u4E3B\u9898\u81EA\u52A8\u9002\u914D\uFF09\uFF0C\u4E0D\u8981\u590D\u5236\u6216\u6539\u5199\u5B83\u3002 -->\n<html lang="zh-CN">\n<head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <title>\u6211\u7684\u7A97\u53E3</title>\n <link rel="stylesheet" href="/api/worktable/template/dshell.css" />\n</head>\n<body>\n <div class="dshell">\n <!-- \u6807\u9898\u533A -->\n <h1 class="dshell-title">\u7A97\u53E3\u6807\u9898</h1>\n <p class="dshell-sub">\u4E00\u53E5\u8BDD\u8BF4\u660E\u8FD9\u4E2A\u7A97\u53E3\u505A\u4EC0\u4E48\u3002</p>\n\n <!-- \u72B6\u6001\u5FBD\u6807\uFF1A\u5DF2\u5B8C\u6210 dshell-badgeDone / \u8FDB\u884C\u4E2D dshell-badgeWait / \u9ED8\u8BA4 -->\n <div>\n <span class="dshell-badge dshell-badgeDone">\u5DF2\u5B8C\u6210</span>\n <span class="dshell-badge dshell-badgeWait">\u8FDB\u884C\u4E2D</span>\n <span class="dshell-badge">\u672A\u5F00\u59CB</span>\n </div>\n\n <!-- \u6807\u7B7E\u9875 -->\n <div class="dshell-tabs">\n <button class="dshell-tab dshell-tabOn">\u6982\u89C8</button>\n <button class="dshell-tab">\u8BE6\u60C5</button>\n <button class="dshell-tab">\u8BBE\u7F6E</button>\n </div>\n\n <!-- \u7EDF\u8BA1\u5361\u7247\u7F51\u683C -->\n <div class="dshell-grid">\n <div class="dshell-stat">\n <div class="dshell-statLabel">\u603B\u6570</div>\n <div class="dshell-statValue">128</div>\n <div class="dshell-statDelta">+12.4%</div>\n </div>\n <div class="dshell-stat">\n <div class="dshell-statLabel">\u8FDB\u884C\u4E2D</div>\n <div class="dshell-statValue">7</div>\n </div>\n <div class="dshell-stat">\n <div class="dshell-statLabel">\u5DF2\u5B8C\u6210</div>\n <div class="dshell-statValue">121</div>\n </div>\n </div>\n\n <!-- \u5217\u8868 -->\n <div class="dshell-list">\n <div class="dshell-listItem">\n <span class="dshell-listItemTitle">\u6761\u76EE\u4E00\uFF1A\u793A\u4F8B\u5185\u5BB9\u6807\u9898</span>\n <span class="dshell-listItemMeta">\u6628\u5929</span>\n </div>\n <div class="dshell-listItem">\n <span class="dshell-listItemTitle">\u6761\u76EE\u4E8C\uFF1A\u793A\u4F8B\u5185\u5BB9\u6807\u9898</span>\n <span class="dshell-badge dshell-badgeDone">\u5DF2\u53D1\u5E03</span>\n </div>\n </div>\n\n <!-- \u5361\u7247 + \u952E\u503C\u5BF9 -->\n <div class="dshell-card">\n <h2 class="dshell-sub" style="margin:0 0 8px">\u8BE6\u60C5</h2>\n <div class="dshell-kv">\n <div class="dshell-kvRow"><span class="dshell-kvKey">\u5B57\u6BB5 A</span><span class="dshell-kvValue">\u503C A</span></div>\n <div class="dshell-kvRow"><span class="dshell-kvKey">\u5B57\u6BB5 B</span><span class="dshell-kvValue">\u503C B</span></div>\n </div>\n <div class="dshell-divider"></div>\n <div class="dshell-progress"><div class="dshell-progressBar" style="width:72%"></div></div>\n </div>\n\n <!-- \u64CD\u4F5C\u533A -->\n <div style="display:flex;gap:8px">\n <button class="dshell-btn">\u4E3B\u8981\u64CD\u4F5C</button>\n <button class="dshell-btn dshell-btnGhost">\u6B21\u8981\u64CD\u4F5C</button>\n </div>\n </div>\n</body>\n</html>\n';
|
||||
|
||||
// src/index.ts
|
||||
var name = "dsh-worktable";
|
||||
var inject = ["webServer", "sessions"];
|
||||
var HEALTH_PATH = "/api/worktable/health";
|
||||
var MAX_ENTRIES = 500;
|
||||
var FILE_TYPES = {
|
||||
html: "text/html; charset=utf-8",
|
||||
htm: "text/html; charset=utf-8",
|
||||
css: "text/css; charset=utf-8",
|
||||
js: "text/javascript; charset=utf-8",
|
||||
mjs: "text/javascript; charset=utf-8",
|
||||
json: "application/json; charset=utf-8",
|
||||
map: "application/json; charset=utf-8",
|
||||
md: "text/markdown; charset=utf-8",
|
||||
markdown: "text/markdown; charset=utf-8",
|
||||
txt: "text/plain; charset=utf-8",
|
||||
log: "text/plain; charset=utf-8",
|
||||
pdf: "application/pdf",
|
||||
svg: "image/svg+xml",
|
||||
png: "image/png",
|
||||
jpg: "image/jpeg",
|
||||
jpeg: "image/jpeg",
|
||||
gif: "image/gif",
|
||||
webp: "image/webp",
|
||||
bmp: "image/bmp",
|
||||
ico: "image/x-icon",
|
||||
avif: "image/avif",
|
||||
woff: "font/woff",
|
||||
woff2: "font/woff2",
|
||||
ttf: "font/ttf",
|
||||
otf: "font/otf",
|
||||
wasm: "application/wasm",
|
||||
mp3: "audio/mpeg",
|
||||
mp4: "video/mp4",
|
||||
webm: "video/webm"
|
||||
};
|
||||
var SITE_PREFIX = "/api/worktable/site";
|
||||
var TEMPLATE_PREFIX = "/api/worktable/template";
|
||||
function loadPkg(pkg) {
|
||||
const starts = /* @__PURE__ */ new Set();
|
||||
try {
|
||||
starts.add(dirname(fileURLToPath(import.meta.url)));
|
||||
} catch {
|
||||
}
|
||||
try {
|
||||
starts.add(realpathSync(dirname(fileURLToPath(import.meta.url))));
|
||||
} catch {
|
||||
}
|
||||
for (const start of starts) {
|
||||
let dir = start;
|
||||
while (dir && dir !== pathResolve(dir, "..")) {
|
||||
try {
|
||||
const req = createRequire(pathToFileURL(pathResolve(dir, "__wt_probe__.js")).href);
|
||||
return req(pkg);
|
||||
} catch {
|
||||
}
|
||||
dir = pathResolve(dir, "..");
|
||||
}
|
||||
}
|
||||
try {
|
||||
const profilesDir = pathResolve(homedir(), ".dsh", "profiles");
|
||||
for (const profile of readdirSync(profilesDir, { withFileTypes: true })) {
|
||||
if (!profile.isDirectory() && !profile.isSymbolicLink()) continue;
|
||||
const nm = pathResolve(profilesDir, profile.name, "node_modules");
|
||||
try {
|
||||
const req = createRequire(pathToFileURL(pathResolve(nm, "__wt_probe__.js")).href);
|
||||
return req(pkg);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function serverCwd(ctx, sessionId, clientCwd) {
|
||||
if (sessionId) {
|
||||
try {
|
||||
const headerCwd = ctx.sessions?.get?.(sessionId)?.header?.cwd;
|
||||
if (typeof headerCwd === "string" && headerCwd) return headerCwd;
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
if (typeof clientCwd === "string" && clientCwd) return clientCwd;
|
||||
return process.cwd();
|
||||
}
|
||||
function json(res, status, body) {
|
||||
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
||||
res.end(JSON.stringify(body));
|
||||
}
|
||||
async function readJsonBody(req) {
|
||||
const chunks = [];
|
||||
for await (const chunk of req) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
||||
const text = Buffer.concat(chunks).toString("utf8");
|
||||
if (!text) return {};
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
async function listDirectory(path) {
|
||||
const abs = pathResolve(path);
|
||||
const dirents = await readdir(abs, { withFileTypes: true });
|
||||
const entries = dirents.map((d) => ({ name: d.name, path: abs + sep + d.name, isDir: d.isDirectory(), hidden: d.name.startsWith(".") })).sort((a, b) => {
|
||||
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
||||
return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
|
||||
});
|
||||
const truncated = entries.length > MAX_ENTRIES;
|
||||
return { path: abs, entries: truncated ? entries.slice(0, MAX_ENTRIES) : entries, truncated };
|
||||
}
|
||||
function gitExec(args, cwd) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
execFile("git", args, { cwd, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => {
|
||||
if (err) reject(err);
|
||||
else resolvePromise(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
async function gitStatus(cwd) {
|
||||
try {
|
||||
const branchRaw = await gitExec(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
||||
const porcelain = await gitExec(["status", "--porcelain=v1", "-z"], cwd);
|
||||
const entries = porcelain.split("\0").filter((s) => s.length > 2).map((s) => ({ xy: s.slice(0, 2), path: s.slice(3) }));
|
||||
return { isRepo: true, branch: branchRaw.trim() || "HEAD", entries };
|
||||
} catch {
|
||||
return { isRepo: false, branch: void 0, entries: [] };
|
||||
}
|
||||
}
|
||||
function setupTerminal(webServer, ctx) {
|
||||
if (typeof webServer.registerUpgrade !== "function") return;
|
||||
const wsMod = loadPkg("ws");
|
||||
const ptyMod = loadPkg("node-pty");
|
||||
ctx.logger?.info?.("[dsh-worktable] term deps: ws=" + (wsMod ? "ok" : "MISSING") + " node-pty=" + (ptyMod ? "ok" : "MISSING"));
|
||||
if (!wsMod || !ptyMod) {
|
||||
ctx.logger?.warn("[dsh-worktable] \u7EC8\u7AEF\u8DEF\u7531\u672A\u6CE8\u518C\uFF1Aws/node-pty \u4E0D\u53EF\u7528");
|
||||
return;
|
||||
}
|
||||
const WebSocketServer = wsMod.WebSocketServer ?? wsMod.default?.WebSocketServer;
|
||||
if (!WebSocketServer) return;
|
||||
const pty = ptyMod.default ?? ptyMod;
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
const spawnShell = () => process.platform === "win32" ? { cmd: "powershell.exe", args: ["-NoLogo", "-NoProfile"] } : { cmd: process.env.SHELL || "/bin/bash", args: [] };
|
||||
const clampDim = (v, fallback) => Math.min(1024, Math.max(2, Number.isFinite(v) ? v : fallback));
|
||||
ctx.effect(() => webServer.registerUpgrade({
|
||||
path: "/api/worktable/term",
|
||||
handler: (req, socket, head) => {
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
const u = new URL(req.url ?? "/", "http://dsh.internal");
|
||||
const cwd = serverCwd(ctx, u.searchParams.get("sessionId") || void 0, u.searchParams.get("cwd") || void 0);
|
||||
const cols = clampDim(Number(u.searchParams.get("cols")), 80);
|
||||
const rows = clampDim(Number(u.searchParams.get("rows")), 24);
|
||||
let term = null;
|
||||
try {
|
||||
const shell = spawnShell();
|
||||
term = pty.spawn(shell.cmd, shell.args, { name: "xterm-256color", cols, rows, cwd, env: process.env });
|
||||
} catch (err) {
|
||||
try {
|
||||
ws.send("\r\n[worktable] \u7EC8\u7AEF\u542F\u52A8\u5931\u8D25\uFF1A" + String(err));
|
||||
} catch {
|
||||
}
|
||||
try {
|
||||
ws.close();
|
||||
} catch {
|
||||
}
|
||||
return;
|
||||
}
|
||||
term.onData((d) => {
|
||||
try {
|
||||
ws.send(d);
|
||||
} catch {
|
||||
}
|
||||
});
|
||||
term.onExit(() => {
|
||||
try {
|
||||
ws.close();
|
||||
} catch {
|
||||
}
|
||||
});
|
||||
ws.on("message", (raw) => {
|
||||
const text = String(raw);
|
||||
try {
|
||||
const msg = JSON.parse(text);
|
||||
if (msg && msg.type === "resize" && Number.isFinite(msg.cols) && Number.isFinite(msg.rows)) {
|
||||
term.resize(clampDim(msg.cols, cols), clampDim(msg.rows, rows));
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
try {
|
||||
term.write(text);
|
||||
} catch {
|
||||
}
|
||||
});
|
||||
ws.on("close", () => {
|
||||
try {
|
||||
term.kill();
|
||||
} catch {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}), "dsh-worktable: terminal upgrade");
|
||||
}
|
||||
function apply(ctx) {
|
||||
const webServer = ctx.webServer;
|
||||
if (!webServer) {
|
||||
ctx.logger?.warn("[dsh-worktable] ctx.webServer \u4E0D\u53EF\u7528\uFF08headless profile\uFF1F\uFF09\uFF0C\u8DF3\u8FC7\u670D\u52A1\u7AEF\u8DEF\u7531");
|
||||
return;
|
||||
}
|
||||
webServer.register({
|
||||
kind: "exact",
|
||||
path: HEALTH_PATH,
|
||||
handler: (_req, res) => {
|
||||
json(res, 200, { plugin: "dsh-worktable", version: "0.2.0", ok: true });
|
||||
}
|
||||
});
|
||||
webServer.register({
|
||||
kind: "exact",
|
||||
path: "/api/worktable/file",
|
||||
handler: async (req, res) => {
|
||||
try {
|
||||
const u = new URL(req.url ?? "/", "http://dsh.internal");
|
||||
const p = u.searchParams.get("path") || "";
|
||||
if (!p) {
|
||||
json(res, 400, { error: "missing path" });
|
||||
return;
|
||||
}
|
||||
const abs = pathResolve(p);
|
||||
const stat = await import("node:fs/promises").then((m) => m.stat(abs));
|
||||
if (stat.size > 20 * 1024 * 1024) {
|
||||
json(res, 413, { error: "file too large" });
|
||||
return;
|
||||
}
|
||||
const data = await readFile(abs);
|
||||
const ext = (abs.split(".").pop() || "").toLowerCase();
|
||||
const types = {
|
||||
html: "text/html; charset=utf-8",
|
||||
htm: "text/html; charset=utf-8",
|
||||
css: "text/css; charset=utf-8",
|
||||
js: "text/javascript; charset=utf-8",
|
||||
mjs: "text/javascript; charset=utf-8",
|
||||
json: "application/json; charset=utf-8",
|
||||
md: "text/markdown; charset=utf-8",
|
||||
markdown: "text/markdown; charset=utf-8",
|
||||
txt: "text/plain; charset=utf-8",
|
||||
log: "text/plain; charset=utf-8",
|
||||
pdf: "application/pdf",
|
||||
svg: "image/svg+xml",
|
||||
png: "image/png",
|
||||
jpg: "image/jpeg",
|
||||
jpeg: "image/jpeg",
|
||||
gif: "image/gif",
|
||||
webp: "image/webp",
|
||||
bmp: "image/bmp",
|
||||
ico: "image/x-icon"
|
||||
};
|
||||
res.writeHead(200, { "content-type": FILE_TYPES[ext] ?? "application/octet-stream", "cache-control": "no-store" });
|
||||
res.end(data);
|
||||
} catch (err) {
|
||||
json(res, 404, { error: String(err) });
|
||||
}
|
||||
}
|
||||
});
|
||||
webServer.register({
|
||||
kind: "prefix",
|
||||
path: TEMPLATE_PREFIX,
|
||||
handler: (req, res) => {
|
||||
try {
|
||||
if (req.method !== "GET") {
|
||||
res.writeHead(405);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
|
||||
const rel = pathname.slice(TEMPLATE_PREFIX.length);
|
||||
if (rel === "/dshell.css") {
|
||||
res.writeHead(200, { "content-type": "text/css; charset=utf-8", "cache-control": "no-store" });
|
||||
res.end(dshell_default);
|
||||
} else {
|
||||
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
||||
res.end(dshell_default2);
|
||||
}
|
||||
} catch (err) {
|
||||
res.writeHead(404);
|
||||
res.end(String(err));
|
||||
}
|
||||
}
|
||||
});
|
||||
webServer.register({
|
||||
kind: "prefix",
|
||||
path: SITE_PREFIX,
|
||||
handler: async (req, res) => {
|
||||
try {
|
||||
if (req.method !== "GET") {
|
||||
res.writeHead(405);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
|
||||
const segs = pathname.slice(SITE_PREFIX.length).split("/").filter(Boolean);
|
||||
const rootToken = decodeURIComponent(segs.shift() ?? "");
|
||||
const rel = segs.map((s) => {
|
||||
try {
|
||||
return decodeURIComponent(s);
|
||||
} catch {
|
||||
return s;
|
||||
}
|
||||
}).join("/");
|
||||
if (!rootToken) {
|
||||
json(res, 400, { error: "missing root" });
|
||||
return;
|
||||
}
|
||||
const root = pathResolve(rootToken);
|
||||
let abs = pathResolve(root, rel);
|
||||
if (abs !== root && !abs.startsWith(root + sep)) {
|
||||
json(res, 403, { error: "outside root" });
|
||||
return;
|
||||
}
|
||||
const statMod = await import("node:fs/promises");
|
||||
let info = await statMod.stat(abs).catch(() => null);
|
||||
if (info && info.isDirectory()) {
|
||||
abs = pathResolve(abs, "index.html");
|
||||
info = await statMod.stat(abs).catch(() => null);
|
||||
}
|
||||
if (!info || !info.isFile()) {
|
||||
json(res, 404, { error: "not found" });
|
||||
return;
|
||||
}
|
||||
if (info.size > 40 * 1024 * 1024) {
|
||||
json(res, 413, { error: "file too large" });
|
||||
return;
|
||||
}
|
||||
const data = await readFile(abs);
|
||||
const ext = (abs.split(".").pop() || "").toLowerCase();
|
||||
res.writeHead(200, { "content-type": FILE_TYPES[ext] ?? "application/octet-stream", "cache-control": "no-store" });
|
||||
res.end(data);
|
||||
} catch (err) {
|
||||
json(res, 404, { error: String(err) });
|
||||
}
|
||||
}
|
||||
});
|
||||
webServer.register({
|
||||
kind: "exact",
|
||||
path: "/api/worktable/fs",
|
||||
handler: async (req, res) => {
|
||||
try {
|
||||
const body = await readJsonBody(req);
|
||||
const path = typeof body.path === "string" && body.path ? body.path : serverCwd(ctx, body.sessionId, body.cwd);
|
||||
json(res, 200, await listDirectory(path));
|
||||
} catch (err) {
|
||||
json(res, 500, { path: "", entries: [], truncated: false, error: String(err) });
|
||||
}
|
||||
}
|
||||
});
|
||||
webServer.register({
|
||||
kind: "exact",
|
||||
path: "/api/worktable/workspaces",
|
||||
handler: async (_req, res) => {
|
||||
try {
|
||||
const file = pathResolve(homedir(), ".dsh", "storages", "workspace.json");
|
||||
const raw = await readFile(file, "utf8");
|
||||
json(res, 200, JSON.parse(raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw));
|
||||
} catch (err) {
|
||||
json(res, 404, { error: String(err) });
|
||||
}
|
||||
}
|
||||
});
|
||||
webServer.register({
|
||||
kind: "exact",
|
||||
path: "/api/worktable/write",
|
||||
handler: async (req, res) => {
|
||||
try {
|
||||
if (req.method !== "POST") {
|
||||
res.writeHead(405);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
const body = await readJsonBody(req);
|
||||
const p = typeof body.path === "string" ? body.path : "";
|
||||
const content = typeof body.content === "string" ? body.content : "";
|
||||
if (!p) {
|
||||
json(res, 400, { error: "missing path" });
|
||||
return;
|
||||
}
|
||||
if (content.length > 20 * 1024 * 1024) {
|
||||
json(res, 413, { error: "content too large" });
|
||||
return;
|
||||
}
|
||||
const abs = pathResolve(p);
|
||||
await import("node:fs/promises").then((m) => m.writeFile(abs, content, "utf8"));
|
||||
json(res, 200, { ok: true });
|
||||
} catch (err) {
|
||||
json(res, 500, { error: String(err) });
|
||||
}
|
||||
}
|
||||
});
|
||||
webServer.register({
|
||||
kind: "exact",
|
||||
path: "/api/worktable/mkdir",
|
||||
handler: async (req, res) => {
|
||||
try {
|
||||
if (req.method !== "POST") {
|
||||
res.writeHead(405);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
const body = await readJsonBody(req);
|
||||
const p = typeof body.path === "string" ? body.path.trim() : "";
|
||||
if (!p) {
|
||||
json(res, 400, { error: "missing path" });
|
||||
return;
|
||||
}
|
||||
const abs = pathResolve(p);
|
||||
const fsx = await import("node:fs/promises");
|
||||
const parent = abs.split(/[\\/]/).slice(0, -1).join("\\") || "\\";
|
||||
try {
|
||||
await fsx.access(parent);
|
||||
} catch {
|
||||
json(res, 400, { error: "parent not found" });
|
||||
return;
|
||||
}
|
||||
await fsx.mkdir(abs);
|
||||
json(res, 200, { ok: true, path: abs });
|
||||
} catch (err) {
|
||||
json(res, err?.code === "EEXIST" ? 200 : 500, err?.code === "EEXIST" ? { ok: true, exists: true } : { error: String(err) });
|
||||
}
|
||||
}
|
||||
});
|
||||
webServer.register({
|
||||
kind: "exact",
|
||||
path: "/api/worktable/git",
|
||||
handler: async (req, res) => {
|
||||
const body = await readJsonBody(req);
|
||||
const cwd = serverCwd(ctx, body.sessionId, body.cwd);
|
||||
json(res, 200, await gitStatus(cwd));
|
||||
}
|
||||
});
|
||||
setupTerminal(webServer, ctx);
|
||||
}
|
||||
export {
|
||||
HEALTH_PATH,
|
||||
apply,
|
||||
inject,
|
||||
name
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
Reference in New Issue
Block a user