@corrreia/hl 0.1.0 → 0.1.1
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.mjs +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createInterface as e}from"node:readline/promises";import t from"yargs";import{mkdir as n,readFile as r,rename as i,writeFile as a}from"node:fs/promises";import{homedir as o}from"node:os";import{dirname as s,join as c}from"node:path";import{Result as l,TaggedError as u}from"better-result";import{z as d}from"zod";import{execFile as f,spawn as p}from"node:child_process";import{promisify as ee}from"node:util";import{Agent as te,fetch as ne}from"undici";var re=`0.1.
|
|
1
|
+
import{createInterface as e}from"node:readline/promises";import t from"yargs";import{mkdir as n,readFile as r,rename as i,writeFile as a}from"node:fs/promises";import{homedir as o}from"node:os";import{dirname as s,join as c}from"node:path";import{Result as l,TaggedError as u}from"better-result";import{z as d}from"zod";import{execFile as f,spawn as p}from"node:child_process";import{promisify as ee}from"node:util";import{Agent as te,fetch as ne}from"undici";var re=`0.1.1`;const m=ee(f),ie=d.discriminatedUnion(`kind`,[d.strictObject({kind:d.literal(`keyring`),account:d.string().min(1)}),d.strictObject({kind:d.literal(`command`),command:d.string().min(1)}),d.strictObject({kind:d.literal(`env`),name:d.string().min(1)}),d.strictObject({kind:d.literal(`file`),value:d.string().min(1)})]);var h=class extends u(`SecretUnavailable`){},g=class extends u(`NoKeyring`){};async function ae(e=process.platform){return e===`darwin`&&await ue(`security`)?l.ok(se):e===`linux`||e===`freebsd`||e===`openbsd`?await ue(`secret-tool`)?l.ok(oe):l.err(new g({message:`secret-tool isn't installed (it's in libsecret: libsecret-tools on Debian, libsecret on Arch)`})):l.err(new g({message:`hl can't use the keyring on ${e}`}))}const oe={name:`Secret Service`,async get(e){try{let{stdout:t}=await m(`secret-tool`,[`lookup`,`service`,`hl`,`account`,e]);return l.ok(t===``?void 0:t.replace(/\n$/,``))}catch(e){return y(e)===1&&b(e)===``?l.ok(void 0):l.err(new h({kind:`keyring`,message:`The Secret Service failed: ${b(e)||`is it running?`}`,cause:e}))}},async set(e,t){return(await le(`secret-tool`,[`store`,`--label`,`hl: ${e}`,`service`,`hl`,`account`,e],t)).mapError(e=>new h({kind:`keyring`,message:`The Secret Service failed to store the key: ${e}`}))},async delete(e){try{return await m(`secret-tool`,[`clear`,`service`,`hl`,`account`,e]),l.ok(void 0)}catch(e){return l.err(new h({kind:`keyring`,message:`The Secret Service failed: ${b(e)}`,cause:e}))}}},se={name:`Keychain`,async get(e){try{let{stdout:t}=await m(`security`,[`find-generic-password`,`-s`,`hl`,`-a`,e,`-w`]);return l.ok(t.replace(/\n$/,``))}catch(e){return y(e)===44?l.ok(void 0):l.err(new h({kind:`keyring`,message:`The Keychain failed: ${b(e)}`,cause:e}))}},async set(e,t){let n=e=>`"${e.replace(/[\\"]/g,`\\$&`)}"`;return(await le(`security`,[`-i`],`add-generic-password -U -s hl -a ${n(e)} -l ${n(`hl: ${e}`)} -w ${n(t)}\n`)).mapError(e=>new h({kind:`keyring`,message:`The Keychain failed to store the key: ${e}`}))},async delete(e){try{return await m(`security`,[`delete-generic-password`,`-s`,`hl`,`-a`,e]),l.ok(void 0)}catch(e){return y(e)===44?l.ok(void 0):l.err(new h({kind:`keyring`,message:`The Keychain failed: ${b(e)}`,cause:e}))}}};async function _(e,t){switch(e.kind){case`file`:return l.ok(e.value);case`env`:{let n=t.env[e.name];return n===void 0||n===``?l.err(new h({kind:`env`,message:`$${e.name} is not set`})):l.ok(n)}case`command`:return ce(e.command);case`keyring`:{let n=await t.keyring();if(n.isErr())return l.err(new h({kind:`keyring`,message:`The key is in the keyring, but ${n.error.message}`}));let r=await n.value.get(e.account);return r.isErr()?r:r.value===void 0||r.value===``?l.err(new h({kind:`keyring`,message:`The ${n.value.name} has no key for ${e.account}`})):l.ok(r.value)}default:return de(e)}}function v(e){switch(e?.kind){case void 0:return`none`;case`keyring`:return`keyring`;case`command`:return`command: ${e.command}`;case`env`:return`$${e.name}`;case`file`:return`config file`;default:return de(e)}}async function ce(e){let[t,n]=process.platform===`win32`?[`cmd.exe`,`/c`]:[`/bin/sh`,`-c`];try{let{stdout:r}=await m(t,[n,e],{timeout:6e4,maxBuffer:1048576}),i=r.replace(/\r?\n$/,``);return i===``?l.err(new h({kind:`command`,message:`\`${e}\` printed no key`})):l.ok(i)}catch(t){let n=b(t).split(`
|
|
2
2
|
`)[0]??``;return l.err(new h({kind:`command`,message:`\`${e}\` failed${n===``?``:`: ${n}`}`,cause:t}))}}function le(e,t,n){return new Promise(r=>{let i=p(e,t,{stdio:[`pipe`,`ignore`,`pipe`]}),a=``;i.stderr.on(`data`,e=>a+=e.toString()),i.on(`error`,e=>r(l.err(e.message))),i.on(`close`,e=>r(e===0?l.ok(void 0):l.err(a.trim()||`exit ${e??`?`}`))),i.stdin.end(n)})}async function ue(e){try{return await m(process.platform===`win32`?`where`:`which`,[e]),!0}catch{return!1}}function y(e){return typeof e==`object`&&e&&`code`in e&&typeof e.code==`number`?e.code:void 0}function b(e){return typeof e==`object`&&e&&`stderr`in e&&typeof e.stderr==`string`?e.stderr.trim():``}function de(e){throw Error(`unexpected case: ${JSON.stringify(e)}`)}const x=d.string().regex(/^[a-z0-9][a-z0-9-]*$/,`use lower-case letters, digits and dashes, starting with a letter or digit`),fe=d.strictObject({version:d.string().min(1),at:d.iso.datetime()}),pe=d.strictObject({url:d.url({protocol:/^https?$/}),secret:ie.optional(),insecure:d.boolean().default(!1),basePath:d.string().startsWith(`/`).optional(),context:d.record(d.string(),d.string().min(1)).default({}),detected:fe.optional(),pinnedVersion:d.string().min(1).optional()}),me=d.strictObject({instances:d.record(d.string(),d.record(x,pe)).default({}),defaults:d.record(d.string(),x).default({})});var he=class extends u(`InvalidConfig`){},ge=class extends u(`ConfigIoFailed`){};function _e(e){let t=e.HL_CONFIG;if(t!==void 0&&t!==``)return t;let n=e.XDG_CONFIG_HOME||c(o(),`.config`);return c(n,`hl`,`config.json`)}function ve(){return{instances:{},defaults:{}}}async function ye(e){let t;try{t=await r(e,`utf8`)}catch(t){return Se(t)?l.ok(ve()):l.err(new ge({path:e,operation:`read`,cause:t,message:`Could not read ${e}`}))}return be(e,t)}function be(e,t){let n;try{n=JSON.parse(t)}catch{return l.err(new he({path:e,issues:`not JSON`,message:`${e} is not valid JSON`}))}let r=me.safeParse(n);if(!r.success){let t=d.prettifyError(r.error);return l.err(new he({path:e,issues:t,message:`${e} is not a valid hl config:\n${t}`}))}return l.ok(r.data)}async function xe(e,t){let r=`${e}.${process.pid}.tmp`;try{return await n(s(e),{recursive:!0,mode:448}),await a(r,`${JSON.stringify(t,void 0,` `)}\n`,{mode:384}),await i(r,e),l.ok(void 0)}catch(t){return l.err(new ge({path:e,operation:`write`,cause:t,message:`Could not write ${e}`}))}}function Se(e){return e instanceof Error&&`code`in e&&e.code===`ENOENT`}var S=class extends u(`HttpStatusError`){},Ce=class extends u(`TlsUnverified`){},we=class extends u(`Unreachable`){};const Te=/* @__PURE__ */ new Set([`DEPTH_ZERO_SELF_SIGNED_CERT`,`SELF_SIGNED_CERT_IN_CHAIN`,`UNABLE_TO_VERIFY_LEAF_SIGNATURE`,`UNABLE_TO_GET_ISSUER_CERT_LOCALLY`,`CERT_HAS_EXPIRED`,`ERR_TLS_CERT_ALTNAME_INVALID`]);function Ee(e){let t=new te({connect:{rejectUnauthorized:!e.insecure}});return{async send(n){let r=n.url.toString(),i,a;try{i=await ne(n.url,{method:n.method,headers:n.headers,body:n.body??null,dispatcher:t,signal:AbortSignal.timeout(e.timeoutMs)}),a=await i.text()}catch(e){let t=C(e);return t!==void 0&&Te.has(t)?l.err(new Ce({url:r,code:t,cause:e,message:`The certificate of ${n.url.host} can't be verified (${t})`})):l.err(new we({url:r,cause:e,message:`Could not reach ${n.url.host}: ${ke(e)}`}))}let o=De(a,i.headers.get(`content-type`));return i.ok?l.ok({status:i.status,body:o}):l.err(new S({method:n.method,url:r,status:i.status,body:o,message:`${n.method} ${n.url.pathname} failed with ${i.status}${Oe(o,i.statusText)}`}))}}}function De(e,t){if(e!==``){if(t!==null&&/[/+]json\b/.test(t))try{return JSON.parse(e)}catch{return e}return e}}function Oe(e,t){let n=[];return typeof e==`string`&&e.length>0&&e.length<300?n.push(e.trim()):typeof e==`object`&&e&&`message`in e&&typeof e.message==`string`?n.push(e.message.trim()):t!==``&&!/^(Bad Request|Unauthorized|Forbidden|Not Found|Internal Server Error)$/i.test(t)&&n.push(t),typeof e==`object`&&e&&`errors`in e&&typeof e.errors==`object`&&e.errors!==null&&n.push(...Object.entries(e.errors).map(([e,t])=>`${e}: ${typeof t==`string`?t.trim():JSON.stringify(t)}`)),n.length===0?``:`: ${n.join(`; `)}`}function C(e){for(let t=e,n=0;t instanceof Error&&n<4;n++){if(`code`in t&&typeof t.code==`string`)return t.code;t=t.cause}}function ke(e){if(e instanceof Error&&e.name===`TimeoutError`)return`timed out`;let t=C(e);return t===void 0?e instanceof Error?e.message:`unknown error`:t}function w(e,t){let n=T(e),r=T(t);for(let e=0;e<Math.max(n.length,r.length);e++){let t=n[e]??0,i=r[e]??0;if(t!==i)return typeof t==`number`&&typeof i==`number`?t-i:String(t).localeCompare(String(i))}return 0}function Ae(e,t){let n=[...e].sort(w),[r]=n;if(r===void 0)throw Error(`pickVersion needs at least one version`);let i={kind:`server-older`,version:r};for(let e of n){let n=w(e,t);if(n>0)break;i={kind:n===0?`exact`:`older`,version:e}}return i}function je(e){let t=[...e].sort(w).at(-1);if(t===void 0)throw Error(`newestVersion needs at least one version`);return t}function T(e){return e.replace(/^v/i,``).split(/[.\-+]/).map(e=>/^\d+$/.test(e)?Number(e):e)}var Me=class extends u(`NoInstances`){},Ne=class extends u(`UnknownInstance`){},Pe=class extends u(`NoDefaultInstance`){},E=class extends u(`MissingApiKey`){};function D(e,t){return Object.keys(e.instances[t]??{}).sort()}function O(e,t,n){let r=e.instances[t.id]??{},i=D(e,t.id),a=`hl instances add ${t.id} <name> --url <url> --api-key <key>`;if(i.length===0)return l.err(new Me({service:t.id,message:`No ${t.title} instances yet; add one with: ${a}`}));let o=n??e.defaults[t.id]??(i.length===1?i[0]:void 0);if(o===void 0)return l.err(new Pe({service:t.id,names:i,message:`${t.title} has ${i.length} instances (${i.join(`, `)}); pick one with ${t.id}@<name>, or set a default with: hl instances use ${t.id} <name>`}));let s=r[o];return s===void 0?l.err(new Ne({service:t.id,name:o,message:`No ${t.id} instance named "${o}"; instances: ${i.join(`, `)}`})):l.ok({name:o,config:s})}async function k(e,t,n){let{secret:r}=t.config,i=`${e.id}@${t.name}`;if(r===void 0)return e.authOptional===!0?l.ok(A(e,t,void 0)):l.err(new E({service:e.id,name:t.name,message:`${i} has no API key`}));let a=await _(r,n);return a.isErr()?l.err(new E({service:e.id,name:t.name,message:`${i}: ${a.error.message}`})):l.ok(A(e,t,a.value))}function A(e,t,n){let{config:r}=t,i=`${r.url.replace(/\/+$/,``)}${r.basePath??e.basePath}`,a=n===void 0?{Accept:`application/json`}:e.authHeaders(n),o=Ee({insecure:r.insecure,timeoutMs:15e3}),s={async get(e,t){return(await o.send({method:`GET`,url:j(i,e,t??{}),headers:a})).map(e=>e.body)}};return{...t,service:e,baseUrl:i,headers:a,http:o,api:s}}function j(e,t,n){let r=new URL(`${e}${t}`);for(let[e,t]of Object.entries(n))for(let n of typeof t==`string`?[t]:t)r.searchParams.append(e,n);return r}function M(e,t){return e.detected===void 0||t.getTime()-Date.parse(e.detected.at)>864e5}async function N(e,t){let n=Object.keys(e.versions),r=t===void 0?void 0:Ae(n,t),i=r?.version??je(n),a=e.versions[i];if(a===void 0)throw Error(`no catalogue loader for ${e.id} ${i}`);return{catalog:(await a()).default,match:r}}const Fe=[`json`,`table`];function P(e,t){if(e===void 0)return``;if(typeof e==`string`)return e.endsWith(`
|
|
3
3
|
`)?e:`${e}\n`;if(t.format===`table`){let n=Le(e);if(n!==void 0)return Re(n,t.columns)}return`${JSON.stringify(e,void 0,t.pretty?2:void 0)}\n`}function Ie(e,t){if(t.format===`table`){let n=[];for(let t of e)for(let e of Le(t.value)??[t.value])n.push({instance:t.instance,...F(e)?e:{value:e}});return Re(n,t.columns===void 0?void 0:[`instance`,...t.columns])}let n=Object.fromEntries(e.map(e=>[e.instance,e.value]));return`${JSON.stringify(n,void 0,t.pretty?2:void 0)}\n`}function Le(e){if(Array.isArray(e))return e;if(F(e)&&Array.isArray(e.data))return e.data}function Re(e,t){if(e.length===0)return`(none)
|
|
4
4
|
`;let n=t??ze(e),r=e.map(e=>n.map(t=>Be(F(e)?e[t]:t===`value`?e:void 0))),i=n.map(e=>Ve(e)),a=i.map((e,t)=>Math.max(e.length,...r.map(e=>(e[t]??``).length))),o=e=>e.map((t,n)=>n===e.length-1?t:t.padEnd(a[n]??0)).join(` `).trimEnd();return`${[o(i),...r.map(o)].join(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corrreia/hl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "One CLI for your homelab's self-hosted services, generated from their OpenAPI specs with Forge",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"url": "git+https://github.com/corrreia/hl-cli.git"
|
|
29
29
|
},
|
|
30
30
|
"bin": {
|
|
31
|
-
"hl": "
|
|
31
|
+
"hl": "bin/hl"
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"bin",
|