@unbrained/pm-web 2026.8.17 → 2026.8.29
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/CHANGELOG.md +28 -0
- package/README.md +34 -4
- package/dist/app.d.ts +25 -1
- package/dist/app.js +69 -9
- package/dist/app.js.map +1 -1
- package/dist/csrf.d.ts +48 -0
- package/dist/csrf.js +124 -0
- package/dist/csrf.js.map +1 -0
- package/dist/health.d.ts +84 -0
- package/dist/health.js +284 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +25 -3
- package/dist/index.js +62 -42
- package/dist/index.js.map +1 -1
- package/dist/rate-limit.d.ts +149 -0
- package/dist/rate-limit.js +194 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/routes/auth.d.ts +16 -0
- package/dist/routes/auth.js +37 -1
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/github.d.ts +19 -0
- package/dist/routes/github.js +49 -4
- package/dist/routes/github.js.map +1 -1
- package/dist/routes/pm.js +28 -38
- package/dist/routes/pm.js.map +1 -1
- package/dist/server.js +33 -2
- package/dist/server.js.map +1 -1
- package/dist/services/mutation-event-watcher.js +2 -1
- package/dist/services/mutation-event-watcher.js.map +1 -1
- package/dist/services/package-catalog.js +2 -2
- package/dist/services/package-catalog.js.map +1 -1
- package/dist/services/pm-runner.d.ts +51 -1
- package/dist/services/pm-runner.js +105 -1
- package/dist/services/pm-runner.js.map +1 -1
- package/manifest.json +3 -5
- package/package.json +24 -19
package/dist/routes/pm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
2
|
import { requireAuth } from "../middleware/auth.js";
|
|
3
|
-
import { ensureGraphExtension, runPm, runGetItemAt, readPmSettings, PmCliError, EXIT_CODE } from "../services/pm-runner.js";
|
|
3
|
+
import { ensureGraphExtension, readCompletePmItems, runPm, runGetItemAt, readPmSettings, PmCliError, EXIT_CODE } from "../services/pm-runner.js";
|
|
4
4
|
// The search-tuning resolvers live only on the narrow sdk/query entrypoint — the
|
|
5
5
|
// aggregate sdk barrel documents itself as re-exporting every supported export but
|
|
6
6
|
// omits 45 of them, these three included (upstream: unbraind/pm-cli#740).
|
|
@@ -392,7 +392,10 @@ function scheduleGraphSync(projectId, project, reason) {
|
|
|
392
392
|
});
|
|
393
393
|
})
|
|
394
394
|
.catch((err) => {
|
|
395
|
-
|
|
395
|
+
// Use `%s` specifiers and pass the values as arguments instead of
|
|
396
|
+
// interpolating them into the format string, so a tainted `reason` or
|
|
397
|
+
// `projectId` cannot inject `console` format specifiers.
|
|
398
|
+
console.error("Neo4j graph auto-sync failed for %s after %s:", projectId, reason, err);
|
|
396
399
|
broadcastProjectEvent(projectId, {
|
|
397
400
|
type: "graph-sync-failed",
|
|
398
401
|
data: { reason, error: err instanceof Error ? err.message : String(err) },
|
|
@@ -428,31 +431,26 @@ function broadcastDependencyEvent(projectId, kind, data) {
|
|
|
428
431
|
},
|
|
429
432
|
});
|
|
430
433
|
}
|
|
431
|
-
function
|
|
434
|
+
function itemsFromCompleteList(parsed) {
|
|
432
435
|
return ((parsed?.items) ?? []);
|
|
433
436
|
}
|
|
434
437
|
/**
|
|
435
|
-
* Build a project graph from
|
|
438
|
+
* Build a project graph from a certified complete pm read when no graph extension is available.
|
|
436
439
|
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
* avoiding an N+1 fan-out. Throws when the
|
|
440
|
+
* Reads every item through the public SDK's high-level complete-list operation
|
|
441
|
+
* and assembles the graph from embedded `deps`/`dependencies` (no per-item
|
|
442
|
+
* calls), avoiding an N+1 fan-out. Throws when the corpus cannot be certified.
|
|
440
443
|
*
|
|
441
444
|
* @param ownerUserId - The project owner's user id.
|
|
442
445
|
* @param slug - The project slug.
|
|
443
446
|
* @returns A pm-web-sourced project graph.
|
|
444
447
|
*/
|
|
445
448
|
async function fallbackGraphForProject(ownerUserId, slug) {
|
|
446
|
-
const itemsResult = await
|
|
447
|
-
args: ["list-all"],
|
|
448
|
-
userId: ownerUserId,
|
|
449
|
-
slug,
|
|
450
|
-
jsonOutput: true,
|
|
451
|
-
});
|
|
449
|
+
const itemsResult = await readCompletePmItems(ownerUserId, slug);
|
|
452
450
|
if (!itemsResult.ok)
|
|
453
451
|
throw new Error(itemsResult.stderr || "Failed to load items for graph");
|
|
454
|
-
const items =
|
|
455
|
-
// Deps are already embedded in
|
|
452
|
+
const items = itemsFromCompleteList(itemsResult.result);
|
|
453
|
+
// Deps are already embedded in the full item rows (item.deps / item.dependencies).
|
|
456
454
|
// Avoid N+1 subprocess calls by using only the embedded data.
|
|
457
455
|
return graphFromItems(items, new Map());
|
|
458
456
|
}
|
|
@@ -694,7 +692,10 @@ router.get("/list-all", async (req, res) => {
|
|
|
694
692
|
return;
|
|
695
693
|
}
|
|
696
694
|
const { type, limit, after } = req.query;
|
|
697
|
-
|
|
695
|
+
// Preserve the public HTTP compatibility route while invoking the canonical
|
|
696
|
+
// CLI/SDK command internally. This route is intentionally paginated and is
|
|
697
|
+
// therefore distinct from readCompletePmItems used by whole-corpus views.
|
|
698
|
+
const args = ["list", "--all"];
|
|
698
699
|
if (type)
|
|
699
700
|
args.push("--type", type);
|
|
700
701
|
if (limit)
|
|
@@ -727,16 +728,16 @@ router.get("/board", async (req, res) => {
|
|
|
727
728
|
? contracts.parsed["runtime_schema"]
|
|
728
729
|
: undefined;
|
|
729
730
|
const statuses = Array.isArray(rt?.["statuses"]) ? rt["statuses"] : [];
|
|
730
|
-
const listed = await
|
|
731
|
+
const listed = await readCompletePmItems(project.ownerUserId, project.slug);
|
|
731
732
|
if (!listed.ok) {
|
|
732
733
|
res.json({ error: listed.stderr, columns: [] });
|
|
733
734
|
return;
|
|
734
735
|
}
|
|
735
|
-
const items =
|
|
736
|
+
const items = itemsFromCompleteList(listed.result);
|
|
736
737
|
res.json({ columns: boardColumns(items, statuses), statuses, count: items.length });
|
|
737
738
|
});
|
|
738
739
|
// GET /api/projects/:projectId/pm/search?q=...
|
|
739
|
-
// Full-text search over id/title/tags/body via
|
|
740
|
+
// Full-text search over id/title/tags/body via one certified complete read.
|
|
740
741
|
router.get("/search", async (req, res) => {
|
|
741
742
|
const project = await verifyProject(req.user.userId, routeParam(req, "projectId"));
|
|
742
743
|
if (!project) {
|
|
@@ -744,12 +745,12 @@ router.get("/search", async (req, res) => {
|
|
|
744
745
|
return;
|
|
745
746
|
}
|
|
746
747
|
const query = String(req.query["q"] ?? "");
|
|
747
|
-
const listed = await
|
|
748
|
+
const listed = await readCompletePmItems(project.ownerUserId, project.slug, true);
|
|
748
749
|
if (!listed.ok) {
|
|
749
750
|
res.json({ error: listed.stderr, items: [] });
|
|
750
751
|
return;
|
|
751
752
|
}
|
|
752
|
-
const items = filterItemsByQuery(
|
|
753
|
+
const items = filterItemsByQuery(itemsFromCompleteList(listed.result), query);
|
|
753
754
|
res.json({ query, items, count: items.length });
|
|
754
755
|
});
|
|
755
756
|
// POST /api/projects/:projectId/pm/create
|
|
@@ -1215,8 +1216,8 @@ router.get("/calendar", async (req, res) => {
|
|
|
1215
1216
|
});
|
|
1216
1217
|
// GET /api/projects/:projectId/pm/calendar.ics
|
|
1217
1218
|
// RFC 5545 iCalendar feed of item deadlines, for subscribing in Google
|
|
1218
|
-
// Calendar / Outlook / Apple Calendar.
|
|
1219
|
-
//
|
|
1219
|
+
// Calendar / Outlook / Apple Calendar. Reads one certified complete corpus.
|
|
1220
|
+
// Auth works via the usual token (header/cookie) or a
|
|
1220
1221
|
// `?token=` query param, since calendar clients cannot send cookies.
|
|
1221
1222
|
router.get("/calendar.ics", async (req, res) => {
|
|
1222
1223
|
const project = await verifyProject(req.user.userId, routeParam(req, "projectId"));
|
|
@@ -1224,17 +1225,12 @@ router.get("/calendar.ics", async (req, res) => {
|
|
|
1224
1225
|
res.status(404).json({ error: "Project not found" });
|
|
1225
1226
|
return;
|
|
1226
1227
|
}
|
|
1227
|
-
const listed = await
|
|
1228
|
-
args: ["list-all", "--json"],
|
|
1229
|
-
userId: project.ownerUserId,
|
|
1230
|
-
slug: project.slug,
|
|
1231
|
-
jsonOutput: true,
|
|
1232
|
-
});
|
|
1228
|
+
const listed = await readCompletePmItems(project.ownerUserId, project.slug);
|
|
1233
1229
|
if (!listed.ok) {
|
|
1234
1230
|
res.status(502).json({ error: listed.stderr || "Failed to load items" });
|
|
1235
1231
|
return;
|
|
1236
1232
|
}
|
|
1237
|
-
const items =
|
|
1233
|
+
const items = itemsFromCompleteList(listed.result)
|
|
1238
1234
|
.filter((i) => Boolean(i.deadline))
|
|
1239
1235
|
.map((i) => ({
|
|
1240
1236
|
id: i.id,
|
|
@@ -1963,18 +1959,12 @@ router.get("/export", async (req, res) => {
|
|
|
1963
1959
|
return;
|
|
1964
1960
|
}
|
|
1965
1961
|
const format = req.query["format"] || "json";
|
|
1966
|
-
|
|
1967
|
-
const result = await runPm({
|
|
1968
|
-
args: ["list-all", "--limit", "10000", "--full", "--include-body"],
|
|
1969
|
-
userId: project.ownerUserId,
|
|
1970
|
-
slug: project.slug,
|
|
1971
|
-
jsonOutput: true,
|
|
1972
|
-
});
|
|
1962
|
+
const result = await readCompletePmItems(project.ownerUserId, project.slug, true);
|
|
1973
1963
|
if (!result.ok) {
|
|
1974
1964
|
res.status(500).json({ error: result.stderr || "Export failed" });
|
|
1975
1965
|
return;
|
|
1976
1966
|
}
|
|
1977
|
-
const data = result.
|
|
1967
|
+
const data = result.result;
|
|
1978
1968
|
const exportedAt = new Date().toISOString();
|
|
1979
1969
|
if (format === "csv") {
|
|
1980
1970
|
const items = data?.items ?? [];
|