From 53eee92bed7bdb6a3ffd4922f31dfe65b2ba273d Mon Sep 17 00:00:00 2001 From: Kiko Beats Date: Tue, 14 Apr 2026 08:56:36 +0200 Subject: [PATCH] feat: headers --- package.json | 4 ++-- src/api.js | 6 +++++- src/cli.js | 21 ++++++++++++++++++++- src/help.js | 2 ++ test/parse-headers.js | 38 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 test/parse-headers.js diff --git a/package.json b/package.json index 70b4774..9ad9aac 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,10 @@ "microlink" ], "dependencies": { - "@kikobeats/content-type": "~1.0.1", + "@kikobeats/content-type": "~1.0.4", "@microlink/mql": "~0.16.0", "clipboardy": "~2.3.0", - "is-local-address": "~2.3.0", + "is-local-address": "~2.3.4", "jsome": "~2.5.0", "mri": "~1.2.0", "nanospinner": "~1.2.2", diff --git a/src/api.js b/src/api.js index b34d74c..ce8864e 100644 --- a/src/api.js +++ b/src/api.js @@ -46,9 +46,13 @@ const fetch = async (cli, gotOpts) => { const mqlOpts = { endpoint, ...queryParams, ...flags } const spinner = print.spinner() + const mergedGotOpts = Object.keys(cli.headers).length > 0 + ? { ...gotOpts, headers: { ...gotOpts.headers, ...cli.headers } } + : gotOpts + try { spinner.start() - const response = await mql.buffer(url, mqlOpts, gotOpts) + const response = await mql.buffer(url, mqlOpts, mergedGotOpts) spinner.stop() return { response, flags: { copy, pretty } } } catch (error) { diff --git a/src/cli.js b/src/cli.js index 97b9c2f..3c9a681 100644 --- a/src/cli.js +++ b/src/cli.js @@ -2,8 +2,10 @@ const mri = require('mri') -const { _, ...flags } = mri(process.argv.slice(2), { +const { _, header, ...flags } = mri(process.argv.slice(2), { + alias: { H: 'header' }, boolean: ['color', 'copy', 'pretty'], + string: ['header'], default: { apiKey: process.env.MICROLINK_API_KEY, pretty: true, @@ -12,11 +14,28 @@ const { _, ...flags } = mri(process.argv.slice(2), { } }) +const parseHeaders = raw => { + if (!raw) return {} + const entries = Array.isArray(raw) ? raw : [raw] + const headers = {} + for (const entry of entries) { + const idx = entry.indexOf(':') + if (idx === -1) continue + headers[entry.slice(0, idx).trim().toLowerCase()] = entry.slice(idx + 1).trim() + } + return headers +} + +const headers = parseHeaders(header) + module.exports = { flags, + headers, input: _, showHelp: () => { console.log(require('./help')) process.exit(0) } } + +module.exports.parseHeaders = parseHeaders diff --git a/src/help.js b/src/help.js index fea705d..3feff62 100644 --- a/src/help.js +++ b/src/help.js @@ -24,10 +24,12 @@ Flags white('true') )}).` )} + ${gray('-H
pass custom HTTP header to the request (repeatable).')} Examples ${gray('microlink https://microlink.io&palette')} ${gray('microlink https://microlink.io&palette --no-pretty')} ${gray('microlink https://microlink.io&palette --api-key=MyApiKey')} + ${gray("microlink https://example.com -H 'x-user-cookie: 1'")} ` diff --git a/test/parse-headers.js b/test/parse-headers.js new file mode 100644 index 0000000..612f78d --- /dev/null +++ b/test/parse-headers.js @@ -0,0 +1,38 @@ +'use strict' + +const test = require('ava') + +const { parseHeaders } = require('../src/cli') + +test('single header', t => { + t.deepEqual(parseHeaders('x-user-cookie: 1'), { 'x-user-cookie': '1' }) +}) + +test('multiple headers as array', t => { + t.deepEqual( + parseHeaders(['x-user-cookie: 1', 'authorization: Bearer token123']), + { 'x-user-cookie': '1', authorization: 'Bearer token123' } + ) +}) + +test('header with colons in value', t => { + t.deepEqual(parseHeaders('authorization: Bearer a:b:c'), { + authorization: 'Bearer a:b:c' + }) +}) + +test('trims whitespace', t => { + t.deepEqual(parseHeaders(' X-Custom : value '), { 'x-custom': 'value' }) +}) + +test('skips entries without colon', t => { + t.deepEqual(parseHeaders(['valid: yes', 'nocolon']), { valid: 'yes' }) +}) + +test('returns empty object for undefined', t => { + t.deepEqual(parseHeaders(undefined), {}) +}) + +test('returns empty object for empty string', t => { + t.deepEqual(parseHeaders(''), {}) +})