@the-portland-company/devnotes 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -6
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/{router-Bt8qbFfY.d.ts → router-3PUkM5T-.d.ts} +2 -1
- package/dist/{router-CPtxoMg5.d.mts → router-DPFI-Ag4.d.mts} +2 -1
- package/dist/server/deno.d.mts +5 -0
- package/dist/server/deno.d.ts +5 -0
- package/dist/server/deno.js +1187 -0
- package/dist/server/deno.mjs +1160 -0
- package/dist/server/express.d.mts +6 -4
- package/dist/server/express.d.ts +6 -4
- package/dist/server/express.js +1191 -1
- package/dist/server/express.mjs +1190 -1
- package/dist/server/next.d.mts +6 -4
- package/dist/server/next.d.ts +6 -4
- package/dist/server/next.js +1171 -1
- package/dist/server/next.mjs +1170 -1
- package/dist/{types-xqGNcAbZ.d.mts → types-CBHExs2F.d.mts} +3 -21
- package/dist/types-CrmObeqp.d.mts +58 -0
- package/dist/types-CrmObeqp.d.ts +58 -0
- package/dist/{types-xqGNcAbZ.d.ts → types-DqIxvo5g.d.ts} +3 -21
- package/package.json +10 -4
- package/templates/ExpressDevNotesProxy.ts +16 -67
- package/templates/NextDevNotesRoute.ts +18 -67
|
@@ -0,0 +1,1187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server/deno.ts
|
|
21
|
+
var deno_exports = {};
|
|
22
|
+
__export(deno_exports, {
|
|
23
|
+
createDenoDevNotesHandler: () => createDenoDevNotesHandler
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(deno_exports);
|
|
26
|
+
|
|
27
|
+
// src/server/forge.ts
|
|
28
|
+
var DEFAULT_BASE_PATH = "/api/devnotes";
|
|
29
|
+
var DEVNOTES_META_MARKER = "[DEVNOTES_META:";
|
|
30
|
+
var DEVNOTES_DEFAULT_TYPE_NAMES = ["Bug", "Feature Request", "UI Issue", "Performance"];
|
|
31
|
+
var DEVNOTES_DEFAULT_TASK_LIST_NAME = "General";
|
|
32
|
+
var UpstreamForgeError = class extends Error {
|
|
33
|
+
constructor(path, baseUrl, response) {
|
|
34
|
+
super(`Focus Forge request failed for ${path}`);
|
|
35
|
+
this.name = "UpstreamForgeError";
|
|
36
|
+
this.path = path;
|
|
37
|
+
this.baseUrl = baseUrl;
|
|
38
|
+
this.response = response;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function coerceObject(value) {
|
|
42
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
function parseJsonSafe(text) {
|
|
48
|
+
if (!text) return null;
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(text);
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function bytesToBase64(bytes) {
|
|
56
|
+
if (typeof Buffer !== "undefined") {
|
|
57
|
+
return Buffer.from(bytes).toString("base64");
|
|
58
|
+
}
|
|
59
|
+
let binary = "";
|
|
60
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
61
|
+
binary += String.fromCharCode(bytes[i]);
|
|
62
|
+
}
|
|
63
|
+
return btoa(binary);
|
|
64
|
+
}
|
|
65
|
+
function base64ToBytes(value) {
|
|
66
|
+
if (typeof Buffer !== "undefined") {
|
|
67
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
68
|
+
}
|
|
69
|
+
const binary = atob(value);
|
|
70
|
+
const bytes = new Uint8Array(binary.length);
|
|
71
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
72
|
+
bytes[i] = binary.charCodeAt(i);
|
|
73
|
+
}
|
|
74
|
+
return bytes;
|
|
75
|
+
}
|
|
76
|
+
function normalizeForgeBoolean(value, fallback = false) {
|
|
77
|
+
if (typeof value === "boolean") return value;
|
|
78
|
+
if (typeof value === "string") {
|
|
79
|
+
const normalized = value.trim().toLowerCase();
|
|
80
|
+
if (normalized === "true") return true;
|
|
81
|
+
if (normalized === "false") return false;
|
|
82
|
+
}
|
|
83
|
+
return fallback;
|
|
84
|
+
}
|
|
85
|
+
function normalizeForgeNumber(value, fallback = 0) {
|
|
86
|
+
const parsed = Number(value);
|
|
87
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
88
|
+
}
|
|
89
|
+
function normalizeForgeStringArray(value) {
|
|
90
|
+
if (!Array.isArray(value)) return [];
|
|
91
|
+
return value.map((item) => String(item || "").trim()).filter(Boolean);
|
|
92
|
+
}
|
|
93
|
+
function mapBugStatusToForge(status) {
|
|
94
|
+
const normalized = status.trim().toLowerCase();
|
|
95
|
+
if (normalized === "in progress") return "in_progress";
|
|
96
|
+
if (normalized === "resolved" || normalized === "closed") return "completed";
|
|
97
|
+
return "open";
|
|
98
|
+
}
|
|
99
|
+
function encodeDevNotesMeta(meta) {
|
|
100
|
+
return bytesToBase64(new TextEncoder().encode(JSON.stringify(meta)));
|
|
101
|
+
}
|
|
102
|
+
function decodeDevNotesMeta(encoded) {
|
|
103
|
+
try {
|
|
104
|
+
const text = new TextDecoder().decode(base64ToBytes(encoded));
|
|
105
|
+
const parsed = JSON.parse(text);
|
|
106
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function splitDevNotesMeta(text) {
|
|
112
|
+
const value = String(text || "");
|
|
113
|
+
const markerIndex = value.lastIndexOf(DEVNOTES_META_MARKER);
|
|
114
|
+
if (markerIndex === -1) {
|
|
115
|
+
return { body: value, meta: null };
|
|
116
|
+
}
|
|
117
|
+
const endIndex = value.indexOf("]", markerIndex);
|
|
118
|
+
if (endIndex === -1) {
|
|
119
|
+
return { body: value, meta: null };
|
|
120
|
+
}
|
|
121
|
+
const encoded = value.slice(markerIndex + DEVNOTES_META_MARKER.length, endIndex).trim();
|
|
122
|
+
const meta = decodeDevNotesMeta(encoded);
|
|
123
|
+
if (!meta) {
|
|
124
|
+
return { body: value, meta: null };
|
|
125
|
+
}
|
|
126
|
+
const body = value.slice(0, markerIndex).replace(/\n+$/, "");
|
|
127
|
+
return { body, meta };
|
|
128
|
+
}
|
|
129
|
+
function appendDevNotesMeta(text, meta) {
|
|
130
|
+
const body = String(text || "").trimEnd();
|
|
131
|
+
const marker = `${DEVNOTES_META_MARKER}${encodeDevNotesMeta(meta)}]`;
|
|
132
|
+
return body ? `${body}
|
|
133
|
+
|
|
134
|
+
${marker}` : marker;
|
|
135
|
+
}
|
|
136
|
+
function parseLegacyDevNotesDescription(description) {
|
|
137
|
+
const legacyMarker = "\n\n---\nSource: Politogy bug report";
|
|
138
|
+
const index = description.indexOf(legacyMarker);
|
|
139
|
+
if (index === -1) {
|
|
140
|
+
return {
|
|
141
|
+
description: description.trim() || null
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const leading = description.slice(0, index).trim() || null;
|
|
145
|
+
const detailLines = description.slice(index + "\n\n---\n".length).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
146
|
+
const values = /* @__PURE__ */ new Map();
|
|
147
|
+
detailLines.forEach((line) => {
|
|
148
|
+
const separator = line.indexOf(":");
|
|
149
|
+
if (separator === -1) return;
|
|
150
|
+
const key = line.slice(0, separator).trim().toLowerCase();
|
|
151
|
+
const value = line.slice(separator + 1).trim();
|
|
152
|
+
if (key) values.set(key, value);
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
description: leading,
|
|
156
|
+
page_url: values.get("page url") || "",
|
|
157
|
+
x_position: normalizeForgeNumber(values.get("coordinates")?.split(",")[0]?.trim(), 0),
|
|
158
|
+
y_position: normalizeForgeNumber(values.get("coordinates")?.split(",")[1]?.trim(), 0),
|
|
159
|
+
severity: values.get("severity") || "Medium",
|
|
160
|
+
status: values.get("status") || "Open",
|
|
161
|
+
types: values.get("types")?.split(",").map((item) => item.trim()).filter(Boolean) || [],
|
|
162
|
+
creator_name: values.get("created by") || ""
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function buildDevNotesReportDescription(report) {
|
|
166
|
+
const description = typeof report.description === "string" ? report.description : "";
|
|
167
|
+
return appendDevNotesMeta(description, {
|
|
168
|
+
kind: "report",
|
|
169
|
+
version: 1,
|
|
170
|
+
task_list_id: String(report.task_list_id || ""),
|
|
171
|
+
page_url: String(report.page_url || ""),
|
|
172
|
+
x_position: normalizeForgeNumber(report.x_position),
|
|
173
|
+
y_position: normalizeForgeNumber(report.y_position),
|
|
174
|
+
target_selector: report.target_selector === null || report.target_selector === void 0 ? null : String(report.target_selector),
|
|
175
|
+
target_relative_x: report.target_relative_x === null || report.target_relative_x === void 0 ? null : normalizeForgeNumber(report.target_relative_x),
|
|
176
|
+
target_relative_y: report.target_relative_y === null || report.target_relative_y === void 0 ? null : normalizeForgeNumber(report.target_relative_y),
|
|
177
|
+
types: normalizeForgeStringArray(report.types),
|
|
178
|
+
severity: String(report.severity || "Medium"),
|
|
179
|
+
expected_behavior: report.expected_behavior === null || report.expected_behavior === void 0 ? null : String(report.expected_behavior),
|
|
180
|
+
actual_behavior: report.actual_behavior === null || report.actual_behavior === void 0 ? null : String(report.actual_behavior),
|
|
181
|
+
capture_context: report.capture_context && typeof report.capture_context === "object" ? report.capture_context : null,
|
|
182
|
+
status: String(report.status || "Open"),
|
|
183
|
+
created_by: String(report.created_by || ""),
|
|
184
|
+
creator_name: report.creator && typeof report.creator === "object" ? String(report.creator.full_name || "") : "",
|
|
185
|
+
creator_email: report.creator && typeof report.creator === "object" ? String(report.creator.email || "") : "",
|
|
186
|
+
assigned_to: report.assigned_to === null || report.assigned_to === void 0 ? null : String(report.assigned_to),
|
|
187
|
+
resolved_at: report.resolved_at === null || report.resolved_at === void 0 ? null : String(report.resolved_at),
|
|
188
|
+
resolved_by: report.resolved_by === null || report.resolved_by === void 0 ? null : String(report.resolved_by),
|
|
189
|
+
approved: normalizeForgeBoolean(report.approved),
|
|
190
|
+
ai_ready: normalizeForgeBoolean(report.ai_ready),
|
|
191
|
+
ai_description: report.ai_description === null || report.ai_description === void 0 ? null : String(report.ai_description),
|
|
192
|
+
response: report.response === null || report.response === void 0 ? null : String(report.response)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function isDevNotesForgeTask(task) {
|
|
196
|
+
const description = String(task.description || "");
|
|
197
|
+
const parsed = splitDevNotesMeta(description);
|
|
198
|
+
if (parsed.meta?.kind === "report") return true;
|
|
199
|
+
return description.includes("Source: Politogy bug report");
|
|
200
|
+
}
|
|
201
|
+
function extractProjectsFromPayload(payload) {
|
|
202
|
+
const root = coerceObject(payload);
|
|
203
|
+
const data = coerceObject(root.data);
|
|
204
|
+
const bootstrap = coerceObject(data.bootstrap);
|
|
205
|
+
const projectArrays = [
|
|
206
|
+
Array.isArray(root.projects) ? root.projects : [],
|
|
207
|
+
Array.isArray(data.projects) ? data.projects : [],
|
|
208
|
+
Array.isArray(bootstrap.projects) ? bootstrap.projects : []
|
|
209
|
+
];
|
|
210
|
+
const seen = /* @__PURE__ */ new Set();
|
|
211
|
+
const projects = [];
|
|
212
|
+
for (const group of projectArrays) {
|
|
213
|
+
for (const projectRaw of group) {
|
|
214
|
+
const project = coerceObject(projectRaw);
|
|
215
|
+
const id = typeof project.id === "string" ? project.id.trim() : "";
|
|
216
|
+
const name = typeof project.name === "string" ? project.name.trim() : "";
|
|
217
|
+
if (!id || !name || seen.has(id)) continue;
|
|
218
|
+
seen.add(id);
|
|
219
|
+
const organizationId = typeof project.organization_id === "string" && project.organization_id.trim() || typeof project.organizationId === "string" && project.organizationId.trim() || void 0;
|
|
220
|
+
projects.push({ id, name, organizationId });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return projects;
|
|
224
|
+
}
|
|
225
|
+
function extractForgeTaskId(payload) {
|
|
226
|
+
const root = coerceObject(payload);
|
|
227
|
+
const data = coerceObject(root.data);
|
|
228
|
+
const task = coerceObject(root.task);
|
|
229
|
+
const dataTask = coerceObject(data.task);
|
|
230
|
+
const candidates = [root.id, task.id, data.id, dataTask.id];
|
|
231
|
+
const invalidSentinelValues = /* @__PURE__ */ new Set(["NOT_FOUND", "not_found", "null", "undefined", ""]);
|
|
232
|
+
for (const candidate of candidates) {
|
|
233
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
234
|
+
const value = candidate.trim();
|
|
235
|
+
if (invalidSentinelValues.has(value)) continue;
|
|
236
|
+
if (value.toLowerCase().startsWith("error-")) continue;
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
function generateDevNotesShareSlug() {
|
|
243
|
+
return crypto.randomUUID().replace(/-/g, "").slice(0, 24);
|
|
244
|
+
}
|
|
245
|
+
function normalizeBasePath(basePath) {
|
|
246
|
+
const trimmed = (basePath || DEFAULT_BASE_PATH).trim();
|
|
247
|
+
if (!trimmed) return DEFAULT_BASE_PATH;
|
|
248
|
+
const normalized = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
249
|
+
return normalized.endsWith("/") && normalized !== "/" ? normalized.slice(0, -1) : normalized;
|
|
250
|
+
}
|
|
251
|
+
function normalizeBaseUrl(baseUrl) {
|
|
252
|
+
return baseUrl.trim().replace(/\/+$/, "");
|
|
253
|
+
}
|
|
254
|
+
function normalizeUser(user) {
|
|
255
|
+
if (!user?.id) return null;
|
|
256
|
+
return {
|
|
257
|
+
id: String(user.id).trim(),
|
|
258
|
+
email: user.email == null ? null : String(user.email).trim(),
|
|
259
|
+
fullName: user.fullName == null ? null : String(user.fullName).trim(),
|
|
260
|
+
role: user.role == null ? null : String(user.role).trim()
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function toCreatorRecord(user) {
|
|
264
|
+
return {
|
|
265
|
+
id: user.id,
|
|
266
|
+
email: user.email == null ? null : user.email,
|
|
267
|
+
full_name: user.fullName == null ? null : user.fullName
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async function resolveCorsHeaders(request, corsHeaders) {
|
|
271
|
+
const headers = new Headers();
|
|
272
|
+
if (!corsHeaders) return headers;
|
|
273
|
+
const resolved = typeof corsHeaders === "function" ? await corsHeaders(request) : corsHeaders;
|
|
274
|
+
new Headers(resolved).forEach((value, key) => headers.set(key, value));
|
|
275
|
+
return headers;
|
|
276
|
+
}
|
|
277
|
+
async function jsonResponse(request, corsHeaders, body, status = 200) {
|
|
278
|
+
const headers = await resolveCorsHeaders(request, corsHeaders);
|
|
279
|
+
headers.set("Content-Type", "application/json");
|
|
280
|
+
return new Response(JSON.stringify(body), { status, headers });
|
|
281
|
+
}
|
|
282
|
+
async function emptyResponse(request, corsHeaders, status = 204) {
|
|
283
|
+
const headers = await resolveCorsHeaders(request, corsHeaders);
|
|
284
|
+
return new Response(null, { status, headers });
|
|
285
|
+
}
|
|
286
|
+
async function passthroughUpstreamResponse(request, corsHeaders, error) {
|
|
287
|
+
const headers = await resolveCorsHeaders(request, corsHeaders);
|
|
288
|
+
if (error.response.contentType) {
|
|
289
|
+
headers.set("Content-Type", error.response.contentType);
|
|
290
|
+
}
|
|
291
|
+
if (error.response.text) {
|
|
292
|
+
headers.set("X-DevNotes-Upstream-Path", error.path);
|
|
293
|
+
headers.set("X-DevNotes-Upstream-Base-Url", error.baseUrl);
|
|
294
|
+
return new Response(error.response.text, {
|
|
295
|
+
status: error.response.status,
|
|
296
|
+
headers
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
headers.set("Content-Type", "application/json");
|
|
300
|
+
return new Response(
|
|
301
|
+
JSON.stringify({
|
|
302
|
+
error: "Focus Forge request failed",
|
|
303
|
+
path: error.path,
|
|
304
|
+
baseUrl: error.baseUrl,
|
|
305
|
+
status: error.response.status
|
|
306
|
+
}),
|
|
307
|
+
{
|
|
308
|
+
status: error.response.status,
|
|
309
|
+
headers
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
async function readJsonBody(request) {
|
|
314
|
+
if (request.method === "GET" || request.method === "HEAD") return null;
|
|
315
|
+
const text = await request.text();
|
|
316
|
+
if (!text) return null;
|
|
317
|
+
try {
|
|
318
|
+
return JSON.parse(text);
|
|
319
|
+
} catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function parseRequestPath(pathname, basePath) {
|
|
324
|
+
if (pathname === basePath) return [];
|
|
325
|
+
if (!pathname.startsWith(`${basePath}/`)) return null;
|
|
326
|
+
return pathname.slice(basePath.length + 1).split("/").filter(Boolean).map((segment) => decodeURIComponent(segment));
|
|
327
|
+
}
|
|
328
|
+
function toProjectDiscovery(discovery) {
|
|
329
|
+
return {
|
|
330
|
+
path: discovery.discoveryPath,
|
|
331
|
+
baseUrl: discovery.resolvedBaseUrl
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function buildKnownUsers(metadataComments, reports, currentUser) {
|
|
335
|
+
const users = /* @__PURE__ */ new Map();
|
|
336
|
+
users.set(currentUser.id, {
|
|
337
|
+
id: currentUser.id,
|
|
338
|
+
email: currentUser.email ?? null,
|
|
339
|
+
full_name: currentUser.fullName ?? null
|
|
340
|
+
});
|
|
341
|
+
reports.forEach((report) => {
|
|
342
|
+
users.set(report.created_by, report.creator || {
|
|
343
|
+
id: report.created_by,
|
|
344
|
+
email: null,
|
|
345
|
+
full_name: null
|
|
346
|
+
});
|
|
347
|
+
if (report.assigned_to && !users.has(report.assigned_to)) {
|
|
348
|
+
users.set(report.assigned_to, { id: report.assigned_to, email: null, full_name: null });
|
|
349
|
+
}
|
|
350
|
+
if (report.resolved_by && !users.has(report.resolved_by)) {
|
|
351
|
+
users.set(report.resolved_by, { id: report.resolved_by, email: null, full_name: null });
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
metadataComments.forEach((comment) => {
|
|
355
|
+
if (comment.meta.kind !== "message") return;
|
|
356
|
+
const authorId = String(comment.meta.authorId || comment.user_id || "").trim();
|
|
357
|
+
if (!authorId) return;
|
|
358
|
+
users.set(authorId, {
|
|
359
|
+
id: authorId,
|
|
360
|
+
email: comment.meta.authorEmail === null || comment.meta.authorEmail === void 0 ? null : String(comment.meta.authorEmail),
|
|
361
|
+
full_name: comment.meta.authorName === null || comment.meta.authorName === void 0 ? null : String(comment.meta.authorName)
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
return users;
|
|
365
|
+
}
|
|
366
|
+
async function maybeResolveUsers(resolveUsers, ids) {
|
|
367
|
+
if (!resolveUsers || ids.length === 0) return [];
|
|
368
|
+
const resolved = await resolveUsers(ids);
|
|
369
|
+
return resolved.filter((user) => Boolean(user?.id)).map((user) => toCreatorRecord({
|
|
370
|
+
id: String(user.id).trim(),
|
|
371
|
+
email: user.email ?? null,
|
|
372
|
+
fullName: user.fullName ?? null
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
function pickTaskArray(payload) {
|
|
376
|
+
const data = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
|
|
377
|
+
return data.map((item) => coerceObject(item));
|
|
378
|
+
}
|
|
379
|
+
function parseDevNotesProjectComment(comment) {
|
|
380
|
+
const id = typeof comment.id === "string" ? comment.id.trim() : "";
|
|
381
|
+
if (!id) return null;
|
|
382
|
+
const parsed = splitDevNotesMeta(String(comment.content || ""));
|
|
383
|
+
const kind = typeof parsed.meta?.kind === "string" ? parsed.meta.kind : "";
|
|
384
|
+
if (!kind) return null;
|
|
385
|
+
return {
|
|
386
|
+
id,
|
|
387
|
+
created_at: String(comment.created_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
388
|
+
updated_at: String(comment.updated_at || comment.created_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
389
|
+
user_id: comment.user_id === null || comment.user_id === void 0 ? null : String(comment.user_id),
|
|
390
|
+
author_name: comment.author_name === null || comment.author_name === void 0 ? null : String(comment.author_name),
|
|
391
|
+
author_email: comment.author_email === null || comment.author_email === void 0 ? null : String(comment.author_email),
|
|
392
|
+
body: parsed.body,
|
|
393
|
+
meta: parsed.meta || {}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function buildTaskListsFromMetadata(comments) {
|
|
397
|
+
return comments.filter((item) => item.meta.kind === "task_list").sort((a, b) => a.created_at.localeCompare(b.created_at)).map((item) => ({
|
|
398
|
+
id: item.id,
|
|
399
|
+
name: String(item.meta.name || DEVNOTES_DEFAULT_TASK_LIST_NAME),
|
|
400
|
+
share_slug: String(item.meta.share_slug || generateDevNotesShareSlug()),
|
|
401
|
+
is_default: normalizeForgeBoolean(item.meta.is_default),
|
|
402
|
+
created_by: item.meta.created_by === null || item.meta.created_by === void 0 ? null : String(item.meta.created_by),
|
|
403
|
+
created_at: String(item.meta.created_at || item.created_at),
|
|
404
|
+
updated_at: String(item.meta.updated_at || item.updated_at)
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
407
|
+
function buildReportTypesFromMetadata(comments) {
|
|
408
|
+
return comments.filter((item) => item.meta.kind === "report_type").sort((a, b) => String(a.meta.name || "").localeCompare(String(b.meta.name || ""))).map((item) => ({
|
|
409
|
+
id: item.id,
|
|
410
|
+
name: String(item.meta.name || ""),
|
|
411
|
+
is_default: normalizeForgeBoolean(item.meta.is_default),
|
|
412
|
+
created_by: item.meta.created_by === null || item.meta.created_by === void 0 ? null : String(item.meta.created_by),
|
|
413
|
+
created_at: String(item.meta.created_at || item.created_at)
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
function buildDevNotesReportFromForgeTask(task, overrides, defaultTaskListId) {
|
|
417
|
+
if (!isDevNotesForgeTask(task)) return null;
|
|
418
|
+
const taskId = typeof task.id === "string" ? task.id.trim() : "";
|
|
419
|
+
if (!taskId) return null;
|
|
420
|
+
const parsed = splitDevNotesMeta(String(task.description || ""));
|
|
421
|
+
const base = parsed.meta?.kind === "report" ? parsed.meta : parseLegacyDevNotesDescription(String(task.description || ""));
|
|
422
|
+
const combined = {
|
|
423
|
+
...base,
|
|
424
|
+
...overrides || {}
|
|
425
|
+
};
|
|
426
|
+
const creatorName = String(combined.creator_name || combined.created_by || "").trim();
|
|
427
|
+
const creatorEmail = String(combined.creator_email || "").trim() || null;
|
|
428
|
+
const createdBy = String(combined.created_by || "").trim() || `forge:${taskId}:creator`;
|
|
429
|
+
const taskCompleted = normalizeForgeBoolean(task.completed);
|
|
430
|
+
const status = String(combined.status || (taskCompleted ? "Resolved" : "Open"));
|
|
431
|
+
const description = overrides && Object.prototype.hasOwnProperty.call(overrides, "description") ? overrides.description === null ? null : String(overrides.description || "") : parsed.body.trim() || (base.description ? String(base.description) : null);
|
|
432
|
+
return {
|
|
433
|
+
id: taskId,
|
|
434
|
+
task_list_id: String(combined.task_list_id || defaultTaskListId || ""),
|
|
435
|
+
page_url: String(combined.page_url || ""),
|
|
436
|
+
x_position: normalizeForgeNumber(combined.x_position),
|
|
437
|
+
y_position: normalizeForgeNumber(combined.y_position),
|
|
438
|
+
target_selector: combined.target_selector === null || combined.target_selector === void 0 ? null : String(combined.target_selector),
|
|
439
|
+
target_relative_x: combined.target_relative_x === null || combined.target_relative_x === void 0 ? null : normalizeForgeNumber(combined.target_relative_x),
|
|
440
|
+
target_relative_y: combined.target_relative_y === null || combined.target_relative_y === void 0 ? null : normalizeForgeNumber(combined.target_relative_y),
|
|
441
|
+
types: normalizeForgeStringArray(combined.types),
|
|
442
|
+
severity: String(combined.severity || "Medium"),
|
|
443
|
+
title: String(overrides?.title || task.name || ""),
|
|
444
|
+
description,
|
|
445
|
+
expected_behavior: combined.expected_behavior === null || combined.expected_behavior === void 0 ? null : String(combined.expected_behavior),
|
|
446
|
+
actual_behavior: combined.actual_behavior === null || combined.actual_behavior === void 0 ? null : String(combined.actual_behavior),
|
|
447
|
+
capture_context: combined.capture_context && typeof combined.capture_context === "object" ? combined.capture_context : null,
|
|
448
|
+
response: combined.response === null || combined.response === void 0 ? null : String(combined.response),
|
|
449
|
+
status,
|
|
450
|
+
created_by: createdBy,
|
|
451
|
+
creator: {
|
|
452
|
+
id: createdBy,
|
|
453
|
+
email: creatorEmail,
|
|
454
|
+
full_name: creatorName || null
|
|
455
|
+
},
|
|
456
|
+
assigned_to: overrides && Object.prototype.hasOwnProperty.call(overrides, "assigned_to") ? overrides.assigned_to === null || overrides.assigned_to === void 0 ? null : String(overrides.assigned_to) : task.assigned_to === null || task.assigned_to === void 0 ? null : String(task.assigned_to),
|
|
457
|
+
resolved_at: combined.resolved_at === null || combined.resolved_at === void 0 ? taskCompleted ? String(task.completed_at || task.updated_at || task.created_at || "") : null : String(combined.resolved_at),
|
|
458
|
+
resolved_by: combined.resolved_by === null || combined.resolved_by === void 0 ? null : String(combined.resolved_by),
|
|
459
|
+
approved: normalizeForgeBoolean(combined.approved),
|
|
460
|
+
ai_ready: normalizeForgeBoolean(combined.ai_ready),
|
|
461
|
+
ai_description: combined.ai_description === null || combined.ai_description === void 0 ? null : String(combined.ai_description),
|
|
462
|
+
created_at: String(task.created_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
463
|
+
updated_at: String(
|
|
464
|
+
overrides?.updated_at || task.updated_at || task.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
465
|
+
)
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
async function fetchFocusForge(context, path, init = {}) {
|
|
469
|
+
const url = `${context.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
470
|
+
const headers = new Headers(init.headers || {});
|
|
471
|
+
headers.set("Authorization", `Bearer ${context.pat}`);
|
|
472
|
+
if (!headers.has("Content-Type") && init.body) {
|
|
473
|
+
headers.set("Content-Type", "application/json");
|
|
474
|
+
}
|
|
475
|
+
const response = await context.fetchImpl(url, {
|
|
476
|
+
...init,
|
|
477
|
+
headers
|
|
478
|
+
});
|
|
479
|
+
const text = await response.text();
|
|
480
|
+
return {
|
|
481
|
+
status: response.status,
|
|
482
|
+
ok: response.ok,
|
|
483
|
+
text,
|
|
484
|
+
payload: parseJsonSafe(text),
|
|
485
|
+
contentType: response.headers.get("content-type")
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
async function fetchForgeOrThrow(context, path, init = {}) {
|
|
489
|
+
const response = await fetchFocusForge(context, path, init);
|
|
490
|
+
if (!response.ok) {
|
|
491
|
+
throw new UpstreamForgeError(path, context.baseUrl, response);
|
|
492
|
+
}
|
|
493
|
+
return response;
|
|
494
|
+
}
|
|
495
|
+
async function discoverForgeProjects(context) {
|
|
496
|
+
const bootstrap = await fetchFocusForge(context, "/api/mobile/bootstrap", { method: "GET" });
|
|
497
|
+
if (!bootstrap.ok) {
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
preferredProjectName: context.projectName,
|
|
501
|
+
resolvedBaseUrl: context.baseUrl,
|
|
502
|
+
discoveryPath: "/api/mobile/bootstrap",
|
|
503
|
+
response: bootstrap
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const bootstrapProjects = extractProjectsFromPayload(bootstrap.payload);
|
|
507
|
+
if (bootstrapProjects.length > 0) {
|
|
508
|
+
return {
|
|
509
|
+
ok: true,
|
|
510
|
+
project: null,
|
|
511
|
+
matched: false,
|
|
512
|
+
preferredProjectName: context.projectName,
|
|
513
|
+
projects: bootstrapProjects,
|
|
514
|
+
resolvedBaseUrl: context.baseUrl,
|
|
515
|
+
discoveryPath: "/api/mobile/bootstrap"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const projectsResponse = await fetchFocusForge(context, "/api/mobile/projects", { method: "GET" });
|
|
519
|
+
if (!projectsResponse.ok) {
|
|
520
|
+
return {
|
|
521
|
+
ok: false,
|
|
522
|
+
preferredProjectName: context.projectName,
|
|
523
|
+
resolvedBaseUrl: context.baseUrl,
|
|
524
|
+
discoveryPath: "/api/mobile/projects",
|
|
525
|
+
response: projectsResponse
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
ok: true,
|
|
530
|
+
project: null,
|
|
531
|
+
matched: false,
|
|
532
|
+
preferredProjectName: context.projectName,
|
|
533
|
+
projects: extractProjectsFromPayload(projectsResponse.payload),
|
|
534
|
+
resolvedBaseUrl: context.baseUrl,
|
|
535
|
+
discoveryPath: "/api/mobile/projects"
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
async function resolveForgeProject(context) {
|
|
539
|
+
const discovery = await discoverForgeProjects(context);
|
|
540
|
+
if (!discovery.ok) return discovery;
|
|
541
|
+
if (!context.projectName) {
|
|
542
|
+
return {
|
|
543
|
+
...discovery,
|
|
544
|
+
preferredProjectName: null,
|
|
545
|
+
matched: false,
|
|
546
|
+
project: null
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
const matchedProject = discovery.projects.find(
|
|
550
|
+
(project) => project.name.trim().toLowerCase() === context.projectName.trim().toLowerCase()
|
|
551
|
+
);
|
|
552
|
+
return {
|
|
553
|
+
...discovery,
|
|
554
|
+
preferredProjectName: context.projectName,
|
|
555
|
+
matched: Boolean(matchedProject),
|
|
556
|
+
project: matchedProject || null
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
async function fetchForgeTasksForProject(context, projectId) {
|
|
560
|
+
const response = await fetchForgeOrThrow(
|
|
561
|
+
context,
|
|
562
|
+
`/api/mobile/tasks?projectId=${encodeURIComponent(projectId)}`,
|
|
563
|
+
{ method: "GET" }
|
|
564
|
+
);
|
|
565
|
+
return pickTaskArray(response.payload);
|
|
566
|
+
}
|
|
567
|
+
async function fetchForgeProjectComments(context, projectId) {
|
|
568
|
+
const response = await fetchForgeOrThrow(
|
|
569
|
+
context,
|
|
570
|
+
`/api/sync/comments?projectId=${encodeURIComponent(projectId)}`,
|
|
571
|
+
{ method: "GET" }
|
|
572
|
+
);
|
|
573
|
+
return pickTaskArray(response.payload);
|
|
574
|
+
}
|
|
575
|
+
async function fetchForgeTaskComments(context, taskId) {
|
|
576
|
+
const response = await fetchForgeOrThrow(
|
|
577
|
+
context,
|
|
578
|
+
`/api/sync/comments?taskId=${encodeURIComponent(taskId)}`,
|
|
579
|
+
{ method: "GET" }
|
|
580
|
+
);
|
|
581
|
+
return pickTaskArray(response.payload);
|
|
582
|
+
}
|
|
583
|
+
async function fetchForgeCommentById(context, commentId) {
|
|
584
|
+
const response = await fetchForgeOrThrow(
|
|
585
|
+
context,
|
|
586
|
+
`/api/sync/comments/${encodeURIComponent(commentId)}`,
|
|
587
|
+
{ method: "GET" }
|
|
588
|
+
);
|
|
589
|
+
const payload = coerceObject(response.payload?.data || response.payload);
|
|
590
|
+
return Object.keys(payload).length > 0 ? payload : null;
|
|
591
|
+
}
|
|
592
|
+
async function createForgeProjectComment(context, projectId, body, meta) {
|
|
593
|
+
const response = await fetchForgeOrThrow(context, "/api/sync/comments", {
|
|
594
|
+
method: "POST",
|
|
595
|
+
body: JSON.stringify({
|
|
596
|
+
projectId,
|
|
597
|
+
content: appendDevNotesMeta(body, meta)
|
|
598
|
+
})
|
|
599
|
+
});
|
|
600
|
+
return coerceObject(response.payload?.data || response.payload);
|
|
601
|
+
}
|
|
602
|
+
async function updateForgeProjectComment(context, commentId, body, meta) {
|
|
603
|
+
const response = await fetchForgeOrThrow(
|
|
604
|
+
context,
|
|
605
|
+
`/api/sync/comments/${encodeURIComponent(commentId)}`,
|
|
606
|
+
{
|
|
607
|
+
method: "PUT",
|
|
608
|
+
body: JSON.stringify({
|
|
609
|
+
content: appendDevNotesMeta(body, meta)
|
|
610
|
+
})
|
|
611
|
+
}
|
|
612
|
+
);
|
|
613
|
+
return coerceObject(response.payload?.data || response.payload);
|
|
614
|
+
}
|
|
615
|
+
async function deleteForgeProjectComment(context, commentId) {
|
|
616
|
+
await fetchForgeOrThrow(context, `/api/sync/comments/${encodeURIComponent(commentId)}`, {
|
|
617
|
+
method: "DELETE"
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
async function ensureDevNotesProjectDefaults(context, projectId, user) {
|
|
621
|
+
let parsed = (await fetchForgeProjectComments(context, projectId)).map(parseDevNotesProjectComment).filter((item) => Boolean(item));
|
|
622
|
+
const typeComments = parsed.filter((item) => item.meta.kind === "report_type");
|
|
623
|
+
const taskListComments = parsed.filter((item) => item.meta.kind === "task_list");
|
|
624
|
+
if (typeComments.length === 0) {
|
|
625
|
+
for (const name of DEVNOTES_DEFAULT_TYPE_NAMES) {
|
|
626
|
+
await createForgeProjectComment(context, projectId, "", {
|
|
627
|
+
kind: "report_type",
|
|
628
|
+
name,
|
|
629
|
+
is_default: true,
|
|
630
|
+
created_by: user.id,
|
|
631
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (taskListComments.length === 0) {
|
|
636
|
+
await createForgeProjectComment(context, projectId, "", {
|
|
637
|
+
kind: "task_list",
|
|
638
|
+
name: DEVNOTES_DEFAULT_TASK_LIST_NAME,
|
|
639
|
+
share_slug: generateDevNotesShareSlug(),
|
|
640
|
+
is_default: true,
|
|
641
|
+
created_by: user.id,
|
|
642
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
643
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (typeComments.length === 0 || taskListComments.length === 0) {
|
|
647
|
+
parsed = (await fetchForgeProjectComments(context, projectId)).map(parseDevNotesProjectComment).filter((item) => Boolean(item));
|
|
648
|
+
}
|
|
649
|
+
return parsed;
|
|
650
|
+
}
|
|
651
|
+
function buildCapabilities() {
|
|
652
|
+
return { ai: false, appLink: true };
|
|
653
|
+
}
|
|
654
|
+
function buildAppLinkStatus(context, discovery) {
|
|
655
|
+
if (!context.pat) {
|
|
656
|
+
return {
|
|
657
|
+
linked: false,
|
|
658
|
+
projectName: context.projectName,
|
|
659
|
+
tokenLast4: null,
|
|
660
|
+
linkedAt: null,
|
|
661
|
+
projectMatched: false,
|
|
662
|
+
availableProjects: [],
|
|
663
|
+
projectDiscovery: null
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
if (!discovery.ok) {
|
|
667
|
+
return {
|
|
668
|
+
linked: true,
|
|
669
|
+
projectName: context.projectName,
|
|
670
|
+
tokenLast4: context.pat.slice(-4),
|
|
671
|
+
linkedAt: null,
|
|
672
|
+
projectMatched: false,
|
|
673
|
+
availableProjects: [],
|
|
674
|
+
projectDiscovery: toProjectDiscovery(discovery)
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
linked: true,
|
|
679
|
+
projectName: context.projectName,
|
|
680
|
+
tokenLast4: context.pat.slice(-4),
|
|
681
|
+
linkedAt: null,
|
|
682
|
+
projectMatched: discovery.matched,
|
|
683
|
+
availableProjects: discovery.matched ? [] : discovery.projects,
|
|
684
|
+
projectDiscovery: toProjectDiscovery(discovery)
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function sortCreators(creators) {
|
|
688
|
+
return creators.sort((left, right) => {
|
|
689
|
+
const a = left.full_name || left.email || left.id;
|
|
690
|
+
const b = right.full_name || right.email || right.id;
|
|
691
|
+
return a.localeCompare(b);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
function createDevNotesServerHandler(options) {
|
|
695
|
+
const basePath = normalizeBasePath(options.basePath);
|
|
696
|
+
const baseUrl = normalizeBaseUrl(options.forge.baseUrl);
|
|
697
|
+
if (!baseUrl) {
|
|
698
|
+
throw new Error("DevNotes server helpers require forge.baseUrl.");
|
|
699
|
+
}
|
|
700
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
701
|
+
if (typeof fetchImpl !== "function") {
|
|
702
|
+
throw new Error("DevNotes server helpers require a fetch implementation.");
|
|
703
|
+
}
|
|
704
|
+
return async function handleDevNotesRequest(request) {
|
|
705
|
+
if (request.method === "OPTIONS") {
|
|
706
|
+
return await emptyResponse(request, options.corsHeaders);
|
|
707
|
+
}
|
|
708
|
+
const url = new URL(request.url);
|
|
709
|
+
const slug = parseRequestPath(url.pathname, basePath);
|
|
710
|
+
if (!slug) {
|
|
711
|
+
return await jsonResponse(request, options.corsHeaders, { error: "Not found" }, 404);
|
|
712
|
+
}
|
|
713
|
+
const user = normalizeUser(await options.getCurrentUser(request));
|
|
714
|
+
if (!user) {
|
|
715
|
+
return await jsonResponse(request, options.corsHeaders, { error: "Unauthorized" }, 401);
|
|
716
|
+
}
|
|
717
|
+
const method = request.method.toUpperCase();
|
|
718
|
+
const body = await readJsonBody(request) || {};
|
|
719
|
+
const [resource, resourceId, nested] = slug;
|
|
720
|
+
const forgeContext = {
|
|
721
|
+
baseUrl,
|
|
722
|
+
pat: String(options.forge.pat || "").trim(),
|
|
723
|
+
projectName: options.forge.projectName?.trim() || null,
|
|
724
|
+
fetchImpl
|
|
725
|
+
};
|
|
726
|
+
if (resource === "capabilities" && method === "GET") {
|
|
727
|
+
return await jsonResponse(request, options.corsHeaders, buildCapabilities());
|
|
728
|
+
}
|
|
729
|
+
if (!forgeContext.pat && resource === "app-link" && method === "GET") {
|
|
730
|
+
return await jsonResponse(
|
|
731
|
+
request,
|
|
732
|
+
options.corsHeaders,
|
|
733
|
+
buildAppLinkStatus(forgeContext, {
|
|
734
|
+
ok: true,
|
|
735
|
+
project: null,
|
|
736
|
+
matched: false,
|
|
737
|
+
preferredProjectName: forgeContext.projectName,
|
|
738
|
+
projects: [],
|
|
739
|
+
resolvedBaseUrl: forgeContext.baseUrl,
|
|
740
|
+
discoveryPath: null
|
|
741
|
+
})
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
if (!forgeContext.pat) {
|
|
745
|
+
return await jsonResponse(
|
|
746
|
+
request,
|
|
747
|
+
options.corsHeaders,
|
|
748
|
+
{
|
|
749
|
+
error: "FOCUS_FORGE_PAT is not configured."
|
|
750
|
+
},
|
|
751
|
+
503
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
const projectResolution = await resolveForgeProject(forgeContext);
|
|
756
|
+
if (resource === "app-link") {
|
|
757
|
+
if (method === "GET") {
|
|
758
|
+
return await jsonResponse(
|
|
759
|
+
request,
|
|
760
|
+
options.corsHeaders,
|
|
761
|
+
buildAppLinkStatus(forgeContext, projectResolution)
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
return await jsonResponse(
|
|
765
|
+
request,
|
|
766
|
+
options.corsHeaders,
|
|
767
|
+
{
|
|
768
|
+
error: "App-level Forge credentials are managed through server environment configuration."
|
|
769
|
+
},
|
|
770
|
+
405
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
if (!projectResolution.ok) {
|
|
774
|
+
throw new UpstreamForgeError(
|
|
775
|
+
projectResolution.discoveryPath || "/api/mobile/bootstrap",
|
|
776
|
+
projectResolution.resolvedBaseUrl,
|
|
777
|
+
projectResolution.response
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
if (!projectResolution.project?.id) {
|
|
781
|
+
return await jsonResponse(
|
|
782
|
+
request,
|
|
783
|
+
options.corsHeaders,
|
|
784
|
+
{
|
|
785
|
+
error: projectResolution.preferredProjectName ? `Could not find Focus Forge project "${projectResolution.preferredProjectName}"` : "FOCUS_FORGE_PROJECT_NAME is not configured",
|
|
786
|
+
available_projects: projectResolution.projects
|
|
787
|
+
},
|
|
788
|
+
projectResolution.preferredProjectName ? 404 : 409
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
const project = projectResolution.project;
|
|
792
|
+
const metadataComments = await ensureDevNotesProjectDefaults(forgeContext, project.id, user);
|
|
793
|
+
const taskLists = buildTaskListsFromMetadata(metadataComments);
|
|
794
|
+
const defaultTaskListId = String(
|
|
795
|
+
taskLists.find((item) => item.is_default)?.id || taskLists[0]?.id || ""
|
|
796
|
+
);
|
|
797
|
+
const reportPatchById = /* @__PURE__ */ new Map();
|
|
798
|
+
const deletedReportIds = /* @__PURE__ */ new Set();
|
|
799
|
+
const readMarkers = /* @__PURE__ */ new Set();
|
|
800
|
+
metadataComments.forEach((comment) => {
|
|
801
|
+
const kind = String(comment.meta.kind || "");
|
|
802
|
+
const reportId = String(comment.meta.reportId || "").trim();
|
|
803
|
+
if (kind === "report_patch" && reportId) {
|
|
804
|
+
const previous = reportPatchById.get(reportId);
|
|
805
|
+
if (!previous || String(previous.updated_at || "") <= comment.updated_at) {
|
|
806
|
+
reportPatchById.set(reportId, {
|
|
807
|
+
...comment.meta.report && typeof comment.meta.report === "object" ? comment.meta.report : {},
|
|
808
|
+
updated_at: comment.updated_at
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (kind === "report_deleted" && reportId) {
|
|
813
|
+
deletedReportIds.add(reportId);
|
|
814
|
+
}
|
|
815
|
+
if (kind === "message_read") {
|
|
816
|
+
const targetMessageId = String(comment.meta.messageId || "").trim();
|
|
817
|
+
const targetUserId = String(comment.meta.userId || "").trim();
|
|
818
|
+
if (targetMessageId && targetUserId === user.id) {
|
|
819
|
+
readMarkers.add(targetMessageId);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
if (resource === "reports" && method === "GET" && !resourceId) {
|
|
824
|
+
const tasks = await fetchForgeTasksForProject(forgeContext, project.id);
|
|
825
|
+
const reports = tasks.map(
|
|
826
|
+
(task) => buildDevNotesReportFromForgeTask(
|
|
827
|
+
task,
|
|
828
|
+
reportPatchById.get(String(task.id || "").trim()) || null,
|
|
829
|
+
defaultTaskListId
|
|
830
|
+
)
|
|
831
|
+
).filter((item) => Boolean(item)).filter((item) => !deletedReportIds.has(String(item.id)));
|
|
832
|
+
return await jsonResponse(request, options.corsHeaders, reports);
|
|
833
|
+
}
|
|
834
|
+
if (resource === "reports" && method === "POST" && !resourceId) {
|
|
835
|
+
const payload = {
|
|
836
|
+
...body,
|
|
837
|
+
created_by: user.id,
|
|
838
|
+
creator: {
|
|
839
|
+
id: user.id,
|
|
840
|
+
email: user.email || null,
|
|
841
|
+
full_name: user.fullName || null
|
|
842
|
+
},
|
|
843
|
+
task_list_id: String(body.task_list_id || defaultTaskListId),
|
|
844
|
+
status: String(body.status || "Open")
|
|
845
|
+
};
|
|
846
|
+
const createPath = "/api/mobile/tasks";
|
|
847
|
+
const response = await fetchForgeOrThrow(forgeContext, createPath, {
|
|
848
|
+
method: "POST",
|
|
849
|
+
body: JSON.stringify({
|
|
850
|
+
name: String(payload.title || ""),
|
|
851
|
+
description: buildDevNotesReportDescription(payload),
|
|
852
|
+
project_id: project.id,
|
|
853
|
+
completed: mapBugStatusToForge(String(payload.status || "Open")) === "completed",
|
|
854
|
+
assigned_to: payload.assigned_to === null || payload.assigned_to === void 0 ? void 0 : String(payload.assigned_to)
|
|
855
|
+
})
|
|
856
|
+
});
|
|
857
|
+
const taskId = extractForgeTaskId(response.payload);
|
|
858
|
+
if (!taskId) {
|
|
859
|
+
throw new UpstreamForgeError(createPath, forgeContext.baseUrl, {
|
|
860
|
+
...response,
|
|
861
|
+
status: response.status || 502,
|
|
862
|
+
text: response.text || JSON.stringify({
|
|
863
|
+
error: "Task endpoint succeeded but did not return a task id"
|
|
864
|
+
}),
|
|
865
|
+
contentType: response.contentType || "application/json"
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
const tasks = await fetchForgeTasksForProject(forgeContext, project.id);
|
|
869
|
+
const createdTask = tasks.find((task) => String(task.id || "") === taskId) || {
|
|
870
|
+
id: taskId,
|
|
871
|
+
name: payload.title,
|
|
872
|
+
description: buildDevNotesReportDescription(payload),
|
|
873
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
874
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
875
|
+
completed: false
|
|
876
|
+
};
|
|
877
|
+
const report = buildDevNotesReportFromForgeTask(createdTask, null, defaultTaskListId);
|
|
878
|
+
return await jsonResponse(request, options.corsHeaders, report);
|
|
879
|
+
}
|
|
880
|
+
if (resource === "reports" && resourceId && !nested && method === "PATCH") {
|
|
881
|
+
const reportId = decodeURIComponent(resourceId);
|
|
882
|
+
const tasks = await fetchForgeTasksForProject(forgeContext, project.id);
|
|
883
|
+
const existingTask = tasks.find((task) => String(task.id || "") === reportId);
|
|
884
|
+
if (!existingTask) {
|
|
885
|
+
return await jsonResponse(
|
|
886
|
+
request,
|
|
887
|
+
options.corsHeaders,
|
|
888
|
+
{ error: "Bug report not found" },
|
|
889
|
+
404
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
const existing = buildDevNotesReportFromForgeTask(
|
|
893
|
+
existingTask,
|
|
894
|
+
reportPatchById.get(reportId) || null,
|
|
895
|
+
defaultTaskListId
|
|
896
|
+
);
|
|
897
|
+
if (!existing) {
|
|
898
|
+
return await jsonResponse(
|
|
899
|
+
request,
|
|
900
|
+
options.corsHeaders,
|
|
901
|
+
{ error: "Bug report not found" },
|
|
902
|
+
404
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
const merged = {
|
|
906
|
+
...existing,
|
|
907
|
+
...body,
|
|
908
|
+
id: existing.id,
|
|
909
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
910
|
+
resolved_at: body.status === "Resolved" || body.status === "Closed" ? (/* @__PURE__ */ new Date()).toISOString() : existing.resolved_at,
|
|
911
|
+
resolved_by: body.status === "Resolved" || body.status === "Closed" ? body.resolved_by || user.id : existing.resolved_by
|
|
912
|
+
};
|
|
913
|
+
const existingPatch = metadataComments.find(
|
|
914
|
+
(comment) => comment.meta.kind === "report_patch" && String(comment.meta.reportId || "") === reportId
|
|
915
|
+
);
|
|
916
|
+
if (existingPatch) {
|
|
917
|
+
await updateForgeProjectComment(forgeContext, existingPatch.id, "", {
|
|
918
|
+
kind: "report_patch",
|
|
919
|
+
reportId,
|
|
920
|
+
report: merged
|
|
921
|
+
});
|
|
922
|
+
} else {
|
|
923
|
+
await createForgeProjectComment(forgeContext, project.id, "", {
|
|
924
|
+
kind: "report_patch",
|
|
925
|
+
reportId,
|
|
926
|
+
report: merged
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
return await jsonResponse(request, options.corsHeaders, merged);
|
|
930
|
+
}
|
|
931
|
+
if (resource === "reports" && resourceId && !nested && method === "DELETE") {
|
|
932
|
+
const reportId = decodeURIComponent(resourceId);
|
|
933
|
+
const existingDelete = metadataComments.find(
|
|
934
|
+
(comment) => comment.meta.kind === "report_deleted" && String(comment.meta.reportId || "") === reportId
|
|
935
|
+
);
|
|
936
|
+
if (existingDelete) {
|
|
937
|
+
await updateForgeProjectComment(forgeContext, existingDelete.id, "", {
|
|
938
|
+
kind: "report_deleted",
|
|
939
|
+
reportId,
|
|
940
|
+
deletedBy: user.id,
|
|
941
|
+
deletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
942
|
+
});
|
|
943
|
+
} else {
|
|
944
|
+
await createForgeProjectComment(forgeContext, project.id, "", {
|
|
945
|
+
kind: "report_deleted",
|
|
946
|
+
reportId,
|
|
947
|
+
deletedBy: user.id,
|
|
948
|
+
deletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
return await jsonResponse(request, options.corsHeaders, { success: true });
|
|
952
|
+
}
|
|
953
|
+
if (resource === "reports" && resourceId && nested === "messages" && method === "GET") {
|
|
954
|
+
const reportId = decodeURIComponent(resourceId);
|
|
955
|
+
const projectMessages = metadataComments.filter(
|
|
956
|
+
(comment) => comment.meta.kind === "message" && String(comment.meta.reportId || "") === reportId
|
|
957
|
+
).sort((a, b) => a.created_at.localeCompare(b.created_at)).map((comment) => ({
|
|
958
|
+
id: comment.id,
|
|
959
|
+
bug_report_id: reportId,
|
|
960
|
+
author_id: String(comment.meta.authorId || comment.user_id || ""),
|
|
961
|
+
body: comment.body,
|
|
962
|
+
created_at: comment.created_at,
|
|
963
|
+
updated_at: comment.updated_at,
|
|
964
|
+
author: {
|
|
965
|
+
id: String(comment.meta.authorId || comment.user_id || ""),
|
|
966
|
+
email: comment.meta.authorEmail === null || comment.meta.authorEmail === void 0 ? null : String(comment.meta.authorEmail),
|
|
967
|
+
full_name: comment.meta.authorName === null || comment.meta.authorName === void 0 ? null : String(comment.meta.authorName)
|
|
968
|
+
}
|
|
969
|
+
}));
|
|
970
|
+
const legacyTaskMessages = (await fetchForgeTaskComments(forgeContext, reportId)).filter((comment) => String(comment.project_id || "") === "").map((comment) => ({
|
|
971
|
+
id: String(comment.id || ""),
|
|
972
|
+
bug_report_id: reportId,
|
|
973
|
+
author_id: String(comment.user_id || ""),
|
|
974
|
+
body: String(comment.content || ""),
|
|
975
|
+
created_at: String(comment.created_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
976
|
+
updated_at: String(comment.updated_at || comment.created_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
977
|
+
author: {
|
|
978
|
+
id: String(comment.user_id || ""),
|
|
979
|
+
email: comment.author_email === null || comment.author_email === void 0 ? null : String(comment.author_email),
|
|
980
|
+
full_name: comment.author_name === null || comment.author_name === void 0 ? null : String(comment.author_name)
|
|
981
|
+
}
|
|
982
|
+
}));
|
|
983
|
+
const merged = [...legacyTaskMessages, ...projectMessages].sort(
|
|
984
|
+
(a, b) => a.created_at.localeCompare(b.created_at)
|
|
985
|
+
);
|
|
986
|
+
return await jsonResponse(request, options.corsHeaders, merged);
|
|
987
|
+
}
|
|
988
|
+
if (resource === "reports" && resourceId && nested === "messages" && method === "POST") {
|
|
989
|
+
const reportId = decodeURIComponent(resourceId);
|
|
990
|
+
const created = await createForgeProjectComment(
|
|
991
|
+
forgeContext,
|
|
992
|
+
project.id,
|
|
993
|
+
String(body.body || "").trim(),
|
|
994
|
+
{
|
|
995
|
+
kind: "message",
|
|
996
|
+
reportId,
|
|
997
|
+
authorId: user.id,
|
|
998
|
+
authorEmail: user.email || null,
|
|
999
|
+
authorName: user.fullName || user.email || ""
|
|
1000
|
+
}
|
|
1001
|
+
);
|
|
1002
|
+
const parsed = parseDevNotesProjectComment(created);
|
|
1003
|
+
if (!parsed) {
|
|
1004
|
+
return await jsonResponse(
|
|
1005
|
+
request,
|
|
1006
|
+
options.corsHeaders,
|
|
1007
|
+
{ error: "Failed to create message" },
|
|
1008
|
+
500
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
const message = {
|
|
1012
|
+
id: parsed.id,
|
|
1013
|
+
bug_report_id: reportId,
|
|
1014
|
+
author_id: String(parsed.meta.authorId || user.id),
|
|
1015
|
+
body: parsed.body,
|
|
1016
|
+
created_at: parsed.created_at,
|
|
1017
|
+
updated_at: parsed.updated_at,
|
|
1018
|
+
author: {
|
|
1019
|
+
id: String(parsed.meta.authorId || user.id),
|
|
1020
|
+
email: parsed.meta.authorEmail === null || parsed.meta.authorEmail === void 0 ? null : String(parsed.meta.authorEmail),
|
|
1021
|
+
full_name: parsed.meta.authorName === null || parsed.meta.authorName === void 0 ? null : String(parsed.meta.authorName)
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
return await jsonResponse(request, options.corsHeaders, message);
|
|
1025
|
+
}
|
|
1026
|
+
if (resource === "report-types" && method === "GET" && !resourceId) {
|
|
1027
|
+
return await jsonResponse(
|
|
1028
|
+
request,
|
|
1029
|
+
options.corsHeaders,
|
|
1030
|
+
buildReportTypesFromMetadata(metadataComments)
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
if (resource === "report-types" && method === "POST" && !resourceId) {
|
|
1034
|
+
const created = await createForgeProjectComment(forgeContext, project.id, "", {
|
|
1035
|
+
kind: "report_type",
|
|
1036
|
+
name: String(body.name || "").trim(),
|
|
1037
|
+
is_default: false,
|
|
1038
|
+
created_by: user.id,
|
|
1039
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1040
|
+
});
|
|
1041
|
+
const parsed = parseDevNotesProjectComment(created);
|
|
1042
|
+
return await jsonResponse(request, options.corsHeaders, {
|
|
1043
|
+
id: parsed?.id || String(created.id || ""),
|
|
1044
|
+
name: String(body.name || "").trim(),
|
|
1045
|
+
is_default: false,
|
|
1046
|
+
created_by: user.id,
|
|
1047
|
+
created_at: parsed?.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
if (resource === "report-types" && resourceId && method === "DELETE") {
|
|
1051
|
+
await deleteForgeProjectComment(forgeContext, decodeURIComponent(resourceId));
|
|
1052
|
+
return await jsonResponse(request, options.corsHeaders, { success: true });
|
|
1053
|
+
}
|
|
1054
|
+
if (resource === "task-lists" && method === "GET" && !resourceId) {
|
|
1055
|
+
return await jsonResponse(request, options.corsHeaders, taskLists);
|
|
1056
|
+
}
|
|
1057
|
+
if (resource === "task-lists" && method === "POST" && !resourceId) {
|
|
1058
|
+
const created = await createForgeProjectComment(forgeContext, project.id, "", {
|
|
1059
|
+
kind: "task_list",
|
|
1060
|
+
name: String(body.name || "").trim(),
|
|
1061
|
+
share_slug: generateDevNotesShareSlug(),
|
|
1062
|
+
is_default: false,
|
|
1063
|
+
created_by: user.id,
|
|
1064
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1065
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1066
|
+
});
|
|
1067
|
+
const parsed = parseDevNotesProjectComment(created);
|
|
1068
|
+
return await jsonResponse(request, options.corsHeaders, {
|
|
1069
|
+
id: parsed?.id || String(created.id || ""),
|
|
1070
|
+
name: String(body.name || "").trim(),
|
|
1071
|
+
share_slug: String(parsed?.meta.share_slug || generateDevNotesShareSlug()),
|
|
1072
|
+
is_default: false,
|
|
1073
|
+
created_by: user.id,
|
|
1074
|
+
created_at: parsed?.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1075
|
+
updated_at: parsed?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
if (resource === "messages" && resourceId === "read" && method === "POST") {
|
|
1079
|
+
const messageIds = Array.isArray(body.messageIds) ? body.messageIds.map((value) => String(value || "").trim()).filter((value) => Boolean(value)) : [];
|
|
1080
|
+
for (const messageId of Array.from(new Set(messageIds))) {
|
|
1081
|
+
if (readMarkers.has(messageId)) continue;
|
|
1082
|
+
await createForgeProjectComment(forgeContext, project.id, "", {
|
|
1083
|
+
kind: "message_read",
|
|
1084
|
+
messageId,
|
|
1085
|
+
userId: user.id,
|
|
1086
|
+
readAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
return await jsonResponse(request, options.corsHeaders, { success: true });
|
|
1090
|
+
}
|
|
1091
|
+
if (resource === "messages" && resourceId && method === "PATCH") {
|
|
1092
|
+
const current = await fetchForgeCommentById(forgeContext, decodeURIComponent(resourceId));
|
|
1093
|
+
if (!current) {
|
|
1094
|
+
return await jsonResponse(
|
|
1095
|
+
request,
|
|
1096
|
+
options.corsHeaders,
|
|
1097
|
+
{ error: "Message not found" },
|
|
1098
|
+
404
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
const parsed = parseDevNotesProjectComment(current);
|
|
1102
|
+
if (!parsed || parsed.meta.kind !== "message") {
|
|
1103
|
+
return await jsonResponse(
|
|
1104
|
+
request,
|
|
1105
|
+
options.corsHeaders,
|
|
1106
|
+
{ error: "Message not found" },
|
|
1107
|
+
404
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
const updated = await updateForgeProjectComment(
|
|
1111
|
+
forgeContext,
|
|
1112
|
+
decodeURIComponent(resourceId),
|
|
1113
|
+
String(body.body || "").trim(),
|
|
1114
|
+
parsed.meta
|
|
1115
|
+
);
|
|
1116
|
+
const updatedParsed = parseDevNotesProjectComment(updated);
|
|
1117
|
+
return await jsonResponse(request, options.corsHeaders, {
|
|
1118
|
+
id: updatedParsed?.id || decodeURIComponent(resourceId),
|
|
1119
|
+
bug_report_id: String(parsed.meta.reportId || ""),
|
|
1120
|
+
author_id: String(parsed.meta.authorId || parsed.user_id || ""),
|
|
1121
|
+
body: updatedParsed?.body || String(body.body || "").trim(),
|
|
1122
|
+
created_at: updatedParsed?.created_at || parsed.created_at,
|
|
1123
|
+
updated_at: updatedParsed?.updated_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1124
|
+
author: {
|
|
1125
|
+
id: String(parsed.meta.authorId || parsed.user_id || ""),
|
|
1126
|
+
email: parsed.meta.authorEmail === null || parsed.meta.authorEmail === void 0 ? null : String(parsed.meta.authorEmail),
|
|
1127
|
+
full_name: parsed.meta.authorName === null || parsed.meta.authorName === void 0 ? null : String(parsed.meta.authorName)
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
if (resource === "messages" && resourceId && method === "DELETE") {
|
|
1132
|
+
await deleteForgeProjectComment(forgeContext, decodeURIComponent(resourceId));
|
|
1133
|
+
return await jsonResponse(request, options.corsHeaders, { success: true });
|
|
1134
|
+
}
|
|
1135
|
+
if (resource === "unread-counts" && method === "GET") {
|
|
1136
|
+
const counts = {};
|
|
1137
|
+
metadataComments.filter((comment) => comment.meta.kind === "message").forEach((comment) => {
|
|
1138
|
+
const reportId = String(comment.meta.reportId || "").trim();
|
|
1139
|
+
const authorId = String(comment.meta.authorId || "").trim();
|
|
1140
|
+
if (!reportId || authorId === user.id || readMarkers.has(comment.id)) return;
|
|
1141
|
+
counts[reportId] = (counts[reportId] || 0) + 1;
|
|
1142
|
+
});
|
|
1143
|
+
return await jsonResponse(request, options.corsHeaders, counts);
|
|
1144
|
+
}
|
|
1145
|
+
if (resource === "collaborators" && method === "GET") {
|
|
1146
|
+
const tasks = await fetchForgeTasksForProject(forgeContext, project.id);
|
|
1147
|
+
const reports = tasks.map(
|
|
1148
|
+
(task) => buildDevNotesReportFromForgeTask(
|
|
1149
|
+
task,
|
|
1150
|
+
reportPatchById.get(String(task.id || "").trim()) || null,
|
|
1151
|
+
defaultTaskListId
|
|
1152
|
+
)
|
|
1153
|
+
).filter((item) => Boolean(item));
|
|
1154
|
+
const knownUsers = buildKnownUsers(metadataComments, reports, user);
|
|
1155
|
+
const ids = (url.searchParams.get("ids") || "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
1156
|
+
const resolvedUsers = await maybeResolveUsers(options.resolveUsers, ids);
|
|
1157
|
+
resolvedUsers.forEach((resolved) => knownUsers.set(resolved.id, resolved));
|
|
1158
|
+
const collaborators = ids.length > 0 ? ids.map((id) => knownUsers.get(id)).filter((value) => Boolean(value)) : Array.from(knownUsers.values());
|
|
1159
|
+
return await jsonResponse(
|
|
1160
|
+
request,
|
|
1161
|
+
options.corsHeaders,
|
|
1162
|
+
sortCreators(collaborators)
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
return await jsonResponse(request, options.corsHeaders, { error: "Not found" }, 404);
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
if (error instanceof UpstreamForgeError) {
|
|
1168
|
+
return await passthroughUpstreamResponse(request, options.corsHeaders, error);
|
|
1169
|
+
}
|
|
1170
|
+
return await jsonResponse(
|
|
1171
|
+
request,
|
|
1172
|
+
options.corsHeaders,
|
|
1173
|
+
{ error: error instanceof Error ? error.message : "Unexpected error" },
|
|
1174
|
+
500
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/server/deno.ts
|
|
1181
|
+
function createDenoDevNotesHandler(options) {
|
|
1182
|
+
return createDevNotesServerHandler(options);
|
|
1183
|
+
}
|
|
1184
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1185
|
+
0 && (module.exports = {
|
|
1186
|
+
createDenoDevNotesHandler
|
|
1187
|
+
});
|