@xbghc/warden 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import path11 from "path";
|
|
5
5
|
import { spawn } from "child_process";
|
|
6
6
|
import { existsSync as existsSync2 } from "fs";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
9
|
// packages/server/src/index.ts
|
|
10
10
|
import net from "net";
|
|
11
|
-
import { randomUUID as
|
|
11
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
12
12
|
|
|
13
13
|
// node_modules/.pnpm/@hono+node-server@1.19.17_hono@4.13.7/node_modules/@hono/node-server/dist/index.mjs
|
|
14
14
|
import { createServer as createServerHTTP } from "http";
|
|
@@ -652,8 +652,8 @@ var serve = (options, listeningListener) => {
|
|
|
652
652
|
};
|
|
653
653
|
|
|
654
654
|
// packages/server/src/app.ts
|
|
655
|
-
import
|
|
656
|
-
import { randomUUID } from "crypto";
|
|
655
|
+
import path9 from "path";
|
|
656
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
657
657
|
|
|
658
658
|
// node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/compose.js
|
|
659
659
|
var compose = (middleware, onError, onNotFound) => {
|
|
@@ -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 = (path12) => {
|
|
816
|
+
const paths = path12.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: path12 } = extractGroupsFromPath(routePath);
|
|
824
|
+
const paths = splitPath(path12);
|
|
825
825
|
return replaceGroupMarks(paths, groups);
|
|
826
826
|
};
|
|
827
|
-
var extractGroupsFromPath = (
|
|
827
|
+
var extractGroupsFromPath = (path12) => {
|
|
828
828
|
const groups = [];
|
|
829
|
-
|
|
829
|
+
path12 = path12.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: path12 };
|
|
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 path12 = url.slice(start, end);
|
|
892
|
+
return tryDecodeURI(path12.includes("%25") ? path12.replace(/%25/g, "%2525") : path12);
|
|
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 = (path12) => {
|
|
910
|
+
if (path12.charCodeAt(path12.length - 1) !== 63 || !path12.includes(":")) {
|
|
911
911
|
return null;
|
|
912
912
|
}
|
|
913
|
-
const segments =
|
|
913
|
+
const segments = path12.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, path12 = "/", matchResult = [[]]) {
|
|
1056
1056
|
this.raw = request;
|
|
1057
|
-
this.path =
|
|
1057
|
+
this.path = path12;
|
|
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, path12, ...handlers) => {
|
|
1830
|
+
for (const p of [path12].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(path12, app) {
|
|
1889
|
+
const subApp = this.basePath(path12);
|
|
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(path12) {
|
|
1916
1916
|
const subApp = this.#clone();
|
|
1917
|
-
subApp._basePath = mergePath(this._basePath,
|
|
1917
|
+
subApp._basePath = mergePath(this._basePath, path12);
|
|
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(path12, 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, path12);
|
|
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(path12, "*"), handler);
|
|
2034
2034
|
return this;
|
|
2035
2035
|
}
|
|
2036
|
-
#addRoute(method,
|
|
2037
|
-
|
|
2036
|
+
#addRoute(method, path12, handler, baseRoutePath) {
|
|
2037
|
+
path12 = mergePath(this._basePath, path12);
|
|
2038
2038
|
const r = {
|
|
2039
2039
|
basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
2040
|
-
path:
|
|
2040
|
+
path: path12,
|
|
2041
2041
|
method,
|
|
2042
2042
|
handler
|
|
2043
2043
|
};
|
|
2044
|
-
this.router.add(method,
|
|
2044
|
+
this.router.add(method, path12, [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 path12 = this.getPath(request, { env });
|
|
2058
|
+
const matchResult = this.router.match(method, path12);
|
|
2059
2059
|
const c = new Context(request, {
|
|
2060
|
-
path:
|
|
2060
|
+
path: path12,
|
|
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, path12) {
|
|
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, path11) {
|
|
|
2176
2176
|
return [matcher[1][index], match3];
|
|
2177
2177
|
});
|
|
2178
2178
|
this.match = match2;
|
|
2179
|
-
return match2(method,
|
|
2179
|
+
return match2(method, path12);
|
|
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(path12, isStatic) {
|
|
2297
2297
|
if (isStatic) {
|
|
2298
|
-
this.#root.insert(
|
|
2298
|
+
this.#root.insert(path12.split(""), 0, [], this.#context, true);
|
|
2299
2299
|
return;
|
|
2300
2300
|
}
|
|
2301
2301
|
const paramAssoc = [];
|
|
2302
2302
|
const groups = [];
|
|
2303
|
-
let markedPath =
|
|
2303
|
+
let markedPath = path12;
|
|
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[path12] = [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(path12) {
|
|
2356
|
+
return wildcardRegExpCache[path12] ??= new RegExp(
|
|
2357
|
+
`^${path12.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, path12) {
|
|
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(path12)) {
|
|
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, path12) {
|
|
2382
2382
|
try {
|
|
2383
|
-
this.#tries[method].insert(
|
|
2383
|
+
this.#tries[method].insert(path12, !/\*|\/:/.test(path12));
|
|
2384
2384
|
} catch (e) {
|
|
2385
|
-
throw e === PATH_ERROR ? new UnsupportedPathError(
|
|
2385
|
+
throw e === PATH_ERROR ? new UnsupportedPathError(path12) : e;
|
|
2386
2386
|
}
|
|
2387
2387
|
}
|
|
2388
|
-
add(method,
|
|
2388
|
+
add(method, path12, 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 (path12 === "/*") {
|
|
2405
|
+
path12 = "*";
|
|
2406
2406
|
}
|
|
2407
2407
|
const methods = method === METHOD_NAME_ALL ? Object.keys(middleware) : [method];
|
|
2408
|
-
if (/\*$/.test(
|
|
2409
|
-
const re = buildWildcardRegExp(
|
|
2408
|
+
if (/\*$/.test(path12)) {
|
|
2409
|
+
const re = buildWildcardRegExp(path12);
|
|
2410
2410
|
for (const m of methods) {
|
|
2411
|
-
if (!middleware[m][
|
|
2412
|
-
this.#insertPath(m,
|
|
2413
|
-
middleware[m][
|
|
2411
|
+
if (!middleware[m][path12]) {
|
|
2412
|
+
this.#insertPath(m, path12);
|
|
2413
|
+
middleware[m][path12] = findMiddleware(middleware[m], path12) || findMiddleware(middleware[METHOD_NAME_ALL], path12) || [];
|
|
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, path12]);
|
|
2420
2420
|
}
|
|
2421
2421
|
}
|
|
2422
2422
|
}
|
|
2423
2423
|
return;
|
|
2424
2424
|
}
|
|
2425
|
-
const paths = checkOptionalParameter(
|
|
2425
|
+
const paths = checkOptionalParameter(path12) || [path12];
|
|
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 path12 in r) {
|
|
2455
|
+
const handlers = r[path12];
|
|
2456
|
+
const pathData = trie.paths[path12];
|
|
2457
2457
|
if (!pathData) {
|
|
2458
|
-
staticMap[
|
|
2458
|
+
staticMap[path12] = [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, path12, 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, path12, handler]);
|
|
2487
2487
|
}
|
|
2488
|
-
match(method,
|
|
2488
|
+
match(method, path12) {
|
|
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, path12);
|
|
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, path12, handler) {
|
|
2539
2539
|
let curNode = this;
|
|
2540
|
-
const parts = splitRoutingPath(
|
|
2540
|
+
const parts = splitRoutingPath(path12);
|
|
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, path12) {
|
|
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(path12);
|
|
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 = path12[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 = path12.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, path12, handler) {
|
|
2694
|
+
for (const result of checkOptionalParameter(path12) || [path12]) {
|
|
2695
2695
|
this.#node.insert(method, result, handler);
|
|
2696
2696
|
}
|
|
2697
2697
|
}
|
|
2698
|
-
match(method,
|
|
2699
|
-
return this.#node.search(method,
|
|
2698
|
+
match(method, path12) {
|
|
2699
|
+
return this.#node.search(method, path12);
|
|
2700
2700
|
}
|
|
2701
2701
|
};
|
|
2702
2702
|
|
|
@@ -2902,6 +2902,11 @@ function parseSuffix(rest) {
|
|
|
2902
2902
|
if (!isValidRef(ref)) throw new TargetKeyError(`invalid base ref: ${ref}`);
|
|
2903
2903
|
return { kind: "base", ref };
|
|
2904
2904
|
}
|
|
2905
|
+
if (rest.startsWith("checkpoint:")) {
|
|
2906
|
+
const n = rest.slice("checkpoint:".length);
|
|
2907
|
+
if (!/^[1-9][0-9]{0,8}$/.test(n)) throw new TargetKeyError(`invalid checkpoint: ${n}`);
|
|
2908
|
+
return { kind: "checkpoint", id: Number(n) };
|
|
2909
|
+
}
|
|
2905
2910
|
if (rest.startsWith("range:")) {
|
|
2906
2911
|
const spec = rest.slice("range:".length);
|
|
2907
2912
|
const idx = spec.indexOf("..");
|
|
@@ -2917,7 +2922,7 @@ function parseTargetKey(key) {
|
|
|
2917
2922
|
if (typeof key !== "string" || !key) throw new TargetKeyError("empty target key");
|
|
2918
2923
|
if (key.startsWith("worktree:")) {
|
|
2919
2924
|
const rest = key.slice("worktree:".length);
|
|
2920
|
-
const m = /^(.+):(working|staged|all|commit:.+|range:.+|base
|
|
2925
|
+
const m = /^(.+):(working|staged|all|commit:.+|range:.+|base:.+|checkpoint:[0-9]+)$/.exec(rest);
|
|
2921
2926
|
if (!m?.[1] || !m[2]) throw new TargetKeyError(`invalid worktree target key: ${key}`);
|
|
2922
2927
|
const worktree = m[1];
|
|
2923
2928
|
if (!worktree.startsWith("/")) throw new TargetKeyError(`worktree path must be absolute: ${worktree}`);
|
|
@@ -2942,6 +2947,9 @@ function formatTargetKey(t) {
|
|
|
2942
2947
|
case "base":
|
|
2943
2948
|
suffix = `base:${t.ref}`;
|
|
2944
2949
|
break;
|
|
2950
|
+
case "checkpoint":
|
|
2951
|
+
suffix = `checkpoint:${t.id}`;
|
|
2952
|
+
break;
|
|
2945
2953
|
}
|
|
2946
2954
|
return t.worktree ? `worktree:${t.worktree}:${suffix}` : suffix;
|
|
2947
2955
|
}
|
|
@@ -3075,6 +3083,14 @@ var GitError = class extends HttpError {
|
|
|
3075
3083
|
exitCode;
|
|
3076
3084
|
stderr;
|
|
3077
3085
|
};
|
|
3086
|
+
function snapshotVars(snapshot) {
|
|
3087
|
+
if (!snapshot) return {};
|
|
3088
|
+
return {
|
|
3089
|
+
GIT_OBJECT_DIRECTORY: snapshot.objects,
|
|
3090
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES: snapshot.alternates,
|
|
3091
|
+
...snapshot.index ? { GIT_INDEX_FILE: snapshot.index } : {}
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3078
3094
|
function assertAllowedGitArgs(args) {
|
|
3079
3095
|
const sub = args[0];
|
|
3080
3096
|
if (!sub || !ALLOWED_SUBCOMMANDS.has(sub)) {
|
|
@@ -3120,7 +3136,8 @@ async function runGit(args, opts) {
|
|
|
3120
3136
|
LC_ALL: "C",
|
|
3121
3137
|
GIT_PAGER: "cat",
|
|
3122
3138
|
PAGER: "cat",
|
|
3123
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
3139
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
3140
|
+
...snapshotVars(opts.snapshot)
|
|
3124
3141
|
},
|
|
3125
3142
|
windowsHide: true
|
|
3126
3143
|
},
|
|
@@ -3212,6 +3229,14 @@ async function applyOnce(cwd, patch, opts) {
|
|
|
3212
3229
|
});
|
|
3213
3230
|
}
|
|
3214
3231
|
async function runGitWrite(args, opts) {
|
|
3232
|
+
return execWrite(args, opts, {});
|
|
3233
|
+
}
|
|
3234
|
+
async function runGitSnapshot(args, opts) {
|
|
3235
|
+
const allowed = args[0] === "add" && args[1] === "--all" && args.slice(2).every((a) => a === "--intent-to-add") || args[0] === "write-tree" && args[1] === "--missing-ok" && args.length === 2;
|
|
3236
|
+
if (!allowed) throw new GitError(`not a snapshot command: git ${args.join(" ")}`, null, "", 400, "git_subcommand_forbidden");
|
|
3237
|
+
return execWrite(args, opts, { GIT_OBJECT_DIRECTORY: opts.objects, GIT_INDEX_FILE: opts.index });
|
|
3238
|
+
}
|
|
3239
|
+
function execWrite(args, opts, extraEnv) {
|
|
3215
3240
|
for (const arg of args) if (arg.includes("\0")) throw new GitError("NUL byte in git argument", null, "", 400, "git_bad_argument");
|
|
3216
3241
|
return new Promise((resolve, reject) => {
|
|
3217
3242
|
execFile(
|
|
@@ -3222,7 +3247,7 @@ async function runGitWrite(args, opts) {
|
|
|
3222
3247
|
timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
|
|
3223
3248
|
maxBuffer: 16 * 1024 * 1024,
|
|
3224
3249
|
encoding: "utf8",
|
|
3225
|
-
env: { ...process.env, LC_ALL: "C", GIT_PAGER: "cat", PAGER: "cat", GIT_TERMINAL_PROMPT: "0" },
|
|
3250
|
+
env: { ...process.env, LC_ALL: "C", GIT_PAGER: "cat", PAGER: "cat", GIT_TERMINAL_PROMPT: "0", ...extraEnv },
|
|
3226
3251
|
windowsHide: true
|
|
3227
3252
|
},
|
|
3228
3253
|
(error, stdout, stderr) => {
|
|
@@ -3374,9 +3399,86 @@ async function getRepoInfo(ctx, defaultTarget) {
|
|
|
3374
3399
|
}
|
|
3375
3400
|
|
|
3376
3401
|
// packages/server/src/targets.ts
|
|
3377
|
-
import
|
|
3402
|
+
import path3 from "path";
|
|
3378
3403
|
import { readFile } from "fs/promises";
|
|
3379
3404
|
|
|
3405
|
+
// packages/server/src/checkpoints.ts
|
|
3406
|
+
import path2 from "path";
|
|
3407
|
+
import { randomUUID } from "crypto";
|
|
3408
|
+
import { copyFile, mkdir, rm, stat, utimes } from "fs/promises";
|
|
3409
|
+
function checkpointStore(stateFile) {
|
|
3410
|
+
return path2.join(path2.dirname(stateFile), "checkpoints");
|
|
3411
|
+
}
|
|
3412
|
+
async function repoObjects(cwd) {
|
|
3413
|
+
const r = await runGit(["rev-parse", "--git-path", "objects"], { cwd });
|
|
3414
|
+
return path2.resolve(cwd, r.stdout.trim());
|
|
3415
|
+
}
|
|
3416
|
+
async function repoIndex(cwd) {
|
|
3417
|
+
const r = await runGit(["rev-parse", "--git-path", "index"], { cwd });
|
|
3418
|
+
return path2.resolve(cwd, r.stdout.trim());
|
|
3419
|
+
}
|
|
3420
|
+
async function checkpointObjects(cwd, store) {
|
|
3421
|
+
return { objects: path2.join(store, "objects"), alternates: await repoObjects(cwd) };
|
|
3422
|
+
}
|
|
3423
|
+
async function withIndexCopy(cwd, store, fn) {
|
|
3424
|
+
const tmp = path2.join(store, "tmp");
|
|
3425
|
+
await mkdir(path2.join(store, "objects"), { recursive: true });
|
|
3426
|
+
await mkdir(tmp, { recursive: true });
|
|
3427
|
+
const [objects, index] = await Promise.all([checkpointObjects(cwd, store), repoIndex(cwd)]);
|
|
3428
|
+
const copy = path2.join(tmp, `index-${randomUUID()}`);
|
|
3429
|
+
try {
|
|
3430
|
+
const st = await stat(index).catch(() => void 0);
|
|
3431
|
+
if (st) {
|
|
3432
|
+
await copyFile(index, copy);
|
|
3433
|
+
const earlier = Math.floor(st.mtimeMs / 1e3) - 1;
|
|
3434
|
+
await utimes(copy, earlier, earlier);
|
|
3435
|
+
}
|
|
3436
|
+
return await fn({ ...objects, index: copy });
|
|
3437
|
+
} finally {
|
|
3438
|
+
await rm(copy, { force: true });
|
|
3439
|
+
await rm(`${copy}.lock`, { force: true });
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
async function snapshotWorktree(cwd, store) {
|
|
3443
|
+
return withIndexCopy(cwd, store, async (snapshot) => {
|
|
3444
|
+
await runGitSnapshot(["add", "--all"], { cwd, ...snapshot });
|
|
3445
|
+
const r = await runGitSnapshot(["write-tree", "--missing-ok"], { cwd, ...snapshot });
|
|
3446
|
+
return r.stdout.trim();
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
async function withCheckpointIndex(cwd, store, fn) {
|
|
3450
|
+
return withIndexCopy(cwd, store, async (snapshot) => {
|
|
3451
|
+
await runGitSnapshot(["add", "--all", "--intent-to-add"], { cwd, ...snapshot });
|
|
3452
|
+
return fn(snapshot);
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
var MAX_CHECKPOINTS = 20;
|
|
3456
|
+
var sameWorktree = (c, worktree) => c.worktree === worktree;
|
|
3457
|
+
function checkpointsOf(state, worktree) {
|
|
3458
|
+
return state.checkpoints.filter((c) => sameWorktree(c, worktree));
|
|
3459
|
+
}
|
|
3460
|
+
function findCheckpoint(state, worktree, id) {
|
|
3461
|
+
return state.checkpoints.find((c) => sameWorktree(c, worktree) && c.id === id);
|
|
3462
|
+
}
|
|
3463
|
+
function checkpointKey(c) {
|
|
3464
|
+
return formatTargetKey({ kind: "checkpoint", id: c.id, ...c.worktree ? { worktree: c.worktree } : {} });
|
|
3465
|
+
}
|
|
3466
|
+
function forgetCheckpoint(state, c) {
|
|
3467
|
+
state.checkpoints = state.checkpoints.filter((x) => x !== c);
|
|
3468
|
+
const key = checkpointKey(c);
|
|
3469
|
+
const gone = new Set((state.targets[key]?.comments ?? []).map((x) => x.id));
|
|
3470
|
+
delete state.targets[key];
|
|
3471
|
+
if (gone.size) for (const issue of state.issues) issue.commentIds = issue.commentIds.filter((id) => !gone.has(id));
|
|
3472
|
+
}
|
|
3473
|
+
function addCheckpoint(state, worktree, tree, head) {
|
|
3474
|
+
const mine = checkpointsOf(state, worktree);
|
|
3475
|
+
const id = mine.reduce((n, c) => Math.max(n, c.id), 0) + 1;
|
|
3476
|
+
const checkpoint = { id, ...worktree ? { worktree } : {}, tree, head, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3477
|
+
state.checkpoints.push(checkpoint);
|
|
3478
|
+
for (const old of mine.slice(0, Math.max(0, mine.length + 1 - MAX_CHECKPOINTS))) forgetCheckpoint(state, old);
|
|
3479
|
+
return checkpoint;
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3380
3482
|
// packages/server/src/hash.ts
|
|
3381
3483
|
import { createHash } from "crypto";
|
|
3382
3484
|
function sha1(input) {
|
|
@@ -3632,6 +3734,17 @@ function markDebugLines(diff, newSide, oldSide) {
|
|
|
3632
3734
|
}
|
|
3633
3735
|
|
|
3634
3736
|
// packages/server/src/targets.ts
|
|
3737
|
+
function checkpointOf(ctx) {
|
|
3738
|
+
if (!ctx.checkpoint) throw new Error(`checkpoint of ${ctx.key} was not looked up`);
|
|
3739
|
+
return ctx.checkpoint;
|
|
3740
|
+
}
|
|
3741
|
+
async function objectsFor(ctx) {
|
|
3742
|
+
return ctx.target.kind === "checkpoint" ? { snapshot: await checkpointObjects(ctx.cwd, checkpointOf(ctx).store) } : {};
|
|
3743
|
+
}
|
|
3744
|
+
async function runDiff(ctx, args) {
|
|
3745
|
+
if (ctx.target.kind !== "checkpoint") return (await runGit(args, { cwd: ctx.cwd })).stdout;
|
|
3746
|
+
return withCheckpointIndex(ctx.cwd, checkpointOf(ctx).store, async (snapshot) => (await runGit(args, { cwd: ctx.cwd, snapshot })).stdout);
|
|
3747
|
+
}
|
|
3635
3748
|
var DIFF_BASE_ARGS = ["diff", "--no-color", "--no-ext-diff", "-U3", "-M", "--find-renames"];
|
|
3636
3749
|
function resolveTargetContext(repo, worktrees, key) {
|
|
3637
3750
|
let target;
|
|
@@ -3685,6 +3798,8 @@ async function diffArgs(ctx) {
|
|
|
3685
3798
|
}
|
|
3686
3799
|
case "base":
|
|
3687
3800
|
return [...DIFF_BASE_ARGS, await baseSha(ctx, t.ref)];
|
|
3801
|
+
case "checkpoint":
|
|
3802
|
+
return [...DIFF_BASE_ARGS, checkpointOf(ctx).tree];
|
|
3688
3803
|
}
|
|
3689
3804
|
}
|
|
3690
3805
|
function includesUntracked(t) {
|
|
@@ -3726,8 +3841,7 @@ async function mapLimit(items, limit, fn) {
|
|
|
3726
3841
|
}
|
|
3727
3842
|
async function listTargetDiffs(ctx) {
|
|
3728
3843
|
const args = await diffArgs(ctx);
|
|
3729
|
-
const
|
|
3730
|
-
const files = parseUnifiedDiff(r.stdout);
|
|
3844
|
+
const files = parseUnifiedDiff(await runDiff(ctx, args));
|
|
3731
3845
|
if (includesUntracked(ctx.target)) {
|
|
3732
3846
|
const untracked = await listUntracked(ctx.cwd);
|
|
3733
3847
|
const extra = await mapLimit(untracked, 8, (f) => untrackedDiff(ctx.cwd, f).catch(() => void 0));
|
|
@@ -3749,8 +3863,7 @@ async function getFileDiff(ctx, filePath, hints = {}) {
|
|
|
3749
3863
|
const args = await diffArgs(ctx);
|
|
3750
3864
|
const pathspec = [literal(filePath)];
|
|
3751
3865
|
if (hints.oldPath && hints.oldPath !== filePath) pathspec.push(literal(hints.oldPath));
|
|
3752
|
-
const
|
|
3753
|
-
const files = parseUnifiedDiff(r.stdout);
|
|
3866
|
+
const files = parseUnifiedDiff(await runDiff(ctx, [...args, "--", ...pathspec]));
|
|
3754
3867
|
return files.find((f) => f.path === filePath) ?? files[0];
|
|
3755
3868
|
}
|
|
3756
3869
|
async function refForSide(ctx, side) {
|
|
@@ -3770,6 +3883,8 @@ async function refForSide(ctx, side) {
|
|
|
3770
3883
|
return await mergeBase(ctx.cwd, t.base, t.head) ?? t.base;
|
|
3771
3884
|
case "base":
|
|
3772
3885
|
return side === "new" ? void 0 : baseSha(ctx, t.ref);
|
|
3886
|
+
case "checkpoint":
|
|
3887
|
+
return side === "new" ? void 0 : checkpointOf(ctx).tree;
|
|
3773
3888
|
}
|
|
3774
3889
|
}
|
|
3775
3890
|
async function getFullFile(ctx, filePath, side) {
|
|
@@ -3777,14 +3892,14 @@ async function getFullFile(ctx, filePath, side) {
|
|
|
3777
3892
|
const ref = await refForSide(ctx, side);
|
|
3778
3893
|
if (ref === void 0) {
|
|
3779
3894
|
try {
|
|
3780
|
-
const buf = await readFile(
|
|
3895
|
+
const buf = await readFile(path3.join(ctx.cwd, filePath));
|
|
3781
3896
|
return buf.toString("utf8");
|
|
3782
3897
|
} catch {
|
|
3783
3898
|
return null;
|
|
3784
3899
|
}
|
|
3785
3900
|
}
|
|
3786
3901
|
try {
|
|
3787
|
-
const r = await runGit(["show", `${ref}:${filePath}`], { cwd: ctx.cwd });
|
|
3902
|
+
const r = await runGit(["show", `${ref}:${filePath}`], { cwd: ctx.cwd, ...await objectsFor(ctx) });
|
|
3788
3903
|
return r.stdout;
|
|
3789
3904
|
} catch (e) {
|
|
3790
3905
|
if (e instanceof HttpError && e.code === "git_failed") return null;
|
|
@@ -3801,9 +3916,10 @@ async function pathsWithMarkers(ctx, side, paths) {
|
|
|
3801
3916
|
else if (ref === ":0") args.push("--cached");
|
|
3802
3917
|
else args.push(ref);
|
|
3803
3918
|
const prefix = ref === void 0 || ref === ":0" ? "" : `${ref}:`;
|
|
3919
|
+
const objects = await objectsFor(ctx);
|
|
3804
3920
|
for (let i = 0; i < paths.length; i += GREP_CHUNK) {
|
|
3805
3921
|
const chunk = paths.slice(i, i + GREP_CHUNK).map(literal);
|
|
3806
|
-
const r = await runGit([...args, "--", ...chunk], { cwd: ctx.cwd, okCodes: [0, 1] });
|
|
3922
|
+
const r = await runGit([...args, "--", ...chunk], { cwd: ctx.cwd, okCodes: [0, 1], ...objects });
|
|
3807
3923
|
for (const p of r.stdout.split("\0")) if (p) found.add(p.startsWith(prefix) ? p.slice(prefix.length) : p);
|
|
3808
3924
|
}
|
|
3809
3925
|
return found;
|
|
@@ -4076,15 +4192,15 @@ function buildStagePatch(diff, mode, selection, opts = {}) {
|
|
|
4076
4192
|
}
|
|
4077
4193
|
|
|
4078
4194
|
// packages/server/src/worktrees.ts
|
|
4079
|
-
import
|
|
4080
|
-
import { readdir, realpath as realpath2, stat } from "fs/promises";
|
|
4195
|
+
import path4 from "path";
|
|
4196
|
+
import { readdir, realpath as realpath2, stat as stat2 } from "fs/promises";
|
|
4081
4197
|
function slotPath(ctx, slot) {
|
|
4082
|
-
return
|
|
4198
|
+
return path4.join(path4.dirname(ctx.commonRoot), `${path4.basename(ctx.commonRoot)}-${slot}`);
|
|
4083
4199
|
}
|
|
4084
4200
|
function slotOf(ctx, p) {
|
|
4085
|
-
if (
|
|
4086
|
-
const prefix = `${
|
|
4087
|
-
const name =
|
|
4201
|
+
if (path4.dirname(p) !== path4.dirname(ctx.commonRoot)) return void 0;
|
|
4202
|
+
const prefix = `${path4.basename(ctx.commonRoot)}-`;
|
|
4203
|
+
const name = path4.basename(p);
|
|
4088
4204
|
const n = name.startsWith(prefix) ? name.slice(prefix.length) : "";
|
|
4089
4205
|
return /^[1-9]\d*$/.test(n) ? Number(n) : void 0;
|
|
4090
4206
|
}
|
|
@@ -4169,7 +4285,7 @@ async function assertBranchName(ctx, name) {
|
|
|
4169
4285
|
}
|
|
4170
4286
|
}
|
|
4171
4287
|
async function dirUsable(p) {
|
|
4172
|
-
const st = await
|
|
4288
|
+
const st = await stat2(p).catch((e) => {
|
|
4173
4289
|
if (e.code === "ENOENT") return void 0;
|
|
4174
4290
|
throw e;
|
|
4175
4291
|
});
|
|
@@ -4266,7 +4382,7 @@ async function dropBranch(ctx, wt, asked) {
|
|
|
4266
4382
|
}
|
|
4267
4383
|
async function releaseWorktree(ctx, req) {
|
|
4268
4384
|
const p = req.path.trim();
|
|
4269
|
-
const wt = (await listWorktreesAll(ctx)).find((w) => w.path === p || p && w.path ===
|
|
4385
|
+
const wt = (await listWorktreesAll(ctx)).find((w) => w.path === p || p && w.path === path4.resolve(p));
|
|
4270
4386
|
if (!wt) throw badRequest(`unknown worktree: ${p}`, "unknown_worktree");
|
|
4271
4387
|
if (wt.isMain) throw badRequest("the main worktree cannot be released", "main_worktree");
|
|
4272
4388
|
if (wt.bare || slotOf(ctx, wt.path) === void 0) throw badRequest(`${wt.path} is not a slot; remove it instead`, "not_a_slot");
|
|
@@ -4282,7 +4398,7 @@ async function releaseWorktree(ctx, req) {
|
|
|
4282
4398
|
}
|
|
4283
4399
|
async function removeWorktree(ctx, req) {
|
|
4284
4400
|
const p = req.path.trim();
|
|
4285
|
-
const wt = (await listWorktreesAll(ctx)).find((w) => w.path === p || p && w.path ===
|
|
4401
|
+
const wt = (await listWorktreesAll(ctx)).find((w) => w.path === p || p && w.path === path4.resolve(p));
|
|
4286
4402
|
if (!wt) throw badRequest(`unknown worktree: ${p}`, "unknown_worktree");
|
|
4287
4403
|
if (wt.isMain) throw badRequest("the main worktree cannot be removed", "main_worktree");
|
|
4288
4404
|
const args = ["worktree", "remove"];
|
|
@@ -4304,24 +4420,24 @@ async function removeWorktree(ctx, req) {
|
|
|
4304
4420
|
|
|
4305
4421
|
// packages/server/src/state.ts
|
|
4306
4422
|
import os from "os";
|
|
4307
|
-
import
|
|
4308
|
-
import { mkdir, open, readFile as readFile2, rename, stat as
|
|
4423
|
+
import path5 from "path";
|
|
4424
|
+
import { mkdir as mkdir2, open, readFile as readFile2, rename, stat as stat3, unlink, writeFile } from "fs/promises";
|
|
4309
4425
|
function dataDir() {
|
|
4310
4426
|
const xdg = process.env.XDG_DATA_HOME;
|
|
4311
|
-
const base = xdg?.trim() ? xdg :
|
|
4312
|
-
return
|
|
4427
|
+
const base = xdg?.trim() ? xdg : path5.join(os.homedir(), ".local", "share");
|
|
4428
|
+
return path5.join(base, "warden");
|
|
4313
4429
|
}
|
|
4314
4430
|
function repoHash(repoRoot) {
|
|
4315
4431
|
return sha1(repoRoot).slice(0, 12);
|
|
4316
4432
|
}
|
|
4317
4433
|
function stateFilePath(repoRoot, baseDir = dataDir()) {
|
|
4318
|
-
return
|
|
4434
|
+
return path5.join(baseDir, repoHash(repoRoot), "state.json");
|
|
4319
4435
|
}
|
|
4320
4436
|
function defaultPrefs() {
|
|
4321
4437
|
return { viewMode: "unified", nvimSocketByRoot: {}, autoRefresh: true, railOpen: false, ignoreDebug: true };
|
|
4322
4438
|
}
|
|
4323
4439
|
function defaultState(repoRoot) {
|
|
4324
|
-
return { schemaVersion: 1, repoRoot, targets: {}, issues: [], todos: [], prefs: defaultPrefs() };
|
|
4440
|
+
return { schemaVersion: 1, repoRoot, targets: {}, issues: [], todos: [], checkpoints: [], prefs: defaultPrefs() };
|
|
4325
4441
|
}
|
|
4326
4442
|
function ensureTarget(state, key) {
|
|
4327
4443
|
let t = state.targets[key];
|
|
@@ -4332,6 +4448,10 @@ function ensureTarget(state, key) {
|
|
|
4332
4448
|
return t;
|
|
4333
4449
|
}
|
|
4334
4450
|
var usable = (v) => !!v && typeof v === "object" && typeof v.id === "string";
|
|
4451
|
+
var usableCheckpoint = (v) => {
|
|
4452
|
+
const c = v;
|
|
4453
|
+
return !!c && typeof c === "object" && Number.isInteger(c.id) && typeof c.tree === "string" && (c.worktree === void 0 || typeof c.worktree === "string");
|
|
4454
|
+
};
|
|
4335
4455
|
var usableComment = (v) => usable(v) && typeof v.anchor === "object" && !!v.anchor;
|
|
4336
4456
|
function normalise(raw2, repoRoot) {
|
|
4337
4457
|
const base = defaultState(repoRoot);
|
|
@@ -4354,6 +4474,7 @@ function normalise(raw2, repoRoot) {
|
|
|
4354
4474
|
targets,
|
|
4355
4475
|
issues: Array.isArray(r.issues) ? r.issues.filter(usable) : [],
|
|
4356
4476
|
todos: Array.isArray(r.todos) ? r.todos.filter(usable) : [],
|
|
4477
|
+
checkpoints: Array.isArray(r.checkpoints) ? r.checkpoints.filter(usableCheckpoint) : [],
|
|
4357
4478
|
prefs: {
|
|
4358
4479
|
...defaultPrefs(),
|
|
4359
4480
|
...r.prefs ?? {},
|
|
@@ -4412,7 +4533,7 @@ var StateStore = class {
|
|
|
4412
4533
|
/** Apply a mutation under lock. The callback receives the freshest on-disk state. */
|
|
4413
4534
|
update(fn) {
|
|
4414
4535
|
const run2 = async () => {
|
|
4415
|
-
await
|
|
4536
|
+
await mkdir2(path5.dirname(this.file), { recursive: true });
|
|
4416
4537
|
const release = await this.acquireLock();
|
|
4417
4538
|
try {
|
|
4418
4539
|
const state = await this.load();
|
|
@@ -4450,7 +4571,7 @@ var StateStore = class {
|
|
|
4450
4571
|
const err = e;
|
|
4451
4572
|
if (err.code !== "EEXIST") throw e;
|
|
4452
4573
|
try {
|
|
4453
|
-
const st = await
|
|
4574
|
+
const st = await stat3(lock);
|
|
4454
4575
|
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
|
|
4455
4576
|
await unlink(lock).catch(() => void 0);
|
|
4456
4577
|
continue;
|
|
@@ -4475,6 +4596,7 @@ function forgetWorktreeTargets(state, worktreePath) {
|
|
|
4475
4596
|
delete state.targets[key];
|
|
4476
4597
|
}
|
|
4477
4598
|
if (gone.size) for (const issue of state.issues) issue.commentIds = issue.commentIds.filter((id) => !gone.has(id));
|
|
4599
|
+
state.checkpoints = state.checkpoints.filter((c) => c.worktree !== worktreePath);
|
|
4478
4600
|
}
|
|
4479
4601
|
|
|
4480
4602
|
// packages/server/src/export.ts
|
|
@@ -4551,9 +4673,9 @@ function formatIssueExport({ repoRoot, issue, comments }) {
|
|
|
4551
4673
|
|
|
4552
4674
|
// packages/server/src/nvim.ts
|
|
4553
4675
|
import os2 from "os";
|
|
4554
|
-
import
|
|
4676
|
+
import path6 from "path";
|
|
4555
4677
|
import { execFile as execFile2 } from "child_process";
|
|
4556
|
-
import { readdir as readdir2, stat as
|
|
4678
|
+
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
4557
4679
|
var QUERY_TIMEOUT_MS = 500;
|
|
4558
4680
|
var CACHE_TTL_MS = 1e4;
|
|
4559
4681
|
function execNvim(args, timeoutMs) {
|
|
@@ -4582,14 +4704,14 @@ function socketDirs(env = process.env) {
|
|
|
4582
4704
|
}
|
|
4583
4705
|
}
|
|
4584
4706
|
const tmp = env.TMPDIR || "/tmp";
|
|
4585
|
-
if (user) dirs.push(
|
|
4586
|
-
if (env.NVIM_LISTEN_ADDRESS) dirs.push(
|
|
4707
|
+
if (user) dirs.push(path6.join(tmp, `nvim.${user}`));
|
|
4708
|
+
if (env.NVIM_LISTEN_ADDRESS) dirs.push(path6.dirname(env.NVIM_LISTEN_ADDRESS));
|
|
4587
4709
|
return [...new Set(dirs)];
|
|
4588
4710
|
}
|
|
4589
4711
|
var SOCKET_NAME = /^nvim\.(\d+)\.\d+$/;
|
|
4590
4712
|
async function isSocket(p) {
|
|
4591
4713
|
try {
|
|
4592
|
-
return (await
|
|
4714
|
+
return (await stat4(p)).isSocket();
|
|
4593
4715
|
} catch {
|
|
4594
4716
|
return false;
|
|
4595
4717
|
}
|
|
@@ -4604,7 +4726,7 @@ async function findSockets(dirs = socketDirs()) {
|
|
|
4604
4726
|
return;
|
|
4605
4727
|
}
|
|
4606
4728
|
for (const ent of entries) {
|
|
4607
|
-
const full =
|
|
4729
|
+
const full = path6.join(dir, ent.name);
|
|
4608
4730
|
const m = SOCKET_NAME.exec(ent.name);
|
|
4609
4731
|
if (m && (ent.isSocket() || await isSocket(full))) {
|
|
4610
4732
|
found.set(full, Number(m[1]));
|
|
@@ -4691,9 +4813,9 @@ var TmuxService = class {
|
|
|
4691
4813
|
const sessions = [];
|
|
4692
4814
|
for (const line of output.trimEnd().split("\n")) {
|
|
4693
4815
|
const [id, name, ...parts] = line.split(" ");
|
|
4694
|
-
const
|
|
4695
|
-
if (!id || !/^\$\d+$/.test(id) || !name || !
|
|
4696
|
-
if (await realpath3(
|
|
4816
|
+
const path12 = parts.join(" ");
|
|
4817
|
+
if (!id || !/^\$\d+$/.test(id) || !name || !path12) continue;
|
|
4818
|
+
if (await realpath3(path12).catch(() => null) === root) sessions.push({ id, name, path: path12 });
|
|
4697
4819
|
}
|
|
4698
4820
|
return sessions;
|
|
4699
4821
|
}
|
|
@@ -4707,8 +4829,8 @@ var TmuxService = class {
|
|
|
4707
4829
|
};
|
|
4708
4830
|
|
|
4709
4831
|
// packages/server/src/watcher.ts
|
|
4710
|
-
import
|
|
4711
|
-
import { stat as
|
|
4832
|
+
import path7 from "path";
|
|
4833
|
+
import { stat as stat5 } from "fs/promises";
|
|
4712
4834
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
4713
4835
|
function statusPaths(out) {
|
|
4714
4836
|
const parts = out.split("\0");
|
|
@@ -4804,7 +4926,7 @@ var RepoWatcher = class {
|
|
|
4804
4926
|
const stats = await Promise.all(
|
|
4805
4927
|
statusPaths(statusRes.stdout).map(async (rel) => {
|
|
4806
4928
|
try {
|
|
4807
|
-
const st = await
|
|
4929
|
+
const st = await stat5(path7.join(this.root, rel));
|
|
4808
4930
|
return `${rel} ${st.mtimeMs} ${st.size}`;
|
|
4809
4931
|
} catch {
|
|
4810
4932
|
return `${rel} -`;
|
|
@@ -4816,8 +4938,8 @@ var RepoWatcher = class {
|
|
|
4816
4938
|
};
|
|
4817
4939
|
|
|
4818
4940
|
// packages/server/src/static.ts
|
|
4819
|
-
import
|
|
4820
|
-
import { readFile as readFile3, stat as
|
|
4941
|
+
import path8 from "path";
|
|
4942
|
+
import { readFile as readFile3, stat as stat6 } from "fs/promises";
|
|
4821
4943
|
var MIME = {
|
|
4822
4944
|
".html": "text/html; charset=utf-8",
|
|
4823
4945
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -4834,20 +4956,20 @@ var MIME = {
|
|
|
4834
4956
|
".txt": "text/plain; charset=utf-8"
|
|
4835
4957
|
};
|
|
4836
4958
|
async function serveStaticFile(c, webDir) {
|
|
4837
|
-
const root =
|
|
4959
|
+
const root = path8.resolve(webDir);
|
|
4838
4960
|
let rel;
|
|
4839
4961
|
try {
|
|
4840
4962
|
rel = decodeURIComponent(c.req.path);
|
|
4841
4963
|
} catch {
|
|
4842
4964
|
return c.text("Bad Request", 400);
|
|
4843
4965
|
}
|
|
4844
|
-
let target =
|
|
4845
|
-
if (target !== root && !target.startsWith(root +
|
|
4966
|
+
let target = path8.resolve(root, "." + rel);
|
|
4967
|
+
if (target !== root && !target.startsWith(root + path8.sep)) return c.text("Forbidden", 403);
|
|
4846
4968
|
try {
|
|
4847
|
-
const st = await
|
|
4848
|
-
if (st.isDirectory()) target =
|
|
4969
|
+
const st = await stat6(target);
|
|
4970
|
+
if (st.isDirectory()) target = path8.join(target, "index.html");
|
|
4849
4971
|
} catch {
|
|
4850
|
-
target =
|
|
4972
|
+
target = path8.join(root, "index.html");
|
|
4851
4973
|
}
|
|
4852
4974
|
let body;
|
|
4853
4975
|
try {
|
|
@@ -4855,7 +4977,7 @@ async function serveStaticFile(c, webDir) {
|
|
|
4855
4977
|
} catch {
|
|
4856
4978
|
return c.text("Not Found", 404);
|
|
4857
4979
|
}
|
|
4858
|
-
const ext =
|
|
4980
|
+
const ext = path8.extname(target).toLowerCase();
|
|
4859
4981
|
const isAsset = rel.startsWith("/assets/");
|
|
4860
4982
|
return new Response(new Uint8Array(body), {
|
|
4861
4983
|
status: 200,
|
|
@@ -4891,7 +5013,8 @@ function createApp(opts) {
|
|
|
4891
5013
|
worktreeCache = { at: Date.now(), list };
|
|
4892
5014
|
return list;
|
|
4893
5015
|
};
|
|
4894
|
-
const
|
|
5016
|
+
const checkpoints = checkpointStore(store.file);
|
|
5017
|
+
const worktreeCtx = async (key) => {
|
|
4895
5018
|
let list = await worktrees();
|
|
4896
5019
|
try {
|
|
4897
5020
|
return resolveTargetContext(repo, list, key);
|
|
@@ -4903,6 +5026,13 @@ function createApp(opts) {
|
|
|
4903
5026
|
throw e;
|
|
4904
5027
|
}
|
|
4905
5028
|
};
|
|
5029
|
+
const targetCtx = async (key) => {
|
|
5030
|
+
const ctx = await worktreeCtx(key);
|
|
5031
|
+
if (ctx.target.kind !== "checkpoint") return ctx;
|
|
5032
|
+
const cp = findCheckpoint(await store.load(), ctx.target.worktree, ctx.target.id);
|
|
5033
|
+
if (!cp) throw notFound(`no checkpoint #${ctx.target.id} in this worktree`, "unknown_checkpoint");
|
|
5034
|
+
return { ...ctx, checkpoint: { tree: cp.tree, store: checkpoints } };
|
|
5035
|
+
};
|
|
4906
5036
|
const fileDiffWithHints = async (ctx, filePath, explicit) => {
|
|
4907
5037
|
const cached = listings.get(ctx.key);
|
|
4908
5038
|
const hint = cached && Date.now() - cached.at < LISTING_HINT_TTL_MS ? cached.files.find((f) => f.path === filePath) : void 0;
|
|
@@ -4939,7 +5069,7 @@ function createApp(opts) {
|
|
|
4939
5069
|
worktreeCache = { at: Date.now(), list: info.worktrees };
|
|
4940
5070
|
const last = state.prefs.lastTarget;
|
|
4941
5071
|
const target = last ? tryParseTargetKey(last) : void 0;
|
|
4942
|
-
const usable2 = !!target && (!target.worktree || info.worktrees.some((w) => w.path === target.worktree));
|
|
5072
|
+
const usable2 = !!target && (!target.worktree || info.worktrees.some((w) => w.path === target.worktree)) && (target.kind !== "checkpoint" || !!findCheckpoint(state, target.worktree, target.id));
|
|
4943
5073
|
if (last && usable2) info.defaultTarget = last;
|
|
4944
5074
|
else if (last) {
|
|
4945
5075
|
await store.update((s) => {
|
|
@@ -5038,6 +5168,33 @@ function createApp(opts) {
|
|
|
5038
5168
|
changedNotices.get(key)?.delete(body.path);
|
|
5039
5169
|
return c.json({ viewed });
|
|
5040
5170
|
});
|
|
5171
|
+
api.get("/targets/:key/checkpoints", async (c) => {
|
|
5172
|
+
const ctx = await worktreeCtx(c.req.param("key"));
|
|
5173
|
+
const res = { checkpoints: checkpointsOf(await store.load(), ctx.target.worktree) };
|
|
5174
|
+
return c.json(res);
|
|
5175
|
+
});
|
|
5176
|
+
api.post("/targets/:key/checkpoints", async (c) => {
|
|
5177
|
+
const ctx = await worktreeCtx(c.req.param("key"));
|
|
5178
|
+
const tree = await snapshotWorktree(ctx.cwd, checkpoints);
|
|
5179
|
+
const head = await revParse(ctx.cwd, "HEAD") ?? "";
|
|
5180
|
+
const res = await store.update((s) => {
|
|
5181
|
+
const newest = checkpointsOf(s, ctx.target.worktree).at(-1);
|
|
5182
|
+
const unchanged = newest?.tree === tree;
|
|
5183
|
+
const checkpoint = unchanged ? newest : addCheckpoint(s, ctx.target.worktree, tree, head);
|
|
5184
|
+
return { checkpoint, unchanged, targetKey: checkpointKey(checkpoint) };
|
|
5185
|
+
});
|
|
5186
|
+
return c.json(res, res.unchanged ? 200 : 201);
|
|
5187
|
+
});
|
|
5188
|
+
api.delete("/targets/:key/checkpoints/:id", async (c) => {
|
|
5189
|
+
const ctx = await worktreeCtx(c.req.param("key"));
|
|
5190
|
+
const id = Number(c.req.param("id"));
|
|
5191
|
+
await store.update((s) => {
|
|
5192
|
+
const cp = Number.isInteger(id) ? findCheckpoint(s, ctx.target.worktree, id) : void 0;
|
|
5193
|
+
if (!cp) throw notFound(`no checkpoint #${c.req.param("id")} in this worktree`, "unknown_checkpoint");
|
|
5194
|
+
forgetCheckpoint(s, cp);
|
|
5195
|
+
});
|
|
5196
|
+
return c.json({ ok: true });
|
|
5197
|
+
});
|
|
5041
5198
|
api.post("/targets/:key/stage", async (c) => {
|
|
5042
5199
|
const key = c.req.param("key");
|
|
5043
5200
|
const ctx = await targetCtx(key);
|
|
@@ -5079,7 +5236,7 @@ function createApp(opts) {
|
|
|
5079
5236
|
if (!anchored) throw badRequest("selection is not fully visible in the diff on that side (must be inside one hunk)", "bad_selection");
|
|
5080
5237
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5081
5238
|
const comment = {
|
|
5082
|
-
id:
|
|
5239
|
+
id: randomUUID2(),
|
|
5083
5240
|
targetKey: key,
|
|
5084
5241
|
filePath: body.filePath,
|
|
5085
5242
|
side: body.side,
|
|
@@ -5278,7 +5435,7 @@ function createApp(opts) {
|
|
|
5278
5435
|
if (!body.title?.trim()) throw badRequest("title is required");
|
|
5279
5436
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5280
5437
|
const issue = {
|
|
5281
|
-
id:
|
|
5438
|
+
id: randomUUID2(),
|
|
5282
5439
|
title: body.title.trim(),
|
|
5283
5440
|
body: typeof body.body === "string" ? body.body : "",
|
|
5284
5441
|
status: body.status === "closed" ? "closed" : "open",
|
|
@@ -5372,7 +5529,7 @@ function createApp(opts) {
|
|
|
5372
5529
|
const branch = body.branch?.trim() || await currentBranch(await knownRoot(body.root));
|
|
5373
5530
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5374
5531
|
const todo = {
|
|
5375
|
-
id:
|
|
5532
|
+
id: randomUUID2(),
|
|
5376
5533
|
branch,
|
|
5377
5534
|
title: body.title.trim(),
|
|
5378
5535
|
body: typeof body.body === "string" ? body.body : "",
|
|
@@ -5574,7 +5731,7 @@ function createApp(opts) {
|
|
|
5574
5731
|
api.post("/nvim/open", async (c) => {
|
|
5575
5732
|
const body = await c.req.json();
|
|
5576
5733
|
if (!body.socket || !body.absPath) throw badRequest("socket and absPath are required");
|
|
5577
|
-
if (!
|
|
5734
|
+
if (!path9.isAbsolute(body.absPath)) throw badRequest("absPath must be absolute");
|
|
5578
5735
|
const scan = await nvim.scan();
|
|
5579
5736
|
if (!scan.instances.some((i) => i.socket === body.socket)) {
|
|
5580
5737
|
const fresh = await nvim.scan(true);
|
|
@@ -5634,14 +5791,14 @@ function reachableFromWindows(port, token, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
|
5634
5791
|
}
|
|
5635
5792
|
|
|
5636
5793
|
// packages/server/src/update.ts
|
|
5637
|
-
import
|
|
5638
|
-
import { mkdir as
|
|
5794
|
+
import path10 from "path";
|
|
5795
|
+
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
|
|
5639
5796
|
var PACKAGE = "@xbghc/warden";
|
|
5640
5797
|
var LATEST_URL = "https://registry.npmjs.org/@xbghc%2fwarden/latest";
|
|
5641
5798
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
5642
5799
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
5643
5800
|
function updateCacheFile(baseDir = dataDir()) {
|
|
5644
|
-
return
|
|
5801
|
+
return path10.join(baseDir, "update-check.json");
|
|
5645
5802
|
}
|
|
5646
5803
|
function updateCheckEnabled(env = process.env) {
|
|
5647
5804
|
return !env.WARDEN_NO_UPDATE_CHECK && !env.NO_UPDATE_NOTIFIER && !env.CI;
|
|
@@ -5675,7 +5832,7 @@ async function readCache(file, now) {
|
|
|
5675
5832
|
}
|
|
5676
5833
|
async function writeCache(file, cache) {
|
|
5677
5834
|
try {
|
|
5678
|
-
await
|
|
5835
|
+
await mkdir3(path10.dirname(file), { recursive: true });
|
|
5679
5836
|
await writeFile2(file, JSON.stringify(cache));
|
|
5680
5837
|
} catch {
|
|
5681
5838
|
}
|
|
@@ -5720,7 +5877,7 @@ async function startServer(opts) {
|
|
|
5720
5877
|
const repo = await resolveRepo(opts.repoPath);
|
|
5721
5878
|
const stateFile = opts.stateFile ?? stateFilePath(repo.commonRoot);
|
|
5722
5879
|
const store = new StateStore(stateFile, repo.commonRoot);
|
|
5723
|
-
const instanceToken =
|
|
5880
|
+
const instanceToken = randomUUID3();
|
|
5724
5881
|
const app = createApp({ repo, store, nvim: new NvimService(), webDir: opts.webDir, instanceToken, update: opts.update });
|
|
5725
5882
|
const listen = (p) => new Promise((resolve, reject) => {
|
|
5726
5883
|
const s = serve({ fetch: app.fetch, hostname: HOST, port: p }, () => resolve(s));
|
|
@@ -5764,7 +5921,7 @@ async function startServer(opts) {
|
|
|
5764
5921
|
}
|
|
5765
5922
|
|
|
5766
5923
|
// bin/cli.ts
|
|
5767
|
-
var VERSION = true ? "0.
|
|
5924
|
+
var VERSION = true ? "0.15.0" : "dev";
|
|
5768
5925
|
function parseArgs(argv) {
|
|
5769
5926
|
const args = { repoPath: process.cwd(), open: true, updateCheck: true, help: false, version: false };
|
|
5770
5927
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -5783,7 +5940,7 @@ function parseArgs(argv) {
|
|
|
5783
5940
|
} else if (a === "--help" || a === "-h") args.help = true;
|
|
5784
5941
|
else if (a === "--version" || a === "-v") args.version = true;
|
|
5785
5942
|
else if (a.startsWith("-")) throw new Error(`unknown option: ${a}`);
|
|
5786
|
-
else args.repoPath =
|
|
5943
|
+
else args.repoPath = path11.resolve(a);
|
|
5787
5944
|
}
|
|
5788
5945
|
return args;
|
|
5789
5946
|
}
|
|
@@ -5828,16 +5985,16 @@ async function openBrowser(url) {
|
|
|
5828
5985
|
return false;
|
|
5829
5986
|
}
|
|
5830
5987
|
function resolveWebDir() {
|
|
5831
|
-
const here =
|
|
5988
|
+
const here = path11.dirname(fileURLToPath(import.meta.url));
|
|
5832
5989
|
const candidates = [
|
|
5833
5990
|
process.env.WARDEN_WEB_DIR,
|
|
5834
|
-
|
|
5991
|
+
path11.join(here, "web"),
|
|
5835
5992
|
// dist/cli.js -> dist/web
|
|
5836
|
-
|
|
5993
|
+
path11.join(here, "..", "dist", "web")
|
|
5837
5994
|
// bin/cli.ts (tsx dev) -> dist/web
|
|
5838
5995
|
].filter((p) => !!p);
|
|
5839
5996
|
for (const dir of candidates) {
|
|
5840
|
-
if (existsSync2(
|
|
5997
|
+
if (existsSync2(path11.join(dir, "index.html"))) return dir;
|
|
5841
5998
|
}
|
|
5842
5999
|
return void 0;
|
|
5843
6000
|
}
|
|
@@ -5862,7 +6019,7 @@ async function main() {
|
|
|
5862
6019
|
if (!webDir) {
|
|
5863
6020
|
console.error("warning: built web assets not found (dist/web). Only the /api endpoints will be served.");
|
|
5864
6021
|
}
|
|
5865
|
-
const viaNpx = fileURLToPath(import.meta.url).includes(`${
|
|
6022
|
+
const viaNpx = fileURLToPath(import.meta.url).includes(`${path11.sep}_npx${path11.sep}`);
|
|
5866
6023
|
const checking = args.updateCheck && VERSION !== "dev" && updateCheckEnabled();
|
|
5867
6024
|
const update = checking ? checkForUpdate({ current: VERSION, npx: viaNpx }) : Promise.resolve(null);
|
|
5868
6025
|
let server;
|