@xbghc/warden 0.6.1 → 0.8.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/README.md +104 -18
- package/dist/cli.js +552 -115
- package/dist/web/assets/index-C8TtGAMN.css +1 -0
- package/dist/web/assets/index-CwQ9JHmk.js +198 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-DxPObMgS.js +0 -197
- package/dist/web/assets/index-pyKaNQqX.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import path9 from "path";
|
|
5
5
|
import { spawn } from "child_process";
|
|
6
6
|
import { existsSync as existsSync2 } from "fs";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
@@ -652,7 +652,7 @@ var serve = (options, listeningListener) => {
|
|
|
652
652
|
};
|
|
653
653
|
|
|
654
654
|
// packages/server/src/app.ts
|
|
655
|
-
import
|
|
655
|
+
import path8 from "path";
|
|
656
656
|
import { randomUUID } from "crypto";
|
|
657
657
|
|
|
658
658
|
// node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/compose.js
|
|
@@ -812,26 +812,26 @@ var throwNestingLimitExceeded = () => {
|
|
|
812
812
|
};
|
|
813
813
|
|
|
814
814
|
// node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/url.js
|
|
815
|
-
var splitPath = (
|
|
816
|
-
const paths =
|
|
815
|
+
var splitPath = (path10) => {
|
|
816
|
+
const paths = path10.split("/");
|
|
817
817
|
if (paths[0] === "") {
|
|
818
818
|
paths.shift();
|
|
819
819
|
}
|
|
820
820
|
return paths;
|
|
821
821
|
};
|
|
822
822
|
var splitRoutingPath = (routePath) => {
|
|
823
|
-
const { groups, path:
|
|
824
|
-
const paths = splitPath(
|
|
823
|
+
const { groups, path: path10 } = extractGroupsFromPath(routePath);
|
|
824
|
+
const paths = splitPath(path10);
|
|
825
825
|
return replaceGroupMarks(paths, groups);
|
|
826
826
|
};
|
|
827
|
-
var extractGroupsFromPath = (
|
|
827
|
+
var extractGroupsFromPath = (path10) => {
|
|
828
828
|
const groups = [];
|
|
829
|
-
|
|
829
|
+
path10 = path10.replace(/\{[^}]+\}/g, (match2, index) => {
|
|
830
830
|
const mark = `@${index}`;
|
|
831
831
|
groups.push([mark, match2]);
|
|
832
832
|
return mark;
|
|
833
833
|
});
|
|
834
|
-
return { groups, path:
|
|
834
|
+
return { groups, path: path10 };
|
|
835
835
|
};
|
|
836
836
|
var replaceGroupMarks = (paths, groups) => {
|
|
837
837
|
for (let i = groups.length - 1; i >= 0; i--) {
|
|
@@ -888,8 +888,8 @@ var getPath = (request) => {
|
|
|
888
888
|
const queryIndex = url.indexOf("?", i);
|
|
889
889
|
const hashIndex = url.indexOf("#", i);
|
|
890
890
|
const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
|
|
891
|
-
const
|
|
892
|
-
return tryDecodeURI(
|
|
891
|
+
const path10 = url.slice(start, end);
|
|
892
|
+
return tryDecodeURI(path10.includes("%25") ? path10.replace(/%25/g, "%2525") : path10);
|
|
893
893
|
} else if (charCode === 63 || charCode === 35) {
|
|
894
894
|
break;
|
|
895
895
|
}
|
|
@@ -906,11 +906,11 @@ var mergePath = (base, sub, ...rest) => {
|
|
|
906
906
|
}
|
|
907
907
|
return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
|
|
908
908
|
};
|
|
909
|
-
var checkOptionalParameter = (
|
|
910
|
-
if (
|
|
909
|
+
var checkOptionalParameter = (path10) => {
|
|
910
|
+
if (path10.charCodeAt(path10.length - 1) !== 63 || !path10.includes(":")) {
|
|
911
911
|
return null;
|
|
912
912
|
}
|
|
913
|
-
const segments =
|
|
913
|
+
const segments = path10.split("/");
|
|
914
914
|
const results = [];
|
|
915
915
|
let basePath = "";
|
|
916
916
|
segments.forEach((segment) => {
|
|
@@ -1052,9 +1052,9 @@ var HonoRequest = class {
|
|
|
1052
1052
|
*/
|
|
1053
1053
|
path;
|
|
1054
1054
|
bodyCache = {};
|
|
1055
|
-
constructor(request,
|
|
1055
|
+
constructor(request, path10 = "/", matchResult = [[]]) {
|
|
1056
1056
|
this.raw = request;
|
|
1057
|
-
this.path =
|
|
1057
|
+
this.path = path10;
|
|
1058
1058
|
this.#matchResult = matchResult;
|
|
1059
1059
|
}
|
|
1060
1060
|
param(key) {
|
|
@@ -1826,8 +1826,8 @@ var Hono = class _Hono {
|
|
|
1826
1826
|
return this;
|
|
1827
1827
|
};
|
|
1828
1828
|
});
|
|
1829
|
-
this.on = (method,
|
|
1830
|
-
for (const p of [
|
|
1829
|
+
this.on = (method, path10, ...handlers) => {
|
|
1830
|
+
for (const p of [path10].flat()) {
|
|
1831
1831
|
this.#path = p;
|
|
1832
1832
|
for (const m of [method].flat()) {
|
|
1833
1833
|
const methodName = m.toUpperCase();
|
|
@@ -1885,8 +1885,8 @@ var Hono = class _Hono {
|
|
|
1885
1885
|
* app.route("/api", app2) // GET /api/user
|
|
1886
1886
|
* ```
|
|
1887
1887
|
*/
|
|
1888
|
-
route(
|
|
1889
|
-
const subApp = this.basePath(
|
|
1888
|
+
route(path10, app) {
|
|
1889
|
+
const subApp = this.basePath(path10);
|
|
1890
1890
|
app.routes.map((r) => {
|
|
1891
1891
|
let handler;
|
|
1892
1892
|
if (app.errorHandler === errorHandler) {
|
|
@@ -1912,9 +1912,9 @@ var Hono = class _Hono {
|
|
|
1912
1912
|
* const api = new Hono().basePath('/api')
|
|
1913
1913
|
* ```
|
|
1914
1914
|
*/
|
|
1915
|
-
basePath(
|
|
1915
|
+
basePath(path10) {
|
|
1916
1916
|
const subApp = this.#clone();
|
|
1917
|
-
subApp._basePath = mergePath(this._basePath,
|
|
1917
|
+
subApp._basePath = mergePath(this._basePath, path10);
|
|
1918
1918
|
return subApp;
|
|
1919
1919
|
}
|
|
1920
1920
|
/**
|
|
@@ -1988,7 +1988,7 @@ var Hono = class _Hono {
|
|
|
1988
1988
|
* })
|
|
1989
1989
|
* ```
|
|
1990
1990
|
*/
|
|
1991
|
-
mount(
|
|
1991
|
+
mount(path10, applicationHandler, options) {
|
|
1992
1992
|
let replaceRequest;
|
|
1993
1993
|
let optionHandler;
|
|
1994
1994
|
if (options) {
|
|
@@ -2015,7 +2015,7 @@ var Hono = class _Hono {
|
|
|
2015
2015
|
return [c.env, executionContext];
|
|
2016
2016
|
};
|
|
2017
2017
|
replaceRequest ||= (() => {
|
|
2018
|
-
const mergedPath = mergePath(this._basePath,
|
|
2018
|
+
const mergedPath = mergePath(this._basePath, path10);
|
|
2019
2019
|
const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
|
|
2020
2020
|
return (request) => {
|
|
2021
2021
|
const url = new URL(request.url);
|
|
@@ -2030,18 +2030,18 @@ var Hono = class _Hono {
|
|
|
2030
2030
|
}
|
|
2031
2031
|
await next();
|
|
2032
2032
|
};
|
|
2033
|
-
this.#addRoute(METHOD_NAME_ALL, mergePath(
|
|
2033
|
+
this.#addRoute(METHOD_NAME_ALL, mergePath(path10, "*"), handler);
|
|
2034
2034
|
return this;
|
|
2035
2035
|
}
|
|
2036
|
-
#addRoute(method,
|
|
2037
|
-
|
|
2036
|
+
#addRoute(method, path10, handler, baseRoutePath) {
|
|
2037
|
+
path10 = mergePath(this._basePath, path10);
|
|
2038
2038
|
const r = {
|
|
2039
2039
|
basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
2040
|
-
path:
|
|
2040
|
+
path: path10,
|
|
2041
2041
|
method,
|
|
2042
2042
|
handler
|
|
2043
2043
|
};
|
|
2044
|
-
this.router.add(method,
|
|
2044
|
+
this.router.add(method, path10, [handler, r]);
|
|
2045
2045
|
this.routes.push(r);
|
|
2046
2046
|
}
|
|
2047
2047
|
#handleError(err, c) {
|
|
@@ -2054,10 +2054,10 @@ var Hono = class _Hono {
|
|
|
2054
2054
|
if (method === "HEAD") {
|
|
2055
2055
|
return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
|
|
2056
2056
|
}
|
|
2057
|
-
const
|
|
2058
|
-
const matchResult = this.router.match(method,
|
|
2057
|
+
const path10 = this.getPath(request, { env });
|
|
2058
|
+
const matchResult = this.router.match(method, path10);
|
|
2059
2059
|
const c = new Context(request, {
|
|
2060
|
-
path:
|
|
2060
|
+
path: path10,
|
|
2061
2061
|
matchResult,
|
|
2062
2062
|
env,
|
|
2063
2063
|
executionCtx,
|
|
@@ -2160,7 +2160,7 @@ var createNullObject = () => /* @__PURE__ */ Object.create(null);
|
|
|
2160
2160
|
|
|
2161
2161
|
// node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
2162
2162
|
var emptyParam = [];
|
|
2163
|
-
function match(method,
|
|
2163
|
+
function match(method, path10) {
|
|
2164
2164
|
const matchers = this.buildAllMatchers();
|
|
2165
2165
|
const match2 = ((method2, path22) => {
|
|
2166
2166
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
|
@@ -2176,7 +2176,7 @@ function match(method, path9) {
|
|
|
2176
2176
|
return [matcher[1][index], match3];
|
|
2177
2177
|
});
|
|
2178
2178
|
this.match = match2;
|
|
2179
|
-
return match2(method,
|
|
2179
|
+
return match2(method, path10);
|
|
2180
2180
|
}
|
|
2181
2181
|
|
|
2182
2182
|
// node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
@@ -2293,14 +2293,14 @@ var Trie = class {
|
|
|
2293
2293
|
#index = 0;
|
|
2294
2294
|
// dynamic path -> [handler index, param assoc]; static paths are not registered
|
|
2295
2295
|
paths = createNullObject();
|
|
2296
|
-
insert(
|
|
2296
|
+
insert(path10, isStatic) {
|
|
2297
2297
|
if (isStatic) {
|
|
2298
|
-
this.#root.insert(
|
|
2298
|
+
this.#root.insert(path10.split(""), 0, [], this.#context, true);
|
|
2299
2299
|
return;
|
|
2300
2300
|
}
|
|
2301
2301
|
const paramAssoc = [];
|
|
2302
2302
|
const groups = [];
|
|
2303
|
-
let markedPath =
|
|
2303
|
+
let markedPath = path10;
|
|
2304
2304
|
for (let i = 0; ; ) {
|
|
2305
2305
|
let replaced = false;
|
|
2306
2306
|
markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
|
|
@@ -2325,7 +2325,7 @@ var Trie = class {
|
|
|
2325
2325
|
}
|
|
2326
2326
|
}
|
|
2327
2327
|
this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
|
|
2328
|
-
this.paths[
|
|
2328
|
+
this.paths[path10] = [this.#index++, paramAssoc];
|
|
2329
2329
|
}
|
|
2330
2330
|
buildRegExp() {
|
|
2331
2331
|
let regexp = this.#root.buildRegExpStr();
|
|
@@ -2352,17 +2352,17 @@ var Trie = class {
|
|
|
2352
2352
|
|
|
2353
2353
|
// node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
2354
2354
|
var wildcardRegExpCache = createNullObject();
|
|
2355
|
-
function buildWildcardRegExp(
|
|
2356
|
-
return wildcardRegExpCache[
|
|
2357
|
-
`^${
|
|
2355
|
+
function buildWildcardRegExp(path10) {
|
|
2356
|
+
return wildcardRegExpCache[path10] ??= new RegExp(
|
|
2357
|
+
`^${path10.replace(
|
|
2358
2358
|
/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g,
|
|
2359
2359
|
(match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ONLY_WILDCARD_REG_EXP_STR : `/:${LABEL_REG_EXP_STR}`
|
|
2360
2360
|
)}$`
|
|
2361
2361
|
);
|
|
2362
2362
|
}
|
|
2363
|
-
function findMiddleware(middleware,
|
|
2363
|
+
function findMiddleware(middleware, path10) {
|
|
2364
2364
|
for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
|
|
2365
|
-
if (buildWildcardRegExp(k).test(
|
|
2365
|
+
if (buildWildcardRegExp(k).test(path10)) {
|
|
2366
2366
|
return [...middleware[k]];
|
|
2367
2367
|
}
|
|
2368
2368
|
}
|
|
@@ -2378,14 +2378,14 @@ var RegExpRouter = class {
|
|
|
2378
2378
|
this.#routes = { [METHOD_NAME_ALL]: createNullObject() };
|
|
2379
2379
|
this.#tries = { [METHOD_NAME_ALL]: new Trie() };
|
|
2380
2380
|
}
|
|
2381
|
-
#insertPath(method,
|
|
2381
|
+
#insertPath(method, path10) {
|
|
2382
2382
|
try {
|
|
2383
|
-
this.#tries[method].insert(
|
|
2383
|
+
this.#tries[method].insert(path10, !/\*|\/:/.test(path10));
|
|
2384
2384
|
} catch (e) {
|
|
2385
|
-
throw e === PATH_ERROR ? new UnsupportedPathError(
|
|
2385
|
+
throw e === PATH_ERROR ? new UnsupportedPathError(path10) : e;
|
|
2386
2386
|
}
|
|
2387
2387
|
}
|
|
2388
|
-
add(method,
|
|
2388
|
+
add(method, path10, handler) {
|
|
2389
2389
|
const middleware = this.#middleware;
|
|
2390
2390
|
const routes = this.#routes;
|
|
2391
2391
|
if (!middleware) {
|
|
@@ -2401,28 +2401,28 @@ var RegExpRouter = class {
|
|
|
2401
2401
|
}
|
|
2402
2402
|
}
|
|
2403
2403
|
}
|
|
2404
|
-
if (
|
|
2405
|
-
|
|
2404
|
+
if (path10 === "/*") {
|
|
2405
|
+
path10 = "*";
|
|
2406
2406
|
}
|
|
2407
2407
|
const methods = method === METHOD_NAME_ALL ? Object.keys(middleware) : [method];
|
|
2408
|
-
if (/\*$/.test(
|
|
2409
|
-
const re = buildWildcardRegExp(
|
|
2408
|
+
if (/\*$/.test(path10)) {
|
|
2409
|
+
const re = buildWildcardRegExp(path10);
|
|
2410
2410
|
for (const m of methods) {
|
|
2411
|
-
if (!middleware[m][
|
|
2412
|
-
this.#insertPath(m,
|
|
2413
|
-
middleware[m][
|
|
2411
|
+
if (!middleware[m][path10]) {
|
|
2412
|
+
this.#insertPath(m, path10);
|
|
2413
|
+
middleware[m][path10] = findMiddleware(middleware[m], path10) || findMiddleware(middleware[METHOD_NAME_ALL], path10) || [];
|
|
2414
2414
|
}
|
|
2415
2415
|
}
|
|
2416
2416
|
for (const handlerMap of [middleware, routes]) {
|
|
2417
2417
|
for (const m of methods) {
|
|
2418
2418
|
for (const p in handlerMap[m]) {
|
|
2419
|
-
re.test(p) && handlerMap[m][p].push([handler,
|
|
2419
|
+
re.test(p) && handlerMap[m][p].push([handler, path10]);
|
|
2420
2420
|
}
|
|
2421
2421
|
}
|
|
2422
2422
|
}
|
|
2423
2423
|
return;
|
|
2424
2424
|
}
|
|
2425
|
-
const paths = checkOptionalParameter(
|
|
2425
|
+
const paths = checkOptionalParameter(path10) || [path10];
|
|
2426
2426
|
for (const path22 of paths) {
|
|
2427
2427
|
for (const m of methods) {
|
|
2428
2428
|
if (!routes[m][path22]) {
|
|
@@ -2451,11 +2451,11 @@ var RegExpRouter = class {
|
|
|
2451
2451
|
const handlerData = [];
|
|
2452
2452
|
const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
|
|
2453
2453
|
for (const r of [middleware, routes]) {
|
|
2454
|
-
for (const
|
|
2455
|
-
const handlers = r[
|
|
2456
|
-
const pathData = trie.paths[
|
|
2454
|
+
for (const path10 in r) {
|
|
2455
|
+
const handlers = r[path10];
|
|
2456
|
+
const pathData = trie.paths[path10];
|
|
2457
2457
|
if (!pathData) {
|
|
2458
|
-
staticMap[
|
|
2458
|
+
staticMap[path10] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
|
|
2459
2459
|
continue;
|
|
2460
2460
|
}
|
|
2461
2461
|
handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [
|
|
@@ -2479,13 +2479,13 @@ var SmartRouter = class {
|
|
|
2479
2479
|
constructor(init) {
|
|
2480
2480
|
this.#routers = init.routers;
|
|
2481
2481
|
}
|
|
2482
|
-
add(method,
|
|
2482
|
+
add(method, path10, handler) {
|
|
2483
2483
|
if (!this.#routes) {
|
|
2484
2484
|
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
2485
2485
|
}
|
|
2486
|
-
this.#routes.push([method,
|
|
2486
|
+
this.#routes.push([method, path10, handler]);
|
|
2487
2487
|
}
|
|
2488
|
-
match(method,
|
|
2488
|
+
match(method, path10) {
|
|
2489
2489
|
if (!this.#routes) {
|
|
2490
2490
|
throw new Error("Fatal error");
|
|
2491
2491
|
}
|
|
@@ -2500,7 +2500,7 @@ var SmartRouter = class {
|
|
|
2500
2500
|
for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) {
|
|
2501
2501
|
router.add(...routes[i2]);
|
|
2502
2502
|
}
|
|
2503
|
-
res = router.match(method,
|
|
2503
|
+
res = router.match(method, path10);
|
|
2504
2504
|
} catch (e) {
|
|
2505
2505
|
if (e instanceof UnsupportedPathError) {
|
|
2506
2506
|
continue;
|
|
@@ -2535,9 +2535,9 @@ var Node2 = class _Node2 {
|
|
|
2535
2535
|
#patterns = [];
|
|
2536
2536
|
#pattern;
|
|
2537
2537
|
#params = emptyParams;
|
|
2538
|
-
insert(method,
|
|
2538
|
+
insert(method, path10, handler) {
|
|
2539
2539
|
let curNode = this;
|
|
2540
|
-
const parts = splitRoutingPath(
|
|
2540
|
+
const parts = splitRoutingPath(path10);
|
|
2541
2541
|
const possibleKeys = /* @__PURE__ */ new Set();
|
|
2542
2542
|
let i = 0;
|
|
2543
2543
|
for (const p of parts) {
|
|
@@ -2577,12 +2577,12 @@ var Node2 = class _Node2 {
|
|
|
2577
2577
|
}
|
|
2578
2578
|
}
|
|
2579
2579
|
}
|
|
2580
|
-
search(method,
|
|
2580
|
+
search(method, path10) {
|
|
2581
2581
|
const handlerSets = [];
|
|
2582
2582
|
this.#params = emptyParams;
|
|
2583
2583
|
const curNode = this;
|
|
2584
2584
|
let curNodes = [curNode];
|
|
2585
|
-
const parts = splitPath(
|
|
2585
|
+
const parts = splitPath(path10);
|
|
2586
2586
|
const curNodesQueue = [];
|
|
2587
2587
|
const len = parts.length;
|
|
2588
2588
|
let partOffsets = null;
|
|
@@ -2624,13 +2624,13 @@ var Node2 = class _Node2 {
|
|
|
2624
2624
|
if (matcher !== true) {
|
|
2625
2625
|
if (!partOffsets) {
|
|
2626
2626
|
partOffsets = [];
|
|
2627
|
-
let offset =
|
|
2627
|
+
let offset = path10[0] === "/" ? 1 : 0;
|
|
2628
2628
|
for (let p = 0; p < len; p++) {
|
|
2629
2629
|
partOffsets[p] = offset;
|
|
2630
2630
|
offset += parts[p].length + 1;
|
|
2631
2631
|
}
|
|
2632
2632
|
}
|
|
2633
|
-
const restPathString =
|
|
2633
|
+
const restPathString = path10.slice(partOffsets[i]);
|
|
2634
2634
|
const m = matcher.exec(restPathString);
|
|
2635
2635
|
if (m) {
|
|
2636
2636
|
params[name] = m[0];
|
|
@@ -2690,13 +2690,13 @@ var Node2 = class _Node2 {
|
|
|
2690
2690
|
var TrieRouter = class {
|
|
2691
2691
|
name = "TrieRouter";
|
|
2692
2692
|
#node = new Node2();
|
|
2693
|
-
add(method,
|
|
2694
|
-
for (const result of checkOptionalParameter(
|
|
2693
|
+
add(method, path10, handler) {
|
|
2694
|
+
for (const result of checkOptionalParameter(path10) || [path10]) {
|
|
2695
2695
|
this.#node.insert(method, result, handler);
|
|
2696
2696
|
}
|
|
2697
2697
|
}
|
|
2698
|
-
match(method,
|
|
2699
|
-
return this.#node.search(method,
|
|
2698
|
+
match(method, path10) {
|
|
2699
|
+
return this.#node.search(method, path10);
|
|
2700
2700
|
}
|
|
2701
2701
|
};
|
|
2702
2702
|
|
|
@@ -2960,6 +2960,11 @@ function commentScopeKey(key) {
|
|
|
2960
2960
|
if (!t || !isLocalTarget(t)) return key;
|
|
2961
2961
|
return t.worktree ? `worktree:${t.worktree}:local` : "local";
|
|
2962
2962
|
}
|
|
2963
|
+
function stageModeFor(t) {
|
|
2964
|
+
if (t.kind === "working") return "stage";
|
|
2965
|
+
if (t.kind === "staged") return "unstage";
|
|
2966
|
+
return void 0;
|
|
2967
|
+
}
|
|
2963
2968
|
function localViewKeys(key) {
|
|
2964
2969
|
const t = tryParseTargetKey(key);
|
|
2965
2970
|
const wt = t?.worktree ? { worktree: t.worktree } : {};
|
|
@@ -2970,6 +2975,24 @@ function localViewKeys(key) {
|
|
|
2970
2975
|
];
|
|
2971
2976
|
}
|
|
2972
2977
|
|
|
2978
|
+
// packages/shared/src/order.ts
|
|
2979
|
+
function insertAfter(list, item, after) {
|
|
2980
|
+
const idx = after === void 0 ? -1 : list.findIndex((x) => x.id === after);
|
|
2981
|
+
if (idx < 0) return [item, ...list];
|
|
2982
|
+
return [...list.slice(0, idx + 1), item, ...list.slice(idx + 1)];
|
|
2983
|
+
}
|
|
2984
|
+
function moveBefore(list, id, before) {
|
|
2985
|
+
const item = list.find((x) => x.id === id);
|
|
2986
|
+
if (!item) return null;
|
|
2987
|
+
if (before === id) return [...list];
|
|
2988
|
+
const rest = list.filter((x) => x.id !== id);
|
|
2989
|
+
if (before === null) return [...rest, item];
|
|
2990
|
+
const idx = rest.findIndex((x) => x.id === before);
|
|
2991
|
+
if (idx < 0) return null;
|
|
2992
|
+
rest.splice(idx, 0, item);
|
|
2993
|
+
return rest;
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2973
2996
|
// packages/server/src/errors.ts
|
|
2974
2997
|
var HttpError = class extends Error {
|
|
2975
2998
|
constructor(status, message, code) {
|
|
@@ -3026,7 +3049,7 @@ function parseCommitLog(stdout) {
|
|
|
3026
3049
|
// packages/server/src/git.ts
|
|
3027
3050
|
import { execFile } from "child_process";
|
|
3028
3051
|
import { existsSync } from "fs";
|
|
3029
|
-
var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set(["rev-parse", "diff", "show", "log", "worktree", "ls-files", "status", "merge-base"]);
|
|
3052
|
+
var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set(["rev-parse", "diff", "show", "log", "worktree", "ls-files", "status", "merge-base", "rev-list", "for-each-ref", "check-ref-format"]);
|
|
3030
3053
|
var FORBIDDEN_OPTION_PREFIXES = ["--output", "--ext-diff", "--textconv", "-c", "--config-env", "--exec-path", "--git-dir", "--work-tree"];
|
|
3031
3054
|
var DEFAULT_GIT_TIMEOUT_MS = 3e4;
|
|
3032
3055
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
@@ -3109,6 +3132,103 @@ async function runGit(args, opts) {
|
|
|
3109
3132
|
);
|
|
3110
3133
|
});
|
|
3111
3134
|
}
|
|
3135
|
+
async function applyToIndex(cwd, patch, opts = {}) {
|
|
3136
|
+
for (let attempt = 1; ; attempt++) {
|
|
3137
|
+
try {
|
|
3138
|
+
await applyOnce(cwd, patch, opts);
|
|
3139
|
+
return;
|
|
3140
|
+
} catch (e) {
|
|
3141
|
+
if (!(e instanceof GitError && e.code === "index_locked") || attempt >= 3) throw e;
|
|
3142
|
+
await new Promise((r) => setTimeout(r, 150 * attempt));
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
async function applyOnce(cwd, patch, opts) {
|
|
3147
|
+
const args = ["apply", "--cached", "--whitespace=nowarn"];
|
|
3148
|
+
if (opts.reverse) args.push("--reverse");
|
|
3149
|
+
args.push("-");
|
|
3150
|
+
await new Promise((resolve, reject) => {
|
|
3151
|
+
const child = execFile(
|
|
3152
|
+
"git",
|
|
3153
|
+
args,
|
|
3154
|
+
{
|
|
3155
|
+
cwd,
|
|
3156
|
+
timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
|
|
3157
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
3158
|
+
encoding: "utf8",
|
|
3159
|
+
env: { ...process.env, LC_ALL: "C", GIT_PAGER: "cat", PAGER: "cat", GIT_TERMINAL_PROMPT: "0" },
|
|
3160
|
+
windowsHide: true
|
|
3161
|
+
},
|
|
3162
|
+
(error, _stdout, stderr) => {
|
|
3163
|
+
const err = error;
|
|
3164
|
+
if (!err) {
|
|
3165
|
+
resolve();
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
if (err.killed || err.signal === "SIGTERM") {
|
|
3169
|
+
reject(new GitError("git apply timed out", null, stderr, 504, "git_timeout"));
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
if (err.code === "ENOENT") {
|
|
3173
|
+
if (!existsSync(cwd)) {
|
|
3174
|
+
reject(new GitError(`working directory no longer exists: ${cwd}`, null, "", 400, "unknown_worktree"));
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
reject(new GitError("git executable not found in PATH", null, "", 500, "git_missing"));
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
const code = typeof err.code === "number" ? err.code : -1;
|
|
3181
|
+
const first = stderr.trim().split("\n")[0] || err.message;
|
|
3182
|
+
if (/index\.lock/.test(stderr)) {
|
|
3183
|
+
reject(new GitError("the index is locked by another git process; try again", code, stderr, 409, "index_locked"));
|
|
3184
|
+
return;
|
|
3185
|
+
}
|
|
3186
|
+
reject(new GitError(first.replace(/^error: /, ""), code, stderr, 409, "apply_failed"));
|
|
3187
|
+
}
|
|
3188
|
+
);
|
|
3189
|
+
child.stdin?.on("error", () => void 0);
|
|
3190
|
+
child.stdin?.end(patch);
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
async function runGitWrite(args, opts) {
|
|
3194
|
+
for (const arg of args) if (arg.includes("\0")) throw new GitError("NUL byte in git argument", null, "", 400, "git_bad_argument");
|
|
3195
|
+
return new Promise((resolve, reject) => {
|
|
3196
|
+
execFile(
|
|
3197
|
+
"git",
|
|
3198
|
+
[...args],
|
|
3199
|
+
{
|
|
3200
|
+
cwd: opts.cwd,
|
|
3201
|
+
timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
|
|
3202
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
3203
|
+
encoding: "utf8",
|
|
3204
|
+
env: { ...process.env, LC_ALL: "C", GIT_PAGER: "cat", PAGER: "cat", GIT_TERMINAL_PROMPT: "0" },
|
|
3205
|
+
windowsHide: true
|
|
3206
|
+
},
|
|
3207
|
+
(error, stdout, stderr) => {
|
|
3208
|
+
const err = error;
|
|
3209
|
+
if (!err) {
|
|
3210
|
+
resolve({ stdout, stderr, code: 0 });
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
if (err.killed || err.signal === "SIGTERM") {
|
|
3214
|
+
reject(new GitError(`git ${args[0]} timed out`, null, stderr, 504, "git_timeout"));
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
if (err.code === "ENOENT") {
|
|
3218
|
+
if (!existsSync(opts.cwd)) {
|
|
3219
|
+
reject(new GitError(`working directory no longer exists: ${opts.cwd}`, null, "", 400, "unknown_worktree"));
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
reject(new GitError("git executable not found in PATH", null, "", 500, "git_missing"));
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
const code = typeof err.code === "number" ? err.code : -1;
|
|
3226
|
+
const first = stderr.split("\n").find((l) => l.trim()) ?? err.message;
|
|
3227
|
+
reject(new GitError(first.trim().replace(/^(fatal|error): /, ""), code, stderr, 500, "git_failed"));
|
|
3228
|
+
}
|
|
3229
|
+
);
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3112
3232
|
async function refExists(cwd, ref) {
|
|
3113
3233
|
try {
|
|
3114
3234
|
await runGit(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], { cwd });
|
|
@@ -3159,19 +3279,20 @@ async function resolveRepo(dir) {
|
|
|
3159
3279
|
const commonRoot = path.basename(commonDir) === ".git" ? path.dirname(commonDir) : root;
|
|
3160
3280
|
return { root, commonRoot };
|
|
3161
3281
|
}
|
|
3162
|
-
async function
|
|
3282
|
+
async function listWorktreesAll(ctx) {
|
|
3163
3283
|
const r = await runGit(["worktree", "list", "--porcelain"], { cwd: ctx.root });
|
|
3164
3284
|
const out = [];
|
|
3165
3285
|
let cur = null;
|
|
3166
3286
|
const flush = () => {
|
|
3167
|
-
if (cur && cur.path
|
|
3287
|
+
if (cur && cur.path) {
|
|
3168
3288
|
out.push({
|
|
3169
3289
|
path: cur.path,
|
|
3170
3290
|
head: cur.head ?? "",
|
|
3171
3291
|
branch: cur.branch,
|
|
3172
3292
|
isMain: false,
|
|
3173
3293
|
detached: cur.detached ?? false,
|
|
3174
|
-
bare: cur.bare ?? false
|
|
3294
|
+
bare: cur.bare ?? false,
|
|
3295
|
+
prunable: cur.prunable ?? false
|
|
3175
3296
|
});
|
|
3176
3297
|
}
|
|
3177
3298
|
cur = null;
|
|
@@ -3199,6 +3320,9 @@ async function listWorktrees(ctx) {
|
|
|
3199
3320
|
}
|
|
3200
3321
|
return out;
|
|
3201
3322
|
}
|
|
3323
|
+
async function listWorktrees(ctx) {
|
|
3324
|
+
return (await listWorktreesAll(ctx)).filter((w) => !w.prunable).map(({ prunable: _prunable, ...w }) => w);
|
|
3325
|
+
}
|
|
3202
3326
|
async function currentBranch(cwd) {
|
|
3203
3327
|
const res = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd }).catch(() => ({ stdout: "" }));
|
|
3204
3328
|
const name = res.stdout.trim();
|
|
@@ -3730,20 +3854,253 @@ function reanchorComment(comment, diff, now = (/* @__PURE__ */ new Date()).toISO
|
|
|
3730
3854
|
};
|
|
3731
3855
|
}
|
|
3732
3856
|
|
|
3857
|
+
// packages/server/src/patch.ts
|
|
3858
|
+
function quotePath(p) {
|
|
3859
|
+
if (!/[\s"\\\x00-\x1f\x7f]|[^\x00-\x7f]/.test(p)) return p;
|
|
3860
|
+
let out = '"';
|
|
3861
|
+
for (const byte of Buffer.from(p, "utf8")) {
|
|
3862
|
+
if (byte === 34) out += '\\"';
|
|
3863
|
+
else if (byte === 92) out += "\\\\";
|
|
3864
|
+
else if (byte === 10) out += "\\n";
|
|
3865
|
+
else if (byte === 9) out += "\\t";
|
|
3866
|
+
else if (byte < 32 || byte >= 127) out += "\\" + byte.toString(8).padStart(3, "0");
|
|
3867
|
+
else out += String.fromCharCode(byte);
|
|
3868
|
+
}
|
|
3869
|
+
return out + '"';
|
|
3870
|
+
}
|
|
3871
|
+
function cutHunk(hunk, picked, mode) {
|
|
3872
|
+
const out = [];
|
|
3873
|
+
hunk.lines.forEach((l, i) => {
|
|
3874
|
+
const noNewline = !!l.noNewline;
|
|
3875
|
+
if (l.type === "context") out.push({ ch: " ", content: l.content, noNewline });
|
|
3876
|
+
else if (picked.has(i)) out.push({ ch: l.type === "add" ? "+" : "-", content: l.content, noNewline });
|
|
3877
|
+
else if (l.type === "del" === (mode === "stage")) out.push({ ch: " ", content: l.content, noNewline });
|
|
3878
|
+
});
|
|
3879
|
+
out.forEach((l, j) => {
|
|
3880
|
+
if (!l.noNewline) return;
|
|
3881
|
+
const rest = out.slice(j + 1);
|
|
3882
|
+
const ok = l.ch === " " ? rest.length === 0 : l.ch === "-" ? rest.every((r) => r.ch === "+") : rest.every((r) => r.ch === "-");
|
|
3883
|
+
if (!ok) throw badRequest("the selection splits the missing newline at the end of the file; include the neighbouring changed lines", "newline_split");
|
|
3884
|
+
});
|
|
3885
|
+
return out;
|
|
3886
|
+
}
|
|
3887
|
+
function pickedLines(diff, selection) {
|
|
3888
|
+
const picked = /* @__PURE__ */ new Map();
|
|
3889
|
+
const changed = (h) => h.lines.map((l, i) => l.type === "context" ? -1 : i).filter((i) => i >= 0);
|
|
3890
|
+
if (selection === void 0) {
|
|
3891
|
+
diff.hunks.forEach((h, i) => picked.set(i, new Set(changed(h))));
|
|
3892
|
+
return picked;
|
|
3893
|
+
}
|
|
3894
|
+
if (!Array.isArray(selection)) throw badRequest("hunks must be an array", "bad_selection");
|
|
3895
|
+
for (const sel of selection) {
|
|
3896
|
+
if (!sel || !Number.isInteger(sel.index) || sel.index < 0 || sel.index >= diff.hunks.length) throw badRequest(`no such hunk: ${sel?.index}`, "bad_selection");
|
|
3897
|
+
const hunk = diff.hunks[sel.index];
|
|
3898
|
+
const set = picked.get(sel.index) ?? /* @__PURE__ */ new Set();
|
|
3899
|
+
picked.set(sel.index, set);
|
|
3900
|
+
if (sel.lines === void 0) {
|
|
3901
|
+
for (const i of changed(hunk)) set.add(i);
|
|
3902
|
+
continue;
|
|
3903
|
+
}
|
|
3904
|
+
if (!Array.isArray(sel.lines)) throw badRequest("lines must be an array of indices", "bad_selection");
|
|
3905
|
+
for (const i of sel.lines) {
|
|
3906
|
+
if (!Number.isInteger(i) || i < 0 || i >= hunk.lines.length) throw badRequest(`no such line in hunk ${sel.index}: ${i}`, "bad_selection");
|
|
3907
|
+
if (hunk.lines[i].type !== "context") set.add(i);
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
return picked;
|
|
3911
|
+
}
|
|
3912
|
+
function buildStagePatch(diff, mode, selection) {
|
|
3913
|
+
if (diff.binary) throw badRequest("binary files cannot be staged from here; use git add", "not_stageable");
|
|
3914
|
+
const whole = selection === void 0;
|
|
3915
|
+
const picked = pickedLines(diff, selection);
|
|
3916
|
+
let lines = 0;
|
|
3917
|
+
for (const set of picked.values()) lines += set.size;
|
|
3918
|
+
const changedTotal = diff.hunks.reduce((n, h) => n + h.lines.filter((l) => l.type !== "context").length, 0);
|
|
3919
|
+
const full = lines === changedTotal;
|
|
3920
|
+
const modeChange = !!(diff.oldMode && diff.newMode && diff.oldMode !== diff.newMode);
|
|
3921
|
+
const rename2 = diff.status === "renamed" && !!diff.oldPath && diff.oldPath !== diff.path;
|
|
3922
|
+
const oldPath = diff.oldPath ?? diff.path;
|
|
3923
|
+
const newPath = diff.path;
|
|
3924
|
+
const gitLine = (a, b) => `diff --git ${quotePath(`a/${a}`)} ${quotePath(`b/${b}`)}`;
|
|
3925
|
+
const from = (p) => p === null ? "--- /dev/null" : `--- ${quotePath(`a/${p}`)}`;
|
|
3926
|
+
const to = (p) => p === null ? "+++ /dev/null" : `+++ ${quotePath(`b/${p}`)}`;
|
|
3927
|
+
const header = [];
|
|
3928
|
+
if (diff.status === "added" && (mode === "stage" || full)) {
|
|
3929
|
+
header.push(gitLine(newPath, newPath), `new file mode ${diff.newMode ?? "100644"}`);
|
|
3930
|
+
if (lines > 0) header.push(from(null), to(newPath));
|
|
3931
|
+
} else if (diff.status === "deleted" && full) {
|
|
3932
|
+
header.push(gitLine(oldPath, oldPath), `deleted file mode ${diff.oldMode ?? "100644"}`);
|
|
3933
|
+
if (lines > 0) header.push(from(oldPath), to(null));
|
|
3934
|
+
} else if (diff.status === "deleted" && mode === "unstage") {
|
|
3935
|
+
throw badRequest("a staged deletion can only be unstaged as a whole", "whole_file_only");
|
|
3936
|
+
} else if (rename2 && full) {
|
|
3937
|
+
header.push(gitLine(oldPath, newPath));
|
|
3938
|
+
if (whole && modeChange) header.push(`old mode ${diff.oldMode}`, `new mode ${diff.newMode}`);
|
|
3939
|
+
header.push(`rename from ${quotePath(oldPath)}`, `rename to ${quotePath(newPath)}`);
|
|
3940
|
+
if (lines > 0) header.push(from(oldPath), to(newPath));
|
|
3941
|
+
} else {
|
|
3942
|
+
header.push(gitLine(newPath, newPath));
|
|
3943
|
+
if (whole && modeChange) header.push(`old mode ${diff.oldMode}`, `new mode ${diff.newMode}`);
|
|
3944
|
+
if (lines > 0) header.push(from(newPath), to(newPath));
|
|
3945
|
+
}
|
|
3946
|
+
if (lines === 0) {
|
|
3947
|
+
const headerOnly = whole && (modeChange || rename2 || (diff.status === "added" || diff.status === "deleted") && diff.hunks.length === 0);
|
|
3948
|
+
if (!headerOnly) throw badRequest("no changed lines selected", "empty_selection");
|
|
3949
|
+
return { patch: header.join("\n") + "\n", lines: 0 };
|
|
3950
|
+
}
|
|
3951
|
+
let delta = 0;
|
|
3952
|
+
const hunks = [];
|
|
3953
|
+
diff.hunks.forEach((hunk, index) => {
|
|
3954
|
+
const set = picked.get(index);
|
|
3955
|
+
if (!set || set.size === 0) return;
|
|
3956
|
+
const out = cutHunk(hunk, set, mode);
|
|
3957
|
+
const oldLines = out.filter((l) => l.ch !== "+").length;
|
|
3958
|
+
const newLines = out.filter((l) => l.ch !== "-").length;
|
|
3959
|
+
const oldBegin = hunk.oldLines === 0 ? hunk.oldStart + 1 : hunk.oldStart;
|
|
3960
|
+
const oldStart = oldLines === 0 ? oldBegin - 1 : oldBegin;
|
|
3961
|
+
const newStart = newLines === 0 ? oldBegin + delta - 1 : oldBegin + delta;
|
|
3962
|
+
delta += newLines - oldLines;
|
|
3963
|
+
const text = [`@@ -${oldStart},${oldLines} +${newStart},${newLines} @@${hunk.header ? " " + hunk.header : ""}`];
|
|
3964
|
+
for (const l of out) {
|
|
3965
|
+
text.push(l.ch + l.content);
|
|
3966
|
+
if (l.noNewline) text.push("\");
|
|
3967
|
+
}
|
|
3968
|
+
hunks.push(text.join("\n"));
|
|
3969
|
+
});
|
|
3970
|
+
return { patch: [...header, ...hunks].join("\n") + "\n", lines };
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
// packages/server/src/worktrees.ts
|
|
3974
|
+
import path3 from "path";
|
|
3975
|
+
import { readdir, realpath as realpath2, stat } from "fs/promises";
|
|
3976
|
+
function worktreePathPrefix(ctx) {
|
|
3977
|
+
return path3.join(path3.dirname(ctx.commonRoot), `${path3.basename(ctx.commonRoot)}-`);
|
|
3978
|
+
}
|
|
3979
|
+
async function dirtyCount(cwd) {
|
|
3980
|
+
try {
|
|
3981
|
+
const r = await runGit(["status", "--porcelain", "--untracked-files=all"], { cwd });
|
|
3982
|
+
return r.stdout.split("\n").filter((l) => l.trim()).length;
|
|
3983
|
+
} catch {
|
|
3984
|
+
return 0;
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
async function listWorktreesDetailed(ctx) {
|
|
3988
|
+
const all = await listWorktreesAll(ctx);
|
|
3989
|
+
return Promise.all(all.map(async (w) => ({ ...w, dirty: w.prunable || w.bare ? 0 : await dirtyCount(w.path) })));
|
|
3990
|
+
}
|
|
3991
|
+
async function listBranches(ctx, worktrees) {
|
|
3992
|
+
const r = await runGit(["for-each-ref", "--format=%(refname:short)%09%(objectname:short)", "refs/heads"], { cwd: ctx.commonRoot });
|
|
3993
|
+
const at = /* @__PURE__ */ new Map();
|
|
3994
|
+
for (const w of worktrees) if (w.branch) at.set(w.branch, w.path);
|
|
3995
|
+
return r.stdout.split("\n").filter(Boolean).map((line) => {
|
|
3996
|
+
const [name = "", sha = ""] = line.split(" ");
|
|
3997
|
+
const worktree = at.get(name);
|
|
3998
|
+
return { name, sha, ...worktree ? { worktree } : {} };
|
|
3999
|
+
});
|
|
4000
|
+
}
|
|
4001
|
+
async function assertBranchName(ctx, name) {
|
|
4002
|
+
if (!isValidRef(name) || name.includes("@{") || name.startsWith("refs/")) throw badRequest(`invalid branch name: ${name}`, "invalid_branch");
|
|
4003
|
+
try {
|
|
4004
|
+
await runGit(["check-ref-format", "--branch", name], { cwd: ctx.commonRoot });
|
|
4005
|
+
} catch {
|
|
4006
|
+
throw badRequest(`invalid branch name: ${name}`, "invalid_branch");
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
async function assertNewWorktreePath(ctx, worktrees, p) {
|
|
4010
|
+
if (!p || !path3.isAbsolute(p)) throw badRequest("worktree path must be absolute", "invalid_path");
|
|
4011
|
+
const target = path3.resolve(p);
|
|
4012
|
+
const parent = path3.dirname(ctx.commonRoot);
|
|
4013
|
+
if (target === parent || !target.startsWith(parent + path3.sep)) throw badRequest(`worktree path must be under ${parent}`, "invalid_path");
|
|
4014
|
+
for (const w of worktrees) {
|
|
4015
|
+
if (target === w.path || target.startsWith(w.path + path3.sep)) throw badRequest(`${target} is inside the worktree at ${w.path}`, "invalid_path");
|
|
4016
|
+
}
|
|
4017
|
+
let st;
|
|
4018
|
+
try {
|
|
4019
|
+
st = await stat(target);
|
|
4020
|
+
} catch (e) {
|
|
4021
|
+
if (e.code === "ENOENT") return target;
|
|
4022
|
+
throw e;
|
|
4023
|
+
}
|
|
4024
|
+
if (!st.isDirectory() || (await readdir(target)).length > 0) throw badRequest(`${target} already exists`, "path_exists");
|
|
4025
|
+
return target;
|
|
4026
|
+
}
|
|
4027
|
+
async function addWorktree(ctx, req) {
|
|
4028
|
+
const branch = req.branch.trim();
|
|
4029
|
+
const base = req.base?.trim();
|
|
4030
|
+
await assertBranchName(ctx, branch);
|
|
4031
|
+
const existing = await listWorktreesAll(ctx);
|
|
4032
|
+
const target = await assertNewWorktreePath(ctx, existing, req.path.trim());
|
|
4033
|
+
const cwd = ctx.commonRoot;
|
|
4034
|
+
const branchExists = await refExists(cwd, `refs/heads/${branch}`);
|
|
4035
|
+
let args;
|
|
4036
|
+
if (base) {
|
|
4037
|
+
if (!isValidRef(base)) throw badRequest(`invalid base ref: ${base}`, "invalid_ref");
|
|
4038
|
+
if (!await refExists(cwd, base)) throw badRequest(`unknown ref: ${base}`, "unknown_ref");
|
|
4039
|
+
if (branchExists) throw new HttpError(409, `branch ${branch} already exists`, "branch_exists");
|
|
4040
|
+
args = ["worktree", "add", "-b", branch, target, base];
|
|
4041
|
+
} else {
|
|
4042
|
+
if (!branchExists) throw badRequest(`unknown branch: ${branch}`, "unknown_ref");
|
|
4043
|
+
const holder = existing.find((w) => w.branch === branch);
|
|
4044
|
+
if (holder) throw new HttpError(409, `${branch} is already checked out at ${holder.path}`, "branch_in_use");
|
|
4045
|
+
args = ["worktree", "add", target, branch];
|
|
4046
|
+
}
|
|
4047
|
+
try {
|
|
4048
|
+
await runGitWrite(args, { cwd });
|
|
4049
|
+
} catch (e) {
|
|
4050
|
+
if (e instanceof GitError && /already (checked out|used by worktree)/.test(e.stderr)) throw new HttpError(409, e.message, "branch_in_use");
|
|
4051
|
+
throw e;
|
|
4052
|
+
}
|
|
4053
|
+
const real = await realpath2(target).catch(() => target);
|
|
4054
|
+
const made = (await listWorktrees(ctx)).find((w) => w.path === real);
|
|
4055
|
+
if (!made) throw new HttpError(500, `git created ${target} but does not list it`, "git_failed");
|
|
4056
|
+
return made;
|
|
4057
|
+
}
|
|
4058
|
+
async function removeWorktree(ctx, req) {
|
|
4059
|
+
const p = req.path.trim();
|
|
4060
|
+
const wt = (await listWorktreesAll(ctx)).find((w) => w.path === p || p && w.path === path3.resolve(p));
|
|
4061
|
+
if (!wt) throw badRequest(`unknown worktree: ${p}`, "unknown_worktree");
|
|
4062
|
+
if (wt.isMain) throw badRequest("the main worktree cannot be removed", "main_worktree");
|
|
4063
|
+
const cwd = ctx.commonRoot;
|
|
4064
|
+
const args = ["worktree", "remove"];
|
|
4065
|
+
if (req.force) args.push("--force");
|
|
4066
|
+
args.push(wt.path);
|
|
4067
|
+
try {
|
|
4068
|
+
await runGitWrite(args, { cwd });
|
|
4069
|
+
} catch (e) {
|
|
4070
|
+
if (e instanceof GitError && /contains modified or untracked files/.test(e.stderr)) {
|
|
4071
|
+
throw new HttpError(409, `${wt.path} has uncommitted changes`, "worktree_dirty");
|
|
4072
|
+
}
|
|
4073
|
+
if (e instanceof GitError && !req.force && /use --force|validation failed/.test(e.stderr)) {
|
|
4074
|
+
throw new HttpError(409, e.message, "needs_force");
|
|
4075
|
+
}
|
|
4076
|
+
throw e;
|
|
4077
|
+
}
|
|
4078
|
+
const res = { ok: true, branchDeleted: false };
|
|
4079
|
+
if (req.deleteBranch && wt.branch) {
|
|
4080
|
+
try {
|
|
4081
|
+
await runGitWrite(["branch", "-d", wt.branch], { cwd });
|
|
4082
|
+
res.branchDeleted = true;
|
|
4083
|
+
} catch (e) {
|
|
4084
|
+
res.branchError = e instanceof Error ? e.message : String(e);
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
return res;
|
|
4088
|
+
}
|
|
4089
|
+
|
|
3733
4090
|
// packages/server/src/state.ts
|
|
3734
4091
|
import os from "os";
|
|
3735
|
-
import
|
|
3736
|
-
import { mkdir, open, readFile as readFile2, rename, stat, unlink, writeFile } from "fs/promises";
|
|
4092
|
+
import path4 from "path";
|
|
4093
|
+
import { mkdir, open, readFile as readFile2, rename, stat as stat2, unlink, writeFile } from "fs/promises";
|
|
3737
4094
|
function dataDir() {
|
|
3738
4095
|
const xdg = process.env.XDG_DATA_HOME;
|
|
3739
|
-
const base = xdg && xdg.trim() ? xdg :
|
|
3740
|
-
return
|
|
4096
|
+
const base = xdg && xdg.trim() ? xdg : path4.join(os.homedir(), ".local", "share");
|
|
4097
|
+
return path4.join(base, "warden");
|
|
3741
4098
|
}
|
|
3742
4099
|
function repoHash(repoRoot) {
|
|
3743
4100
|
return sha1(repoRoot).slice(0, 12);
|
|
3744
4101
|
}
|
|
3745
4102
|
function stateFilePath(repoRoot, baseDir = dataDir()) {
|
|
3746
|
-
return
|
|
4103
|
+
return path4.join(baseDir, repoHash(repoRoot), "state.json");
|
|
3747
4104
|
}
|
|
3748
4105
|
function defaultPrefs() {
|
|
3749
4106
|
return { viewMode: "unified", nvimSocketByRoot: {}, autoRefresh: true };
|
|
@@ -3838,7 +4195,7 @@ var StateStore = class {
|
|
|
3838
4195
|
/** Apply a mutation under lock. The callback receives the freshest on-disk state. */
|
|
3839
4196
|
update(fn) {
|
|
3840
4197
|
const run2 = async () => {
|
|
3841
|
-
await mkdir(
|
|
4198
|
+
await mkdir(path4.dirname(this.file), { recursive: true });
|
|
3842
4199
|
const release = await this.acquireLock();
|
|
3843
4200
|
try {
|
|
3844
4201
|
const state = await this.load();
|
|
@@ -3876,7 +4233,7 @@ var StateStore = class {
|
|
|
3876
4233
|
const err = e;
|
|
3877
4234
|
if (err.code !== "EEXIST") throw e;
|
|
3878
4235
|
try {
|
|
3879
|
-
const st = await
|
|
4236
|
+
const st = await stat2(lock);
|
|
3880
4237
|
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
|
|
3881
4238
|
await unlink(lock).catch(() => void 0);
|
|
3882
4239
|
continue;
|
|
@@ -3973,9 +4330,9 @@ function formatIssueExport({ repoRoot, issue, comments }) {
|
|
|
3973
4330
|
|
|
3974
4331
|
// packages/server/src/nvim.ts
|
|
3975
4332
|
import os2 from "os";
|
|
3976
|
-
import
|
|
4333
|
+
import path5 from "path";
|
|
3977
4334
|
import { execFile as execFile2 } from "child_process";
|
|
3978
|
-
import { readdir, stat as
|
|
4335
|
+
import { readdir as readdir2, stat as stat3 } from "fs/promises";
|
|
3979
4336
|
var QUERY_TIMEOUT_MS = 500;
|
|
3980
4337
|
var CACHE_TTL_MS = 1e4;
|
|
3981
4338
|
function execNvim(args, timeoutMs) {
|
|
@@ -4004,14 +4361,14 @@ function socketDirs(env = process.env) {
|
|
|
4004
4361
|
}
|
|
4005
4362
|
}
|
|
4006
4363
|
const tmp = env.TMPDIR || "/tmp";
|
|
4007
|
-
if (user) dirs.push(
|
|
4008
|
-
if (env.NVIM_LISTEN_ADDRESS) dirs.push(
|
|
4364
|
+
if (user) dirs.push(path5.join(tmp, `nvim.${user}`));
|
|
4365
|
+
if (env.NVIM_LISTEN_ADDRESS) dirs.push(path5.dirname(env.NVIM_LISTEN_ADDRESS));
|
|
4009
4366
|
return [...new Set(dirs)];
|
|
4010
4367
|
}
|
|
4011
4368
|
var SOCKET_NAME = /^nvim\.(\d+)\.\d+$/;
|
|
4012
4369
|
async function isSocket(p) {
|
|
4013
4370
|
try {
|
|
4014
|
-
return (await
|
|
4371
|
+
return (await stat3(p)).isSocket();
|
|
4015
4372
|
} catch {
|
|
4016
4373
|
return false;
|
|
4017
4374
|
}
|
|
@@ -4021,12 +4378,12 @@ async function findSockets(dirs = socketDirs()) {
|
|
|
4021
4378
|
const visit = async (dir, depth) => {
|
|
4022
4379
|
let entries;
|
|
4023
4380
|
try {
|
|
4024
|
-
entries = await
|
|
4381
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
4025
4382
|
} catch {
|
|
4026
4383
|
return;
|
|
4027
4384
|
}
|
|
4028
4385
|
for (const ent of entries) {
|
|
4029
|
-
const full =
|
|
4386
|
+
const full = path5.join(dir, ent.name);
|
|
4030
4387
|
const m = SOCKET_NAME.exec(ent.name);
|
|
4031
4388
|
if (m && (ent.isSocket() || await isSocket(full))) {
|
|
4032
4389
|
found.set(full, Number(m[1]));
|
|
@@ -4091,8 +4448,8 @@ var NvimService = class {
|
|
|
4091
4448
|
};
|
|
4092
4449
|
|
|
4093
4450
|
// packages/server/src/watcher.ts
|
|
4094
|
-
import
|
|
4095
|
-
import { stat as
|
|
4451
|
+
import path6 from "path";
|
|
4452
|
+
import { stat as stat4 } from "fs/promises";
|
|
4096
4453
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
4097
4454
|
function statusPaths(out) {
|
|
4098
4455
|
const parts = out.split("\0");
|
|
@@ -4188,7 +4545,7 @@ var RepoWatcher = class {
|
|
|
4188
4545
|
const stats = await Promise.all(
|
|
4189
4546
|
statusPaths(statusRes.stdout).map(async (rel) => {
|
|
4190
4547
|
try {
|
|
4191
|
-
const st = await
|
|
4548
|
+
const st = await stat4(path6.join(this.root, rel));
|
|
4192
4549
|
return `${rel} ${st.mtimeMs} ${st.size}`;
|
|
4193
4550
|
} catch {
|
|
4194
4551
|
return `${rel} -`;
|
|
@@ -4200,8 +4557,8 @@ var RepoWatcher = class {
|
|
|
4200
4557
|
};
|
|
4201
4558
|
|
|
4202
4559
|
// packages/server/src/static.ts
|
|
4203
|
-
import
|
|
4204
|
-
import { readFile as readFile3, stat as
|
|
4560
|
+
import path7 from "path";
|
|
4561
|
+
import { readFile as readFile3, stat as stat5 } from "fs/promises";
|
|
4205
4562
|
var MIME = {
|
|
4206
4563
|
".html": "text/html; charset=utf-8",
|
|
4207
4564
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -4218,20 +4575,20 @@ var MIME = {
|
|
|
4218
4575
|
".txt": "text/plain; charset=utf-8"
|
|
4219
4576
|
};
|
|
4220
4577
|
async function serveStaticFile(c, webDir) {
|
|
4221
|
-
const root =
|
|
4578
|
+
const root = path7.resolve(webDir);
|
|
4222
4579
|
let rel;
|
|
4223
4580
|
try {
|
|
4224
4581
|
rel = decodeURIComponent(c.req.path);
|
|
4225
4582
|
} catch {
|
|
4226
4583
|
return c.text("Bad Request", 400);
|
|
4227
4584
|
}
|
|
4228
|
-
let target =
|
|
4229
|
-
if (target !== root && !target.startsWith(root +
|
|
4585
|
+
let target = path7.resolve(root, "." + rel);
|
|
4586
|
+
if (target !== root && !target.startsWith(root + path7.sep)) return c.text("Forbidden", 403);
|
|
4230
4587
|
try {
|
|
4231
|
-
const st = await
|
|
4232
|
-
if (st.isDirectory()) target =
|
|
4588
|
+
const st = await stat5(target);
|
|
4589
|
+
if (st.isDirectory()) target = path7.join(target, "index.html");
|
|
4233
4590
|
} catch {
|
|
4234
|
-
target =
|
|
4591
|
+
target = path7.join(root, "index.html");
|
|
4235
4592
|
}
|
|
4236
4593
|
let body;
|
|
4237
4594
|
try {
|
|
@@ -4239,7 +4596,7 @@ async function serveStaticFile(c, webDir) {
|
|
|
4239
4596
|
} catch {
|
|
4240
4597
|
return c.text("Not Found", 404);
|
|
4241
4598
|
}
|
|
4242
|
-
const ext =
|
|
4599
|
+
const ext = path7.extname(target).toLowerCase();
|
|
4243
4600
|
const isAsset = rel.startsWith("/assets/");
|
|
4244
4601
|
return new Response(new Uint8Array(body), {
|
|
4245
4602
|
status: 200,
|
|
@@ -4310,6 +4667,12 @@ function createApp(opts) {
|
|
|
4310
4667
|
return c.json({ error: err instanceof Error ? err.message : String(err), code: "internal" }, 500);
|
|
4311
4668
|
});
|
|
4312
4669
|
const api = new Hono2();
|
|
4670
|
+
api.use("*", async (c, next) => {
|
|
4671
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD" && c.req.header("sec-fetch-site") === "cross-site") {
|
|
4672
|
+
throw new HttpError(403, "cross-site requests are refused", "cross_site");
|
|
4673
|
+
}
|
|
4674
|
+
await next();
|
|
4675
|
+
});
|
|
4313
4676
|
api.get("/ping", (c) => c.text(opts.instanceToken ?? ""));
|
|
4314
4677
|
api.get("/repo", async (c) => {
|
|
4315
4678
|
const state = await store.load();
|
|
@@ -4407,6 +4770,24 @@ function createApp(opts) {
|
|
|
4407
4770
|
changedNotices.get(key)?.delete(body.path);
|
|
4408
4771
|
return c.json({ viewed });
|
|
4409
4772
|
});
|
|
4773
|
+
api.post("/targets/:key/stage", async (c) => {
|
|
4774
|
+
const key = c.req.param("key");
|
|
4775
|
+
const ctx = await targetCtx(key);
|
|
4776
|
+
const mode = stageModeFor(ctx.target);
|
|
4777
|
+
if (!mode) throw badRequest("only the working (stage) and staged (unstage) views can be staged from", "not_stageable");
|
|
4778
|
+
const body = await c.req.json();
|
|
4779
|
+
if (!body.path || typeof body.path !== "string") throw badRequest("missing path");
|
|
4780
|
+
if (!body.contentHash || typeof body.contentHash !== "string") throw badRequest("contentHash is required");
|
|
4781
|
+
if (body.hunks !== void 0 && !Array.isArray(body.hunks)) throw badRequest("hunks must be an array", "bad_selection");
|
|
4782
|
+
const diff = await fileDiffWithHints(ctx, body.path);
|
|
4783
|
+
if (!diff) throw badRequest(`file ${body.path} is not part of ${key}`, "no_diff");
|
|
4784
|
+
if (diff.contentHash !== body.contentHash) throw new HttpError(409, "the diff changed since it was loaded; refresh and pick the lines again", "diff_changed");
|
|
4785
|
+
const { patch, lines } = buildStagePatch(diff, mode, body.hunks);
|
|
4786
|
+
await applyToIndex(ctx.cwd, patch, { reverse: mode === "unstage" });
|
|
4787
|
+
void watchers.get(ctx.cwd)?.poll();
|
|
4788
|
+
const res = { ok: true, lines };
|
|
4789
|
+
return c.json(res);
|
|
4790
|
+
});
|
|
4410
4791
|
api.get("/targets/:key/comments", async (c) => {
|
|
4411
4792
|
const key = c.req.param("key");
|
|
4412
4793
|
const state = await store.load();
|
|
@@ -4629,16 +5010,28 @@ function createApp(opts) {
|
|
|
4629
5010
|
id: randomUUID(),
|
|
4630
5011
|
title: body.title.trim(),
|
|
4631
5012
|
body: typeof body.body === "string" ? body.body : "",
|
|
4632
|
-
status: "open",
|
|
5013
|
+
status: body.status === "closed" ? "closed" : "open",
|
|
4633
5014
|
commentIds: Array.isArray(body.commentIds) ? body.commentIds.filter((x) => typeof x === "string") : [],
|
|
4634
5015
|
createdAt: now,
|
|
4635
5016
|
updatedAt: now
|
|
4636
5017
|
};
|
|
4637
5018
|
await store.update((s) => {
|
|
4638
|
-
s.issues.
|
|
5019
|
+
s.issues = insertAfter(s.issues, issue, typeof body.after === "string" ? body.after : void 0);
|
|
4639
5020
|
});
|
|
4640
5021
|
return c.json(issue, 201);
|
|
4641
5022
|
});
|
|
5023
|
+
api.post("/issues/:id/move", async (c) => {
|
|
5024
|
+
const id = c.req.param("id");
|
|
5025
|
+
const body = await c.req.json();
|
|
5026
|
+
if (body.before !== null && typeof body.before !== "string") throw badRequest("before must be an id or null");
|
|
5027
|
+
const issues = await store.update((s) => {
|
|
5028
|
+
const next = moveBefore(s.issues, id, body.before);
|
|
5029
|
+
if (!next) throw notFound("issue not found");
|
|
5030
|
+
s.issues = next;
|
|
5031
|
+
return s.issues;
|
|
5032
|
+
});
|
|
5033
|
+
return c.json({ issues });
|
|
5034
|
+
});
|
|
4642
5035
|
api.patch("/issues/:id", async (c) => {
|
|
4643
5036
|
const id = c.req.param("id");
|
|
4644
5037
|
const body = await c.req.json();
|
|
@@ -4712,15 +5105,28 @@ function createApp(opts) {
|
|
|
4712
5105
|
branch,
|
|
4713
5106
|
title: body.title.trim(),
|
|
4714
5107
|
body: typeof body.body === "string" ? body.body : "",
|
|
4715
|
-
status: "open",
|
|
5108
|
+
status: body.status === "done" ? "done" : "open",
|
|
4716
5109
|
createdAt: now,
|
|
4717
5110
|
updatedAt: now
|
|
4718
5111
|
};
|
|
4719
5112
|
await store.update((s) => {
|
|
4720
|
-
s.todos.
|
|
5113
|
+
s.todos = insertAfter(s.todos, todo, typeof body.after === "string" ? body.after : void 0);
|
|
4721
5114
|
});
|
|
4722
5115
|
return c.json(todo, 201);
|
|
4723
5116
|
});
|
|
5117
|
+
api.post("/todos/:id/move", async (c) => {
|
|
5118
|
+
const id = c.req.param("id");
|
|
5119
|
+
const body = await c.req.json();
|
|
5120
|
+
if (body.before !== null && typeof body.before !== "string") throw badRequest("before must be an id or null");
|
|
5121
|
+
const todos = await store.update((s) => {
|
|
5122
|
+
const next = moveBefore(s.todos, id, body.before);
|
|
5123
|
+
if (!next) throw notFound("todo not found");
|
|
5124
|
+
s.todos = next;
|
|
5125
|
+
return s.todos;
|
|
5126
|
+
});
|
|
5127
|
+
const res = { todos };
|
|
5128
|
+
return c.json(res);
|
|
5129
|
+
});
|
|
4724
5130
|
api.patch("/todos/:id", async (c) => {
|
|
4725
5131
|
const id = c.req.param("id");
|
|
4726
5132
|
const body = await c.req.json();
|
|
@@ -4792,6 +5198,37 @@ function createApp(opts) {
|
|
|
4792
5198
|
const res = { commits, hasMore };
|
|
4793
5199
|
return c.json(res);
|
|
4794
5200
|
});
|
|
5201
|
+
api.get("/fork-point", async (c) => {
|
|
5202
|
+
const base = (c.req.query("base") ?? "").trim();
|
|
5203
|
+
if (!isValidRef(base)) throw badRequest("invalid base ref");
|
|
5204
|
+
const cwd = await knownRoot(c.req.query("root") || void 0);
|
|
5205
|
+
if (!await refExists(cwd, base)) throw badRequest(`unknown ref: ${base}`, "unknown_ref");
|
|
5206
|
+
const sha = await refExists(cwd, "HEAD") ? await mergeBase(cwd, base, "HEAD") : void 0;
|
|
5207
|
+
if (!sha) throw badRequest(`${base} and HEAD share no history`, "no_merge_base");
|
|
5208
|
+
const counts = (await runGit(["rev-list", "--left-right", "--count", `${base}...HEAD`], { cwd })).stdout.trim().split(/\s+/);
|
|
5209
|
+
const res = { base, sha, ahead: Number(counts[1] ?? 0), behind: Number(counts[0] ?? 0) };
|
|
5210
|
+
return c.json(res);
|
|
5211
|
+
});
|
|
5212
|
+
api.get("/worktrees", async (c) => {
|
|
5213
|
+
const list = await listWorktreesDetailed(repo);
|
|
5214
|
+
const res = { worktrees: list, branches: await listBranches(repo, list), pathPrefix: worktreePathPrefix(repo) };
|
|
5215
|
+
return c.json(res);
|
|
5216
|
+
});
|
|
5217
|
+
api.post("/worktrees", async (c) => {
|
|
5218
|
+
const body = await c.req.json();
|
|
5219
|
+
if (typeof body.path !== "string" || typeof body.branch !== "string") throw badRequest("path and branch are required");
|
|
5220
|
+
if (body.base !== void 0 && typeof body.base !== "string") throw badRequest("base must be a ref");
|
|
5221
|
+
const made = await addWorktree(repo, body);
|
|
5222
|
+
worktreeCache = null;
|
|
5223
|
+
return c.json(made, 201);
|
|
5224
|
+
});
|
|
5225
|
+
api.post("/worktrees/remove", async (c) => {
|
|
5226
|
+
const body = await c.req.json();
|
|
5227
|
+
if (typeof body.path !== "string") throw badRequest("path is required");
|
|
5228
|
+
const res = await removeWorktree(repo, body);
|
|
5229
|
+
worktreeCache = null;
|
|
5230
|
+
return c.json(res);
|
|
5231
|
+
});
|
|
4795
5232
|
api.get("/events", async (c) => {
|
|
4796
5233
|
const root = c.req.query("root") || repo.root;
|
|
4797
5234
|
const known = root === repo.root || root === repo.commonRoot || (await worktrees()).some((w) => w.path === root);
|
|
@@ -4843,7 +5280,7 @@ function createApp(opts) {
|
|
|
4843
5280
|
api.post("/nvim/open", async (c) => {
|
|
4844
5281
|
const body = await c.req.json();
|
|
4845
5282
|
if (!body.socket || !body.absPath) throw badRequest("socket and absPath are required");
|
|
4846
|
-
if (!
|
|
5283
|
+
if (!path8.isAbsolute(body.absPath)) throw badRequest("absPath must be absolute");
|
|
4847
5284
|
const scan = await nvim.scan();
|
|
4848
5285
|
if (!scan.instances.some((i) => i.socket === body.socket)) {
|
|
4849
5286
|
const fresh = await nvim.scan(true);
|
|
@@ -4969,7 +5406,7 @@ async function startServer(opts) {
|
|
|
4969
5406
|
}
|
|
4970
5407
|
|
|
4971
5408
|
// bin/cli.ts
|
|
4972
|
-
var VERSION = true ? "0.
|
|
5409
|
+
var VERSION = true ? "0.8.0" : "dev";
|
|
4973
5410
|
function parseArgs(argv) {
|
|
4974
5411
|
const args = { repoPath: process.cwd(), open: true, help: false, version: false };
|
|
4975
5412
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -4987,7 +5424,7 @@ function parseArgs(argv) {
|
|
|
4987
5424
|
} else if (a === "--help" || a === "-h") args.help = true;
|
|
4988
5425
|
else if (a === "--version" || a === "-v") args.version = true;
|
|
4989
5426
|
else if (a.startsWith("-")) throw new Error(`unknown option: ${a}`);
|
|
4990
|
-
else args.repoPath =
|
|
5427
|
+
else args.repoPath = path9.resolve(a);
|
|
4991
5428
|
}
|
|
4992
5429
|
return args;
|
|
4993
5430
|
}
|
|
@@ -5030,16 +5467,16 @@ async function openBrowser(url) {
|
|
|
5030
5467
|
return false;
|
|
5031
5468
|
}
|
|
5032
5469
|
function resolveWebDir() {
|
|
5033
|
-
const here =
|
|
5470
|
+
const here = path9.dirname(fileURLToPath(import.meta.url));
|
|
5034
5471
|
const candidates = [
|
|
5035
5472
|
process.env.WARDEN_WEB_DIR,
|
|
5036
|
-
|
|
5473
|
+
path9.join(here, "web"),
|
|
5037
5474
|
// dist/cli.js -> dist/web
|
|
5038
|
-
|
|
5475
|
+
path9.join(here, "..", "dist", "web")
|
|
5039
5476
|
// bin/cli.ts (tsx dev) -> dist/web
|
|
5040
5477
|
].filter((p) => !!p);
|
|
5041
5478
|
for (const dir of candidates) {
|
|
5042
|
-
if (existsSync2(
|
|
5479
|
+
if (existsSync2(path9.join(dir, "index.html"))) return dir;
|
|
5043
5480
|
}
|
|
5044
5481
|
return void 0;
|
|
5045
5482
|
}
|