@xbghc/warden 0.13.1 → 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
|
}
|
|
@@ -3059,7 +3067,8 @@ var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
3059
3067
|
"merge-base",
|
|
3060
3068
|
"rev-list",
|
|
3061
3069
|
"for-each-ref",
|
|
3062
|
-
"check-ref-format"
|
|
3070
|
+
"check-ref-format",
|
|
3071
|
+
"grep"
|
|
3063
3072
|
]);
|
|
3064
3073
|
var FORBIDDEN_OPTION_PREFIXES = ["--output", "--ext-diff", "--textconv", "-c", "--config-env", "--exec-path", "--git-dir", "--work-tree"];
|
|
3065
3074
|
var DEFAULT_GIT_TIMEOUT_MS = 3e4;
|
|
@@ -3074,6 +3083,14 @@ var GitError = class extends HttpError {
|
|
|
3074
3083
|
exitCode;
|
|
3075
3084
|
stderr;
|
|
3076
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
|
+
}
|
|
3077
3094
|
function assertAllowedGitArgs(args) {
|
|
3078
3095
|
const sub = args[0];
|
|
3079
3096
|
if (!sub || !ALLOWED_SUBCOMMANDS.has(sub)) {
|
|
@@ -3082,6 +3099,15 @@ function assertAllowedGitArgs(args) {
|
|
|
3082
3099
|
if (sub === "worktree" && args[1] !== "list") {
|
|
3083
3100
|
throw new GitError(`git worktree ${args[1] ?? ""} is not allowed`, null, "", 400, "git_subcommand_forbidden");
|
|
3084
3101
|
}
|
|
3102
|
+
if (sub === "grep") {
|
|
3103
|
+
for (const arg of args.slice(1)) {
|
|
3104
|
+
if (arg === "--") break;
|
|
3105
|
+
const name = arg.split("=")[0];
|
|
3106
|
+
if (arg.startsWith("-O") || name.length > 3 && "--open-files-in-pager".startsWith(name)) {
|
|
3107
|
+
throw new GitError(`git option not allowed: ${arg}`, null, "", 400, "git_option_forbidden");
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3085
3111
|
for (const arg of args.slice(1)) {
|
|
3086
3112
|
if (arg.includes("\0")) throw new GitError("NUL byte in git argument", null, "", 400, "git_bad_argument");
|
|
3087
3113
|
for (const prefix of FORBIDDEN_OPTION_PREFIXES) {
|
|
@@ -3110,7 +3136,8 @@ async function runGit(args, opts) {
|
|
|
3110
3136
|
LC_ALL: "C",
|
|
3111
3137
|
GIT_PAGER: "cat",
|
|
3112
3138
|
PAGER: "cat",
|
|
3113
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
3139
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
3140
|
+
...snapshotVars(opts.snapshot)
|
|
3114
3141
|
},
|
|
3115
3142
|
windowsHide: true
|
|
3116
3143
|
},
|
|
@@ -3202,6 +3229,14 @@ async function applyOnce(cwd, patch, opts) {
|
|
|
3202
3229
|
});
|
|
3203
3230
|
}
|
|
3204
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) {
|
|
3205
3240
|
for (const arg of args) if (arg.includes("\0")) throw new GitError("NUL byte in git argument", null, "", 400, "git_bad_argument");
|
|
3206
3241
|
return new Promise((resolve, reject) => {
|
|
3207
3242
|
execFile(
|
|
@@ -3212,7 +3247,7 @@ async function runGitWrite(args, opts) {
|
|
|
3212
3247
|
timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
|
|
3213
3248
|
maxBuffer: 16 * 1024 * 1024,
|
|
3214
3249
|
encoding: "utf8",
|
|
3215
|
-
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 },
|
|
3216
3251
|
windowsHide: true
|
|
3217
3252
|
},
|
|
3218
3253
|
(error, stdout, stderr) => {
|
|
@@ -3364,9 +3399,86 @@ async function getRepoInfo(ctx, defaultTarget) {
|
|
|
3364
3399
|
}
|
|
3365
3400
|
|
|
3366
3401
|
// packages/server/src/targets.ts
|
|
3367
|
-
import
|
|
3402
|
+
import path3 from "path";
|
|
3368
3403
|
import { readFile } from "fs/promises";
|
|
3369
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
|
+
|
|
3370
3482
|
// packages/server/src/hash.ts
|
|
3371
3483
|
import { createHash } from "crypto";
|
|
3372
3484
|
function sha1(input) {
|
|
@@ -3587,7 +3699,52 @@ function parseUnifiedDiff(text) {
|
|
|
3587
3699
|
return files;
|
|
3588
3700
|
}
|
|
3589
3701
|
|
|
3702
|
+
// packages/server/src/debug.ts
|
|
3703
|
+
var LEADER = String.raw`(?:\/\/+|\/\*+|#+|--|<!--|;+|%+)\s*`;
|
|
3704
|
+
var BLOCK_START = new RegExp(`${LEADER}(?:debug|develblock):start\\b`, "i");
|
|
3705
|
+
var BLOCK_END = new RegExp(`${LEADER}(?:debug|develblock):end\\b`, "i");
|
|
3706
|
+
var LINE_TAG = new RegExp(`${LEADER}[@!]?(?:no-?commit|do not commit)\\b`, "i");
|
|
3707
|
+
var DEBUG_MARKER_STRINGS = ["debug:start", "develblock:start", "nocommit", "no-commit", "do not commit"];
|
|
3708
|
+
function debugLineNumbers(text) {
|
|
3709
|
+
const out = /* @__PURE__ */ new Set();
|
|
3710
|
+
const lines = text.split("\n");
|
|
3711
|
+
let depth = 0;
|
|
3712
|
+
lines.forEach((line, i) => {
|
|
3713
|
+
const no = i + 1;
|
|
3714
|
+
if (BLOCK_START.test(line)) depth++;
|
|
3715
|
+
if (depth > 0 || LINE_TAG.test(line)) out.add(no);
|
|
3716
|
+
if (depth > 0 && BLOCK_END.test(line)) depth--;
|
|
3717
|
+
});
|
|
3718
|
+
return out;
|
|
3719
|
+
}
|
|
3720
|
+
function markDebugLines(diff, newSide, oldSide) {
|
|
3721
|
+
let added = 0;
|
|
3722
|
+
let deleted = 0;
|
|
3723
|
+
for (const hunk of diff.hunks) {
|
|
3724
|
+
for (const line of hunk.lines) {
|
|
3725
|
+
const debug = line.type === "del" ? !!oldSide?.has(line.oldLineNo) : !!newSide?.has(line.newLineNo);
|
|
3726
|
+
if (!debug) continue;
|
|
3727
|
+
line.debug = true;
|
|
3728
|
+
if (line.type === "add") added++;
|
|
3729
|
+
else if (line.type === "del") deleted++;
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
if (added) diff.debugAdditions = added;
|
|
3733
|
+
if (deleted) diff.debugDeletions = deleted;
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3590
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
|
+
}
|
|
3591
3748
|
var DIFF_BASE_ARGS = ["diff", "--no-color", "--no-ext-diff", "-U3", "-M", "--find-renames"];
|
|
3592
3749
|
function resolveTargetContext(repo, worktrees, key) {
|
|
3593
3750
|
let target;
|
|
@@ -3641,6 +3798,8 @@ async function diffArgs(ctx) {
|
|
|
3641
3798
|
}
|
|
3642
3799
|
case "base":
|
|
3643
3800
|
return [...DIFF_BASE_ARGS, await baseSha(ctx, t.ref)];
|
|
3801
|
+
case "checkpoint":
|
|
3802
|
+
return [...DIFF_BASE_ARGS, checkpointOf(ctx).tree];
|
|
3644
3803
|
}
|
|
3645
3804
|
}
|
|
3646
3805
|
function includesUntracked(t) {
|
|
@@ -3682,8 +3841,7 @@ async function mapLimit(items, limit, fn) {
|
|
|
3682
3841
|
}
|
|
3683
3842
|
async function listTargetDiffs(ctx) {
|
|
3684
3843
|
const args = await diffArgs(ctx);
|
|
3685
|
-
const
|
|
3686
|
-
const files = parseUnifiedDiff(r.stdout);
|
|
3844
|
+
const files = parseUnifiedDiff(await runDiff(ctx, args));
|
|
3687
3845
|
if (includesUntracked(ctx.target)) {
|
|
3688
3846
|
const untracked = await listUntracked(ctx.cwd);
|
|
3689
3847
|
const extra = await mapLimit(untracked, 8, (f) => untrackedDiff(ctx.cwd, f).catch(() => void 0));
|
|
@@ -3705,8 +3863,7 @@ async function getFileDiff(ctx, filePath, hints = {}) {
|
|
|
3705
3863
|
const args = await diffArgs(ctx);
|
|
3706
3864
|
const pathspec = [literal(filePath)];
|
|
3707
3865
|
if (hints.oldPath && hints.oldPath !== filePath) pathspec.push(literal(hints.oldPath));
|
|
3708
|
-
const
|
|
3709
|
-
const files = parseUnifiedDiff(r.stdout);
|
|
3866
|
+
const files = parseUnifiedDiff(await runDiff(ctx, [...args, "--", ...pathspec]));
|
|
3710
3867
|
return files.find((f) => f.path === filePath) ?? files[0];
|
|
3711
3868
|
}
|
|
3712
3869
|
async function refForSide(ctx, side) {
|
|
@@ -3722,9 +3879,12 @@ async function refForSide(ctx, side) {
|
|
|
3722
3879
|
if (side === "new") return t.sha;
|
|
3723
3880
|
return await revParse(ctx.cwd, `${t.sha}^`) ?? EMPTY_TREE_SHA;
|
|
3724
3881
|
case "range":
|
|
3725
|
-
|
|
3882
|
+
if (side === "new") return t.head;
|
|
3883
|
+
return await mergeBase(ctx.cwd, t.base, t.head) ?? t.base;
|
|
3726
3884
|
case "base":
|
|
3727
3885
|
return side === "new" ? void 0 : baseSha(ctx, t.ref);
|
|
3886
|
+
case "checkpoint":
|
|
3887
|
+
return side === "new" ? void 0 : checkpointOf(ctx).tree;
|
|
3728
3888
|
}
|
|
3729
3889
|
}
|
|
3730
3890
|
async function getFullFile(ctx, filePath, side) {
|
|
@@ -3732,20 +3892,64 @@ async function getFullFile(ctx, filePath, side) {
|
|
|
3732
3892
|
const ref = await refForSide(ctx, side);
|
|
3733
3893
|
if (ref === void 0) {
|
|
3734
3894
|
try {
|
|
3735
|
-
const buf = await readFile(
|
|
3895
|
+
const buf = await readFile(path3.join(ctx.cwd, filePath));
|
|
3736
3896
|
return buf.toString("utf8");
|
|
3737
3897
|
} catch {
|
|
3738
3898
|
return null;
|
|
3739
3899
|
}
|
|
3740
3900
|
}
|
|
3741
3901
|
try {
|
|
3742
|
-
const r = await runGit(["show", `${ref}:${filePath}`], { cwd: ctx.cwd });
|
|
3902
|
+
const r = await runGit(["show", `${ref}:${filePath}`], { cwd: ctx.cwd, ...await objectsFor(ctx) });
|
|
3743
3903
|
return r.stdout;
|
|
3744
3904
|
} catch (e) {
|
|
3745
3905
|
if (e instanceof HttpError && e.code === "git_failed") return null;
|
|
3746
3906
|
throw e;
|
|
3747
3907
|
}
|
|
3748
3908
|
}
|
|
3909
|
+
var GREP_CHUNK = 500;
|
|
3910
|
+
async function pathsWithMarkers(ctx, side, paths) {
|
|
3911
|
+
const found = /* @__PURE__ */ new Set();
|
|
3912
|
+
if (paths.length === 0) return found;
|
|
3913
|
+
const ref = await refForSide(ctx, side);
|
|
3914
|
+
const args = ["grep", "-l", "-z", "-I", "-i", "-F", ...DEBUG_MARKER_STRINGS.flatMap((m) => ["-e", m])];
|
|
3915
|
+
if (ref === void 0) args.push("--untracked");
|
|
3916
|
+
else if (ref === ":0") args.push("--cached");
|
|
3917
|
+
else args.push(ref);
|
|
3918
|
+
const prefix = ref === void 0 || ref === ":0" ? "" : `${ref}:`;
|
|
3919
|
+
const objects = await objectsFor(ctx);
|
|
3920
|
+
for (let i = 0; i < paths.length; i += GREP_CHUNK) {
|
|
3921
|
+
const chunk = paths.slice(i, i + GREP_CHUNK).map(literal);
|
|
3922
|
+
const r = await runGit([...args, "--", ...chunk], { cwd: ctx.cwd, okCodes: [0, 1], ...objects });
|
|
3923
|
+
for (const p of r.stdout.split("\0")) if (p) found.add(p.startsWith(prefix) ? p.slice(prefix.length) : p);
|
|
3924
|
+
}
|
|
3925
|
+
return found;
|
|
3926
|
+
}
|
|
3927
|
+
async function annotateDebug(ctx, files) {
|
|
3928
|
+
const text = files.filter((f) => !f.binary && f.hunks.length > 0);
|
|
3929
|
+
const oldPathOf = (f) => f.oldPath ?? f.path;
|
|
3930
|
+
const none = () => /* @__PURE__ */ new Set();
|
|
3931
|
+
const [onNew, onOld] = await Promise.all([
|
|
3932
|
+
pathsWithMarkers(
|
|
3933
|
+
ctx,
|
|
3934
|
+
"new",
|
|
3935
|
+
text.filter((f) => f.status !== "deleted").map((f) => f.path)
|
|
3936
|
+
).catch(none),
|
|
3937
|
+
pathsWithMarkers(ctx, "old", text.filter((f) => f.status !== "added").map(oldPathOf)).catch(none)
|
|
3938
|
+
]);
|
|
3939
|
+
const read = async (p, side, marked) => {
|
|
3940
|
+
if (!marked.has(p)) return void 0;
|
|
3941
|
+
const content = await getFullFile(ctx, p, side).catch(() => null);
|
|
3942
|
+
return content === null ? void 0 : debugLineNumbers(content);
|
|
3943
|
+
};
|
|
3944
|
+
await mapLimit(
|
|
3945
|
+
text.filter((f) => onNew.has(f.path) || onOld.has(oldPathOf(f))),
|
|
3946
|
+
8,
|
|
3947
|
+
async (f) => {
|
|
3948
|
+
const [n, o] = await Promise.all([read(f.path, "new", onNew), read(oldPathOf(f), "old", onOld)]);
|
|
3949
|
+
markDebugLines(f, n, o);
|
|
3950
|
+
}
|
|
3951
|
+
);
|
|
3952
|
+
}
|
|
3749
3953
|
|
|
3750
3954
|
// packages/server/src/anchor.ts
|
|
3751
3955
|
function sideLines(diff, side) {
|
|
@@ -3895,9 +4099,9 @@ function cutHunk(hunk, picked, mode) {
|
|
|
3895
4099
|
});
|
|
3896
4100
|
return out;
|
|
3897
4101
|
}
|
|
3898
|
-
function pickedLines(diff, selection) {
|
|
4102
|
+
function pickedLines(diff, selection, skipDebug) {
|
|
3899
4103
|
const picked = /* @__PURE__ */ new Map();
|
|
3900
|
-
const changed = (h) => h.lines.map((l, i) => l.type === "context" ? -1 : i).filter((i) => i >= 0);
|
|
4104
|
+
const changed = (h) => h.lines.map((l, i) => l.type === "context" || skipDebug && l.debug ? -1 : i).filter((i) => i >= 0);
|
|
3901
4105
|
if (selection === void 0) {
|
|
3902
4106
|
for (const [i, h] of diff.hunks.entries()) picked.set(i, new Set(changed(h)));
|
|
3903
4107
|
return picked;
|
|
@@ -3921,10 +4125,11 @@ function pickedLines(diff, selection) {
|
|
|
3921
4125
|
}
|
|
3922
4126
|
return picked;
|
|
3923
4127
|
}
|
|
3924
|
-
function buildStagePatch(diff, mode, selection) {
|
|
4128
|
+
function buildStagePatch(diff, mode, selection, opts = {}) {
|
|
3925
4129
|
if (diff.binary) throw badRequest("binary files cannot be staged from here; use git add", "not_stageable");
|
|
3926
4130
|
const whole = selection === void 0;
|
|
3927
|
-
const
|
|
4131
|
+
const skipDebug = !!opts.skipDebug && mode === "stage";
|
|
4132
|
+
const picked = pickedLines(diff, selection, skipDebug);
|
|
3928
4133
|
let lines = 0;
|
|
3929
4134
|
for (const set of picked.values()) lines += set.size;
|
|
3930
4135
|
const changedTotal = diff.hunks.reduce((n, h) => n + h.lines.filter((l) => l.type !== "context").length, 0);
|
|
@@ -3957,7 +4162,11 @@ function buildStagePatch(diff, mode, selection) {
|
|
|
3957
4162
|
}
|
|
3958
4163
|
if (lines === 0) {
|
|
3959
4164
|
const headerOnly = whole && (modeChange || rename2 || (diff.status === "added" || diff.status === "deleted") && diff.hunks.length === 0);
|
|
3960
|
-
if (!headerOnly)
|
|
4165
|
+
if (!headerOnly) {
|
|
4166
|
+
if (skipDebug && [...pickedLines(diff, selection, false).values()].some((set) => set.size > 0))
|
|
4167
|
+
throw badRequest("every changed line picked is debug code; pick those lines one by one to stage them anyway", "debug_only");
|
|
4168
|
+
throw badRequest("no changed lines selected", "empty_selection");
|
|
4169
|
+
}
|
|
3961
4170
|
return { patch: header.join("\n") + "\n", lines: 0 };
|
|
3962
4171
|
}
|
|
3963
4172
|
let delta = 0;
|
|
@@ -3983,15 +4192,15 @@ function buildStagePatch(diff, mode, selection) {
|
|
|
3983
4192
|
}
|
|
3984
4193
|
|
|
3985
4194
|
// packages/server/src/worktrees.ts
|
|
3986
|
-
import
|
|
3987
|
-
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";
|
|
3988
4197
|
function slotPath(ctx, slot) {
|
|
3989
|
-
return
|
|
4198
|
+
return path4.join(path4.dirname(ctx.commonRoot), `${path4.basename(ctx.commonRoot)}-${slot}`);
|
|
3990
4199
|
}
|
|
3991
4200
|
function slotOf(ctx, p) {
|
|
3992
|
-
if (
|
|
3993
|
-
const prefix = `${
|
|
3994
|
-
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);
|
|
3995
4204
|
const n = name.startsWith(prefix) ? name.slice(prefix.length) : "";
|
|
3996
4205
|
return /^[1-9]\d*$/.test(n) ? Number(n) : void 0;
|
|
3997
4206
|
}
|
|
@@ -4076,7 +4285,7 @@ async function assertBranchName(ctx, name) {
|
|
|
4076
4285
|
}
|
|
4077
4286
|
}
|
|
4078
4287
|
async function dirUsable(p) {
|
|
4079
|
-
const st = await
|
|
4288
|
+
const st = await stat2(p).catch((e) => {
|
|
4080
4289
|
if (e.code === "ENOENT") return void 0;
|
|
4081
4290
|
throw e;
|
|
4082
4291
|
});
|
|
@@ -4173,7 +4382,7 @@ async function dropBranch(ctx, wt, asked) {
|
|
|
4173
4382
|
}
|
|
4174
4383
|
async function releaseWorktree(ctx, req) {
|
|
4175
4384
|
const p = req.path.trim();
|
|
4176
|
-
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));
|
|
4177
4386
|
if (!wt) throw badRequest(`unknown worktree: ${p}`, "unknown_worktree");
|
|
4178
4387
|
if (wt.isMain) throw badRequest("the main worktree cannot be released", "main_worktree");
|
|
4179
4388
|
if (wt.bare || slotOf(ctx, wt.path) === void 0) throw badRequest(`${wt.path} is not a slot; remove it instead`, "not_a_slot");
|
|
@@ -4189,7 +4398,7 @@ async function releaseWorktree(ctx, req) {
|
|
|
4189
4398
|
}
|
|
4190
4399
|
async function removeWorktree(ctx, req) {
|
|
4191
4400
|
const p = req.path.trim();
|
|
4192
|
-
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));
|
|
4193
4402
|
if (!wt) throw badRequest(`unknown worktree: ${p}`, "unknown_worktree");
|
|
4194
4403
|
if (wt.isMain) throw badRequest("the main worktree cannot be removed", "main_worktree");
|
|
4195
4404
|
const args = ["worktree", "remove"];
|
|
@@ -4211,24 +4420,24 @@ async function removeWorktree(ctx, req) {
|
|
|
4211
4420
|
|
|
4212
4421
|
// packages/server/src/state.ts
|
|
4213
4422
|
import os from "os";
|
|
4214
|
-
import
|
|
4215
|
-
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";
|
|
4216
4425
|
function dataDir() {
|
|
4217
4426
|
const xdg = process.env.XDG_DATA_HOME;
|
|
4218
|
-
const base = xdg?.trim() ? xdg :
|
|
4219
|
-
return
|
|
4427
|
+
const base = xdg?.trim() ? xdg : path5.join(os.homedir(), ".local", "share");
|
|
4428
|
+
return path5.join(base, "warden");
|
|
4220
4429
|
}
|
|
4221
4430
|
function repoHash(repoRoot) {
|
|
4222
4431
|
return sha1(repoRoot).slice(0, 12);
|
|
4223
4432
|
}
|
|
4224
4433
|
function stateFilePath(repoRoot, baseDir = dataDir()) {
|
|
4225
|
-
return
|
|
4434
|
+
return path5.join(baseDir, repoHash(repoRoot), "state.json");
|
|
4226
4435
|
}
|
|
4227
4436
|
function defaultPrefs() {
|
|
4228
|
-
return { viewMode: "unified", nvimSocketByRoot: {}, autoRefresh: true, railOpen: false };
|
|
4437
|
+
return { viewMode: "unified", nvimSocketByRoot: {}, autoRefresh: true, railOpen: false, ignoreDebug: true };
|
|
4229
4438
|
}
|
|
4230
4439
|
function defaultState(repoRoot) {
|
|
4231
|
-
return { schemaVersion: 1, repoRoot, targets: {}, issues: [], todos: [], prefs: defaultPrefs() };
|
|
4440
|
+
return { schemaVersion: 1, repoRoot, targets: {}, issues: [], todos: [], checkpoints: [], prefs: defaultPrefs() };
|
|
4232
4441
|
}
|
|
4233
4442
|
function ensureTarget(state, key) {
|
|
4234
4443
|
let t = state.targets[key];
|
|
@@ -4239,6 +4448,10 @@ function ensureTarget(state, key) {
|
|
|
4239
4448
|
return t;
|
|
4240
4449
|
}
|
|
4241
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
|
+
};
|
|
4242
4455
|
var usableComment = (v) => usable(v) && typeof v.anchor === "object" && !!v.anchor;
|
|
4243
4456
|
function normalise(raw2, repoRoot) {
|
|
4244
4457
|
const base = defaultState(repoRoot);
|
|
@@ -4261,12 +4474,14 @@ function normalise(raw2, repoRoot) {
|
|
|
4261
4474
|
targets,
|
|
4262
4475
|
issues: Array.isArray(r.issues) ? r.issues.filter(usable) : [],
|
|
4263
4476
|
todos: Array.isArray(r.todos) ? r.todos.filter(usable) : [],
|
|
4477
|
+
checkpoints: Array.isArray(r.checkpoints) ? r.checkpoints.filter(usableCheckpoint) : [],
|
|
4264
4478
|
prefs: {
|
|
4265
4479
|
...defaultPrefs(),
|
|
4266
4480
|
...r.prefs ?? {},
|
|
4267
4481
|
nvimSocketByRoot: r.prefs?.nvimSocketByRoot ?? {},
|
|
4268
4482
|
autoRefresh: typeof r.prefs?.autoRefresh === "boolean" ? r.prefs.autoRefresh : true,
|
|
4269
|
-
railOpen: typeof r.prefs?.railOpen === "boolean" ? r.prefs.railOpen : false
|
|
4483
|
+
railOpen: typeof r.prefs?.railOpen === "boolean" ? r.prefs.railOpen : false,
|
|
4484
|
+
ignoreDebug: typeof r.prefs?.ignoreDebug === "boolean" ? r.prefs.ignoreDebug : true
|
|
4270
4485
|
}
|
|
4271
4486
|
};
|
|
4272
4487
|
}
|
|
@@ -4318,7 +4533,7 @@ var StateStore = class {
|
|
|
4318
4533
|
/** Apply a mutation under lock. The callback receives the freshest on-disk state. */
|
|
4319
4534
|
update(fn) {
|
|
4320
4535
|
const run2 = async () => {
|
|
4321
|
-
await
|
|
4536
|
+
await mkdir2(path5.dirname(this.file), { recursive: true });
|
|
4322
4537
|
const release = await this.acquireLock();
|
|
4323
4538
|
try {
|
|
4324
4539
|
const state = await this.load();
|
|
@@ -4356,7 +4571,7 @@ var StateStore = class {
|
|
|
4356
4571
|
const err = e;
|
|
4357
4572
|
if (err.code !== "EEXIST") throw e;
|
|
4358
4573
|
try {
|
|
4359
|
-
const st = await
|
|
4574
|
+
const st = await stat3(lock);
|
|
4360
4575
|
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
|
|
4361
4576
|
await unlink(lock).catch(() => void 0);
|
|
4362
4577
|
continue;
|
|
@@ -4381,6 +4596,7 @@ function forgetWorktreeTargets(state, worktreePath) {
|
|
|
4381
4596
|
delete state.targets[key];
|
|
4382
4597
|
}
|
|
4383
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);
|
|
4384
4600
|
}
|
|
4385
4601
|
|
|
4386
4602
|
// packages/server/src/export.ts
|
|
@@ -4457,9 +4673,9 @@ function formatIssueExport({ repoRoot, issue, comments }) {
|
|
|
4457
4673
|
|
|
4458
4674
|
// packages/server/src/nvim.ts
|
|
4459
4675
|
import os2 from "os";
|
|
4460
|
-
import
|
|
4676
|
+
import path6 from "path";
|
|
4461
4677
|
import { execFile as execFile2 } from "child_process";
|
|
4462
|
-
import { readdir as readdir2, stat as
|
|
4678
|
+
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
4463
4679
|
var QUERY_TIMEOUT_MS = 500;
|
|
4464
4680
|
var CACHE_TTL_MS = 1e4;
|
|
4465
4681
|
function execNvim(args, timeoutMs) {
|
|
@@ -4488,14 +4704,14 @@ function socketDirs(env = process.env) {
|
|
|
4488
4704
|
}
|
|
4489
4705
|
}
|
|
4490
4706
|
const tmp = env.TMPDIR || "/tmp";
|
|
4491
|
-
if (user) dirs.push(
|
|
4492
|
-
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));
|
|
4493
4709
|
return [...new Set(dirs)];
|
|
4494
4710
|
}
|
|
4495
4711
|
var SOCKET_NAME = /^nvim\.(\d+)\.\d+$/;
|
|
4496
4712
|
async function isSocket(p) {
|
|
4497
4713
|
try {
|
|
4498
|
-
return (await
|
|
4714
|
+
return (await stat4(p)).isSocket();
|
|
4499
4715
|
} catch {
|
|
4500
4716
|
return false;
|
|
4501
4717
|
}
|
|
@@ -4510,7 +4726,7 @@ async function findSockets(dirs = socketDirs()) {
|
|
|
4510
4726
|
return;
|
|
4511
4727
|
}
|
|
4512
4728
|
for (const ent of entries) {
|
|
4513
|
-
const full =
|
|
4729
|
+
const full = path6.join(dir, ent.name);
|
|
4514
4730
|
const m = SOCKET_NAME.exec(ent.name);
|
|
4515
4731
|
if (m && (ent.isSocket() || await isSocket(full))) {
|
|
4516
4732
|
found.set(full, Number(m[1]));
|
|
@@ -4597,9 +4813,9 @@ var TmuxService = class {
|
|
|
4597
4813
|
const sessions = [];
|
|
4598
4814
|
for (const line of output.trimEnd().split("\n")) {
|
|
4599
4815
|
const [id, name, ...parts] = line.split(" ");
|
|
4600
|
-
const
|
|
4601
|
-
if (!id || !/^\$\d+$/.test(id) || !name || !
|
|
4602
|
-
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 });
|
|
4603
4819
|
}
|
|
4604
4820
|
return sessions;
|
|
4605
4821
|
}
|
|
@@ -4613,8 +4829,8 @@ var TmuxService = class {
|
|
|
4613
4829
|
};
|
|
4614
4830
|
|
|
4615
4831
|
// packages/server/src/watcher.ts
|
|
4616
|
-
import
|
|
4617
|
-
import { stat as
|
|
4832
|
+
import path7 from "path";
|
|
4833
|
+
import { stat as stat5 } from "fs/promises";
|
|
4618
4834
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
4619
4835
|
function statusPaths(out) {
|
|
4620
4836
|
const parts = out.split("\0");
|
|
@@ -4710,7 +4926,7 @@ var RepoWatcher = class {
|
|
|
4710
4926
|
const stats = await Promise.all(
|
|
4711
4927
|
statusPaths(statusRes.stdout).map(async (rel) => {
|
|
4712
4928
|
try {
|
|
4713
|
-
const st = await
|
|
4929
|
+
const st = await stat5(path7.join(this.root, rel));
|
|
4714
4930
|
return `${rel} ${st.mtimeMs} ${st.size}`;
|
|
4715
4931
|
} catch {
|
|
4716
4932
|
return `${rel} -`;
|
|
@@ -4722,8 +4938,8 @@ var RepoWatcher = class {
|
|
|
4722
4938
|
};
|
|
4723
4939
|
|
|
4724
4940
|
// packages/server/src/static.ts
|
|
4725
|
-
import
|
|
4726
|
-
import { readFile as readFile3, stat as
|
|
4941
|
+
import path8 from "path";
|
|
4942
|
+
import { readFile as readFile3, stat as stat6 } from "fs/promises";
|
|
4727
4943
|
var MIME = {
|
|
4728
4944
|
".html": "text/html; charset=utf-8",
|
|
4729
4945
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -4740,20 +4956,20 @@ var MIME = {
|
|
|
4740
4956
|
".txt": "text/plain; charset=utf-8"
|
|
4741
4957
|
};
|
|
4742
4958
|
async function serveStaticFile(c, webDir) {
|
|
4743
|
-
const root =
|
|
4959
|
+
const root = path8.resolve(webDir);
|
|
4744
4960
|
let rel;
|
|
4745
4961
|
try {
|
|
4746
4962
|
rel = decodeURIComponent(c.req.path);
|
|
4747
4963
|
} catch {
|
|
4748
4964
|
return c.text("Bad Request", 400);
|
|
4749
4965
|
}
|
|
4750
|
-
let target =
|
|
4751
|
-
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);
|
|
4752
4968
|
try {
|
|
4753
|
-
const st = await
|
|
4754
|
-
if (st.isDirectory()) target =
|
|
4969
|
+
const st = await stat6(target);
|
|
4970
|
+
if (st.isDirectory()) target = path8.join(target, "index.html");
|
|
4755
4971
|
} catch {
|
|
4756
|
-
target =
|
|
4972
|
+
target = path8.join(root, "index.html");
|
|
4757
4973
|
}
|
|
4758
4974
|
let body;
|
|
4759
4975
|
try {
|
|
@@ -4761,7 +4977,7 @@ async function serveStaticFile(c, webDir) {
|
|
|
4761
4977
|
} catch {
|
|
4762
4978
|
return c.text("Not Found", 404);
|
|
4763
4979
|
}
|
|
4764
|
-
const ext =
|
|
4980
|
+
const ext = path8.extname(target).toLowerCase();
|
|
4765
4981
|
const isAsset = rel.startsWith("/assets/");
|
|
4766
4982
|
return new Response(new Uint8Array(body), {
|
|
4767
4983
|
status: 200,
|
|
@@ -4797,7 +5013,8 @@ function createApp(opts) {
|
|
|
4797
5013
|
worktreeCache = { at: Date.now(), list };
|
|
4798
5014
|
return list;
|
|
4799
5015
|
};
|
|
4800
|
-
const
|
|
5016
|
+
const checkpoints = checkpointStore(store.file);
|
|
5017
|
+
const worktreeCtx = async (key) => {
|
|
4801
5018
|
let list = await worktrees();
|
|
4802
5019
|
try {
|
|
4803
5020
|
return resolveTargetContext(repo, list, key);
|
|
@@ -4809,6 +5026,13 @@ function createApp(opts) {
|
|
|
4809
5026
|
throw e;
|
|
4810
5027
|
}
|
|
4811
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
|
+
};
|
|
4812
5036
|
const fileDiffWithHints = async (ctx, filePath, explicit) => {
|
|
4813
5037
|
const cached = listings.get(ctx.key);
|
|
4814
5038
|
const hint = cached && Date.now() - cached.at < LISTING_HINT_TTL_MS ? cached.files.find((f) => f.path === filePath) : void 0;
|
|
@@ -4845,7 +5069,7 @@ function createApp(opts) {
|
|
|
4845
5069
|
worktreeCache = { at: Date.now(), list: info.worktrees };
|
|
4846
5070
|
const last = state.prefs.lastTarget;
|
|
4847
5071
|
const target = last ? tryParseTargetKey(last) : void 0;
|
|
4848
|
-
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));
|
|
4849
5073
|
if (last && usable2) info.defaultTarget = last;
|
|
4850
5074
|
else if (last) {
|
|
4851
5075
|
await store.update((s) => {
|
|
@@ -4863,6 +5087,7 @@ function createApp(opts) {
|
|
|
4863
5087
|
if (typeof body.lastTarget === "string") s.prefs.lastTarget = body.lastTarget;
|
|
4864
5088
|
if (typeof body.autoRefresh === "boolean") s.prefs.autoRefresh = body.autoRefresh;
|
|
4865
5089
|
if (typeof body.railOpen === "boolean") s.prefs.railOpen = body.railOpen;
|
|
5090
|
+
if (typeof body.ignoreDebug === "boolean") s.prefs.ignoreDebug = body.ignoreDebug;
|
|
4866
5091
|
if (body.nvimSocketByRoot && typeof body.nvimSocketByRoot === "object") {
|
|
4867
5092
|
s.prefs.nvimSocketByRoot = { ...s.prefs.nvimSocketByRoot, ...body.nvimSocketByRoot };
|
|
4868
5093
|
}
|
|
@@ -4874,6 +5099,7 @@ function createApp(opts) {
|
|
|
4874
5099
|
const key = c.req.param("key");
|
|
4875
5100
|
const ctx = await targetCtx(key);
|
|
4876
5101
|
const diffs = await listTargetDiffs(ctx);
|
|
5102
|
+
await annotateDebug(ctx, diffs);
|
|
4877
5103
|
listings.set(key, { at: Date.now(), files: diffs });
|
|
4878
5104
|
const notices = changedNotices.get(key) ?? /* @__PURE__ */ new Set();
|
|
4879
5105
|
changedNotices.set(key, notices);
|
|
@@ -4914,6 +5140,7 @@ function createApp(opts) {
|
|
|
4914
5140
|
const untracked = c.req.query("untracked") === "1" ? true : void 0;
|
|
4915
5141
|
const diff = await fileDiffWithHints(ctx, filePath, { oldPath, untracked });
|
|
4916
5142
|
if (!diff) throw notFound(`no diff for ${filePath} in ${key}`, "no_diff");
|
|
5143
|
+
await annotateDebug(ctx, [diff]);
|
|
4917
5144
|
return c.json(diff);
|
|
4918
5145
|
});
|
|
4919
5146
|
api.get("/targets/:key/file/full", async (c) => {
|
|
@@ -4941,6 +5168,33 @@ function createApp(opts) {
|
|
|
4941
5168
|
changedNotices.get(key)?.delete(body.path);
|
|
4942
5169
|
return c.json({ viewed });
|
|
4943
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
|
+
});
|
|
4944
5198
|
api.post("/targets/:key/stage", async (c) => {
|
|
4945
5199
|
const key = c.req.param("key");
|
|
4946
5200
|
const ctx = await targetCtx(key);
|
|
@@ -4950,11 +5204,13 @@ function createApp(opts) {
|
|
|
4950
5204
|
if (!body.path || typeof body.path !== "string") throw badRequest("missing path");
|
|
4951
5205
|
if (!body.contentHash || typeof body.contentHash !== "string") throw badRequest("contentHash is required");
|
|
4952
5206
|
if (body.hunks !== void 0 && !Array.isArray(body.hunks)) throw badRequest("hunks must be an array", "bad_selection");
|
|
5207
|
+
if (body.skipDebug !== void 0 && typeof body.skipDebug !== "boolean") throw badRequest("skipDebug must be a boolean", "bad_selection");
|
|
4953
5208
|
const diff = await fileDiffWithHints(ctx, body.path);
|
|
4954
5209
|
if (!diff) throw badRequest(`file ${body.path} is not part of ${key}`, "no_diff");
|
|
4955
5210
|
if (diff.contentHash !== body.contentHash)
|
|
4956
5211
|
throw new HttpError(409, "the diff changed since it was loaded; refresh and pick the lines again", "diff_changed");
|
|
4957
|
-
|
|
5212
|
+
if (body.skipDebug) await annotateDebug(ctx, [diff]);
|
|
5213
|
+
const { patch, lines } = buildStagePatch(diff, mode, body.hunks, { skipDebug: body.skipDebug });
|
|
4958
5214
|
await applyToIndex(ctx.cwd, patch, { reverse: mode === "unstage" });
|
|
4959
5215
|
void watchers.get(ctx.cwd)?.poll();
|
|
4960
5216
|
const res = { ok: true, lines };
|
|
@@ -4980,7 +5236,7 @@ function createApp(opts) {
|
|
|
4980
5236
|
if (!anchored) throw badRequest("selection is not fully visible in the diff on that side (must be inside one hunk)", "bad_selection");
|
|
4981
5237
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4982
5238
|
const comment = {
|
|
4983
|
-
id:
|
|
5239
|
+
id: randomUUID2(),
|
|
4984
5240
|
targetKey: key,
|
|
4985
5241
|
filePath: body.filePath,
|
|
4986
5242
|
side: body.side,
|
|
@@ -5179,7 +5435,7 @@ function createApp(opts) {
|
|
|
5179
5435
|
if (!body.title?.trim()) throw badRequest("title is required");
|
|
5180
5436
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5181
5437
|
const issue = {
|
|
5182
|
-
id:
|
|
5438
|
+
id: randomUUID2(),
|
|
5183
5439
|
title: body.title.trim(),
|
|
5184
5440
|
body: typeof body.body === "string" ? body.body : "",
|
|
5185
5441
|
status: body.status === "closed" ? "closed" : "open",
|
|
@@ -5273,7 +5529,7 @@ function createApp(opts) {
|
|
|
5273
5529
|
const branch = body.branch?.trim() || await currentBranch(await knownRoot(body.root));
|
|
5274
5530
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5275
5531
|
const todo = {
|
|
5276
|
-
id:
|
|
5532
|
+
id: randomUUID2(),
|
|
5277
5533
|
branch,
|
|
5278
5534
|
title: body.title.trim(),
|
|
5279
5535
|
body: typeof body.body === "string" ? body.body : "",
|
|
@@ -5475,7 +5731,7 @@ function createApp(opts) {
|
|
|
5475
5731
|
api.post("/nvim/open", async (c) => {
|
|
5476
5732
|
const body = await c.req.json();
|
|
5477
5733
|
if (!body.socket || !body.absPath) throw badRequest("socket and absPath are required");
|
|
5478
|
-
if (!
|
|
5734
|
+
if (!path9.isAbsolute(body.absPath)) throw badRequest("absPath must be absolute");
|
|
5479
5735
|
const scan = await nvim.scan();
|
|
5480
5736
|
if (!scan.instances.some((i) => i.socket === body.socket)) {
|
|
5481
5737
|
const fresh = await nvim.scan(true);
|
|
@@ -5535,14 +5791,14 @@ function reachableFromWindows(port, token, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
|
5535
5791
|
}
|
|
5536
5792
|
|
|
5537
5793
|
// packages/server/src/update.ts
|
|
5538
|
-
import
|
|
5539
|
-
import { mkdir as
|
|
5794
|
+
import path10 from "path";
|
|
5795
|
+
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
|
|
5540
5796
|
var PACKAGE = "@xbghc/warden";
|
|
5541
5797
|
var LATEST_URL = "https://registry.npmjs.org/@xbghc%2fwarden/latest";
|
|
5542
5798
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
5543
5799
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
5544
5800
|
function updateCacheFile(baseDir = dataDir()) {
|
|
5545
|
-
return
|
|
5801
|
+
return path10.join(baseDir, "update-check.json");
|
|
5546
5802
|
}
|
|
5547
5803
|
function updateCheckEnabled(env = process.env) {
|
|
5548
5804
|
return !env.WARDEN_NO_UPDATE_CHECK && !env.NO_UPDATE_NOTIFIER && !env.CI;
|
|
@@ -5576,7 +5832,7 @@ async function readCache(file, now) {
|
|
|
5576
5832
|
}
|
|
5577
5833
|
async function writeCache(file, cache) {
|
|
5578
5834
|
try {
|
|
5579
|
-
await
|
|
5835
|
+
await mkdir3(path10.dirname(file), { recursive: true });
|
|
5580
5836
|
await writeFile2(file, JSON.stringify(cache));
|
|
5581
5837
|
} catch {
|
|
5582
5838
|
}
|
|
@@ -5621,7 +5877,7 @@ async function startServer(opts) {
|
|
|
5621
5877
|
const repo = await resolveRepo(opts.repoPath);
|
|
5622
5878
|
const stateFile = opts.stateFile ?? stateFilePath(repo.commonRoot);
|
|
5623
5879
|
const store = new StateStore(stateFile, repo.commonRoot);
|
|
5624
|
-
const instanceToken =
|
|
5880
|
+
const instanceToken = randomUUID3();
|
|
5625
5881
|
const app = createApp({ repo, store, nvim: new NvimService(), webDir: opts.webDir, instanceToken, update: opts.update });
|
|
5626
5882
|
const listen = (p) => new Promise((resolve, reject) => {
|
|
5627
5883
|
const s = serve({ fetch: app.fetch, hostname: HOST, port: p }, () => resolve(s));
|
|
@@ -5665,7 +5921,7 @@ async function startServer(opts) {
|
|
|
5665
5921
|
}
|
|
5666
5922
|
|
|
5667
5923
|
// bin/cli.ts
|
|
5668
|
-
var VERSION = true ? "0.
|
|
5924
|
+
var VERSION = true ? "0.15.0" : "dev";
|
|
5669
5925
|
function parseArgs(argv) {
|
|
5670
5926
|
const args = { repoPath: process.cwd(), open: true, updateCheck: true, help: false, version: false };
|
|
5671
5927
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -5684,7 +5940,7 @@ function parseArgs(argv) {
|
|
|
5684
5940
|
} else if (a === "--help" || a === "-h") args.help = true;
|
|
5685
5941
|
else if (a === "--version" || a === "-v") args.version = true;
|
|
5686
5942
|
else if (a.startsWith("-")) throw new Error(`unknown option: ${a}`);
|
|
5687
|
-
else args.repoPath =
|
|
5943
|
+
else args.repoPath = path11.resolve(a);
|
|
5688
5944
|
}
|
|
5689
5945
|
return args;
|
|
5690
5946
|
}
|
|
@@ -5729,16 +5985,16 @@ async function openBrowser(url) {
|
|
|
5729
5985
|
return false;
|
|
5730
5986
|
}
|
|
5731
5987
|
function resolveWebDir() {
|
|
5732
|
-
const here =
|
|
5988
|
+
const here = path11.dirname(fileURLToPath(import.meta.url));
|
|
5733
5989
|
const candidates = [
|
|
5734
5990
|
process.env.WARDEN_WEB_DIR,
|
|
5735
|
-
|
|
5991
|
+
path11.join(here, "web"),
|
|
5736
5992
|
// dist/cli.js -> dist/web
|
|
5737
|
-
|
|
5993
|
+
path11.join(here, "..", "dist", "web")
|
|
5738
5994
|
// bin/cli.ts (tsx dev) -> dist/web
|
|
5739
5995
|
].filter((p) => !!p);
|
|
5740
5996
|
for (const dir of candidates) {
|
|
5741
|
-
if (existsSync2(
|
|
5997
|
+
if (existsSync2(path11.join(dir, "index.html"))) return dir;
|
|
5742
5998
|
}
|
|
5743
5999
|
return void 0;
|
|
5744
6000
|
}
|
|
@@ -5763,7 +6019,7 @@ async function main() {
|
|
|
5763
6019
|
if (!webDir) {
|
|
5764
6020
|
console.error("warning: built web assets not found (dist/web). Only the /api endpoints will be served.");
|
|
5765
6021
|
}
|
|
5766
|
-
const viaNpx = fileURLToPath(import.meta.url).includes(`${
|
|
6022
|
+
const viaNpx = fileURLToPath(import.meta.url).includes(`${path11.sep}_npx${path11.sep}`);
|
|
5767
6023
|
const checking = args.updateCheck && VERSION !== "dev" && updateCheckEnabled();
|
|
5768
6024
|
const update = checking ? checkForUpdate({ current: VERSION, npx: viaNpx }) : Promise.resolve(null);
|
|
5769
6025
|
let server;
|