#!/usr/bin/env node /** * API Tester CLI — reuses src/api/index.js directly. * * Usage: * node src/api/api-tester.mjs # interactive REPL * node src/api/api-tester.mjs [args...] # one-shot call * * Auth: * 1. Copy your session cookie from the browser DevTools (name: "token", value: the JWT) * 2. Paste it into ~/.dashfront-token (or pass --token-file ) * 3. Or pass --token on the command line * * Examples: * node src/api/api-tester.mjs posterminal.list * node src/api/api-tester.mjs category.getTree * node src/api/api-tester.mjs posterminal.create '{"name":"Kassa 3"}' * node src/api/api-tester.mjs auth.me */ import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; import { createInterface } from 'node:readline'; // ── CLI args ───────────────────────────────────────────────────────────────── const args = process.argv.slice(2); function flag(name) { const idx = args.indexOf(name); if (idx === -1) return null; const val = args[idx + 1]; args.splice(idx, 2); return val; } const BASE_URL = flag('--base') || 'http://localhost:4208'; const tokenFileArg = flag('--token-file'); const tokenArg = flag('--token'); function loadToken() { if (tokenArg) return tokenArg; const paths = [ tokenFileArg, resolve(homedir(), '.dashfront-token'), './.dashfront-token', ].filter(Boolean); for (const p of paths) { try { const t = readFileSync(p, 'utf-8').trim(); if (t) return t; } catch { /* ignore */ } } console.error('No token found. Options:'); console.error(' 1. Place JWT in ~/.dashfront-token'); console.error(' 2. Pass --token '); console.error(' 3. Pass --token-file '); process.exit(1); } const TOKEN = loadToken(); // ── Patch globals so src/api/index.js works in Node ────────────────────────── // Fake browser globals so src/api/index.js works in Node globalThis.window = { location: { origin: BASE_URL }, }; globalThis.document = { get cookie() { return ''; }, set cookie(_v) { /* no-op */ }, }; // Patch fetch to send the session cookie const originalFetch = globalThis.fetch; globalThis.fetch = async (input, init) => { const url = typeof input === 'string' ? input : input?.url ?? String(input); const fullUrl = url.startsWith('http') ? url : `${BASE_URL}${url}`; const headers = new Headers(init?.headers); headers.set('Cookie', `token=${TOKEN}`); const res = await originalFetch(fullUrl, { ...init, headers }); return res; }; // ── Load the API module ────────────────────────────────────────────────────── const api = (await import('./index.js')).default; // ── Helpers ────────────────────────────────────────────────────────────────── const flatten = (obj, prefix = '') => { const entries = []; for (const [key, val] of Object.entries(obj)) { const path = prefix ? `${prefix}.${key}` : key; if (typeof val === 'function') { entries.push([path, val]); } else if (typeof val === 'object' && val !== null) { entries.push(...flatten(val, path)); } } return entries; }; const methods = new Map(flatten(api)); function parseArg(raw) { const trimmed = raw.trim(); if (trimmed === 'undefined') return undefined; if (trimmed === 'null') return null; if (trimmed === 'true') return true; if (trimmed === 'false') return false; try { return JSON.parse(trimmed); } catch { return trimmed; } } function invoke(methodPath, argStrings = []) { const fn = methods.get(methodPath); if (!fn) { console.error(`Unknown method: ${methodPath}`); console.error(`Available: ${[...methods.keys()].join(', ')}`); return; } const args = argStrings.map(parseArg); return fn(...args) .then((result) => { console.log(JSON.stringify(result, null, 2)); return result; }) .catch((err) => { console.error(`[ERROR] ${err.message}`); if (err.status) console.error(` status: ${err.status}`); if (err.data) console.error(` data:`, err.data); }); } // ── One-shot mode ──────────────────────────────────────────────────────────── if (args.length > 0) { const [method, ...rest] = args; await invoke(method, rest); process.exit(0); } // ── Interactive REPL ───────────────────────────────────────────────────────── console.log(`API Tester — ${BASE_URL}`); console.log(`Token: ${TOKEN.slice(0, 20)}…`); console.log(`Methods: ${[...methods.keys()].join(', ')}`); console.log('Type a method call, or "quit" to exit.\n'); const rl = createInterface({ input: process.stdin, output: process.stdout }); function prompt() { rl.question('api> ', async (line) => { const trimmed = line.trim(); if (!trimmed || trimmed === 'quit' || trimmed === 'exit') { rl.close(); process.exit(0); } // Parse: method arg1 arg2 ... // Arguments are space-separated; wrap objects in quotes: method '{"key":"val"}' const parts = trimmed.match(/(?:[^\s"]+|"[^"]*")+/g) || []; const [method, ...rest] = parts.map((p) => p.replace(/^"|"$/g, '')); await invoke(method, rest); prompt(); }); } prompt();