@openspecui/server 2.0.0 → 2.1.0
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/dist/index.mjs +62 -60
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { createServer as createServer$1 } from "node:net";
|
|
2
2
|
import { serve } from "@hono/node-server";
|
|
3
|
-
import { CliExecutor, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, OpenSpecAdapter, OpenSpecWatcher, OpsxKernel, PtyClientMessageSchema, ReactiveContext, TerminalConfigSchema, TerminalRendererEngineSchema, contextStorage, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getWatcherRuntimeStatus, initWatcherPool, isWatcherPoolInitialized,
|
|
3
|
+
import { CliExecutor, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, OpenSpecAdapter, OpenSpecWatcher, OpsxKernel, PtyClientMessageSchema, ReactiveContext, TerminalConfigSchema, TerminalRendererEngineSchema, contextStorage, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getWatcherRuntimeStatus, initWatcherPool, isWatcherPoolInitialized, reactiveReadDir, reactiveReadFile, sniffGlobalCli } from "@openspecui/core";
|
|
4
4
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
5
5
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
6
6
|
import { Hono } from "hono";
|
|
7
7
|
import { cors } from "hono/cors";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
8
11
|
import { WebSocketServer } from "ws";
|
|
9
12
|
import * as pty from "@lydell/node-pty";
|
|
10
13
|
import { EventEmitter } from "events";
|
|
@@ -13,8 +16,7 @@ import { initTRPC } from "@trpc/server";
|
|
|
13
16
|
import { observable } from "@trpc/server/observable";
|
|
14
17
|
import { execFile } from "node:child_process";
|
|
15
18
|
import { EventEmitter as EventEmitter$1 } from "node:events";
|
|
16
|
-
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
17
|
-
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
19
|
+
import { mkdir, rm, stat, writeFile } from "node:fs/promises";
|
|
18
20
|
import { promisify } from "node:util";
|
|
19
21
|
import { z } from "zod";
|
|
20
22
|
import { NodeWorkerSearchProvider } from "@openspecui/search/node";
|
|
@@ -999,43 +1001,51 @@ function endDashboardGitTask(error) {
|
|
|
999
1001
|
if (error) dashboardGitTaskStatus.lastError = error instanceof Error ? error.message : String(error);
|
|
1000
1002
|
emitDashboardGitTaskStatus();
|
|
1001
1003
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1004
|
+
const DASHBOARD_GIT_REFRESH_STAMP_NAME = "openspecui-dashboard-git-refresh.stamp";
|
|
1005
|
+
async function resolveGitMetadataDir(projectDir) {
|
|
1006
|
+
try {
|
|
1007
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--git-dir"], {
|
|
1008
|
+
cwd: projectDir,
|
|
1009
|
+
maxBuffer: 1024 * 1024,
|
|
1010
|
+
encoding: "utf8"
|
|
1011
|
+
});
|
|
1012
|
+
const gitDirRaw = stdout.trim();
|
|
1013
|
+
if (!gitDirRaw) return null;
|
|
1014
|
+
const gitDirPath = resolve(projectDir, gitDirRaw);
|
|
1015
|
+
if (!(await stat(gitDirPath)).isDirectory()) return null;
|
|
1016
|
+
return gitDirPath;
|
|
1017
|
+
} catch {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1007
1020
|
}
|
|
1008
|
-
function
|
|
1009
|
-
|
|
1021
|
+
async function resolveGitMetadataDirReactive(projectDir) {
|
|
1022
|
+
const gitMetadataDir = await resolveGitMetadataDir(projectDir);
|
|
1023
|
+
if (!gitMetadataDir) return null;
|
|
1024
|
+
await reactiveReadDir(gitMetadataDir, { includeHidden: true });
|
|
1025
|
+
return gitMetadataDir;
|
|
1026
|
+
}
|
|
1027
|
+
function getDashboardGitRefreshStampPath(gitMetadataDir) {
|
|
1028
|
+
return join(gitMetadataDir, DASHBOARD_GIT_REFRESH_STAMP_NAME);
|
|
1010
1029
|
}
|
|
1011
1030
|
async function touchDashboardGitRefreshStamp(projectDir, reason) {
|
|
1012
|
-
const
|
|
1031
|
+
const gitMetadataDir = await resolveGitMetadataDir(projectDir);
|
|
1032
|
+
if (!gitMetadataDir) return { skipped: true };
|
|
1033
|
+
const stampPath = getDashboardGitRefreshStampPath(gitMetadataDir);
|
|
1013
1034
|
await mkdir(dirname(stampPath), { recursive: true });
|
|
1014
1035
|
await writeFile(stampPath, `${Date.now()} ${reason}\n`, "utf8");
|
|
1036
|
+
return { skipped: false };
|
|
1015
1037
|
}
|
|
1016
1038
|
async function registerDashboardGitReactiveDeps(projectDir) {
|
|
1017
1039
|
await reactiveReadDir(projectDir, {
|
|
1018
1040
|
includeHidden: true,
|
|
1019
1041
|
exclude: ["node_modules"]
|
|
1020
1042
|
});
|
|
1021
|
-
await
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
if (!gitDirRaw) return;
|
|
1028
|
-
const gitDirPath = resolve(projectDir, gitDirRaw);
|
|
1029
|
-
await reactiveReadDir(gitDirPath, { includeHidden: true });
|
|
1030
|
-
await reactiveReadFile(join(gitDirPath, "HEAD"));
|
|
1031
|
-
await reactiveReadFile(join(gitDirPath, "index"));
|
|
1032
|
-
await reactiveReadFile(join(gitDirPath, "packed-refs"));
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
await reactiveReadDir(dotGitPath, { includeHidden: true });
|
|
1036
|
-
await reactiveReadFile(join(dotGitPath, "HEAD"));
|
|
1037
|
-
await reactiveReadFile(join(dotGitPath, "index"));
|
|
1038
|
-
await reactiveReadFile(join(dotGitPath, "packed-refs"));
|
|
1043
|
+
const gitMetadataDir = await resolveGitMetadataDirReactive(projectDir);
|
|
1044
|
+
if (!gitMetadataDir) return;
|
|
1045
|
+
await reactiveReadFile(getDashboardGitRefreshStampPath(gitMetadataDir));
|
|
1046
|
+
await reactiveReadFile(join(gitMetadataDir, "HEAD"));
|
|
1047
|
+
await reactiveReadFile(join(gitMetadataDir, "index"));
|
|
1048
|
+
await reactiveReadFile(join(gitMetadataDir, "packed-refs"));
|
|
1039
1049
|
}
|
|
1040
1050
|
function requireChangeId(changeId) {
|
|
1041
1051
|
if (!changeId) throw new Error("change is required");
|
|
@@ -1251,35 +1261,12 @@ async function fetchDashboardOverview(ctx, reason = "dashboard-refresh") {
|
|
|
1251
1261
|
ctx.adapter.listChangesWithMeta(),
|
|
1252
1262
|
ctx.adapter.listArchivedChangesWithMeta()
|
|
1253
1263
|
]);
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
const activeChanges = (await Promise.all([...activeChangeIds].map(async (changeId) => {
|
|
1261
|
-
const status = statusByChange.get(changeId);
|
|
1262
|
-
const changeMeta = changeMetaMap.get(changeId);
|
|
1263
|
-
const statInfo = await reactiveStat(join(ctx.projectDir, "openspec", "changes", changeId));
|
|
1264
|
-
let progress = changeMeta?.progress ?? {
|
|
1265
|
-
total: 0,
|
|
1266
|
-
completed: 0
|
|
1267
|
-
};
|
|
1268
|
-
if (status) try {
|
|
1269
|
-
await ctx.kernel.ensureApplyInstructions(changeId, status.schemaName);
|
|
1270
|
-
const apply = ctx.kernel.getApplyInstructions(changeId, status.schemaName);
|
|
1271
|
-
progress = {
|
|
1272
|
-
total: apply.progress.total,
|
|
1273
|
-
completed: apply.progress.complete
|
|
1274
|
-
};
|
|
1275
|
-
} catch {}
|
|
1276
|
-
return {
|
|
1277
|
-
id: changeId,
|
|
1278
|
-
name: changeMeta?.name ?? changeId,
|
|
1279
|
-
progress,
|
|
1280
|
-
updatedAt: changeMeta?.updatedAt ?? statInfo?.mtime ?? 0
|
|
1281
|
-
};
|
|
1282
|
-
}))).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
1264
|
+
const activeChanges = changeMetas.map((changeMeta) => ({
|
|
1265
|
+
id: changeMeta.id,
|
|
1266
|
+
name: changeMeta.name ?? changeMeta.id,
|
|
1267
|
+
progress: changeMeta.progress,
|
|
1268
|
+
updatedAt: changeMeta.updatedAt
|
|
1269
|
+
})).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
1283
1270
|
const archivedChanges = (await Promise.all(archiveMetas.map(async (meta) => {
|
|
1284
1271
|
const change = await ctx.adapter.readArchivedChange(meta.id);
|
|
1285
1272
|
if (!change) return null;
|
|
@@ -1631,6 +1618,7 @@ const configRouter = router({
|
|
|
1631
1618
|
"system"
|
|
1632
1619
|
]).optional(),
|
|
1633
1620
|
codeEditor: z.object({ theme: CodeEditorThemeSchema.optional() }).optional(),
|
|
1621
|
+
appBaseUrl: z.string().optional(),
|
|
1634
1622
|
terminal: TerminalConfigSchema.omit({ rendererEngine: true }).partial().extend({ rendererEngine: TerminalRendererEngineSchema.optional() }).optional(),
|
|
1635
1623
|
dashboard: DashboardConfigSchema.partial().optional()
|
|
1636
1624
|
})).mutation(async ({ ctx, input }) => {
|
|
@@ -1638,9 +1626,10 @@ const configRouter = router({
|
|
|
1638
1626
|
const hasCliArgs = input.cli !== void 0 && Object.prototype.hasOwnProperty.call(input.cli, "args");
|
|
1639
1627
|
if (hasCliCommand && !hasCliArgs) {
|
|
1640
1628
|
await ctx.configManager.setCliCommand(input.cli?.command ?? "");
|
|
1641
|
-
if (input.theme !== void 0 || input.codeEditor !== void 0 || input.terminal !== void 0 || input.dashboard !== void 0) await ctx.configManager.writeConfig({
|
|
1629
|
+
if (input.theme !== void 0 || input.codeEditor !== void 0 || input.appBaseUrl !== void 0 || input.terminal !== void 0 || input.dashboard !== void 0) await ctx.configManager.writeConfig({
|
|
1642
1630
|
theme: input.theme,
|
|
1643
1631
|
codeEditor: input.codeEditor,
|
|
1632
|
+
appBaseUrl: input.appBaseUrl,
|
|
1644
1633
|
terminal: input.terminal,
|
|
1645
1634
|
dashboard: input.dashboard
|
|
1646
1635
|
});
|
|
@@ -2315,6 +2304,17 @@ var SearchService = class {
|
|
|
2315
2304
|
*
|
|
2316
2305
|
* @module server
|
|
2317
2306
|
*/
|
|
2307
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
2308
|
+
function getServerPackageVersion() {
|
|
2309
|
+
try {
|
|
2310
|
+
const packageJsonPath = join(__dirname, "..", "package.json");
|
|
2311
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
2312
|
+
return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
2313
|
+
} catch {
|
|
2314
|
+
return "0.0.0";
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
const SERVER_PACKAGE_VERSION = getServerPackageVersion();
|
|
2318
2318
|
/**
|
|
2319
2319
|
* Create an OpenSpecUI HTTP server with optional WebSocket support
|
|
2320
2320
|
*/
|
|
@@ -2335,7 +2335,9 @@ function createServer(config) {
|
|
|
2335
2335
|
return c.json({
|
|
2336
2336
|
status: "ok",
|
|
2337
2337
|
projectDir: config.projectDir,
|
|
2338
|
-
|
|
2338
|
+
projectName: basename(config.projectDir) || config.projectDir,
|
|
2339
|
+
watcherEnabled: !!watcher,
|
|
2340
|
+
openspecuiVersion: SERVER_PACKAGE_VERSION
|
|
2339
2341
|
});
|
|
2340
2342
|
});
|
|
2341
2343
|
app.use("/trpc/*", async (c) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openspecui/server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"exports": {
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"yaml": "^2.8.0",
|
|
21
21
|
"yargs": "^18.0.0",
|
|
22
22
|
"zod": "^3.24.1",
|
|
23
|
-
"@openspecui/
|
|
24
|
-
"@openspecui/
|
|
23
|
+
"@openspecui/core": "2.1.0",
|
|
24
|
+
"@openspecui/search": "1.1.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^22.10.2",
|