dbp-wp 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/dist/index.js CHANGED
@@ -69,7 +69,7 @@ import { createReadStream } from "fs";
69
69
  import { stat } from "fs/promises";
70
70
  import { createServer } from "http";
71
71
  import { extname, join, relative, resolve } from "path";
72
- import { WpClient, WpRequestError } from "@dbp-wp/core";
72
+ import { RelationError, WpClient, WpRequestError } from "@dbp-wp/core";
73
73
 
74
74
  // src/host.ts
75
75
  var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
@@ -220,6 +220,33 @@ function parseMetaInput(value) {
220
220
  }
221
221
  return result;
222
222
  }
223
+ function parseRelation(body) {
224
+ if (typeof body !== "object" || body === null) {
225
+ return null;
226
+ }
227
+ const record = body;
228
+ if (typeof record.childId !== "number" || !Number.isSafeInteger(record.childId) || record.childId <= 0) {
229
+ return null;
230
+ }
231
+ const childType = parsePostTypeSlug(record.childType);
232
+ if (childType === null) {
233
+ return null;
234
+ }
235
+ if (record.parentId === null) {
236
+ return { childId: record.childId, childType, relation: null };
237
+ }
238
+ if (typeof record.parentId !== "number" || !Number.isSafeInteger(record.parentId) || record.parentId <= 0) {
239
+ return null;
240
+ }
241
+ if (typeof record.parentType !== "string" || !ROUTE_SLUG.test(record.parentType)) {
242
+ return null;
243
+ }
244
+ return {
245
+ childId: record.childId,
246
+ childType,
247
+ relation: { parentId: record.parentId, parentType: record.parentType }
248
+ };
249
+ }
223
250
  function parseMetaDelete(body) {
224
251
  if (typeof body !== "object" || body === null) {
225
252
  return null;
@@ -335,6 +362,32 @@ async function handlePosts(req, res, url, options) {
335
362
  }
336
363
  }
337
364
  }
