@xbghc/warden 0.7.0 → 0.9.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 +74 -29
- package/dist/cli.js +332 -138
- package/dist/web/assets/index-BAWRMudD.js +198 -0
- package/dist/web/assets/index-DI521Osx.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +5 -1
- package/dist/web/assets/index-Cr74Kv2S.css +0 -1
- package/dist/web/assets/index-DNRG_SPg.js +0 -198
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
|
|
|
@@ -2918,7 +2918,7 @@ function parseTargetKey(key) {
|
|
|
2918
2918
|
if (key.startsWith("worktree:")) {
|
|
2919
2919
|
const rest = key.slice("worktree:".length);
|
|
2920
2920
|
const m = /^(.+):(working|staged|all|commit:.+|range:.+|base:.+)$/.exec(rest);
|
|
2921
|
-
if (!m
|
|
2921
|
+
if (!m?.[1] || !m[2]) throw new TargetKeyError(`invalid worktree target key: ${key}`);
|
|
2922
2922
|
const worktree = m[1];
|
|
2923
2923
|
if (!worktree.startsWith("/")) throw new TargetKeyError(`worktree path must be absolute: ${worktree}`);
|
|
2924
2924
|
return { ...parseSuffix(m[2]), worktree };
|
|
@@ -2968,11 +2968,7 @@ function stageModeFor(t) {
|
|
|
2968
2968
|
function localViewKeys(key) {
|
|
2969
2969
|
const t = tryParseTargetKey(key);
|
|
2970
2970
|
const wt = t?.worktree ? { worktree: t.worktree } : {};
|
|
2971
|
-
return [
|
|
2972
|
-
formatTargetKey({ kind: "working", ...wt }),
|
|
2973
|
-
formatTargetKey({ kind: "staged", ...wt }),
|
|
2974
|
-
formatTargetKey({ kind: "all", ...wt })
|
|
2975
|
-
];
|
|
2971
|
+
return [formatTargetKey({ kind: "working", ...wt }), formatTargetKey({ kind: "staged", ...wt }), formatTargetKey({ kind: "all", ...wt })];
|
|
2976
2972
|
}
|
|
2977
2973
|
|
|
2978
2974
|
// packages/shared/src/order.ts
|
|
@@ -3049,7 +3045,19 @@ function parseCommitLog(stdout) {
|
|
|
3049
3045
|
// packages/server/src/git.ts
|
|
3050
3046
|
import { execFile } from "child_process";
|
|
3051
3047
|
import { existsSync } from "fs";
|
|
3052
|
-
var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
3048
|
+
var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
3049
|
+
"rev-parse",
|
|
3050
|
+
"diff",
|
|
3051
|
+
"show",
|
|
3052
|
+
"log",
|
|
3053
|
+
"worktree",
|
|
3054
|
+
"ls-files",
|
|
3055
|
+
"status",
|
|
3056
|
+
"merge-base",
|
|
3057
|
+
"rev-list",
|
|
3058
|
+
"for-each-ref",
|
|
3059
|
+
"check-ref-format"
|
|
3060
|
+
]);
|
|
3053
3061
|
var FORBIDDEN_OPTION_PREFIXES = ["--output", "--ext-diff", "--textconv", "-c", "--config-env", "--exec-path", "--git-dir", "--work-tree"];
|
|
3054
3062
|
var DEFAULT_GIT_TIMEOUT_MS = 3e4;
|
|
3055
3063
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
@@ -3190,6 +3198,45 @@ async function applyOnce(cwd, patch, opts) {
|
|
|
3190
3198
|
child.stdin?.end(patch);
|
|
3191
3199
|
});
|
|
3192
3200
|
}
|
|
3201
|
+
async function runGitWrite(args, opts) {
|
|
3202
|
+
for (const arg of args) if (arg.includes("\0")) throw new GitError("NUL byte in git argument", null, "", 400, "git_bad_argument");
|
|
3203
|
+
return new Promise((resolve, reject) => {
|
|
3204
|
+
execFile(
|
|
3205
|
+
"git",
|
|
3206
|
+
[...args],
|
|
3207
|
+
{
|
|
3208
|
+
cwd: opts.cwd,
|
|
3209
|
+
timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
|
|
3210
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
3211
|
+
encoding: "utf8",
|
|
3212
|
+
env: { ...process.env, LC_ALL: "C", GIT_PAGER: "cat", PAGER: "cat", GIT_TERMINAL_PROMPT: "0" },
|
|
3213
|
+
windowsHide: true
|
|
3214
|
+
},
|
|
3215
|
+
(error, stdout, stderr) => {
|
|
3216
|
+
const err = error;
|
|
3217
|
+
if (!err) {
|
|
3218
|
+
resolve({ stdout, stderr, code: 0 });
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
if (err.killed || err.signal === "SIGTERM") {
|
|
3222
|
+
reject(new GitError(`git ${args[0]} timed out`, null, stderr, 504, "git_timeout"));
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
if (err.code === "ENOENT") {
|
|
3226
|
+
if (!existsSync(opts.cwd)) {
|
|
3227
|
+
reject(new GitError(`working directory no longer exists: ${opts.cwd}`, null, "", 400, "unknown_worktree"));
|
|
3228
|
+
return;
|
|
3229
|
+
}
|
|
3230
|
+
reject(new GitError("git executable not found in PATH", null, "", 500, "git_missing"));
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
const code = typeof err.code === "number" ? err.code : -1;
|
|
3234
|
+
const first = stderr.split("\n").find((l) => l.trim()) ?? err.message;
|
|
3235
|
+
reject(new GitError(first.trim().replace(/^(fatal|error): /, ""), code, stderr, 500, "git_failed"));
|
|
3236
|
+
}
|
|
3237
|
+
);
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3193
3240
|
async function refExists(cwd, ref) {
|
|
3194
3241
|
try {
|
|
3195
3242
|
await runGit(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], { cwd });
|
|
@@ -3230,7 +3277,7 @@ async function resolveRepo(dir) {
|
|
|
3230
3277
|
try {
|
|
3231
3278
|
const r = await runGit(["rev-parse", "--show-toplevel"], { cwd: dir });
|
|
3232
3279
|
top = r.stdout.trim();
|
|
3233
|
-
} catch
|
|
3280
|
+
} catch {
|
|
3234
3281
|
throw new HttpError(400, `${dir} is not inside a git repository`, "not_a_repo");
|
|
3235
3282
|
}
|
|
3236
3283
|
if (!top) throw new HttpError(400, `${dir} is not inside a git worktree (bare repository?)`, "not_a_repo");
|
|
@@ -3240,19 +3287,20 @@ async function resolveRepo(dir) {
|
|
|
3240
3287
|
const commonRoot = path.basename(commonDir) === ".git" ? path.dirname(commonDir) : root;
|
|
3241
3288
|
return { root, commonRoot };
|
|
3242
3289
|
}
|
|
3243
|
-
async function
|
|
3290
|
+
async function listWorktreesAll(ctx) {
|
|
3244
3291
|
const r = await runGit(["worktree", "list", "--porcelain"], { cwd: ctx.root });
|
|
3245
3292
|
const out = [];
|
|
3246
3293
|
let cur = null;
|
|
3247
3294
|
const flush = () => {
|
|
3248
|
-
if (cur
|
|
3295
|
+
if (cur?.path) {
|
|
3249
3296
|
out.push({
|
|
3250
3297
|
path: cur.path,
|
|
3251
3298
|
head: cur.head ?? "",
|
|
3252
3299
|
branch: cur.branch,
|
|
3253
3300
|
isMain: false,
|
|
3254
3301
|
detached: cur.detached ?? false,
|
|
3255
|
-
bare: cur.bare ?? false
|
|
3302
|
+
bare: cur.bare ?? false,
|
|
3303
|
+
prunable: cur.prunable ?? false
|
|
3256
3304
|
});
|
|
3257
3305
|
}
|
|
3258
3306
|
cur = null;
|
|
@@ -3266,7 +3314,6 @@ async function listWorktrees(ctx) {
|
|
|
3266
3314
|
flush();
|
|
3267
3315
|
cur = { path: line.slice("worktree ".length) };
|
|
3268
3316
|
} else if (!cur) {
|
|
3269
|
-
continue;
|
|
3270
3317
|
} else if (line.startsWith("HEAD ")) cur.head = line.slice(5);
|
|
3271
3318
|
else if (line.startsWith("branch ")) cur.branch = line.slice(7).replace(/^refs\/heads\//, "");
|
|
3272
3319
|
else if (line === "detached") cur.detached = true;
|
|
@@ -3280,6 +3327,9 @@ async function listWorktrees(ctx) {
|
|
|
3280
3327
|
}
|
|
3281
3328
|
return out;
|
|
3282
3329
|
}
|
|
3330
|
+
async function listWorktrees(ctx) {
|
|
3331
|
+
return (await listWorktreesAll(ctx)).filter((w) => !w.prunable).map(({ prunable: _prunable, ...w }) => w);
|
|
3332
|
+
}
|
|
3283
3333
|
async function currentBranch(cwd) {
|
|
3284
3334
|
const res = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd }).catch(() => ({ stdout: "" }));
|
|
3285
3335
|
const name = res.stdout.trim();
|
|
@@ -3323,7 +3373,9 @@ function lineHash(content) {
|
|
|
3323
3373
|
return sha1(content.trimEnd());
|
|
3324
3374
|
}
|
|
3325
3375
|
function hunkHash(lineContents) {
|
|
3326
|
-
return sha1(
|
|
3376
|
+
return sha1(
|
|
3377
|
+
lineContents.map((l) => l.trimEnd()).join("\n").trim()
|
|
3378
|
+
);
|
|
3327
3379
|
}
|
|
3328
3380
|
|
|
3329
3381
|
// packages/server/src/diffparse.ts
|
|
@@ -3364,7 +3416,7 @@ function stripPrefix(p, prefix) {
|
|
|
3364
3416
|
function splitGitHeader(rest) {
|
|
3365
3417
|
if (rest.startsWith('"')) {
|
|
3366
3418
|
const m = /^("(?:[^"\\]|\\.)*")\s+("(?:[^"\\]|\\.)*")$/.exec(rest);
|
|
3367
|
-
if (m
|
|
3419
|
+
if (m?.[1] && m[2]) return { a: stripPrefix(m[1], "a/"), b: stripPrefix(m[2], "b/") };
|
|
3368
3420
|
}
|
|
3369
3421
|
for (let i = 0; i < rest.length; i++) {
|
|
3370
3422
|
if (rest[i] !== " ") continue;
|
|
@@ -3521,7 +3573,6 @@ function parseUnifiedDiff(text) {
|
|
|
3521
3573
|
if (line.startsWith("Binary files ") || line === "GIT binary patch") {
|
|
3522
3574
|
c.binary = true;
|
|
3523
3575
|
c.raw.push(line);
|
|
3524
|
-
continue;
|
|
3525
3576
|
}
|
|
3526
3577
|
}
|
|
3527
3578
|
flush();
|
|
@@ -3845,12 +3896,13 @@ function pickedLines(diff, selection) {
|
|
|
3845
3896
|
const picked = /* @__PURE__ */ new Map();
|
|
3846
3897
|
const changed = (h) => h.lines.map((l, i) => l.type === "context" ? -1 : i).filter((i) => i >= 0);
|
|
3847
3898
|
if (selection === void 0) {
|
|
3848
|
-
diff.hunks.
|
|
3899
|
+
for (const [i, h] of diff.hunks.entries()) picked.set(i, new Set(changed(h)));
|
|
3849
3900
|
return picked;
|
|
3850
3901
|
}
|
|
3851
3902
|
if (!Array.isArray(selection)) throw badRequest("hunks must be an array", "bad_selection");
|
|
3852
3903
|
for (const sel of selection) {
|
|
3853
|
-
if (!sel || !Number.isInteger(sel.index) || sel.index < 0 || sel.index >= diff.hunks.length)
|
|
3904
|
+
if (!sel || !Number.isInteger(sel.index) || sel.index < 0 || sel.index >= diff.hunks.length)
|
|
3905
|
+
throw badRequest(`no such hunk: ${sel?.index}`, "bad_selection");
|
|
3854
3906
|
const hunk = diff.hunks[sel.index];
|
|
3855
3907
|
const set = picked.get(sel.index) ?? /* @__PURE__ */ new Set();
|
|
3856
3908
|
picked.set(sel.index, set);
|
|
@@ -3927,23 +3979,138 @@ function buildStagePatch(diff, mode, selection) {
|
|
|
3927
3979
|
return { patch: [...header, ...hunks].join("\n") + "\n", lines };
|
|
3928
3980
|
}
|
|
3929
3981
|
|
|
3982
|
+
// packages/server/src/worktrees.ts
|
|
3983
|
+
import path3 from "path";
|
|
3984
|
+
import { readdir, realpath as realpath2, stat } from "fs/promises";
|
|
3985
|
+
function worktreePathPrefix(ctx) {
|
|
3986
|
+
return path3.join(path3.dirname(ctx.commonRoot), `${path3.basename(ctx.commonRoot)}-`);
|
|
3987
|
+
}
|
|
3988
|
+
async function dirtyCount(cwd) {
|
|
3989
|
+
try {
|
|
3990
|
+
const r = await runGit(["status", "--porcelain", "--untracked-files=all"], { cwd });
|
|
3991
|
+
return r.stdout.split("\n").filter((l) => l.trim()).length;
|
|
3992
|
+
} catch {
|
|
3993
|
+
return 0;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
async function listWorktreesDetailed(ctx) {
|
|
3997
|
+
const all = await listWorktreesAll(ctx);
|
|
3998
|
+
return Promise.all(all.map(async (w) => ({ ...w, dirty: w.prunable || w.bare ? 0 : await dirtyCount(w.path) })));
|
|
3999
|
+
}
|
|
4000
|
+
async function listBranches(ctx, worktrees) {
|
|
4001
|
+
const r = await runGit(["for-each-ref", "--format=%(refname:short)%09%(objectname:short)", "refs/heads"], { cwd: ctx.commonRoot });
|
|
4002
|
+
const at = /* @__PURE__ */ new Map();
|
|
4003
|
+
for (const w of worktrees) if (w.branch) at.set(w.branch, w.path);
|
|
4004
|
+
return r.stdout.split("\n").filter(Boolean).map((line) => {
|
|
4005
|
+
const [name = "", sha = ""] = line.split(" ");
|
|
4006
|
+
const worktree = at.get(name);
|
|
4007
|
+
return { name, sha, ...worktree ? { worktree } : {} };
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
async function assertBranchName(ctx, name) {
|
|
4011
|
+
if (!isValidRef(name) || name.includes("@{") || name.startsWith("refs/")) throw badRequest(`invalid branch name: ${name}`, "invalid_branch");
|
|
4012
|
+
try {
|
|
4013
|
+
await runGit(["check-ref-format", "--branch", name], { cwd: ctx.commonRoot });
|
|
4014
|
+
} catch {
|
|
4015
|
+
throw badRequest(`invalid branch name: ${name}`, "invalid_branch");
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
async function assertNewWorktreePath(ctx, worktrees, p) {
|
|
4019
|
+
if (!p || !path3.isAbsolute(p)) throw badRequest("worktree path must be absolute", "invalid_path");
|
|
4020
|
+
const target = path3.resolve(p);
|
|
4021
|
+
const parent = path3.dirname(ctx.commonRoot);
|
|
4022
|
+
if (target === parent || !target.startsWith(parent + path3.sep)) throw badRequest(`worktree path must be under ${parent}`, "invalid_path");
|
|
4023
|
+
for (const w of worktrees) {
|
|
4024
|
+
if (target === w.path || target.startsWith(w.path + path3.sep)) throw badRequest(`${target} is inside the worktree at ${w.path}`, "invalid_path");
|
|
4025
|
+
}
|
|
4026
|
+
const st = await stat(target).catch((e) => {
|
|
4027
|
+
if (e.code === "ENOENT") return void 0;
|
|
4028
|
+
throw e;
|
|
4029
|
+
});
|
|
4030
|
+
if (!st) return target;
|
|
4031
|
+
if (!st.isDirectory() || (await readdir(target)).length > 0) throw badRequest(`${target} already exists`, "path_exists");
|
|
4032
|
+
return target;
|
|
4033
|
+
}
|
|
4034
|
+
async function addWorktree(ctx, req) {
|
|
4035
|
+
const branch = req.branch.trim();
|
|
4036
|
+
const base = req.base?.trim();
|
|
4037
|
+
await assertBranchName(ctx, branch);
|
|
4038
|
+
const existing = await listWorktreesAll(ctx);
|
|
4039
|
+
const target = await assertNewWorktreePath(ctx, existing, req.path.trim());
|
|
4040
|
+
const cwd = ctx.commonRoot;
|
|
4041
|
+
const branchExists = await refExists(cwd, `refs/heads/${branch}`);
|
|
4042
|
+
let args;
|
|
4043
|
+
if (base) {
|
|
4044
|
+
if (!isValidRef(base)) throw badRequest(`invalid base ref: ${base}`, "invalid_ref");
|
|
4045
|
+
if (!await refExists(cwd, base)) throw badRequest(`unknown ref: ${base}`, "unknown_ref");
|
|
4046
|
+
if (branchExists) throw new HttpError(409, `branch ${branch} already exists`, "branch_exists");
|
|
4047
|
+
args = ["worktree", "add", "-b", branch, target, base];
|
|
4048
|
+
} else {
|
|
4049
|
+
if (!branchExists) throw badRequest(`unknown branch: ${branch}`, "unknown_ref");
|
|
4050
|
+
const holder = existing.find((w) => w.branch === branch);
|
|
4051
|
+
if (holder) throw new HttpError(409, `${branch} is already checked out at ${holder.path}`, "branch_in_use");
|
|
4052
|
+
args = ["worktree", "add", target, branch];
|
|
4053
|
+
}
|
|
4054
|
+
try {
|
|
4055
|
+
await runGitWrite(args, { cwd });
|
|
4056
|
+
} catch (e) {
|
|
4057
|
+
if (e instanceof GitError && /already (checked out|used by worktree)/.test(e.stderr)) throw new HttpError(409, e.message, "branch_in_use");
|
|
4058
|
+
throw e;
|
|
4059
|
+
}
|
|
4060
|
+
const real = await realpath2(target).catch(() => target);
|
|
4061
|
+
const made = (await listWorktrees(ctx)).find((w) => w.path === real);
|
|
4062
|
+
if (!made) throw new HttpError(500, `git created ${target} but does not list it`, "git_failed");
|
|
4063
|
+
return made;
|
|
4064
|
+
}
|
|
4065
|
+
async function removeWorktree(ctx, req) {
|
|
4066
|
+
const p = req.path.trim();
|
|
4067
|
+
const wt = (await listWorktreesAll(ctx)).find((w) => w.path === p || p && w.path === path3.resolve(p));
|
|
4068
|
+
if (!wt) throw badRequest(`unknown worktree: ${p}`, "unknown_worktree");
|
|
4069
|
+
if (wt.isMain) throw badRequest("the main worktree cannot be removed", "main_worktree");
|
|
4070
|
+
const cwd = ctx.commonRoot;
|
|
4071
|
+
const args = ["worktree", "remove"];
|
|
4072
|
+
if (req.force) args.push("--force");
|
|
4073
|
+
args.push(wt.path);
|
|
4074
|
+
try {
|
|
4075
|
+
await runGitWrite(args, { cwd });
|
|
4076
|
+
} catch (e) {
|
|
4077
|
+
if (e instanceof GitError && /contains modified or untracked files/.test(e.stderr)) {
|
|
4078
|
+
throw new HttpError(409, `${wt.path} has uncommitted changes`, "worktree_dirty");
|
|
4079
|
+
}
|
|
4080
|
+
if (e instanceof GitError && !req.force && /use --force|validation failed/.test(e.stderr)) {
|
|
4081
|
+
throw new HttpError(409, e.message, "needs_force");
|
|
4082
|
+
}
|
|
4083
|
+
throw e;
|
|
4084
|
+
}
|
|
4085
|
+
const res = { ok: true, branchDeleted: false };
|
|
4086
|
+
if (req.deleteBranch && wt.branch) {
|
|
4087
|
+
try {
|
|
4088
|
+
await runGitWrite(["branch", "-d", wt.branch], { cwd });
|
|
4089
|
+
res.branchDeleted = true;
|
|
4090
|
+
} catch (e) {
|
|
4091
|
+
res.branchError = e instanceof Error ? e.message : String(e);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
return res;
|
|
4095
|
+
}
|
|
4096
|
+
|
|
3930
4097
|
// packages/server/src/state.ts
|
|
3931
4098
|
import os from "os";
|
|
3932
|
-
import
|
|
3933
|
-
import { mkdir, open, readFile as readFile2, rename, stat, unlink, writeFile } from "fs/promises";
|
|
4099
|
+
import path4 from "path";
|
|
4100
|
+
import { mkdir, open, readFile as readFile2, rename, stat as stat2, unlink, writeFile } from "fs/promises";
|
|
3934
4101
|
function dataDir() {
|
|
3935
4102
|
const xdg = process.env.XDG_DATA_HOME;
|
|
3936
|
-
const base = xdg
|
|
3937
|
-
return
|
|
4103
|
+
const base = xdg?.trim() ? xdg : path4.join(os.homedir(), ".local", "share");
|
|
4104
|
+
return path4.join(base, "warden");
|
|
3938
4105
|
}
|
|
3939
4106
|
function repoHash(repoRoot) {
|
|
3940
4107
|
return sha1(repoRoot).slice(0, 12);
|
|
3941
4108
|
}
|
|
3942
4109
|
function stateFilePath(repoRoot, baseDir = dataDir()) {
|
|
3943
|
-
return
|
|
4110
|
+
return path4.join(baseDir, repoHash(repoRoot), "state.json");
|
|
3944
4111
|
}
|
|
3945
4112
|
function defaultPrefs() {
|
|
3946
|
-
return { viewMode: "unified", nvimSocketByRoot: {}, autoRefresh: true };
|
|
4113
|
+
return { viewMode: "unified", nvimSocketByRoot: {}, autoRefresh: true, railOpen: false };
|
|
3947
4114
|
}
|
|
3948
4115
|
function defaultState(repoRoot) {
|
|
3949
4116
|
return { schemaVersion: 1, repoRoot, targets: {}, issues: [], todos: [], prefs: defaultPrefs() };
|
|
@@ -3983,7 +4150,8 @@ function normalise(raw2, repoRoot) {
|
|
|
3983
4150
|
...defaultPrefs(),
|
|
3984
4151
|
...r.prefs ?? {},
|
|
3985
4152
|
nvimSocketByRoot: r.prefs?.nvimSocketByRoot ?? {},
|
|
3986
|
-
autoRefresh: typeof r.prefs?.autoRefresh === "boolean" ? r.prefs.autoRefresh : true
|
|
4153
|
+
autoRefresh: typeof r.prefs?.autoRefresh === "boolean" ? r.prefs.autoRefresh : true,
|
|
4154
|
+
railOpen: typeof r.prefs?.railOpen === "boolean" ? r.prefs.railOpen : false
|
|
3987
4155
|
}
|
|
3988
4156
|
};
|
|
3989
4157
|
}
|
|
@@ -4035,7 +4203,7 @@ var StateStore = class {
|
|
|
4035
4203
|
/** Apply a mutation under lock. The callback receives the freshest on-disk state. */
|
|
4036
4204
|
update(fn) {
|
|
4037
4205
|
const run2 = async () => {
|
|
4038
|
-
await mkdir(
|
|
4206
|
+
await mkdir(path4.dirname(this.file), { recursive: true });
|
|
4039
4207
|
const release = await this.acquireLock();
|
|
4040
4208
|
try {
|
|
4041
4209
|
const state = await this.load();
|
|
@@ -4073,7 +4241,7 @@ var StateStore = class {
|
|
|
4073
4241
|
const err = e;
|
|
4074
4242
|
if (err.code !== "EEXIST") throw e;
|
|
4075
4243
|
try {
|
|
4076
|
-
const st = await
|
|
4244
|
+
const st = await stat2(lock);
|
|
4077
4245
|
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
|
|
4078
4246
|
await unlink(lock).catch(() => void 0);
|
|
4079
4247
|
continue;
|
|
@@ -4134,7 +4302,7 @@ var LANG_BY_EXT = {
|
|
|
4134
4302
|
};
|
|
4135
4303
|
function langForPath(filePath) {
|
|
4136
4304
|
const m = /\.([A-Za-z0-9]+)$/.exec(filePath);
|
|
4137
|
-
if (!m
|
|
4305
|
+
if (!m?.[1]) return "";
|
|
4138
4306
|
return LANG_BY_EXT[m[1].toLowerCase()] ?? m[1].toLowerCase();
|
|
4139
4307
|
}
|
|
4140
4308
|
function formatCommentSection(c) {
|
|
@@ -4155,13 +4323,7 @@ function formatCommentsExport({ repoRoot, comments }) {
|
|
|
4155
4323
|
}
|
|
4156
4324
|
function formatIssueExport({ repoRoot, issue, comments }) {
|
|
4157
4325
|
const targets = [...new Set(comments.map((c) => c.targetKey))];
|
|
4158
|
-
const head = [
|
|
4159
|
-
`# Issue: ${issue.title}`,
|
|
4160
|
-
`Status: ${issue.status}`,
|
|
4161
|
-
`Target: ${targets.join(", ") || "-"}`,
|
|
4162
|
-
`Repo: ${repoRoot}`,
|
|
4163
|
-
`Count: ${comments.length}`
|
|
4164
|
-
];
|
|
4326
|
+
const head = [`# Issue: ${issue.title}`, `Status: ${issue.status}`, `Target: ${targets.join(", ") || "-"}`, `Repo: ${repoRoot}`, `Count: ${comments.length}`];
|
|
4165
4327
|
const parts = [head.join("\n")];
|
|
4166
4328
|
if (issue.body.trim()) parts.push(issue.body.trim());
|
|
4167
4329
|
parts.push(...comments.map(formatCommentSection));
|
|
@@ -4170,9 +4332,9 @@ function formatIssueExport({ repoRoot, issue, comments }) {
|
|
|
4170
4332
|
|
|
4171
4333
|
// packages/server/src/nvim.ts
|
|
4172
4334
|
import os2 from "os";
|
|
4173
|
-
import
|
|
4335
|
+
import path5 from "path";
|
|
4174
4336
|
import { execFile as execFile2 } from "child_process";
|
|
4175
|
-
import { readdir, stat as
|
|
4337
|
+
import { readdir as readdir2, stat as stat3 } from "fs/promises";
|
|
4176
4338
|
var QUERY_TIMEOUT_MS = 500;
|
|
4177
4339
|
var CACHE_TTL_MS = 1e4;
|
|
4178
4340
|
function execNvim(args, timeoutMs) {
|
|
@@ -4201,14 +4363,14 @@ function socketDirs(env = process.env) {
|
|
|
4201
4363
|
}
|
|
4202
4364
|
}
|
|
4203
4365
|
const tmp = env.TMPDIR || "/tmp";
|
|
4204
|
-
if (user) dirs.push(
|
|
4205
|
-
if (env.NVIM_LISTEN_ADDRESS) dirs.push(
|
|
4366
|
+
if (user) dirs.push(path5.join(tmp, `nvim.${user}`));
|
|
4367
|
+
if (env.NVIM_LISTEN_ADDRESS) dirs.push(path5.dirname(env.NVIM_LISTEN_ADDRESS));
|
|
4206
4368
|
return [...new Set(dirs)];
|
|
4207
4369
|
}
|
|
4208
4370
|
var SOCKET_NAME = /^nvim\.(\d+)\.\d+$/;
|
|
4209
4371
|
async function isSocket(p) {
|
|
4210
4372
|
try {
|
|
4211
|
-
return (await
|
|
4373
|
+
return (await stat3(p)).isSocket();
|
|
4212
4374
|
} catch {
|
|
4213
4375
|
return false;
|
|
4214
4376
|
}
|
|
@@ -4218,12 +4380,12 @@ async function findSockets(dirs = socketDirs()) {
|
|
|
4218
4380
|
const visit = async (dir, depth) => {
|
|
4219
4381
|
let entries;
|
|
4220
4382
|
try {
|
|
4221
|
-
entries = await
|
|
4383
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
4222
4384
|
} catch {
|
|
4223
4385
|
return;
|
|
4224
4386
|
}
|
|
4225
4387
|
for (const ent of entries) {
|
|
4226
|
-
const full =
|
|
4388
|
+
const full = path5.join(dir, ent.name);
|
|
4227
4389
|
const m = SOCKET_NAME.exec(ent.name);
|
|
4228
4390
|
if (m && (ent.isSocket() || await isSocket(full))) {
|
|
4229
4391
|
found.set(full, Number(m[1]));
|
|
@@ -4288,8 +4450,8 @@ var NvimService = class {
|
|
|
4288
4450
|
};
|
|
4289
4451
|
|
|
4290
4452
|
// packages/server/src/watcher.ts
|
|
4291
|
-
import
|
|
4292
|
-
import { stat as
|
|
4453
|
+
import path6 from "path";
|
|
4454
|
+
import { stat as stat4 } from "fs/promises";
|
|
4293
4455
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
4294
4456
|
function statusPaths(out) {
|
|
4295
4457
|
const parts = out.split("\0");
|
|
@@ -4385,7 +4547,7 @@ var RepoWatcher = class {
|
|
|
4385
4547
|
const stats = await Promise.all(
|
|
4386
4548
|
statusPaths(statusRes.stdout).map(async (rel) => {
|
|
4387
4549
|
try {
|
|
4388
|
-
const st = await
|
|
4550
|
+
const st = await stat4(path6.join(this.root, rel));
|
|
4389
4551
|
return `${rel} ${st.mtimeMs} ${st.size}`;
|
|
4390
4552
|
} catch {
|
|
4391
4553
|
return `${rel} -`;
|
|
@@ -4397,8 +4559,8 @@ var RepoWatcher = class {
|
|
|
4397
4559
|
};
|
|
4398
4560
|
|
|
4399
4561
|
// packages/server/src/static.ts
|
|
4400
|
-
import
|
|
4401
|
-
import { readFile as readFile3, stat as
|
|
4562
|
+
import path7 from "path";
|
|
4563
|
+
import { readFile as readFile3, stat as stat5 } from "fs/promises";
|
|
4402
4564
|
var MIME = {
|
|
4403
4565
|
".html": "text/html; charset=utf-8",
|
|
4404
4566
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -4415,20 +4577,20 @@ var MIME = {
|
|
|
4415
4577
|
".txt": "text/plain; charset=utf-8"
|
|
4416
4578
|
};
|
|
4417
4579
|
async function serveStaticFile(c, webDir) {
|
|
4418
|
-
const root =
|
|
4580
|
+
const root = path7.resolve(webDir);
|
|
4419
4581
|
let rel;
|
|
4420
4582
|
try {
|
|
4421
4583
|
rel = decodeURIComponent(c.req.path);
|
|
4422
4584
|
} catch {
|
|
4423
4585
|
return c.text("Bad Request", 400);
|
|
4424
4586
|
}
|
|
4425
|
-
let target =
|
|
4426
|
-
if (target !== root && !target.startsWith(root +
|
|
4587
|
+
let target = path7.resolve(root, "." + rel);
|
|
4588
|
+
if (target !== root && !target.startsWith(root + path7.sep)) return c.text("Forbidden", 403);
|
|
4427
4589
|
try {
|
|
4428
|
-
const st = await
|
|
4429
|
-
if (st.isDirectory()) target =
|
|
4590
|
+
const st = await stat5(target);
|
|
4591
|
+
if (st.isDirectory()) target = path7.join(target, "index.html");
|
|
4430
4592
|
} catch {
|
|
4431
|
-
target =
|
|
4593
|
+
target = path7.join(root, "index.html");
|
|
4432
4594
|
}
|
|
4433
4595
|
let body;
|
|
4434
4596
|
try {
|
|
@@ -4436,7 +4598,7 @@ async function serveStaticFile(c, webDir) {
|
|
|
4436
4598
|
} catch {
|
|
4437
4599
|
return c.text("Not Found", 404);
|
|
4438
4600
|
}
|
|
4439
|
-
const ext =
|
|
4601
|
+
const ext = path7.extname(target).toLowerCase();
|
|
4440
4602
|
const isAsset = rel.startsWith("/assets/");
|
|
4441
4603
|
return new Response(new Uint8Array(body), {
|
|
4442
4604
|
status: 200,
|
|
@@ -4507,6 +4669,12 @@ function createApp(opts) {
|
|
|
4507
4669
|
return c.json({ error: err instanceof Error ? err.message : String(err), code: "internal" }, 500);
|
|
4508
4670
|
});
|
|
4509
4671
|
const api = new Hono2();
|
|
4672
|
+
api.use("*", async (c, next) => {
|
|
4673
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD" && c.req.header("sec-fetch-site") === "cross-site") {
|
|
4674
|
+
throw new HttpError(403, "cross-site requests are refused", "cross_site");
|
|
4675
|
+
}
|
|
4676
|
+
await next();
|
|
4677
|
+
});
|
|
4510
4678
|
api.get("/ping", (c) => c.text(opts.instanceToken ?? ""));
|
|
4511
4679
|
api.get("/repo", async (c) => {
|
|
4512
4680
|
const state = await store.load();
|
|
@@ -4516,7 +4684,11 @@ function createApp(opts) {
|
|
|
4516
4684
|
const target = last ? tryParseTargetKey(last) : void 0;
|
|
4517
4685
|
const usable2 = !!target && (!target.worktree || info.worktrees.some((w) => w.path === target.worktree));
|
|
4518
4686
|
if (last && usable2) info.defaultTarget = last;
|
|
4519
|
-
else if (last)
|
|
4687
|
+
else if (last) {
|
|
4688
|
+
await store.update((s) => {
|
|
4689
|
+
s.prefs.lastTarget = "working";
|
|
4690
|
+
});
|
|
4691
|
+
}
|
|
4520
4692
|
return c.json(info);
|
|
4521
4693
|
});
|
|
4522
4694
|
api.get("/state", async (c) => c.json(await store.load()));
|
|
@@ -4526,6 +4698,7 @@ function createApp(opts) {
|
|
|
4526
4698
|
if (body.viewMode === "unified" || body.viewMode === "split") s.prefs.viewMode = body.viewMode;
|
|
4527
4699
|
if (typeof body.lastTarget === "string") s.prefs.lastTarget = body.lastTarget;
|
|
4528
4700
|
if (typeof body.autoRefresh === "boolean") s.prefs.autoRefresh = body.autoRefresh;
|
|
4701
|
+
if (typeof body.railOpen === "boolean") s.prefs.railOpen = body.railOpen;
|
|
4529
4702
|
if (body.nvimSocketByRoot && typeof body.nvimSocketByRoot === "object") {
|
|
4530
4703
|
s.prefs.nvimSocketByRoot = { ...s.prefs.nvimSocketByRoot, ...body.nvimSocketByRoot };
|
|
4531
4704
|
}
|
|
@@ -4615,7 +4788,8 @@ function createApp(opts) {
|
|
|
4615
4788
|
if (body.hunks !== void 0 && !Array.isArray(body.hunks)) throw badRequest("hunks must be an array", "bad_selection");
|
|
4616
4789
|
const diff = await fileDiffWithHints(ctx, body.path);
|
|
4617
4790
|
if (!diff) throw badRequest(`file ${body.path} is not part of ${key}`, "no_diff");
|
|
4618
|
-
if (diff.contentHash !== body.contentHash)
|
|
4791
|
+
if (diff.contentHash !== body.contentHash)
|
|
4792
|
+
throw new HttpError(409, "the diff changed since it was loaded; refresh and pick the lines again", "diff_changed");
|
|
4619
4793
|
const { patch, lines } = buildStagePatch(diff, mode, body.hunks);
|
|
4620
4794
|
await applyToIndex(ctx.cwd, patch, { reverse: mode === "unstage" });
|
|
4621
4795
|
void watchers.get(ctx.cwd)?.poll();
|
|
@@ -4838,7 +5012,7 @@ function createApp(opts) {
|
|
|
4838
5012
|
});
|
|
4839
5013
|
api.post("/issues", async (c) => {
|
|
4840
5014
|
const body = await c.req.json();
|
|
4841
|
-
if (!body.title
|
|
5015
|
+
if (!body.title?.trim()) throw badRequest("title is required");
|
|
4842
5016
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4843
5017
|
const issue = {
|
|
4844
5018
|
id: randomUUID(),
|
|
@@ -4931,7 +5105,7 @@ function createApp(opts) {
|
|
|
4931
5105
|
});
|
|
4932
5106
|
api.post("/todos", async (c) => {
|
|
4933
5107
|
const body = await c.req.json();
|
|
4934
|
-
if (!body.title
|
|
5108
|
+
if (!body.title?.trim()) throw badRequest("title is required");
|
|
4935
5109
|
const branch = body.branch?.trim() || await currentBranch(await knownRoot(body.root));
|
|
4936
5110
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4937
5111
|
const todo = {
|
|
@@ -5043,6 +5217,26 @@ function createApp(opts) {
|
|
|
5043
5217
|
const res = { base, sha, ahead: Number(counts[1] ?? 0), behind: Number(counts[0] ?? 0) };
|
|
5044
5218
|
return c.json(res);
|
|
5045
5219
|
});
|
|
5220
|
+
api.get("/worktrees", async (c) => {
|
|
5221
|
+
const list = await listWorktreesDetailed(repo);
|
|
5222
|
+
const res = { worktrees: list, branches: await listBranches(repo, list), pathPrefix: worktreePathPrefix(repo) };
|
|
5223
|
+
return c.json(res);
|
|
5224
|
+
});
|
|
5225
|
+
api.post("/worktrees", async (c) => {
|
|
5226
|
+
const body = await c.req.json();
|
|
5227
|
+
if (typeof body.path !== "string" || typeof body.branch !== "string") throw badRequest("path and branch are required");
|
|
5228
|
+
if (body.base !== void 0 && typeof body.base !== "string") throw badRequest("base must be a ref");
|
|
5229
|
+
const made = await addWorktree(repo, body);
|
|
5230
|
+
worktreeCache = null;
|
|
5231
|
+
return c.json(made, 201);
|
|
5232
|
+
});
|
|
5233
|
+
api.post("/worktrees/remove", async (c) => {
|
|
5234
|
+
const body = await c.req.json();
|
|
5235
|
+
if (typeof body.path !== "string") throw badRequest("path is required");
|
|
5236
|
+
const res = await removeWorktree(repo, body);
|
|
5237
|
+
worktreeCache = null;
|
|
5238
|
+
return c.json(res);
|
|
5239
|
+
});
|
|
5046
5240
|
api.get("/events", async (c) => {
|
|
5047
5241
|
const root = c.req.query("root") || repo.root;
|
|
5048
5242
|
const known = root === repo.root || root === repo.commonRoot || (await worktrees()).some((w) => w.path === root);
|
|
@@ -5094,7 +5288,7 @@ function createApp(opts) {
|
|
|
5094
5288
|
api.post("/nvim/open", async (c) => {
|
|
5095
5289
|
const body = await c.req.json();
|
|
5096
5290
|
if (!body.socket || !body.absPath) throw badRequest("socket and absPath are required");
|
|
5097
|
-
if (!
|
|
5291
|
+
if (!path8.isAbsolute(body.absPath)) throw badRequest("absPath must be absolute");
|
|
5098
5292
|
const scan = await nvim.scan();
|
|
5099
5293
|
if (!scan.instances.some((i) => i.socket === body.socket)) {
|
|
5100
5294
|
const fresh = await nvim.scan(true);
|
|
@@ -5220,7 +5414,7 @@ async function startServer(opts) {
|
|
|
5220
5414
|
}
|
|
5221
5415
|
|
|
5222
5416
|
// bin/cli.ts
|
|
5223
|
-
var VERSION = true ? "0.
|
|
5417
|
+
var VERSION = true ? "0.9.0" : "dev";
|
|
5224
5418
|
function parseArgs(argv) {
|
|
5225
5419
|
const args = { repoPath: process.cwd(), open: true, help: false, version: false };
|
|
5226
5420
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -5238,7 +5432,7 @@ function parseArgs(argv) {
|
|
|
5238
5432
|
} else if (a === "--help" || a === "-h") args.help = true;
|
|
5239
5433
|
else if (a === "--version" || a === "-v") args.version = true;
|
|
5240
5434
|
else if (a.startsWith("-")) throw new Error(`unknown option: ${a}`);
|
|
5241
|
-
else args.repoPath =
|
|
5435
|
+
else args.repoPath = path9.resolve(a);
|
|
5242
5436
|
}
|
|
5243
5437
|
return args;
|
|
5244
5438
|
}
|
|
@@ -5281,16 +5475,16 @@ async function openBrowser(url) {
|
|
|
5281
5475
|
return false;
|
|
5282
5476
|
}
|
|
5283
5477
|
function resolveWebDir() {
|
|
5284
|
-
const here =
|
|
5478
|
+
const here = path9.dirname(fileURLToPath(import.meta.url));
|
|
5285
5479
|
const candidates = [
|
|
5286
5480
|
process.env.WARDEN_WEB_DIR,
|
|
5287
|
-
|
|
5481
|
+
path9.join(here, "web"),
|
|
5288
5482
|
// dist/cli.js -> dist/web
|
|
5289
|
-
|
|
5483
|
+
path9.join(here, "..", "dist", "web")
|
|
5290
5484
|
// bin/cli.ts (tsx dev) -> dist/web
|
|
5291
5485
|
].filter((p) => !!p);
|
|
5292
5486
|
for (const dir of candidates) {
|
|
5293
|
-
if (existsSync2(
|
|
5487
|
+
if (existsSync2(path9.join(dir, "index.html"))) return dir;
|
|
5294
5488
|
}
|
|
5295
5489
|
return void 0;
|
|
5296
5490
|
}
|