openspecui 2.0.2 → 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/cli.mjs +272 -23
- package/dist/index.mjs +1 -1
- package/dist/{open-DDagk2eo.mjs → open-DfwCb8mL.mjs} +1 -1
- package/dist/{src-Cx7GJTGT.mjs → src-B5XQ-ERE.mjs} +63 -46
- package/package.json +6 -4
- package/web/assets/{BufferResource-Cipj3hQ5.js → BufferResource-WPSrP4iZ.js} +1 -1
- package/web/assets/{CanvasRenderer-C151fKcy.js → CanvasRenderer-yL0hHLLA.js} +1 -1
- package/web/assets/{Filter-BpxymkGv.js → Filter-MPAI5-Oj.js} +1 -1
- package/web/assets/{RenderTargetSystem-C0zk81Mh.js → RenderTargetSystem-D4qtRaUW.js} +1 -1
- package/web/assets/{WebGLRenderer-De3GrxdN.js → WebGLRenderer-BHfrXEBc.js} +1 -1
- package/web/assets/{WebGPURenderer-BkpejvST.js → WebGPURenderer-DE5Q1Ilu.js} +1 -1
- package/web/assets/{browserAll-B8KqF-Xa.js → browserAll-qg0ACMg1.js} +1 -1
- package/web/assets/{ghostty-web-BO5ww0eG.js → ghostty-web-D1cPLYOq.js} +1 -1
- package/web/assets/{index-Nl7iD8Yi.js → index-B2AIdjev.js} +1 -1
- package/web/assets/{index-CDX9fioY.js → index-BEpMDdcu.js} +1 -1
- package/web/assets/{index-B-vvVL83.js → index-BLmH2_gh.js} +197 -193
- package/web/assets/{index-Bp2dpDFJ.js → index-BM_5Tg9c.js} +1 -1
- package/web/assets/{index-Cj_nv8ue.js → index-BQWvXpvf.js} +1 -1
- package/web/assets/{index-a8osabOh.js → index-BTEmB46j.js} +1 -1
- package/web/assets/{index-CxDip8xJ.js → index-C8igR5_C.js} +1 -1
- package/web/assets/{index-DL08rl2O.js → index-CQRaC2Gn.js} +1 -1
- package/web/assets/{index-2DXouwnE.js → index-CSledjj-.js} +1 -1
- package/web/assets/index-D1IhiEyy.css +1 -0
- package/web/assets/{index-CwA_uPvf.js → index-D7oe-Hg-.js} +1 -1
- package/web/assets/{index-Cv-LON1K.js → index-DBI0U-_m.js} +1 -1
- package/web/assets/{index-CFAzjIVm.js → index-DMpt0Kcu.js} +1 -1
- package/web/assets/{index-iROJdLzk.js → index-DYz8XhtF.js} +1 -1
- package/web/assets/{index-DtTSWBHJ.js → index-DfNI0fNY.js} +1 -1
- package/web/assets/{index-Bxm-n0do.js → index-DlK0frtF.js} +1 -1
- package/web/assets/{index-DzK6gT7C.js → index-Gknjn7E7.js} +1 -1
- package/web/assets/{webworkerAll-DPcI1cDK.js → webworkerAll-iHUuedbL.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-aWxXp1oO.css +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as SchemaInfoSchema, c as toOpsxDisplayPath, d as
|
|
2
|
+
import { a as SchemaInfoSchema, c as toOpsxDisplayPath, d as CliExecutor, f as ConfigManager, g as __toESM, h as __commonJS, i as SchemaDetailSchema, l as buildHostedLaunchUrl, m as OpenSpecAdapter, o as SchemaResolutionSchema, p as DEFAULT_CONFIG, r as require_dist, s as TemplatesSchema, t as startServer, u as resolveHostedAppBaseUrl } from "./src-B5XQ-ERE.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
import { createServer } from "node:net";
|
|
4
5
|
import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
|
|
5
6
|
import { readFile } from "node:fs/promises";
|
|
6
7
|
import { dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
|
|
7
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { readFileSync as readFileSync$1, readdirSync as readdirSync$1, statSync as statSync$1, writeFile as writeFile$1 } from "fs";
|
|
9
10
|
import { format, inspect } from "util";
|
|
10
|
-
import { fileURLToPath } from "url";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { fileURLToPath as fileURLToPath$1 } from "url";
|
|
11
13
|
import { execFile, spawn } from "node:child_process";
|
|
12
14
|
import { promisify as promisify$1 } from "node:util";
|
|
13
|
-
import { fileURLToPath as fileURLToPath$1 } from "node:url";
|
|
14
15
|
import { notStrictEqual, strictEqual } from "assert";
|
|
15
16
|
|
|
16
17
|
//#region ../../node_modules/.pnpm/cliui@9.0.1/node_modules/cliui/build/lib/index.js
|
|
@@ -1606,7 +1607,7 @@ var require_get_caller_file = /* @__PURE__ */ __commonJS({ "../../node_modules/.
|
|
|
1606
1607
|
//#endregion
|
|
1607
1608
|
//#region ../../node_modules/.pnpm/yargs@18.0.0/node_modules/yargs/lib/platform-shims/esm.mjs
|
|
1608
1609
|
var import_get_caller_file = /* @__PURE__ */ __toESM(require_get_caller_file(), 1);
|
|
1609
|
-
const __dirname$2 = fileURLToPath(import.meta.url);
|
|
1610
|
+
const __dirname$2 = fileURLToPath$1(import.meta.url);
|
|
1610
1611
|
const mainFilename = __dirname$2.substring(0, __dirname$2.lastIndexOf("node_modules"));
|
|
1611
1612
|
const require = createRequire(import.meta.url);
|
|
1612
1613
|
var esm_default = {
|
|
@@ -1647,7 +1648,7 @@ var esm_default = {
|
|
|
1647
1648
|
require,
|
|
1648
1649
|
getCallerFile: () => {
|
|
1649
1650
|
const callerFile = (0, import_get_caller_file.default)(3);
|
|
1650
|
-
return callerFile.match(/^file:\/\//) ? fileURLToPath(callerFile) : callerFile;
|
|
1651
|
+
return callerFile.match(/^file:\/\//) ? fileURLToPath$1(callerFile) : callerFile;
|
|
1651
1652
|
},
|
|
1652
1653
|
stringWidth,
|
|
1653
1654
|
y18n: y18n_default({
|
|
@@ -4507,11 +4508,11 @@ var yargs_default = Yargs;
|
|
|
4507
4508
|
//#endregion
|
|
4508
4509
|
//#region package.json
|
|
4509
4510
|
var import_dist = require_dist();
|
|
4510
|
-
var version = "2.0
|
|
4511
|
+
var version = "2.1.0";
|
|
4511
4512
|
|
|
4512
4513
|
//#endregion
|
|
4513
4514
|
//#region src/export.ts
|
|
4514
|
-
const __dirname$1 = dirname$1(fileURLToPath
|
|
4515
|
+
const __dirname$1 = dirname$1(fileURLToPath(import.meta.url));
|
|
4515
4516
|
const execFileAsync = promisify$1(execFile);
|
|
4516
4517
|
function parseCliJson(raw, schema, label) {
|
|
4517
4518
|
const trimmed = raw.trim();
|
|
@@ -4943,7 +4944,7 @@ function findLocalWebPackage() {
|
|
|
4943
4944
|
/**
|
|
4944
4945
|
* Run a command and wait for it to complete
|
|
4945
4946
|
*/
|
|
4946
|
-
function runCommand(cmd, args, cwd) {
|
|
4947
|
+
function runCommand$1(cmd, args, cwd) {
|
|
4947
4948
|
return new Promise((resolvePromise, reject) => {
|
|
4948
4949
|
const child = spawn(cmd, args, {
|
|
4949
4950
|
stdio: "inherit",
|
|
@@ -5110,7 +5111,7 @@ async function exportHtml(options) {
|
|
|
5110
5111
|
if (isLocalPackageRange(webPackageRange)) {
|
|
5111
5112
|
if (!localWebPkg) throw new Error(`Detected local/dev @openspecui/web range "${webPackageRange}" but local web package was not found`);
|
|
5112
5113
|
console.log("\n[Local dev mode] Running SSG from local web package...");
|
|
5113
|
-
await runCommand("pnpm", [
|
|
5114
|
+
await runCommand$1("pnpm", [
|
|
5114
5115
|
"tsx",
|
|
5115
5116
|
join$1(localWebPkg, "src", "ssg", "cli.ts"),
|
|
5116
5117
|
"--data",
|
|
@@ -5125,7 +5126,7 @@ async function exportHtml(options) {
|
|
|
5125
5126
|
const pm = detectPackageManager();
|
|
5126
5127
|
const execCmd = getExecCommand(pm, `@openspecui/web@${webPackageRange || version}`);
|
|
5127
5128
|
try {
|
|
5128
|
-
await runCommand(execCmd.cmd, [
|
|
5129
|
+
await runCommand$1(execCmd.cmd, [
|
|
5129
5130
|
...execCmd.args,
|
|
5130
5131
|
"--data",
|
|
5131
5132
|
dataJsonPath,
|
|
@@ -5152,7 +5153,7 @@ async function exportHtml(options) {
|
|
|
5152
5153
|
if (previewHost) viteArgs.push("--host", previewHost);
|
|
5153
5154
|
viteArgs.push("--open");
|
|
5154
5155
|
const { cmd, args } = getRunCommand(detectPackageManager(), "vite");
|
|
5155
|
-
await runCommand(cmd, [...args, ...viteArgs], outputDir);
|
|
5156
|
+
await runCommand$1(cmd, [...args, ...viteArgs], outputDir);
|
|
5156
5157
|
}
|
|
5157
5158
|
}
|
|
5158
5159
|
/**
|
|
@@ -5166,12 +5167,210 @@ async function exportStaticSite(options) {
|
|
|
5166
5167
|
else await exportHtml(options);
|
|
5167
5168
|
}
|
|
5168
5169
|
|
|
5170
|
+
//#endregion
|
|
5171
|
+
//#region src/hosted-app.ts
|
|
5172
|
+
function buildHostedAppLaunchUrl(options) {
|
|
5173
|
+
return buildHostedLaunchUrl({
|
|
5174
|
+
baseUrl: options.baseUrl,
|
|
5175
|
+
apiBaseUrl: options.apiBaseUrl
|
|
5176
|
+
});
|
|
5177
|
+
}
|
|
5178
|
+
function resolveEffectiveHostedAppBaseUrl(options) {
|
|
5179
|
+
return resolveHostedAppBaseUrl(options);
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5182
|
+
//#endregion
|
|
5183
|
+
//#region src/local-hosted-app-dev.ts
|
|
5184
|
+
const LOCAL_HOSTED_APP_DEV_DEFAULT_PORT = 13005;
|
|
5185
|
+
function resolveLocalHostedAppWorkspace(cliDir) {
|
|
5186
|
+
const repoRoot = resolve$1(cliDir, "..", "..", "..");
|
|
5187
|
+
const rootPackageJson = join$1(repoRoot, "package.json");
|
|
5188
|
+
const appPackageJson = join$1(repoRoot, "packages", "app", "package.json");
|
|
5189
|
+
if (!existsSync(rootPackageJson) || !existsSync(appPackageJson)) return null;
|
|
5190
|
+
return {
|
|
5191
|
+
repoRoot,
|
|
5192
|
+
appDir: join$1(repoRoot, "packages", "app"),
|
|
5193
|
+
webDistDir: join$1(repoRoot, "packages", "web", "dist")
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
function shouldUseLocalHostedAppDevMode(options) {
|
|
5197
|
+
return options.appValue === "" && options.workspace !== null;
|
|
5198
|
+
}
|
|
5199
|
+
function createHostedAppWebBuildCommand(workspace) {
|
|
5200
|
+
return {
|
|
5201
|
+
command: resolvePnpmCommand(),
|
|
5202
|
+
args: [
|
|
5203
|
+
"--filter",
|
|
5204
|
+
"@openspecui/web",
|
|
5205
|
+
"build"
|
|
5206
|
+
],
|
|
5207
|
+
cwd: workspace.repoRoot,
|
|
5208
|
+
env: { ...process.env }
|
|
5209
|
+
};
|
|
5210
|
+
}
|
|
5211
|
+
function createLocalHostedAppDevCommand(options) {
|
|
5212
|
+
return {
|
|
5213
|
+
command: resolvePnpmCommand(),
|
|
5214
|
+
args: [
|
|
5215
|
+
"--filter",
|
|
5216
|
+
"@openspecui/app",
|
|
5217
|
+
"exec",
|
|
5218
|
+
"vite",
|
|
5219
|
+
"--host",
|
|
5220
|
+
"127.0.0.1",
|
|
5221
|
+
"--port",
|
|
5222
|
+
String(options.port),
|
|
5223
|
+
"--strictPort"
|
|
5224
|
+
],
|
|
5225
|
+
cwd: options.workspace.repoRoot,
|
|
5226
|
+
env: {
|
|
5227
|
+
...process.env,
|
|
5228
|
+
OPENSPECUI_APP_DEV_MODE: "1",
|
|
5229
|
+
OPENSPECUI_APP_DEV_WEB_DIST: options.workspace.webDistDir,
|
|
5230
|
+
OPENSPECUI_APP_DEV_VERSION: options.resolvedVersion
|
|
5231
|
+
}
|
|
5232
|
+
};
|
|
5233
|
+
}
|
|
5234
|
+
async function startLocalHostedAppDev(options) {
|
|
5235
|
+
await ensureLocalHostedAppWebDist(options.workspace);
|
|
5236
|
+
const port = await findAvailablePort(options.preferredPort ?? LOCAL_HOSTED_APP_DEV_DEFAULT_PORT);
|
|
5237
|
+
const command$1 = createLocalHostedAppDevCommand({
|
|
5238
|
+
workspace: options.workspace,
|
|
5239
|
+
port,
|
|
5240
|
+
resolvedVersion: options.resolvedVersion
|
|
5241
|
+
});
|
|
5242
|
+
const child = spawn(command$1.command, command$1.args, {
|
|
5243
|
+
cwd: command$1.cwd,
|
|
5244
|
+
env: command$1.env,
|
|
5245
|
+
stdio: "inherit",
|
|
5246
|
+
detached: process.platform !== "win32"
|
|
5247
|
+
});
|
|
5248
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
5249
|
+
try {
|
|
5250
|
+
await waitForHostedAppDevReady({
|
|
5251
|
+
baseUrl,
|
|
5252
|
+
child,
|
|
5253
|
+
timeoutMs: options.readinessTimeoutMs ?? 15e3
|
|
5254
|
+
});
|
|
5255
|
+
} catch (error) {
|
|
5256
|
+
await stopChildProcess(child);
|
|
5257
|
+
throw error;
|
|
5258
|
+
}
|
|
5259
|
+
return {
|
|
5260
|
+
baseUrl,
|
|
5261
|
+
close: () => stopChildProcess(child)
|
|
5262
|
+
};
|
|
5263
|
+
}
|
|
5264
|
+
async function ensureLocalHostedAppWebDist(workspace) {
|
|
5265
|
+
const command$1 = createHostedAppWebBuildCommand(workspace);
|
|
5266
|
+
console.log("🛠️ Building packages/web for local hosted app dev...");
|
|
5267
|
+
await runCommand(command$1);
|
|
5268
|
+
}
|
|
5269
|
+
function resolvePnpmCommand() {
|
|
5270
|
+
return process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
5271
|
+
}
|
|
5272
|
+
async function runCommand(command$1) {
|
|
5273
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
5274
|
+
const child = spawn(command$1.command, command$1.args, {
|
|
5275
|
+
cwd: command$1.cwd,
|
|
5276
|
+
env: command$1.env,
|
|
5277
|
+
stdio: "inherit"
|
|
5278
|
+
});
|
|
5279
|
+
child.once("error", (error) => {
|
|
5280
|
+
rejectPromise(error);
|
|
5281
|
+
});
|
|
5282
|
+
child.once("exit", (code, signal) => {
|
|
5283
|
+
if (code === 0) {
|
|
5284
|
+
resolvePromise();
|
|
5285
|
+
return;
|
|
5286
|
+
}
|
|
5287
|
+
rejectPromise(/* @__PURE__ */ new Error(`Command failed: ${command$1.command} ${command$1.args.join(" ")} (${signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`})`));
|
|
5288
|
+
});
|
|
5289
|
+
});
|
|
5290
|
+
}
|
|
5291
|
+
async function waitForHostedAppDevReady(options) {
|
|
5292
|
+
let exitMessage = null;
|
|
5293
|
+
let startupError = null;
|
|
5294
|
+
options.child.once("error", (error) => {
|
|
5295
|
+
startupError = error;
|
|
5296
|
+
});
|
|
5297
|
+
options.child.once("exit", (code, signal) => {
|
|
5298
|
+
exitMessage = signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`;
|
|
5299
|
+
});
|
|
5300
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
5301
|
+
while (Date.now() < deadline) {
|
|
5302
|
+
if (startupError) throw startupError;
|
|
5303
|
+
if (exitMessage) throw new Error(`Local hosted app dev server exited before becoming ready (${exitMessage})`);
|
|
5304
|
+
try {
|
|
5305
|
+
if ((await fetch(`${options.baseUrl}/version.json`, {
|
|
5306
|
+
headers: { accept: "application/json" },
|
|
5307
|
+
cache: "no-store"
|
|
5308
|
+
})).ok) return;
|
|
5309
|
+
} catch {}
|
|
5310
|
+
await delay(250);
|
|
5311
|
+
}
|
|
5312
|
+
throw new Error(`Timed out waiting for local hosted app dev server at ${options.baseUrl}`);
|
|
5313
|
+
}
|
|
5314
|
+
async function stopChildProcess(child) {
|
|
5315
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
5316
|
+
killChildProcess(child, "SIGTERM");
|
|
5317
|
+
if (await waitForChildExit(child, 5e3)) return;
|
|
5318
|
+
killChildProcess(child, "SIGKILL");
|
|
5319
|
+
await waitForChildExit(child, 1e3);
|
|
5320
|
+
}
|
|
5321
|
+
function killChildProcess(child, signal) {
|
|
5322
|
+
if (process.platform !== "win32" && child.pid) try {
|
|
5323
|
+
process.kill(-child.pid, signal);
|
|
5324
|
+
return;
|
|
5325
|
+
} catch {}
|
|
5326
|
+
child.kill(signal);
|
|
5327
|
+
}
|
|
5328
|
+
function waitForChildExit(child, timeoutMs) {
|
|
5329
|
+
return new Promise((resolvePromise) => {
|
|
5330
|
+
const timer = setTimeout(() => {
|
|
5331
|
+
cleanup();
|
|
5332
|
+
resolvePromise(false);
|
|
5333
|
+
}, timeoutMs);
|
|
5334
|
+
const onExit = () => {
|
|
5335
|
+
cleanup();
|
|
5336
|
+
resolvePromise(true);
|
|
5337
|
+
};
|
|
5338
|
+
const cleanup = () => {
|
|
5339
|
+
clearTimeout(timer);
|
|
5340
|
+
child.off("exit", onExit);
|
|
5341
|
+
};
|
|
5342
|
+
child.once("exit", onExit);
|
|
5343
|
+
});
|
|
5344
|
+
}
|
|
5345
|
+
function delay(ms) {
|
|
5346
|
+
return new Promise((resolve$2) => {
|
|
5347
|
+
setTimeout(resolve$2, ms);
|
|
5348
|
+
});
|
|
5349
|
+
}
|
|
5350
|
+
async function isPortAvailable(port) {
|
|
5351
|
+
return await new Promise((resolve$2) => {
|
|
5352
|
+
const server = createServer();
|
|
5353
|
+
server.once("error", () => {
|
|
5354
|
+
resolve$2(false);
|
|
5355
|
+
});
|
|
5356
|
+
server.once("listening", () => {
|
|
5357
|
+
server.close(() => resolve$2(true));
|
|
5358
|
+
});
|
|
5359
|
+
server.listen(port, "127.0.0.1");
|
|
5360
|
+
});
|
|
5361
|
+
}
|
|
5362
|
+
async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
5363
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
5364
|
+
const port = startPort + attempt;
|
|
5365
|
+
if (await isPortAvailable(port)) return port;
|
|
5366
|
+
}
|
|
5367
|
+
throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`);
|
|
5368
|
+
}
|
|
5369
|
+
|
|
5169
5370
|
//#endregion
|
|
5170
5371
|
//#region src/cli.ts
|
|
5171
|
-
const __dirname = dirname$1(fileURLToPath
|
|
5172
|
-
|
|
5173
|
-
* Read version from package.json
|
|
5174
|
-
*/
|
|
5372
|
+
const __dirname = dirname$1(fileURLToPath(import.meta.url));
|
|
5373
|
+
const DEFAULT_HOSTED_CORS_ORIGINS = ["http://localhost:5173", "http://localhost:3000"];
|
|
5175
5374
|
function getVersion() {
|
|
5176
5375
|
try {
|
|
5177
5376
|
const pkgPath = join$1(__dirname, "..", "package.json");
|
|
@@ -5180,6 +5379,11 @@ function getVersion() {
|
|
|
5180
5379
|
return "0.0.0";
|
|
5181
5380
|
}
|
|
5182
5381
|
}
|
|
5382
|
+
function buildHostedCorsOrigins(baseUrl) {
|
|
5383
|
+
const origins = new Set(DEFAULT_HOSTED_CORS_ORIGINS);
|
|
5384
|
+
origins.add(new URL(baseUrl).origin);
|
|
5385
|
+
return [...origins];
|
|
5386
|
+
}
|
|
5183
5387
|
async function main() {
|
|
5184
5388
|
const originalCwd = process.env.INIT_CWD || process.cwd();
|
|
5185
5389
|
await yargs_default(hideBin(process.argv)).scriptName("openspecui").command(["$0 [project-dir]", "start [project-dir]"], "Start the OpenSpec UI server", (yargs) => {
|
|
@@ -5199,9 +5403,13 @@ async function main() {
|
|
|
5199
5403
|
describe: "Automatically open the browser",
|
|
5200
5404
|
type: "boolean",
|
|
5201
5405
|
default: true
|
|
5406
|
+
}).option("app", {
|
|
5407
|
+
describe: "Open the hosted app at the official or custom base URL. Supports --app and --app=<baseUrl>.",
|
|
5408
|
+
type: "string"
|
|
5202
5409
|
});
|
|
5203
5410
|
}, async (argv) => {
|
|
5204
5411
|
const projectDir = resolve$1(originalCwd, argv["project-dir"] || argv.dir || ".");
|
|
5412
|
+
const useHostedApp = argv.app !== void 0;
|
|
5205
5413
|
console.log(`
|
|
5206
5414
|
┌─────────────────────────────────────────────┐
|
|
5207
5415
|
│ OpenSpec UI │
|
|
@@ -5210,31 +5418,69 @@ async function main() {
|
|
|
5210
5418
|
`);
|
|
5211
5419
|
console.log(`📁 Project: ${projectDir}`);
|
|
5212
5420
|
console.log("");
|
|
5421
|
+
let server = null;
|
|
5422
|
+
let localHostedApp = null;
|
|
5213
5423
|
try {
|
|
5214
|
-
const
|
|
5424
|
+
const localVersion = getVersion();
|
|
5425
|
+
let hostedBaseUrl = null;
|
|
5426
|
+
if (useHostedApp) {
|
|
5427
|
+
const workspace = resolveLocalHostedAppWorkspace(__dirname);
|
|
5428
|
+
const localHostedAppMode = {
|
|
5429
|
+
appValue: argv.app,
|
|
5430
|
+
workspace
|
|
5431
|
+
};
|
|
5432
|
+
if (shouldUseLocalHostedAppDevMode(localHostedAppMode)) {
|
|
5433
|
+
localHostedApp = await startLocalHostedAppDev({
|
|
5434
|
+
workspace: localHostedAppMode.workspace,
|
|
5435
|
+
resolvedVersion: localVersion
|
|
5436
|
+
});
|
|
5437
|
+
hostedBaseUrl = localHostedApp.baseUrl;
|
|
5438
|
+
} else {
|
|
5439
|
+
const config = await new ConfigManager(projectDir).readConfig();
|
|
5440
|
+
hostedBaseUrl = resolveEffectiveHostedAppBaseUrl({
|
|
5441
|
+
override: argv.app,
|
|
5442
|
+
configured: config.appBaseUrl
|
|
5443
|
+
});
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
server = await startServer({
|
|
5215
5447
|
projectDir,
|
|
5216
5448
|
port: argv.port,
|
|
5217
|
-
open:
|
|
5449
|
+
open: false,
|
|
5450
|
+
corsOrigins: hostedBaseUrl ? buildHostedCorsOrigins(hostedBaseUrl) : void 0
|
|
5218
5451
|
});
|
|
5219
5452
|
if (server.port !== server.preferredPort) console.log(`⚠️ Port ${server.preferredPort} is in use, using ${server.port} instead`);
|
|
5220
5453
|
console.log(`✅ Server running at ${server.url}`);
|
|
5454
|
+
let browserUrl = server.url;
|
|
5455
|
+
if (useHostedApp && hostedBaseUrl) {
|
|
5456
|
+
browserUrl = buildHostedAppLaunchUrl({
|
|
5457
|
+
baseUrl: hostedBaseUrl,
|
|
5458
|
+
apiBaseUrl: server.url
|
|
5459
|
+
});
|
|
5460
|
+
console.log(`🌐 Hosted app base: ${hostedBaseUrl}`);
|
|
5461
|
+
console.log(`🔗 Hosted URL: ${browserUrl}`);
|
|
5462
|
+
}
|
|
5221
5463
|
console.log("");
|
|
5222
5464
|
if (argv.open) {
|
|
5223
|
-
await (await import("./open-
|
|
5224
|
-
console.log("🌐 Browser opened");
|
|
5465
|
+
await (await import("./open-DfwCb8mL.mjs")).default(browserUrl);
|
|
5466
|
+
console.log(useHostedApp ? "🌐 Hosted app opened" : "🌐 Browser opened");
|
|
5225
5467
|
}
|
|
5226
5468
|
console.log("");
|
|
5227
5469
|
console.log("Press Ctrl+C to stop the server");
|
|
5228
5470
|
process.on("SIGINT", async () => {
|
|
5229
5471
|
console.log("\n\n👋 Shutting down...");
|
|
5230
|
-
await
|
|
5472
|
+
await localHostedApp?.close();
|
|
5473
|
+
await server?.close();
|
|
5231
5474
|
process.exit(0);
|
|
5232
5475
|
});
|
|
5233
5476
|
process.on("SIGTERM", async () => {
|
|
5234
|
-
await
|
|
5477
|
+
await localHostedApp?.close();
|
|
5478
|
+
await server?.close();
|
|
5235
5479
|
process.exit(0);
|
|
5236
5480
|
});
|
|
5237
5481
|
} catch (error) {
|
|
5482
|
+
await localHostedApp?.close();
|
|
5483
|
+
await server?.close();
|
|
5238
5484
|
console.error("❌ Failed to start server:", error);
|
|
5239
5485
|
process.exit(1);
|
|
5240
5486
|
}
|
|
@@ -5297,9 +5543,12 @@ async function main() {
|
|
|
5297
5543
|
console.error("❌ Export failed:", error);
|
|
5298
5544
|
process.exit(1);
|
|
5299
5545
|
}
|
|
5300
|
-
}).
|
|
5546
|
+
}).help().alias("help", "h").version(getVersion()).alias("version", "v").strict().parseAsync();
|
|
5301
5547
|
}
|
|
5302
|
-
main()
|
|
5548
|
+
main().catch((error) => {
|
|
5549
|
+
console.error("Fatal error:", error);
|
|
5550
|
+
process.exit(1);
|
|
5551
|
+
});
|
|
5303
5552
|
|
|
5304
5553
|
//#endregion
|
|
5305
5554
|
export { };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs, { constants } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import fs$1 from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import childProcess, { execFile } from "node:child_process";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import { Buffer } from "node:buffer";
|
|
9
9
|
import os from "node:os";
|
|
@@ -8,18 +8,18 @@ import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
|
8
8
|
import { dirname, join } from "path";
|
|
9
9
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
10
|
import { mkdir as mkdir$1, readFile as readFile$1, readdir, rm, stat, writeFile as writeFile$1 } from "node:fs/promises";
|
|
11
|
-
import { dirname as dirname$1, join as join$1, matchesGlob, relative as relative$1, resolve as resolve$1, sep } from "node:path";
|
|
11
|
+
import { basename as basename$1, dirname as dirname$1, join as join$1, matchesGlob, relative as relative$1, resolve as resolve$1, sep } from "node:path";
|
|
12
12
|
import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
13
13
|
import { EventEmitter } from "events";
|
|
14
14
|
import { watch } from "fs";
|
|
15
15
|
import { exec, spawn } from "child_process";
|
|
16
16
|
import { promisify } from "util";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
17
18
|
import * as pty from "@lydell/node-pty";
|
|
18
19
|
import { execFile } from "node:child_process";
|
|
19
20
|
import { EventEmitter as EventEmitter$1 } from "node:events";
|
|
20
21
|
import { promisify as promisify$1 } from "node:util";
|
|
21
22
|
import { Worker as Worker$1 } from "node:worker_threads";
|
|
22
|
-
import { fileURLToPath } from "node:url";
|
|
23
23
|
|
|
24
24
|
//#region rolldown:runtime
|
|
25
25
|
var __create$1 = Object.create;
|
|
@@ -6308,6 +6308,7 @@ const OpenSpecUIConfigSchema = objectType({
|
|
|
6308
6308
|
}).default({}),
|
|
6309
6309
|
theme: enumType(THEME_VALUES).default("system"),
|
|
6310
6310
|
codeEditor: CodeEditorConfigSchema.default(CodeEditorConfigSchema.parse({})),
|
|
6311
|
+
appBaseUrl: stringType().default(""),
|
|
6311
6312
|
terminal: TerminalConfigSchema.default(TerminalConfigSchema.parse({})),
|
|
6312
6313
|
dashboard: DashboardConfigSchema.default(DashboardConfigSchema.parse({}))
|
|
6313
6314
|
});
|
|
@@ -6316,6 +6317,7 @@ const DEFAULT_CONFIG = {
|
|
|
6316
6317
|
cli: {},
|
|
6317
6318
|
theme: "system",
|
|
6318
6319
|
codeEditor: CodeEditorConfigSchema.parse({}),
|
|
6320
|
+
appBaseUrl: "",
|
|
6319
6321
|
terminal: TerminalConfigSchema.parse({}),
|
|
6320
6322
|
dashboard: DashboardConfigSchema.parse({})
|
|
6321
6323
|
};
|
|
@@ -6384,6 +6386,7 @@ var ConfigManager = class {
|
|
|
6384
6386
|
...current.codeEditor,
|
|
6385
6387
|
...config.codeEditor
|
|
6386
6388
|
},
|
|
6389
|
+
appBaseUrl: config.appBaseUrl ?? current.appBaseUrl,
|
|
6387
6390
|
terminal: {
|
|
6388
6391
|
...current.terminal,
|
|
6389
6392
|
...config.terminal
|
|
@@ -7134,6 +7137,37 @@ const DASHBOARD_METRIC_KEYS = [
|
|
|
7134
7137
|
"taskCompletionPercent"
|
|
7135
7138
|
];
|
|
7136
7139
|
|
|
7140
|
+
//#endregion
|
|
7141
|
+
//#region ../core/src/hosted-app.ts
|
|
7142
|
+
const OFFICIAL_APP_BASE_URL = "https://app.openspecui.com";
|
|
7143
|
+
function withHttpsProtocol(value) {
|
|
7144
|
+
if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(value)) return value;
|
|
7145
|
+
return `https://${value}`;
|
|
7146
|
+
}
|
|
7147
|
+
function normalizeHostedAppBaseUrl(input) {
|
|
7148
|
+
const trimmed = input.trim();
|
|
7149
|
+
if (!trimmed) throw new Error("Hosted app base URL must not be empty");
|
|
7150
|
+
let parsed;
|
|
7151
|
+
try {
|
|
7152
|
+
parsed = new URL(withHttpsProtocol(trimmed));
|
|
7153
|
+
} catch (error) {
|
|
7154
|
+
throw new Error(`Invalid hosted app base URL: ${error instanceof Error ? error.message : String(error)}`);
|
|
7155
|
+
}
|
|
7156
|
+
parsed.hash = "";
|
|
7157
|
+
parsed.search = "";
|
|
7158
|
+
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
7159
|
+
parsed.pathname = pathname.length > 0 ? pathname : "/";
|
|
7160
|
+
return parsed.toString().replace(/\/$/, parsed.pathname === "/" ? "" : "");
|
|
7161
|
+
}
|
|
7162
|
+
function resolveHostedAppBaseUrl(options) {
|
|
7163
|
+
return normalizeHostedAppBaseUrl(options.override?.trim() || options.configured?.trim() || OFFICIAL_APP_BASE_URL);
|
|
7164
|
+
}
|
|
7165
|
+
function buildHostedLaunchUrl(options) {
|
|
7166
|
+
const url = new URL(normalizeHostedAppBaseUrl(options.baseUrl));
|
|
7167
|
+
url.searchParams.set("api", options.apiBaseUrl);
|
|
7168
|
+
return url.toString();
|
|
7169
|
+
}
|
|
7170
|
+
|
|
7137
7171
|
//#endregion
|
|
7138
7172
|
//#region ../core/src/opsx-display-path.ts
|
|
7139
7173
|
const VIRTUAL_PROJECT_DIRNAME = "project";
|
|
@@ -24287,35 +24321,12 @@ async function fetchDashboardOverview(ctx, reason = "dashboard-refresh") {
|
|
|
24287
24321
|
ctx.adapter.listChangesWithMeta(),
|
|
24288
24322
|
ctx.adapter.listArchivedChangesWithMeta()
|
|
24289
24323
|
]);
|
|
24290
|
-
|
|
24291
|
-
|
|
24292
|
-
|
|
24293
|
-
|
|
24294
|
-
|
|
24295
|
-
|
|
24296
|
-
const activeChanges = (await Promise.all([...activeChangeIds].map(async (changeId) => {
|
|
24297
|
-
const status = statusByChange.get(changeId);
|
|
24298
|
-
const changeMeta = changeMetaMap.get(changeId);
|
|
24299
|
-
const statInfo = await reactiveStat(join$1(ctx.projectDir, "openspec", "changes", changeId));
|
|
24300
|
-
let progress = changeMeta?.progress ?? {
|
|
24301
|
-
total: 0,
|
|
24302
|
-
completed: 0
|
|
24303
|
-
};
|
|
24304
|
-
if (status) try {
|
|
24305
|
-
await ctx.kernel.ensureApplyInstructions(changeId, status.schemaName);
|
|
24306
|
-
const apply = ctx.kernel.getApplyInstructions(changeId, status.schemaName);
|
|
24307
|
-
progress = {
|
|
24308
|
-
total: apply.progress.total,
|
|
24309
|
-
completed: apply.progress.complete
|
|
24310
|
-
};
|
|
24311
|
-
} catch {}
|
|
24312
|
-
return {
|
|
24313
|
-
id: changeId,
|
|
24314
|
-
name: changeMeta?.name ?? changeId,
|
|
24315
|
-
progress,
|
|
24316
|
-
updatedAt: changeMeta?.updatedAt ?? statInfo?.mtime ?? 0
|
|
24317
|
-
};
|
|
24318
|
-
}))).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
24324
|
+
const activeChanges = changeMetas.map((changeMeta) => ({
|
|
24325
|
+
id: changeMeta.id,
|
|
24326
|
+
name: changeMeta.name ?? changeMeta.id,
|
|
24327
|
+
progress: changeMeta.progress,
|
|
24328
|
+
updatedAt: changeMeta.updatedAt
|
|
24329
|
+
})).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
24319
24330
|
const archivedChanges = (await Promise.all(archiveMetas.map(async (meta) => {
|
|
24320
24331
|
const change = await ctx.adapter.readArchivedChange(meta.id);
|
|
24321
24332
|
if (!change) return null;
|
|
@@ -24667,6 +24678,7 @@ const configRouter = router({
|
|
|
24667
24678
|
"system"
|
|
24668
24679
|
]).optional(),
|
|
24669
24680
|
codeEditor: objectType({ theme: CodeEditorThemeSchema.optional() }).optional(),
|
|
24681
|
+
appBaseUrl: stringType().optional(),
|
|
24670
24682
|
terminal: TerminalConfigSchema.omit({ rendererEngine: true }).partial().extend({ rendererEngine: TerminalRendererEngineSchema.optional() }).optional(),
|
|
24671
24683
|
dashboard: DashboardConfigSchema.partial().optional()
|
|
24672
24684
|
})).mutation(async ({ ctx, input }) => {
|
|
@@ -24674,9 +24686,10 @@ const configRouter = router({
|
|
|
24674
24686
|
const hasCliArgs = input.cli !== void 0 && Object.prototype.hasOwnProperty.call(input.cli, "args");
|
|
24675
24687
|
if (hasCliCommand && !hasCliArgs) {
|
|
24676
24688
|
await ctx.configManager.setCliCommand(input.cli?.command ?? "");
|
|
24677
|
-
if (input.theme !== void 0 || input.codeEditor !== void 0 || input.terminal !== void 0 || input.dashboard !== void 0) await ctx.configManager.writeConfig({
|
|
24689
|
+
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({
|
|
24678
24690
|
theme: input.theme,
|
|
24679
24691
|
codeEditor: input.codeEditor,
|
|
24692
|
+
appBaseUrl: input.appBaseUrl,
|
|
24680
24693
|
terminal: input.terminal,
|
|
24681
24694
|
dashboard: input.dashboard
|
|
24682
24695
|
});
|
|
@@ -25438,6 +25451,17 @@ var SearchService = class {
|
|
|
25438
25451
|
*
|
|
25439
25452
|
* @module server
|
|
25440
25453
|
*/
|
|
25454
|
+
const __dirname$1 = dirname$1(fileURLToPath(import.meta.url));
|
|
25455
|
+
function getServerPackageVersion() {
|
|
25456
|
+
try {
|
|
25457
|
+
const packageJsonPath = join$1(__dirname$1, "..", "package.json");
|
|
25458
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
25459
|
+
return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
25460
|
+
} catch {
|
|
25461
|
+
return "0.0.0";
|
|
25462
|
+
}
|
|
25463
|
+
}
|
|
25464
|
+
const SERVER_PACKAGE_VERSION = getServerPackageVersion();
|
|
25441
25465
|
/**
|
|
25442
25466
|
* Create an OpenSpecUI HTTP server with optional WebSocket support
|
|
25443
25467
|
*/
|
|
@@ -25458,7 +25482,9 @@ function createServer$2(config) {
|
|
|
25458
25482
|
return c.json({
|
|
25459
25483
|
status: "ok",
|
|
25460
25484
|
projectDir: config.projectDir,
|
|
25461
|
-
|
|
25485
|
+
projectName: basename$1(config.projectDir) || config.projectDir,
|
|
25486
|
+
watcherEnabled: !!watcher,
|
|
25487
|
+
openspecuiVersion: SERVER_PACKAGE_VERSION
|
|
25462
25488
|
});
|
|
25463
25489
|
});
|
|
25464
25490
|
app.use("/trpc/*", async (c) => {
|
|
@@ -25590,9 +25616,6 @@ async function startServer(config, setupApp) {
|
|
|
25590
25616
|
//#endregion
|
|
25591
25617
|
//#region src/index.ts
|
|
25592
25618
|
const __dirname = dirname$1(fileURLToPath(import.meta.url));
|
|
25593
|
-
/**
|
|
25594
|
-
* Get the path to the web assets directory
|
|
25595
|
-
*/
|
|
25596
25619
|
function getWebAssetsDir() {
|
|
25597
25620
|
const devPath = join$1(__dirname, "..", "..", "web", "dist");
|
|
25598
25621
|
const prodPath = join$1(__dirname, "..", "web");
|
|
@@ -25600,9 +25623,6 @@ function getWebAssetsDir() {
|
|
|
25600
25623
|
if (existsSync(devPath)) return devPath;
|
|
25601
25624
|
throw new Error("Web assets not found. Make sure to build the web package first.");
|
|
25602
25625
|
}
|
|
25603
|
-
/**
|
|
25604
|
-
* Setup static file serving middleware for the Hono app
|
|
25605
|
-
*/
|
|
25606
25626
|
function setupStaticFiles(app) {
|
|
25607
25627
|
const webDir = getWebAssetsDir();
|
|
25608
25628
|
const mimeTypes = {
|
|
@@ -25639,18 +25659,15 @@ function setupStaticFiles(app) {
|
|
|
25639
25659
|
return c.notFound();
|
|
25640
25660
|
});
|
|
25641
25661
|
}
|
|
25642
|
-
/**
|
|
25643
|
-
* Start the OpenSpec UI server with WebSocket support for realtime updates.
|
|
25644
|
-
* Includes static file serving for the web UI.
|
|
25645
|
-
*/
|
|
25646
25662
|
async function startServer$1(options = {}) {
|
|
25647
|
-
const { projectDir = process.cwd(), port = 3100, enableWatcher = true } = options;
|
|
25663
|
+
const { projectDir = process.cwd(), port = 3100, enableWatcher = true, corsOrigins } = options;
|
|
25648
25664
|
return await startServer({
|
|
25649
25665
|
projectDir,
|
|
25650
25666
|
port,
|
|
25651
|
-
enableWatcher
|
|
25667
|
+
enableWatcher,
|
|
25668
|
+
corsOrigins
|
|
25652
25669
|
}, setupStaticFiles);
|
|
25653
25670
|
}
|
|
25654
25671
|
|
|
25655
25672
|
//#endregion
|
|
25656
|
-
export { SchemaInfoSchema as a, toOpsxDisplayPath as c,
|
|
25673
|
+
export { SchemaInfoSchema as a, toOpsxDisplayPath as c, CliExecutor as d, ConfigManager as f, __toESM$1 as g, __commonJS$1 as h, SchemaDetailSchema as i, buildHostedLaunchUrl as l, OpenSpecAdapter as m, createServer$2 as n, SchemaResolutionSchema as o, DEFAULT_CONFIG as p, require_dist as r, TemplatesSchema as s, startServer$1 as t, resolveHostedAppBaseUrl as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openspecui",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "OpenSpec UI - Visual interface for spec-driven development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@lydell/node-pty": "^1.1.0",
|
|
21
21
|
"@parcel/watcher": "^2.5.1",
|
|
22
|
-
"yaml": "^2.8.0"
|
|
22
|
+
"yaml": "^2.8.0",
|
|
23
|
+
"semver": "^7.7.3"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@hono/node-server": "^1.14.1",
|
|
@@ -33,8 +34,9 @@
|
|
|
33
34
|
"typescript": "^5.7.2",
|
|
34
35
|
"vitest": "^2.1.8",
|
|
35
36
|
"yargs": "^18.0.0",
|
|
36
|
-
"@
|
|
37
|
-
"@openspecui/server": "2.0
|
|
37
|
+
"@types/semver": "^7.7.1",
|
|
38
|
+
"@openspecui/server": "2.1.0",
|
|
39
|
+
"@openspecui/web": "2.1.0"
|
|
38
40
|
},
|
|
39
41
|
"scripts": {
|
|
40
42
|
"build": "pnpm run build:web && pnpm run build:copy-web && tsdown",
|