365
+ async function handlePrintPosts(req, res, url, options) {
366
+ try {
367
+ if (req.method !== "GET") {
368
+ sendJson(res, 405, { error: "Method not allowed" });
369
+ return;
370
+ }
371
+ const credentials = options.state.credentials;
372
+ if (!credentials) {
373
+ sendJson(res, 200, { records: [], unconfigured: true });
374
+ return;
375
+ }
376
+ const client = new WpClient(credentials);
377
+ const type = url.searchParams.get("type");
378
+ const pageRaw = url.searchParams.get("page");
379
+ const page = pageRaw ? Number.parseInt(pageRaw, 10) : void 0;
380
+ const records = await client.listPostsForPrint({
381
+ ...type ? { type } : {},
382
+ ...page && page > 0 ? { page } : {}
383
+ });
384
+ sendJson(res, 200, { records, unconfigured: false });
385
+ } catch (e) {
386
+ if (!res.headersSent) {
387
+ sendJson(res, 502, { error: e instanceof Error ? e.message : "Upstream request failed" });
388
+ }
389
+ }
390
+ }
338
391
  async function handleTypes(req, res, options) {
339
392
  if (req.method !== "GET") {
340
393
  sendJson(res, 405, { error: "Method not allowed" });
@@ -525,6 +578,59 @@ async function handleBulkMetaDelete(req, res, options) {
525
578
  }
526
579
  sendJson(res, 200, { results });
527
580
  }
581
+ async function handleRelation(req, res, options) {
582
+ if (req.method !== "POST") {
583
+ sendJson(res, 405, { error: "Method not allowed" });
584
+ return;
585
+ }
586
+ if (!isJsonContentType(req.headers["content-type"])) {
587
+ sendJson(res, 415, { error: "Content-Type must be application/json." });
588
+ return;
589
+ }
590
+ const credentials = options.state.credentials;
591
+ if (!credentials) {
592
+ sendJson(res, 409, { error: "Not connected" });
593
+ return;
594
+ }
595
+ if (options.state.connectorAvailable === null) {
596
+ try {
597
+ options.state.connectorAvailable = await new WpClient(credentials).detectConnector();
598
+ } catch {
599
+ options.state.connectorAvailable = false;
600
+ }
601
+ }
602
+ if (!options.state.connectorAvailable) {
603
+ sendJson(res, 409, {
604
+ error: "The companion plugin is required to edit relations, but it was not found on the site."
605
+ });
606
+ return;
607
+ }
608
+ let body;
609
+ try {
610
+ body = await readJsonBody(req);
611
+ } catch (e) {
612
+ sendJson(res, 400, { error: e instanceof Error ? e.message : "Invalid request body" });
613
+ return;
614
+ }
615
+ const request = parseRelation(body);
616
+ if (!request) {
617
+ sendJson(res, 400, { error: "Invalid relation payload." });
618
+ return;
619
+ }
620
+ try {
621
+ const client = new WpClient(credentials);
622
+ const post = request.relation ? await client.setRelation(request.childId, request.childType, request.relation) : await client.clearRelation(request.childId, request.childType);
623
+ sendJson(res, 200, { post });
624
+ } catch (e) {
625
+ if (!res.headersSent) {
626
+ if (e instanceof RelationError) {
627
+ sendJson(res, 400, { error: e.message });
628
+ } else {
629
+ sendJson(res, 502, { error: e instanceof Error ? e.message : "Relation update failed" });
630
+ }
631
+ }
632
+ }
633
+ }
528
634
  async function handleConnection(req, res, options) {
529
635
  if (req.method === "GET") {
530
636
  const c = options.state.credentials;
@@ -622,6 +728,14 @@ async function handleApiRoutes(req, res, url, options) {
622
728
  await handleMetaDelete(req, res, options);
623
729
  return;
624
730
  }
731
+ if (url.pathname === "/api/relation") {
732
+ await handleRelation(req, res, options);
733
+ return;
734
+ }
735
+ if (url.pathname === "/api/print/posts") {
736
+ await handlePrintPosts(req, res, url, options);
737
+ return;
738
+ }
625
739
  if (url.pathname === "/api/posts") {
626
740
  await handlePosts(req, res, url, options);
627
741
  return;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/open-browser.ts","../src/server.ts","../src/host.ts","../src/updates.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { readCredentials, readPort } from './config';\nimport { openBrowser } from './open-browser';\nimport { createDbpServer, type ConnectionState } from './server';\n\n/**\n * Locate the built UI assets. Works both in the workspace (symlinked package) and\n * when installed from npm, as long as `@dbp-wp/ui` ships its `dist` directory.\n */\nfunction resolveUiDir(): string | null {\n try {\n const require = createRequire(import.meta.url);\n const pkgPath = require.resolve('@dbp-wp/ui/package.json');\n const dist = join(dirname(pkgPath), 'dist');\n if (existsSync(join(dist, 'index.html'))) {\n return dist;\n }\n } catch {\n // Package not resolvable yet (e.g. UI not built); fall back to skeleton page.\n }\n return null;\n}\n\nfunction main(): void {\n // Credentials live in memory only; the env vars seed an initial connection.\n const state: ConnectionState = { credentials: readCredentials(), connectorAvailable: null };\n const port = readPort();\n const uiDir = resolveUiDir();\n\n const server = createDbpServer({ state, uiDir });\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n process.stderr.write(\n `Port ${port} is already in use. Set DBP_WP_CLI_PORT to choose another port.\\n`,\n );\n } else if (err.code === 'EACCES') {\n process.stderr.write(`Permission denied binding port ${port}. Try a port >= 1024.\\n`);\n } else {\n process.stderr.write(`Server error: ${err.message}\\n`);\n }\n process.exit(1);\n });\n server.listen(port, '127.0.0.1', () => {\n const url = `http://localhost:${port}/`;\n process.stdout.write(`DBP WP is running at ${url}\\n`);\n if (!state.credentials) {\n process.stdout.write(\n 'No WordPress connection yet; connect from the browser UI (or set DBP_WP_SITE_URL / DBP_WP_USERNAME / DBP_WP_APP_PASSWORD).\\n',\n );\n }\n if (!uiDir) {\n process.stdout.write('UI assets not found; run `npm run build` first.\\n');\n }\n openBrowser(url);\n });\n}\n\nmain();\n","import type { WpCredentials } from '@dbp-wp/core';\n\n/** Default localhost port for the CLI server. */\nexport const DEFAULT_PORT = 4317;\n\n/**\n * Read WordPress credentials from the environment. Returns null unless all three\n * fields are present, so the app can run in skeleton mode without a connection.\n */\nexport function readCredentials(env: NodeJS.ProcessEnv = process.env): WpCredentials | null {\n const siteUrl = env.DBP_WP_SITE_URL?.trim();\n const username = env.DBP_WP_USERNAME?.trim();\n const applicationPassword = env.DBP_WP_APP_PASSWORD?.trim();\n if (!siteUrl || !username || !applicationPassword) {\n return null;\n }\n return { siteUrl, username, applicationPassword };\n}\n\n/**\n * Parse credentials submitted over the API. Returns null unless all three fields are\n * present non-empty strings. Input is untrusted, so the shape is validated explicitly.\n */\nexport function parseCredentialsInput(body: unknown): WpCredentials | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const fields = body as Record<string, unknown>;\n const siteUrl = typeof fields.siteUrl === 'string' ? fields.siteUrl.trim() : '';\n const username = typeof fields.username === 'string' ? fields.username.trim() : '';\n const applicationPassword =\n typeof fields.applicationPassword === 'string' ? fields.applicationPassword.trim() : '';\n if (!siteUrl || !username || !applicationPassword) {\n return null;\n }\n return { siteUrl, username, applicationPassword };\n}\n\n/** Resolve the server port from the environment, falling back to {@link DEFAULT_PORT}. */\nexport function readPort(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.DBP_WP_CLI_PORT?.trim();\n // Digits only: Number.parseInt would otherwise accept values like \"3000x\" as 3000.\n if (!raw || !/^\\d+$/.test(raw)) {\n return DEFAULT_PORT;\n }\n const port = Number.parseInt(raw, 10);\n if (port < 1 || port > 65535) {\n return DEFAULT_PORT;\n }\n return port;\n}\n","import { spawn } from 'node:child_process';\n\nexport interface BrowserCommand {\n command: string;\n args: string[];\n}\n\n/** Return the OS-specific command to open a URL in the default browser. */\nexport function browserCommand(url: string, platform: NodeJS.Platform = process.platform): BrowserCommand {\n switch (platform) {\n case 'darwin':\n return { command: 'open', args: [url] };\n case 'win32':\n // `start` is a cmd builtin; the empty title argument avoids quoting pitfalls.\n return { command: 'cmd', args: ['/c', 'start', '', url] };\n default:\n return { command: 'xdg-open', args: [url] };\n }\n}\n\n/** Best-effort: open the URL in the default browser. Never throws. */\nexport function openBrowser(url: string): void {\n const { command, args } = browserCommand(url);\n try {\n const child = spawn(command, args, { stdio: 'ignore', detached: true });\n // If the launcher is missing, the user can still open the URL manually.\n child.on('error', () => {});\n child.unref();\n } catch {\n // Ignore: opening a browser is a convenience, not a requirement.\n }\n}\n","import { createReadStream } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { extname, join, relative, resolve } from 'node:path';\nimport { WpClient, WpRequestError, type WpCredentials } from '@dbp-wp/core';\nimport { parseCredentialsInput } from './config';\nimport { isAllowedHost, isCrossSiteRequest, isJsonContentType } from './host';\nimport {\n parseBatchUpdates,\n parseBulkMetaDelete,\n parseImportCreates,\n parseMetaDelete,\n parsePostTypeSlug,\n} from './updates';\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.png': 'image/png',\n '.woff2': 'font/woff2',\n '.map': 'application/json; charset=utf-8',\n};\n\nconst MAX_BODY_BYTES = 1_000_000;\n\n/** Mutable connection state, held in memory only (never written to disk). */\nexport interface ConnectionState {\n credentials: WpCredentials | null;\n /**\n * Whether the companion plugin is active on the connected site. `null` until first\n * detected (e.g. an env-seeded connection that has not been probed yet).\n */\n connectorAvailable: boolean | null;\n}\n\nexport interface ServerOptions {\n state: ConnectionState;\n /** Absolute path to the built UI `dist` directory, or null when not built. */\n uiDir: string | null;\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(body));\n}\n\nfunction sendError(res: ServerResponse, status: number, message: string): void {\n if (!res.headersSent) {\n res.writeHead(status, { 'Content-Type': 'text/plain; charset=utf-8' });\n }\n res.end(message);\n}\n\n/** Read and JSON-parse a request body, rejecting bodies over the size limit. */\nasync function readJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolvePromise, reject) => {\n let size = 0;\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n reject(new Error('Request body too large'));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on('end', () => {\n const text = Buffer.concat(chunks).toString('utf-8');\n if (!text) {\n resolvePromise({});\n return;\n }\n try {\n resolvePromise(JSON.parse(text));\n } catch {\n reject(new Error('Invalid JSON body'));\n }\n });\n req.on('error', reject);\n });\n}\n\nasync function handlePosts(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n options: ServerOptions,\n): Promise<void> {\n try {\n if (req.method !== 'GET') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 200, { posts: [], unconfigured: true });\n return;\n }\n const client = new WpClient(credentials);\n const type = url.searchParams.get('type');\n const pageRaw = url.searchParams.get('page');\n const page = pageRaw ? Number.parseInt(pageRaw, 10) : undefined;\n const posts = await client.listPosts({\n ...(type ? { type } : {}),\n ...(page && page > 0 ? { page } : {}),\n });\n sendJson(res, 200, { posts, unconfigured: false });\n } catch (e) {\n if (!res.headersSent) {\n sendJson(res, 502, { error: e instanceof Error ? e.message : 'Upstream request failed' });\n }\n }\n}\n\nasync function handleTypes(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'GET') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 200, { types: [] });\n return;\n }\n try {\n const types = await new WpClient(credentials).listPostTypes();\n sendJson(res, 200, { types });\n } catch (e) {\n if (!res.headersSent) {\n sendJson(res, 502, { error: e instanceof Error ? e.message : 'Upstream request failed' });\n }\n }\n}\n\nasync function handlePostsBatch(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'POST') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const updates = parseBatchUpdates(body);\n if (!updates) {\n sendJson(res, 400, { error: 'Invalid updates payload.' });\n return;\n }\n const type = parsePostTypeSlug((body as Record<string, unknown>).type);\n if (type === null) {\n sendJson(res, 400, { error: 'Invalid post type.' });\n return;\n }\n\n // Apply sequentially; report success/failure per row rather than failing the batch.\n // The whole batch targets one post type (the slug threaded from the UI's selector).\n const client = new WpClient(credentials);\n const results: Array<{ id: number; ok: boolean; error?: string }> = [];\n for (const update of updates) {\n try {\n // Standard fields and connector meta ride a single request per row.\n await client.updatePost(update.id, update.fields, type, update.meta);\n results.push({ id: update.id, ok: true });\n } catch (e) {\n results.push({ id: update.id, ok: false, error: e instanceof Error ? e.message : 'Update failed' });\n }\n }\n sendJson(res, 200, { results });\n}\n\nasync function handlePostsImport(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'POST') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const creates = parseImportCreates(body);\n if (!creates) {\n sendJson(res, 400, { error: 'Invalid import payload.' });\n return;\n }\n const type = parsePostTypeSlug((body as Record<string, unknown>).type);\n if (type === null) {\n sendJson(res, 400, { error: 'Invalid post type.' });\n return;\n }\n\n // Create sequentially; report success/failure per row (with the new id) rather than\n // failing the whole import. The batch targets one post type (slug from the UI).\n const client = new WpClient(credentials);\n const results: Array<{ index: number; ok: boolean; id?: number; error?: string }> = [];\n for (const [i, create] of creates.entries()) {\n try {\n const post = await client.createPost(create.fields, type, create.meta);\n results.push({ index: i, ok: true, id: post.id });\n } catch (e) {\n results.push({\n index: i,\n ok: false,\n error: e instanceof Error ? e.message : 'Create failed',\n });\n }\n }\n sendJson(res, 200, { results });\n}\n\nasync function handleMetaDelete(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'DELETE') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const request = parseMetaDelete(body);\n if (!request) {\n sendJson(res, 400, { error: 'Invalid meta-delete payload.' });\n return;\n }\n\n try {\n const client = new WpClient(credentials);\n const result = await client.deletePostMeta(request.id, request.keys);\n sendJson(res, 200, result);\n } catch (e) {\n if (!res.headersSent) {\n if (e instanceof WpRequestError && e.status === 404) {\n // A 404 on the connector route means the companion plugin is not active.\n sendJson(res, 409, {\n error: 'The companion plugin is required to delete meta, but it was not found on the site.',\n });\n } else {\n sendJson(res, 502, { error: e instanceof Error ? e.message : 'Meta delete failed' });\n }\n }\n }\n}\n\nasync function handleBulkMetaDelete(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'DELETE') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const deletes = parseBulkMetaDelete(body);\n if (!deletes) {\n sendJson(res, 400, { error: 'Invalid bulk meta-delete payload.' });\n return;\n }\n\n // Apply sequentially; report success/failure per post rather than failing the batch.\n const client = new WpClient(credentials);\n const results: Array<{ id: number; ok: boolean; error?: string }> = [];\n for (const del of deletes) {\n try {\n await client.deletePostMeta(del.id, del.keys);\n results.push({ id: del.id, ok: true });\n } catch (e) {\n const error =\n e instanceof WpRequestError && e.status === 404\n ? 'Companion plugin not found.'\n : e instanceof Error\n ? e.message\n : 'Delete failed';\n results.push({ id: del.id, ok: false, error });\n }\n }\n sendJson(res, 200, { results });\n}\n\nasync function handleConnection(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method === 'GET') {\n const c = options.state.credentials;\n // Lazily detect the connector for connections that were never probed (e.g. seeded\n // from env vars), caching the result so we only hit the REST index once. Concurrent\n // first requests may probe more than once (benign; detection is deterministic), and a\n // transient probe failure caches restricted mode until the next reconnect.\n if (c && options.state.connectorAvailable === null) {\n try {\n options.state.connectorAvailable = await new WpClient(c).detectConnector();\n } catch {\n options.state.connectorAvailable = false;\n }\n }\n sendJson(res, 200, {\n connected: c !== null,\n siteUrl: c?.siteUrl ?? null,\n connectorAvailable: options.state.connectorAvailable ?? false,\n });\n return;\n }\n\n if (req.method === 'DELETE') {\n options.state.credentials = null;\n options.state.connectorAvailable = null;\n sendJson(res, 200, { connected: false, siteUrl: null, connectorAvailable: false });\n return;\n }\n\n if (req.method === 'POST') {\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const credentials = parseCredentialsInput(body);\n if (!credentials) {\n sendJson(res, 400, {\n error: 'siteUrl, username, and applicationPassword are required.',\n });\n return;\n }\n let client: WpClient;\n try {\n client = new WpClient(credentials);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid site URL' });\n return;\n }\n try {\n // Probe the connection so bad credentials fail here, not on first use.\n await client.listPosts({ perPage: 1 });\n } catch (e) {\n // Return a fixed message rather than echoing raw upstream details.\n const status = e instanceof WpRequestError ? e.status : 0;\n const message =\n status === 401 || status === 403\n ? 'Authentication failed. Check the username and Application Password.'\n : 'Could not connect to the WordPress REST API. Check the site URL.';\n sendJson(res, 502, { error: message });\n return;\n }\n options.state.credentials = credentials;\n // Detect the companion plugin; absence is not a connection failure (restricted mode).\n try {\n options.state.connectorAvailable = await client.detectConnector();\n } catch {\n options.state.connectorAvailable = false;\n }\n sendJson(res, 200, {\n connected: true,\n siteUrl: credentials.siteUrl,\n connectorAvailable: options.state.connectorAvailable,\n });\n return;\n }\n\n sendJson(res, 405, { error: 'Method not allowed' });\n}\n\nasync function handleApiRoutes(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n options: ServerOptions,\n): Promise<void> {\n if (url.pathname === '/api/connection') {\n await handleConnection(req, res, options);\n return;\n }\n if (url.pathname === '/api/types') {\n await handleTypes(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts/batch') {\n await handlePostsBatch(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts/import') {\n await handlePostsImport(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts/meta/bulk') {\n await handleBulkMetaDelete(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts/meta') {\n await handleMetaDelete(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts') {\n await handlePosts(req, res, url, options);\n return;\n }\n sendJson(res, 404, { error: 'Not found' });\n}\n\n/** Resolve a request path to a file inside uiDir, or null if it escapes the root. */\nfunction resolveSafe(uiDir: string, pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n const candidate = resolve(uiDir, '.' + (decoded === '/' ? '/index.html' : decoded));\n const rel = relative(uiDir, candidate);\n if (rel === '') {\n return join(uiDir, 'index.html');\n }\n if (rel.startsWith('..') || resolve(uiDir, rel) !== candidate) {\n return null;\n }\n return candidate;\n}\n\n/** Stream a file to the response with an error handler so failures never crash. */\nfunction pipeFile(res: ServerResponse, filePath: string, contentType: string): void {\n const stream = createReadStream(filePath);\n stream.on('error', () => {\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal error');\n } else {\n res.destroy();\n }\n });\n res.writeHead(200, { 'Content-Type': contentType });\n stream.pipe(res);\n}\n\nasync function serveStatic(\n res: ServerResponse,\n uiDir: string | null,\n pathname: string,\n): Promise<void> {\n if (!uiDir) {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(notBuiltPage());\n return;\n }\n\n const filePath = resolveSafe(uiDir, pathname);\n if (!filePath) {\n sendError(res, 403, 'Forbidden');\n return;\n }\n\n if (await statFile(filePath)) {\n pipeFile(res, filePath, MIME[extname(filePath)] ?? 'application/octet-stream');\n return;\n }\n\n // SPA fallback: serve index.html for extension-less routes; otherwise 404.\n if (extname(pathname) === '') {\n const indexPath = join(uiDir, 'index.html');\n if (await statFile(indexPath)) {\n pipeFile(res, indexPath, 'text/html; charset=utf-8');\n return;\n }\n }\n sendError(res, 404, 'Not found');\n}\n\nasync function statFile(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\nfunction notBuiltPage(): string {\n return [\n '<!doctype html><meta charset=\"utf-8\"><title>DBP WP</title>',\n '<body style=\"font-family:system-ui;padding:2rem\">',\n '<h1>DBP WP</h1>',\n '<p>The UI has not been built yet. Run <code>npm run build</code> and restart.</p>',\n '</body>',\n ].join('');\n}\n\n/** Create the local HTTP server: serves the UI and a small JSON API. */\nexport function createDbpServer(options: ServerOptions): Server {\n return createServer((req, res) => {\n // Reject non-loopback Host headers (DNS rebinding protection).\n if (!isAllowedHost(req.headers.host)) {\n sendError(res, 403, 'Forbidden');\n return;\n }\n\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {\n // Block cross-site browser requests to the API (CSRF / outbound-probe abuse).\n if (isCrossSiteRequest(req.headers['sec-fetch-site'])) {\n sendError(res, 403, 'Forbidden');\n return;\n }\n void handleApiRoutes(req, res, url, options).catch(() =>\n sendError(res, 500, 'Internal error'),\n );\n return;\n }\n void serveStatic(res, options.uiDir, url.pathname).catch(() =>\n sendError(res, 500, 'Internal error'),\n );\n });\n}\n","// Only loopback hosts are allowed. Rejecting any other Host header blocks DNS\n// rebinding attacks, which would otherwise let a remote page reach this local API\n// through the victim's browser.\nconst ALLOWED_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);\n\n/** True when the request's Host header names a loopback address. */\nexport function isAllowedHost(hostHeader: string | undefined): boolean {\n if (!hostHeader) {\n return false;\n }\n let hostname: string;\n try {\n hostname = new URL(`http://${hostHeader}`).hostname;\n } catch {\n return false;\n }\n return ALLOWED_HOSTS.has(hostname);\n}\n\n/**\n * True when the browser reports this as a cross-site request. Blocks a malicious page\n * from driving the local API (CSRF). Non-browser clients (curl, tests) omit the header\n * and are allowed.\n */\nexport function isCrossSiteRequest(secFetchSite: string | string[] | undefined): boolean {\n return secFetchSite === 'cross-site';\n}\n\n/** True when the Content-Type declares a JSON body. */\nexport function isJsonContentType(contentType: string | undefined): boolean {\n if (typeof contentType !== 'string') {\n return false;\n }\n return contentType.split(';')[0]?.trim().toLowerCase() === 'application/json';\n}\n","import type { UpdatePostFields } from '@dbp-wp/core';\n\n/** A single validated post update parsed from an API request. */\nexport interface BatchUpdate {\n id: number;\n fields: UpdatePostFields;\n /** Arbitrary meta to write via the companion plugin (omitted when none/empty). */\n meta?: Record<string, unknown>;\n}\n\n/** Maximum updates accepted in one batch request. */\nexport const MAX_UPDATES = 500;\n\n/** Allowed characters for a REST post-type route slug. No dots: a `.`/`..` segment\n * would be resolved by the URL parser and traverse the REST path. */\nconst ROUTE_SLUG = /^[a-z0-9_-]+$/i;\n\n/**\n * Validate an optional REST post-type slug from a request body. Returns the default\n * `posts` when absent, the slug when valid, or null when present but malformed.\n */\nexport function parsePostTypeSlug(value: unknown): string | null {\n if (value === undefined) {\n return 'posts';\n }\n if (typeof value !== 'string' || !ROUTE_SLUG.test(value)) {\n return null;\n }\n return value;\n}\n\n// WordPress stores menu_order in a signed 32-bit column; reject values outside that range.\nconst MENU_ORDER_MIN = -2_147_483_648;\nconst MENU_ORDER_MAX = 2_147_483_647;\n\n/**\n * Parse the three editable standard fields (title / menuOrder / status) from a record.\n * Returns a (possibly empty) fields object, or null if a present field has the wrong\n * type or `menuOrder` falls outside the signed 32-bit range. The caller decides whether\n * an empty result (no fields set) is acceptable.\n */\nfunction parseEditableFields(record: Record<string, unknown>): UpdatePostFields | null {\n const fields: UpdatePostFields = {};\n if (record.title !== undefined) {\n if (typeof record.title !== 'string') {\n return null;\n }\n fields.title = record.title;\n }\n if (record.menuOrder !== undefined) {\n if (\n typeof record.menuOrder !== 'number' ||\n !Number.isInteger(record.menuOrder) ||\n record.menuOrder < MENU_ORDER_MIN ||\n record.menuOrder > MENU_ORDER_MAX\n ) {\n return null;\n }\n fields.menuOrder = record.menuOrder;\n }\n if (record.status !== undefined) {\n if (typeof record.status !== 'string') {\n return null;\n }\n fields.status = record.status;\n }\n return fields;\n}\n\n/**\n * Parse optional meta from a record. Returns `undefined` when absent or empty, the\n * cleaned meta map when present, or null when malformed. Distinguishes \"no meta\" from\n * \"invalid meta\" via the `ok` flag so callers can reject the latter.\n */\nfunction parseOptionalMeta(\n record: Record<string, unknown>,\n): { ok: true; meta: Record<string, unknown> | undefined } | { ok: false } {\n if (record.meta === undefined) {\n return { ok: true, meta: undefined };\n }\n const parsedMeta = parseMetaInput(record.meta);\n if (parsedMeta === null) {\n return { ok: false };\n }\n return { ok: true, meta: Object.keys(parsedMeta).length > 0 ? parsedMeta : undefined };\n}\n\n/**\n * Validate a batch-update payload from untrusted input. Returns null on any malformed\n * item, empty list, or oversized batch. Each item must have a positive integer `id`\n * and at least one editable field (title / menuOrder / status) of the right type.\n */\nexport function parseBatchUpdates(body: unknown): BatchUpdate[] | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const updates = (body as Record<string, unknown>).updates;\n if (!Array.isArray(updates) || updates.length === 0 || updates.length > MAX_UPDATES) {\n return null;\n }\n\n const result: BatchUpdate[] = [];\n for (const item of updates) {\n if (typeof item !== 'object' || item === null) {\n return null;\n }\n const record = item as Record<string, unknown>;\n if (typeof record.id !== 'number' || !Number.isSafeInteger(record.id) || record.id <= 0) {\n return null;\n }\n\n const fields = parseEditableFields(record);\n if (fields === null) {\n return null;\n }\n const meta = parseOptionalMeta(record);\n if (!meta.ok) {\n return null;\n }\n\n if (Object.keys(fields).length === 0 && meta.meta === undefined) {\n return null;\n }\n result.push(\n meta.meta !== undefined ? { id: record.id, fields, meta: meta.meta } : { id: record.id, fields },\n );\n }\n return result;\n}\n\n/** A single validated new-post creation parsed from an import request. */\nexport interface ImportCreate {\n fields: UpdatePostFields;\n /** Arbitrary meta to write via the companion plugin (omitted when none/empty). */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Validate an import payload (`{ creates: [{ title?, menuOrder?, status?, meta? }] }`)\n * from untrusted input. Returns null on an empty/oversized list or any malformed item.\n * Unlike a batch update there is no `id` (these are new posts), but each item must still\n * carry at least one field or meta entry so it does not create a blank post.\n */\nexport function parseImportCreates(body: unknown): ImportCreate[] | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const creates = (body as Record<string, unknown>).creates;\n if (!Array.isArray(creates) || creates.length === 0 || creates.length > MAX_UPDATES) {\n return null;\n }\n\n const result: ImportCreate[] = [];\n for (const item of creates) {\n if (typeof item !== 'object' || item === null) {\n return null;\n }\n const record = item as Record<string, unknown>;\n const fields = parseEditableFields(record);\n if (fields === null) {\n return null;\n }\n const meta = parseOptionalMeta(record);\n if (!meta.ok) {\n return null;\n }\n if (Object.keys(fields).length === 0 && meta.meta === undefined) {\n return null;\n }\n result.push(meta.meta !== undefined ? { fields, meta: meta.meta } : { fields });\n }\n return result;\n}\n\n/**\n * Validate a meta map from untrusted input: a plain object whose values are scalars\n * (string / number / boolean) or null, with non-empty keys. Returns null on any\n * malformed shape. The companion plugin writes scalar values only.\n */\nexport function parseMetaInput(value: unknown): Record<string, unknown> | null {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return null;\n }\n // Null-prototype map so pathological keys (`__proto__`, `constructor`) are stored as\n // ordinary own properties rather than dropped or touching any object prototype.\n const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n if (key.length === 0) {\n return null;\n }\n if (\n entry !== null &&\n typeof entry !== 'string' &&\n typeof entry !== 'number' &&\n typeof entry !== 'boolean'\n ) {\n return null;\n }\n result[key] = entry;\n }\n return result;\n}\n\n/** A validated per-post meta-deletion request. */\nexport interface MetaDelete {\n id: number;\n keys: string[];\n}\n\n/**\n * Validate a meta-delete payload from untrusted input. Returns null unless `id` is a\n * positive integer and `keys` is an array with at least one non-empty string\n * (non-string / empty entries are dropped).\n */\nexport function parseMetaDelete(body: unknown): MetaDelete | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const record = body as Record<string, unknown>;\n if (typeof record.id !== 'number' || !Number.isSafeInteger(record.id) || record.id <= 0) {\n return null;\n }\n if (!Array.isArray(record.keys)) {\n return null;\n }\n const keys = record.keys.filter(\n (key): key is string => typeof key === 'string' && key.length > 0,\n );\n if (keys.length === 0) {\n return null;\n }\n return { id: record.id, keys };\n}\n\n/**\n * Validate a bulk meta-delete payload: `{ deletes: [{ id, keys }] }`. Returns null on an\n * empty/oversized list or any malformed item (each item is validated like a single\n * {@link parseMetaDelete}).\n */\nexport function parseBulkMetaDelete(body: unknown): MetaDelete[] | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const deletes = (body as Record<string, unknown>).deletes;\n if (!Array.isArray(deletes) || deletes.length === 0 || deletes.length > MAX_UPDATES) {\n return null;\n }\n const result: MetaDelete[] = [];\n for (const item of deletes) {\n const parsed = parseMetaDelete(item);\n if (!parsed) {\n return null;\n }\n result.push(parsed);\n }\n return result;\n}\n"],"mappings":"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","names":["join","require","join"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/open-browser.ts","../src/server.ts","../src/host.ts","../src/updates.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { readCredentials, readPort } from './config';\nimport { openBrowser } from './open-browser';\nimport { createDbpServer, type ConnectionState } from './server';\n\n/**\n * Locate the built UI assets. Works both in the workspace (symlinked package) and\n * when installed from npm, as long as `@dbp-wp/ui` ships its `dist` directory.\n */\nfunction resolveUiDir(): string | null {\n try {\n const require = createRequire(import.meta.url);\n const pkgPath = require.resolve('@dbp-wp/ui/package.json');\n const dist = join(dirname(pkgPath), 'dist');\n if (existsSync(join(dist, 'index.html'))) {\n return dist;\n }\n } catch {\n // Package not resolvable yet (e.g. UI not built); fall back to skeleton page.\n }\n return null;\n}\n\nfunction main(): void {\n // Credentials live in memory only; the env vars seed an initial connection.\n const state: ConnectionState = { credentials: readCredentials(), connectorAvailable: null };\n const port = readPort();\n const uiDir = resolveUiDir();\n\n const server = createDbpServer({ state, uiDir });\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n process.stderr.write(\n `Port ${port} is already in use. Set DBP_WP_CLI_PORT to choose another port.\\n`,\n );\n } else if (err.code === 'EACCES') {\n process.stderr.write(`Permission denied binding port ${port}. Try a port >= 1024.\\n`);\n } else {\n process.stderr.write(`Server error: ${err.message}\\n`);\n }\n process.exit(1);\n });\n server.listen(port, '127.0.0.1', () => {\n const url = `http://localhost:${port}/`;\n process.stdout.write(`DBP WP is running at ${url}\\n`);\n if (!state.credentials) {\n process.stdout.write(\n 'No WordPress connection yet; connect from the browser UI (or set DBP_WP_SITE_URL / DBP_WP_USERNAME / DBP_WP_APP_PASSWORD).\\n',\n );\n }\n if (!uiDir) {\n process.stdout.write('UI assets not found; run `npm run build` first.\\n');\n }\n openBrowser(url);\n });\n}\n\nmain();\n","import type { WpCredentials } from '@dbp-wp/core';\n\n/** Default localhost port for the CLI server. */\nexport const DEFAULT_PORT = 4317;\n\n/**\n * Read WordPress credentials from the environment. Returns null unless all three\n * fields are present, so the app can run in skeleton mode without a connection.\n */\nexport function readCredentials(env: NodeJS.ProcessEnv = process.env): WpCredentials | null {\n const siteUrl = env.DBP_WP_SITE_URL?.trim();\n const username = env.DBP_WP_USERNAME?.trim();\n const applicationPassword = env.DBP_WP_APP_PASSWORD?.trim();\n if (!siteUrl || !username || !applicationPassword) {\n return null;\n }\n return { siteUrl, username, applicationPassword };\n}\n\n/**\n * Parse credentials submitted over the API. Returns null unless all three fields are\n * present non-empty strings. Input is untrusted, so the shape is validated explicitly.\n */\nexport function parseCredentialsInput(body: unknown): WpCredentials | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const fields = body as Record<string, unknown>;\n const siteUrl = typeof fields.siteUrl === 'string' ? fields.siteUrl.trim() : '';\n const username = typeof fields.username === 'string' ? fields.username.trim() : '';\n const applicationPassword =\n typeof fields.applicationPassword === 'string' ? fields.applicationPassword.trim() : '';\n if (!siteUrl || !username || !applicationPassword) {\n return null;\n }\n return { siteUrl, username, applicationPassword };\n}\n\n/** Resolve the server port from the environment, falling back to {@link DEFAULT_PORT}. */\nexport function readPort(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.DBP_WP_CLI_PORT?.trim();\n // Digits only: Number.parseInt would otherwise accept values like \"3000x\" as 3000.\n if (!raw || !/^\\d+$/.test(raw)) {\n return DEFAULT_PORT;\n }\n const port = Number.parseInt(raw, 10);\n if (port < 1 || port > 65535) {\n return DEFAULT_PORT;\n }\n return port;\n}\n","import { spawn } from 'node:child_process';\n\nexport interface BrowserCommand {\n command: string;\n args: string[];\n}\n\n/** Return the OS-specific command to open a URL in the default browser. */\nexport function browserCommand(url: string, platform: NodeJS.Platform = process.platform): BrowserCommand {\n switch (platform) {\n case 'darwin':\n return { command: 'open', args: [url] };\n case 'win32':\n // `start` is a cmd builtin; the empty title argument avoids quoting pitfalls.\n return { command: 'cmd', args: ['/c', 'start', '', url] };\n default:\n return { command: 'xdg-open', args: [url] };\n }\n}\n\n/** Best-effort: open the URL in the default browser. Never throws. */\nexport function openBrowser(url: string): void {\n const { command, args } = browserCommand(url);\n try {\n const child = spawn(command, args, { stdio: 'ignore', detached: true });\n // If the launcher is missing, the user can still open the URL manually.\n child.on('error', () => {});\n child.unref();\n } catch {\n // Ignore: opening a browser is a convenience, not a requirement.\n }\n}\n","import { createReadStream } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { extname, join, relative, resolve } from 'node:path';\nimport { RelationError, WpClient, WpRequestError, type WpCredentials } from '@dbp-wp/core';\nimport { parseCredentialsInput } from './config';\nimport { isAllowedHost, isCrossSiteRequest, isJsonContentType } from './host';\nimport {\n parseBatchUpdates,\n parseBulkMetaDelete,\n parseImportCreates,\n parseMetaDelete,\n parsePostTypeSlug,\n parseRelation,\n} from './updates';\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.png': 'image/png',\n '.woff2': 'font/woff2',\n '.map': 'application/json; charset=utf-8',\n};\n\nconst MAX_BODY_BYTES = 1_000_000;\n\n/** Mutable connection state, held in memory only (never written to disk). */\nexport interface ConnectionState {\n credentials: WpCredentials | null;\n /**\n * Whether the companion plugin is active on the connected site. `null` until first\n * detected (e.g. an env-seeded connection that has not been probed yet).\n */\n connectorAvailable: boolean | null;\n}\n\nexport interface ServerOptions {\n state: ConnectionState;\n /** Absolute path to the built UI `dist` directory, or null when not built. */\n uiDir: string | null;\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(JSON.stringify(body));\n}\n\nfunction sendError(res: ServerResponse, status: number, message: string): void {\n if (!res.headersSent) {\n res.writeHead(status, { 'Content-Type': 'text/plain; charset=utf-8' });\n }\n res.end(message);\n}\n\n/** Read and JSON-parse a request body, rejecting bodies over the size limit. */\nasync function readJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolvePromise, reject) => {\n let size = 0;\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n reject(new Error('Request body too large'));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on('end', () => {\n const text = Buffer.concat(chunks).toString('utf-8');\n if (!text) {\n resolvePromise({});\n return;\n }\n try {\n resolvePromise(JSON.parse(text));\n } catch {\n reject(new Error('Invalid JSON body'));\n }\n });\n req.on('error', reject);\n });\n}\n\nasync function handlePosts(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n options: ServerOptions,\n): Promise<void> {\n try {\n if (req.method !== 'GET') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 200, { posts: [], unconfigured: true });\n return;\n }\n const client = new WpClient(credentials);\n const type = url.searchParams.get('type');\n const pageRaw = url.searchParams.get('page');\n const page = pageRaw ? Number.parseInt(pageRaw, 10) : undefined;\n const posts = await client.listPosts({\n ...(type ? { type } : {}),\n ...(page && page > 0 ? { page } : {}),\n });\n sendJson(res, 200, { posts, unconfigured: false });\n } catch (e) {\n if (!res.headersSent) {\n sendJson(res, 502, { error: e instanceof Error ? e.message : 'Upstream request failed' });\n }\n }\n}\n\nasync function handlePrintPosts(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n options: ServerOptions,\n): Promise<void> {\n try {\n if (req.method !== 'GET') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 200, { records: [], unconfigured: true });\n return;\n }\n const client = new WpClient(credentials);\n const type = url.searchParams.get('type');\n const pageRaw = url.searchParams.get('page');\n const page = pageRaw ? Number.parseInt(pageRaw, 10) : undefined;\n // Print needs content/excerpt + embedded media/terms, so this uses the `_embed`\n // listing rather than the lean table/spreadsheet one.\n const records = await client.listPostsForPrint({\n ...(type ? { type } : {}),\n ...(page && page > 0 ? { page } : {}),\n });\n sendJson(res, 200, { records, unconfigured: false });\n } catch (e) {\n if (!res.headersSent) {\n sendJson(res, 502, { error: e instanceof Error ? e.message : 'Upstream request failed' });\n }\n }\n}\n\nasync function handleTypes(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'GET') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 200, { types: [] });\n return;\n }\n try {\n const types = await new WpClient(credentials).listPostTypes();\n sendJson(res, 200, { types });\n } catch (e) {\n if (!res.headersSent) {\n sendJson(res, 502, { error: e instanceof Error ? e.message : 'Upstream request failed' });\n }\n }\n}\n\nasync function handlePostsBatch(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'POST') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const updates = parseBatchUpdates(body);\n if (!updates) {\n sendJson(res, 400, { error: 'Invalid updates payload.' });\n return;\n }\n const type = parsePostTypeSlug((body as Record<string, unknown>).type);\n if (type === null) {\n sendJson(res, 400, { error: 'Invalid post type.' });\n return;\n }\n\n // Apply sequentially; report success/failure per row rather than failing the batch.\n // The whole batch targets one post type (the slug threaded from the UI's selector).\n const client = new WpClient(credentials);\n const results: Array<{ id: number; ok: boolean; error?: string }> = [];\n for (const update of updates) {\n try {\n // Standard fields and connector meta ride a single request per row.\n await client.updatePost(update.id, update.fields, type, update.meta);\n results.push({ id: update.id, ok: true });\n } catch (e) {\n results.push({ id: update.id, ok: false, error: e instanceof Error ? e.message : 'Update failed' });\n }\n }\n sendJson(res, 200, { results });\n}\n\nasync function handlePostsImport(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'POST') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const creates = parseImportCreates(body);\n if (!creates) {\n sendJson(res, 400, { error: 'Invalid import payload.' });\n return;\n }\n const type = parsePostTypeSlug((body as Record<string, unknown>).type);\n if (type === null) {\n sendJson(res, 400, { error: 'Invalid post type.' });\n return;\n }\n\n // Create sequentially; report success/failure per row (with the new id) rather than\n // failing the whole import. The batch targets one post type (slug from the UI).\n const client = new WpClient(credentials);\n const results: Array<{ index: number; ok: boolean; id?: number; error?: string }> = [];\n for (const [i, create] of creates.entries()) {\n try {\n const post = await client.createPost(create.fields, type, create.meta);\n results.push({ index: i, ok: true, id: post.id });\n } catch (e) {\n results.push({\n index: i,\n ok: false,\n error: e instanceof Error ? e.message : 'Create failed',\n });\n }\n }\n sendJson(res, 200, { results });\n}\n\nasync function handleMetaDelete(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'DELETE') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const request = parseMetaDelete(body);\n if (!request) {\n sendJson(res, 400, { error: 'Invalid meta-delete payload.' });\n return;\n }\n\n try {\n const client = new WpClient(credentials);\n const result = await client.deletePostMeta(request.id, request.keys);\n sendJson(res, 200, result);\n } catch (e) {\n if (!res.headersSent) {\n if (e instanceof WpRequestError && e.status === 404) {\n // A 404 on the connector route means the companion plugin is not active.\n sendJson(res, 409, {\n error: 'The companion plugin is required to delete meta, but it was not found on the site.',\n });\n } else {\n sendJson(res, 502, { error: e instanceof Error ? e.message : 'Meta delete failed' });\n }\n }\n }\n}\n\nasync function handleBulkMetaDelete(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'DELETE') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const deletes = parseBulkMetaDelete(body);\n if (!deletes) {\n sendJson(res, 400, { error: 'Invalid bulk meta-delete payload.' });\n return;\n }\n\n // Apply sequentially; report success/failure per post rather than failing the batch.\n const client = new WpClient(credentials);\n const results: Array<{ id: number; ok: boolean; error?: string }> = [];\n for (const del of deletes) {\n try {\n await client.deletePostMeta(del.id, del.keys);\n results.push({ id: del.id, ok: true });\n } catch (e) {\n const error =\n e instanceof WpRequestError && e.status === 404\n ? 'Companion plugin not found.'\n : e instanceof Error\n ? e.message\n : 'Delete failed';\n results.push({ id: del.id, ok: false, error });\n }\n }\n sendJson(res, 200, { results });\n}\n\nasync function handleRelation(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method !== 'POST') {\n sendJson(res, 405, { error: 'Method not allowed' });\n return;\n }\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n const credentials = options.state.credentials;\n if (!credentials) {\n sendJson(res, 409, { error: 'Not connected' });\n return;\n }\n\n // Relation meta is registered by the companion plugin; without it WordPress silently\n // ignores the keys, so a write would falsely look successful. Require the connector and\n // surface a clear 409. Probe lazily for connections that were never probed (env-seeded).\n if (options.state.connectorAvailable === null) {\n try {\n options.state.connectorAvailable = await new WpClient(credentials).detectConnector();\n } catch {\n options.state.connectorAvailable = false;\n }\n }\n if (!options.state.connectorAvailable) {\n sendJson(res, 409, {\n error: 'The companion plugin is required to edit relations, but it was not found on the site.',\n });\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const request = parseRelation(body);\n if (!request) {\n sendJson(res, 400, { error: 'Invalid relation payload.' });\n return;\n }\n\n try {\n const client = new WpClient(credentials);\n const post = request.relation\n ? await client.setRelation(request.childId, request.childType, request.relation)\n : await client.clearRelation(request.childId, request.childType);\n sendJson(res, 200, { post });\n } catch (e) {\n if (!res.headersSent) {\n // A rejected assignment (bad id/type, self-parent) is the caller's fault → 400.\n if (e instanceof RelationError) {\n sendJson(res, 400, { error: e.message });\n } else {\n sendJson(res, 502, { error: e instanceof Error ? e.message : 'Relation update failed' });\n }\n }\n }\n}\n\nasync function handleConnection(\n req: IncomingMessage,\n res: ServerResponse,\n options: ServerOptions,\n): Promise<void> {\n if (req.method === 'GET') {\n const c = options.state.credentials;\n // Lazily detect the connector for connections that were never probed (e.g. seeded\n // from env vars), caching the result so we only hit the REST index once. Concurrent\n // first requests may probe more than once (benign; detection is deterministic), and a\n // transient probe failure caches restricted mode until the next reconnect.\n if (c && options.state.connectorAvailable === null) {\n try {\n options.state.connectorAvailable = await new WpClient(c).detectConnector();\n } catch {\n options.state.connectorAvailable = false;\n }\n }\n sendJson(res, 200, {\n connected: c !== null,\n siteUrl: c?.siteUrl ?? null,\n connectorAvailable: options.state.connectorAvailable ?? false,\n });\n return;\n }\n\n if (req.method === 'DELETE') {\n options.state.credentials = null;\n options.state.connectorAvailable = null;\n sendJson(res, 200, { connected: false, siteUrl: null, connectorAvailable: false });\n return;\n }\n\n if (req.method === 'POST') {\n if (!isJsonContentType(req.headers['content-type'])) {\n sendJson(res, 415, { error: 'Content-Type must be application/json.' });\n return;\n }\n let body: unknown;\n try {\n body = await readJsonBody(req);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid request body' });\n return;\n }\n const credentials = parseCredentialsInput(body);\n if (!credentials) {\n sendJson(res, 400, {\n error: 'siteUrl, username, and applicationPassword are required.',\n });\n return;\n }\n let client: WpClient;\n try {\n client = new WpClient(credentials);\n } catch (e) {\n sendJson(res, 400, { error: e instanceof Error ? e.message : 'Invalid site URL' });\n return;\n }\n try {\n // Probe the connection so bad credentials fail here, not on first use.\n await client.listPosts({ perPage: 1 });\n } catch (e) {\n // Return a fixed message rather than echoing raw upstream details.\n const status = e instanceof WpRequestError ? e.status : 0;\n const message =\n status === 401 || status === 403\n ? 'Authentication failed. Check the username and Application Password.'\n : 'Could not connect to the WordPress REST API. Check the site URL.';\n sendJson(res, 502, { error: message });\n return;\n }\n options.state.credentials = credentials;\n // Detect the companion plugin; absence is not a connection failure (restricted mode).\n try {\n options.state.connectorAvailable = await client.detectConnector();\n } catch {\n options.state.connectorAvailable = false;\n }\n sendJson(res, 200, {\n connected: true,\n siteUrl: credentials.siteUrl,\n connectorAvailable: options.state.connectorAvailable,\n });\n return;\n }\n\n sendJson(res, 405, { error: 'Method not allowed' });\n}\n\nasync function handleApiRoutes(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n options: ServerOptions,\n): Promise<void> {\n if (url.pathname === '/api/connection') {\n await handleConnection(req, res, options);\n return;\n }\n if (url.pathname === '/api/types') {\n await handleTypes(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts/batch') {\n await handlePostsBatch(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts/import') {\n await handlePostsImport(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts/meta/bulk') {\n await handleBulkMetaDelete(req, res, options);\n return;\n }\n if (url.pathname === '/api/posts/meta') {\n await handleMetaDelete(req, res, options);\n return;\n }\n if (url.pathname === '/api/relation') {\n await handleRelation(req, res, options);\n return;\n }\n if (url.pathname === '/api/print/posts') {\n await handlePrintPosts(req, res, url, options);\n return;\n }\n if (url.pathname === '/api/posts') {\n await handlePosts(req, res, url, options);\n return;\n }\n sendJson(res, 404, { error: 'Not found' });\n}\n\n/** Resolve a request path to a file inside uiDir, or null if it escapes the root. */\nfunction resolveSafe(uiDir: string, pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n const candidate = resolve(uiDir, '.' + (decoded === '/' ? '/index.html' : decoded));\n const rel = relative(uiDir, candidate);\n if (rel === '') {\n return join(uiDir, 'index.html');\n }\n if (rel.startsWith('..') || resolve(uiDir, rel) !== candidate) {\n return null;\n }\n return candidate;\n}\n\n/** Stream a file to the response with an error handler so failures never crash. */\nfunction pipeFile(res: ServerResponse, filePath: string, contentType: string): void {\n const stream = createReadStream(filePath);\n stream.on('error', () => {\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Internal error');\n } else {\n res.destroy();\n }\n });\n res.writeHead(200, { 'Content-Type': contentType });\n stream.pipe(res);\n}\n\nasync function serveStatic(\n res: ServerResponse,\n uiDir: string | null,\n pathname: string,\n): Promise<void> {\n if (!uiDir) {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(notBuiltPage());\n return;\n }\n\n const filePath = resolveSafe(uiDir, pathname);\n if (!filePath) {\n sendError(res, 403, 'Forbidden');\n return;\n }\n\n if (await statFile(filePath)) {\n pipeFile(res, filePath, MIME[extname(filePath)] ?? 'application/octet-stream');\n return;\n }\n\n // SPA fallback: serve index.html for extension-less routes; otherwise 404.\n if (extname(pathname) === '') {\n const indexPath = join(uiDir, 'index.html');\n if (await statFile(indexPath)) {\n pipeFile(res, indexPath, 'text/html; charset=utf-8');\n return;\n }\n }\n sendError(res, 404, 'Not found');\n}\n\nasync function statFile(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\nfunction notBuiltPage(): string {\n return [\n '<!doctype html><meta charset=\"utf-8\"><title>DBP WP</title>',\n '<body style=\"font-family:system-ui;padding:2rem\">',\n '<h1>DBP WP</h1>',\n '<p>The UI has not been built yet. Run <code>npm run build</code> and restart.</p>',\n '</body>',\n ].join('');\n}\n\n/** Create the local HTTP server: serves the UI and a small JSON API. */\nexport function createDbpServer(options: ServerOptions): Server {\n return createServer((req, res) => {\n // Reject non-loopback Host headers (DNS rebinding protection).\n if (!isAllowedHost(req.headers.host)) {\n sendError(res, 403, 'Forbidden');\n return;\n }\n\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {\n // Block cross-site browser requests to the API (CSRF / outbound-probe abuse).\n if (isCrossSiteRequest(req.headers['sec-fetch-site'])) {\n sendError(res, 403, 'Forbidden');\n return;\n }\n void handleApiRoutes(req, res, url, options).catch(() =>\n sendError(res, 500, 'Internal error'),\n );\n return;\n }\n void serveStatic(res, options.uiDir, url.pathname).catch(() =>\n sendError(res, 500, 'Internal error'),\n );\n });\n}\n","// Only loopback hosts are allowed. Rejecting any other Host header blocks DNS\n// rebinding attacks, which would otherwise let a remote page reach this local API\n// through the victim's browser.\nconst ALLOWED_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);\n\n/** True when the request's Host header names a loopback address. */\nexport function isAllowedHost(hostHeader: string | undefined): boolean {\n if (!hostHeader) {\n return false;\n }\n let hostname: string;\n try {\n hostname = new URL(`http://${hostHeader}`).hostname;\n } catch {\n return false;\n }\n return ALLOWED_HOSTS.has(hostname);\n}\n\n/**\n * True when the browser reports this as a cross-site request. Blocks a malicious page\n * from driving the local API (CSRF). Non-browser clients (curl, tests) omit the header\n * and are allowed.\n */\nexport function isCrossSiteRequest(secFetchSite: string | string[] | undefined): boolean {\n return secFetchSite === 'cross-site';\n}\n\n/** True when the Content-Type declares a JSON body. */\nexport function isJsonContentType(contentType: string | undefined): boolean {\n if (typeof contentType !== 'string') {\n return false;\n }\n return contentType.split(';')[0]?.trim().toLowerCase() === 'application/json';\n}\n","import type { RelationTarget, UpdatePostFields } from '@dbp-wp/core';\n\n/** A single validated post update parsed from an API request. */\nexport interface BatchUpdate {\n id: number;\n fields: UpdatePostFields;\n /** Arbitrary meta to write via the companion plugin (omitted when none/empty). */\n meta?: Record<string, unknown>;\n}\n\n/** Maximum updates accepted in one batch request. */\nexport const MAX_UPDATES = 500;\n\n/** Allowed characters for a REST post-type route slug. No dots: a `.`/`..` segment\n * would be resolved by the URL parser and traverse the REST path. */\nconst ROUTE_SLUG = /^[a-z0-9_-]+$/i;\n\n/**\n * Validate an optional REST post-type slug from a request body. Returns the default\n * `posts` when absent, the slug when valid, or null when present but malformed.\n */\nexport function parsePostTypeSlug(value: unknown): string | null {\n if (value === undefined) {\n return 'posts';\n }\n if (typeof value !== 'string' || !ROUTE_SLUG.test(value)) {\n return null;\n }\n return value;\n}\n\n// WordPress stores menu_order in a signed 32-bit column; reject values outside that range.\nconst MENU_ORDER_MIN = -2_147_483_648;\nconst MENU_ORDER_MAX = 2_147_483_647;\n\n/**\n * Parse the three editable standard fields (title / menuOrder / status) from a record.\n * Returns a (possibly empty) fields object, or null if a present field has the wrong\n * type or `menuOrder` falls outside the signed 32-bit range. The caller decides whether\n * an empty result (no fields set) is acceptable.\n */\nfunction parseEditableFields(record: Record<string, unknown>): UpdatePostFields | null {\n const fields: UpdatePostFields = {};\n if (record.title !== undefined) {\n if (typeof record.title !== 'string') {\n return null;\n }\n fields.title = record.title;\n }\n if (record.menuOrder !== undefined) {\n if (\n typeof record.menuOrder !== 'number' ||\n !Number.isInteger(record.menuOrder) ||\n record.menuOrder < MENU_ORDER_MIN ||\n record.menuOrder > MENU_ORDER_MAX\n ) {\n return null;\n }\n fields.menuOrder = record.menuOrder;\n }\n if (record.status !== undefined) {\n if (typeof record.status !== 'string') {\n return null;\n }\n fields.status = record.status;\n }\n return fields;\n}\n\n/**\n * Parse optional meta from a record. Returns `undefined` when absent or empty, the\n * cleaned meta map when present, or null when malformed. Distinguishes \"no meta\" from\n * \"invalid meta\" via the `ok` flag so callers can reject the latter.\n */\nfunction parseOptionalMeta(\n record: Record<string, unknown>,\n): { ok: true; meta: Record<string, unknown> | undefined } | { ok: false } {\n if (record.meta === undefined) {\n return { ok: true, meta: undefined };\n }\n const parsedMeta = parseMetaInput(record.meta);\n if (parsedMeta === null) {\n return { ok: false };\n }\n return { ok: true, meta: Object.keys(parsedMeta).length > 0 ? parsedMeta : undefined };\n}\n\n/**\n * Validate a batch-update payload from untrusted input. Returns null on any malformed\n * item, empty list, or oversized batch. Each item must have a positive integer `id`\n * and at least one editable field (title / menuOrder / status) of the right type.\n */\nexport function parseBatchUpdates(body: unknown): BatchUpdate[] | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const updates = (body as Record<string, unknown>).updates;\n if (!Array.isArray(updates) || updates.length === 0 || updates.length > MAX_UPDATES) {\n return null;\n }\n\n const result: BatchUpdate[] = [];\n for (const item of updates) {\n if (typeof item !== 'object' || item === null) {\n return null;\n }\n const record = item as Record<string, unknown>;\n if (typeof record.id !== 'number' || !Number.isSafeInteger(record.id) || record.id <= 0) {\n return null;\n }\n\n const fields = parseEditableFields(record);\n if (fields === null) {\n return null;\n }\n const meta = parseOptionalMeta(record);\n if (!meta.ok) {\n return null;\n }\n\n if (Object.keys(fields).length === 0 && meta.meta === undefined) {\n return null;\n }\n result.push(\n meta.meta !== undefined ? { id: record.id, fields, meta: meta.meta } : { id: record.id, fields },\n );\n }\n return result;\n}\n\n/** A single validated new-post creation parsed from an import request. */\nexport interface ImportCreate {\n fields: UpdatePostFields;\n /** Arbitrary meta to write via the companion plugin (omitted when none/empty). */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Validate an import payload (`{ creates: [{ title?, menuOrder?, status?, meta? }] }`)\n * from untrusted input. Returns null on an empty/oversized list or any malformed item.\n * Unlike a batch update there is no `id` (these are new posts), but each item must still\n * carry at least one field or meta entry so it does not create a blank post.\n */\nexport function parseImportCreates(body: unknown): ImportCreate[] | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const creates = (body as Record<string, unknown>).creates;\n if (!Array.isArray(creates) || creates.length === 0 || creates.length > MAX_UPDATES) {\n return null;\n }\n\n const result: ImportCreate[] = [];\n for (const item of creates) {\n if (typeof item !== 'object' || item === null) {\n return null;\n }\n const record = item as Record<string, unknown>;\n const fields = parseEditableFields(record);\n if (fields === null) {\n return null;\n }\n const meta = parseOptionalMeta(record);\n if (!meta.ok) {\n return null;\n }\n if (Object.keys(fields).length === 0 && meta.meta === undefined) {\n return null;\n }\n result.push(meta.meta !== undefined ? { fields, meta: meta.meta } : { fields });\n }\n return result;\n}\n\n/**\n * Validate a meta map from untrusted input: a plain object whose values are scalars\n * (string / number / boolean) or null, with non-empty keys. Returns null on any\n * malformed shape. The companion plugin writes scalar values only.\n */\nexport function parseMetaInput(value: unknown): Record<string, unknown> | null {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return null;\n }\n // Null-prototype map so pathological keys (`__proto__`, `constructor`) are stored as\n // ordinary own properties rather than dropped or touching any object prototype.\n const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n if (key.length === 0) {\n return null;\n }\n if (\n entry !== null &&\n typeof entry !== 'string' &&\n typeof entry !== 'number' &&\n typeof entry !== 'boolean'\n ) {\n return null;\n }\n result[key] = entry;\n }\n return result;\n}\n\n/** A validated parent-relation request: set a child's parent, or clear it. */\nexport interface RelationRequest {\n childId: number;\n childType: string;\n /** Parent to set, or null to clear the relation. */\n relation: RelationTarget | null;\n}\n\n/**\n * Validate a relation payload (`{ childId, childType?, parentId, parentType? }`) from\n * untrusted input. `parentId: null` clears the relation; a positive integer sets it (and\n * `parentType` is then required and must be a valid route slug). Returns null on any\n * malformed shape. Relation semantics beyond shape (e.g. no self-parent) are enforced by\n * the core client when the write is built.\n */\nexport function parseRelation(body: unknown): RelationRequest | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const record = body as Record<string, unknown>;\n if (\n typeof record.childId !== 'number' ||\n !Number.isSafeInteger(record.childId) ||\n record.childId <= 0\n ) {\n return null;\n }\n const childType = parsePostTypeSlug(record.childType);\n if (childType === null) {\n return null;\n }\n\n // Explicit null clears the relation; absence/other types are malformed (not a clear).\n if (record.parentId === null) {\n return { childId: record.childId, childType, relation: null };\n }\n if (\n typeof record.parentId !== 'number' ||\n !Number.isSafeInteger(record.parentId) ||\n record.parentId <= 0\n ) {\n return null;\n }\n if (typeof record.parentType !== 'string' || !ROUTE_SLUG.test(record.parentType)) {\n return null;\n }\n return {\n childId: record.childId,\n childType,\n relation: { parentId: record.parentId, parentType: record.parentType },\n };\n}\n\n/** A validated per-post meta-deletion request. */\nexport interface MetaDelete {\n id: number;\n keys: string[];\n}\n\n/**\n * Validate a meta-delete payload from untrusted input. Returns null unless `id` is a\n * positive integer and `keys` is an array with at least one non-empty string\n * (non-string / empty entries are dropped).\n */\nexport function parseMetaDelete(body: unknown): MetaDelete | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const record = body as Record<string, unknown>;\n if (typeof record.id !== 'number' || !Number.isSafeInteger(record.id) || record.id <= 0) {\n return null;\n }\n if (!Array.isArray(record.keys)) {\n return null;\n }\n const keys = record.keys.filter(\n (key): key is string => typeof key === 'string' && key.length > 0,\n );\n if (keys.length === 0) {\n return null;\n }\n return { id: record.id, keys };\n}\n\n/**\n * Validate a bulk meta-delete payload: `{ deletes: [{ id, keys }] }`. Returns null on an\n * empty/oversized list or any malformed item (each item is validated like a single\n * {@link parseMetaDelete}).\n */\nexport function parseBulkMetaDelete(body: unknown): MetaDelete[] | null {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n const deletes = (body as Record<string, unknown>).deletes;\n if (!Array.isArray(deletes) || deletes.length === 0 || deletes.length > MAX_UPDATES) {\n return null;\n }\n const result: MetaDelete[] = [];\n for (const item of deletes) {\n const parsed = parseMetaDelete(item);\n if (!parsed) {\n return null;\n }\n result.push(parsed);\n }\n return result;\n}\n"],"mappings":"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","names":["join","require","join"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbp-wp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Local-first app to bulk-edit WordPress content over the REST API. Runs via npx: starts a localhost server, serves the web UI, and opens your browser.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Takashi Matsuyama",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "type": "module",
24
24
  "bin": {
25
- "dbp-wp": "./dist/index.js"
25
+ "dbp-wp": "dist/index.js"
26
26
  },
27
27
  "files": [
28
28
  "dist"
@@ -36,8 +36,8 @@
36
36
  "start": "node dist/index.js"
37
37
  },
38
38
  "dependencies": {
39
- "@dbp-wp/core": "0.1.0",
40
- "@dbp-wp/ui": "0.1.0"
39
+ "@dbp-wp/core": "0.2.0",
40
+ "@dbp-wp/ui": "0.2.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "tsup": "^8.3.0"