178 lines
5.7 KiB
JavaScript
178 lines
5.7 KiB
JavaScript
#!/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 <method> [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 <path>)
|
|
* 3. Or pass --token <jwt-string> 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 <jwt>');
|
|
console.error(' 3. Pass --token-file <path>');
|
|
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();
|