@lowlighter/run 1.0.2 → 2.0.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/README.md CHANGED
@@ -9,6 +9,15 @@
9
9
  > [!WARNING]
10
10
  > Deno exclusive!
11
11
 
12
+ ## ✨ Features
13
+
14
+ - Supports `stdin` interactivity through callbacks.
15
+ - Make it possible to monitor `stdout` and `stderr` content and react accordingly.
16
+ - Auto-detects os and can automatically append an extension when running on Windows.
17
+ - Supports both `sync` and `async` modes in a single function.
18
+ - Optionally decide to throw an error when the process exits with a non-zero code.
19
+ - Generates a `stdio` history that contains timestamped entries with configurable buffering
20
+
12
21
  ## 📜 Licenses
13
22
 
14
23
  ```plaintext
package/command.mjs CHANGED
@@ -1,33 +1,91 @@
1
- var e=class e{constructor({level:t,format:n,output:o,tags:r,options:s}={}){if("granted"===globalThis.Deno?.permissions.querySync?.({name:"env",variable:"LOG_LEVEL"}).state){const n=globalThis.Deno?.env.get("LOG_LEVEL")??"";n in e.level&&(t??=e.level[n]),Number.isNaN(Number.parseInt(n))||(t??=Number.parseInt(n))}this.level=t??e.level.log,this.#e=o||null===o?o:console,this.#t=n??e.format.text,this.tags=r??{},this.options={date:!1,time:!1,delta:!0,...s,caller:!1!==s?.caller&&{file:!1,name:!1,line:!1,...s?.caller}}}level;#t;#e;tags;options;error(...t){return this.level>=e.level.error&&this.#e?.error(...this.#t(this,{level:e.level.error,content:t})),this}warn(...t){return this.level>=e.level.warn&&this.#e?.warn(...this.#t(this,{level:e.level.warn,content:t})),this}info(...t){return this.level>=e.level.info&&this.#e?.info(...this.#t(this,{level:e.level.info,content:t})),this}log(...t){return this.level>=e.level.log&&this.#e?.log(...this.#t(this,{level:e.level.log,content:t})),this}debug(...t){return this.level>=e.level.debug&&this.#e?.debug(...this.#t(this,{level:e.level.debug,content:t})),this}with(t={}){return new e({level:this.level,format:this.#t,output:this.#e,options:{...this.options},tags:{...this.tags,...t}})}#n(e=3){const t=Error,n=t.prepareStackTrace;t.prepareStackTrace=(e,t)=>t;const{stack:o}=new Error;t.prepareStackTrace=n;const r=o[e];return{file:r.getFileName(),name:r.getFunctionName(),line:r.getLineNumber(),column:r.getColumnNumber()}}static level=Object.freeze({disabled:NaN,error:0,warn:1,info:2,log:3,debug:4});static format={text(t,{level:n=0,content:o}){const r=["red","orange","cyan","white","gray"][n],s=[`%c ${Object.keys(e.level).find((t=>e.level[t]===n)).toLocaleUpperCase().padEnd(5)} │%c`],i=[`color: black; background-color: ${r}`,""];if(t.options.date||t.options.time||t.options.delta){const e=(new Date).toISOString(),n=[];if(t.options.delta){const e=performance.now()/1e3;let t=e.toPrecision(4);e<1&&(t=e.toPrecision(2)),n.push(`+${t}`)}t.options.date&&t.options.time?n.push(e):t.options.date?n.push(e.slice(0,e.indexOf("T"))):t.options.time&&n.push(e.slice(e.indexOf("T")+1,-1)),s.push(`%c ${n.join(" ¦ ").trim()} %c`),i.push(`color: black; background-color: ${r}`,"")}if(t.options.caller){const e=t.#n();if(e){const n=[];t.options.caller.file&&n.push(`${e.file.replace(t.options.caller.fileformat,"$<file>")}`),t.options.caller.name&&e.name&&n.push(e.name),t.options.caller.line&&n.push(e.line,e.column),s.push(`%c ${n.join(":").trim()} %c`),i.push("color: black; background-color: gray","")}}{const n=[];for(const[o,r]of Object.entries(t.tags))n.push(`${o}:${e.inspect(r)}`);s.push(`%c ${n.join(" ").trim()} %c`),i.push("background-color: black","")}return[s.join(""),...i,...o.map(e.inspect)]},json(t,{level:n=0,content:o}){const r={level:Object.keys(e.level).find((t=>e.level[t]===n)),timestamp:Date.now(),tags:t.tags,content:o},s={};if(t.options.date||t.options.time){const e=new Date(r.timestamp).toISOString();t.options.date&&(s.date=e.slice(0,e.indexOf("T"))),t.options.time&&(s.time=e.slice(e.indexOf("T")+1,-1))}if(t.options.delta&&(s.delta=performance.now()/1e3),t.options.caller){const e=t.#n();e&&(s.caller={},t.options.caller.file&&(s.caller.file=`${e.file.replace(t.options.caller.fileformat,"$<file>")}`),t.options.caller.name&&e.name&&(s.caller.name=e.name),t.options.caller.line&&(s.caller.line=[e.line,e.column]))}return[JSON.stringify({...r,...s})]}};static inspect(e){return globalThis.Deno?.inspect(e,{colors:!0,depth:1/0,strAbbreviateSize:1/0})??e}};TransformStream;function t(e){let t=0;for(const n of e)t+=n.length;const n=new Uint8Array(t);let o=0;for(const t of e)n.set(t,o),o+=t.length;return n}function n(e){const t=e.length,n=new Uint8Array(t);n[0]=0;let o=0,r=1;for(;r<t;)e[r]===e[o]?(o++,n[r]=o,r++):0===o?(n[r]=0,r++):o=n[o-1];return n}TransformStream,TransformStream,TransformStream,TransformStream;var o=class extends TransformStream{#o="";constructor(e={allowCR:!1}){super({transform:(t,n)=>{for(t=this.#o+t;;){const o=t.indexOf("\n"),r=e.allowCR?t.indexOf("\r"):-1;if(-1!==r&&r!==t.length-1&&(-1===o||o-1>r)){n.enqueue(t.slice(0,r)),t=t.slice(r+1);continue}if(-1===o)break;const s="\r"===t[o-1]?o-1:o;n.enqueue(t.slice(0,s)),t=t.slice(o+1)}this.#o=t},flush:t=>{if(""===this.#o)return;const n=e.allowCR&&this.#o.endsWith("\r")?this.#o.slice(0,-1):this.#o;t.enqueue(n)}})}};new TextDecoder;var r=new TextEncoder,s=new TextDecoder;function i(t,n,{log:i=new e,stdin:a=null,stdout:c="debug",stderr:u="error",env:d,cwd:p,raw:h,callback:f,buffering:m,sync:w,throw:g,dryrun:b,winext:v="",os:T=Deno.build.os}={}){"windows"===T&&(t=`${t}${v}`),i=i.with({bin:t}),f&&"piped"!==l(a)&&(a="piped");const $=new Deno.Command(t,{args:n,stdin:w?"null":l(a),stdout:l(c),stderr:l(u),env:d,cwd:p,windowsRawArguments:h});if(b){i.debug(`dryrun: ${t} not executed`);const e={success:!0,code:0,stdio:[],stdin:"",stdout:"",stderr:""};return w?e:Promise.resolve(e)}return w?function(e,{bin:t,log:n,throw:o,stdout:r,stderr:i}){const a=Date.now(),c=e.outputSync(),{success:u,code:d}=c,p=Date.now()-a,h={get stdio(){return[[p,1,this.stdout],[p,2,this.stderr]]},stdin:"",stdout:"piped"===l(r)?s.decode(c.stdout):"",stderr:"piped"===l(i)?s.decode(c.stderr):""};for(const{channel:e,mode:t}of[{channel:"stdout",mode:r},{channel:"stderr",mode:i}])"piped"===l(t)&&h[e]&&n.with({t:p,channel:e})[t]?.(h[e]);if(!u&&o)throw new EvalError(`${t} exited with non-zero code ${d}:\n${h.stdout}\n${h.stderr}`);return{success:u,code:d,...h}}($,{bin:t,log:i,throw:g,stdout:c,stderr:u}):async function(e,{bin:t,log:n,callback:s=(({close:e})=>e?.()),buffering:i=250,throw:a,...c}){const u=e.spawn(),d=Date.now(),p={stdio:[],get stdin(){return this.stdio.filter((([e,t])=>0===t)).map((([e,t,n])=>n)).join("\n")},get stdout(){return this.stdio.filter((([e,t])=>1===t)).map((([e,t,n])=>n)).join("\n")},get stderr(){return this.stdio.filter((([e,t])=>2===t)).map((([e,t,n])=>n)).join("\n")}},h={};let f="";const m=function(e,t){let n=null,o=null;const r=(...s)=>{r.clear(),o=()=>{r.clear(),e.call(r,...s)},n=setTimeout(o,t)};return r.clear=()=>{"number"==typeof n&&(clearTimeout(n),n=null,o=null)},r.flush=()=>{o?.()},Object.defineProperty(r,"pending",{get:()=>"number"==typeof n}),r}((async e=>{n.with({t:e}).debug("debounced"),f="",await s({stdio:p,i:p.stdin.length,...h})}),i);if("piped"===l(c.stdin)){const e=u.stdin.getWriter();Object.assign(h,{async write(t,o=!0){const s=Date.now()-d;c.stdin&&n.with({t:s,channel:"stdin"})[c.stdin]?.(t),p.stdio.push([s,0,t]),o&&!t.endsWith("\n")&&(t+="\n"),await e.write(r.encode(t)),f="stdin",e.releaseLock()},async close(){try{e.releaseLock(),await u.stdin.close(),n.with({t:Date.now()-d,closed:"stdin"}).debug()}catch{}},async wait(e=1e3){const t=Date.now()-d;n.with({t:t,waiting:e}).debug(),await function(e,t={}){const{signal:n,persistent:o=!0}=t;return n?.aborted?Promise.reject(n.reason):new Promise(((t,r)=>{const s=()=>{clearTimeout(i),r(n?.reason)},i=setTimeout((()=>{n?.removeEventListener("abort",s),t()}),e);if(n?.addEventListener("abort",s,{once:!0}),!1===o)try{Deno.unrefTimer(i)}catch(e){if(!(e instanceof ReferenceError))throw e;console.error("`persistent` option is only available in Deno")}}))}(e),m(t)}}),m(Date.now()-d)}await Promise.all(["stdout","stderr"].filter((e=>"piped"===l(c[e]))).map((async e=>{for await(const t of u[e].pipeThrough(new TextDecoderStream).pipeThrough(new o)){const o=Date.now()-d,r={stdout:1,stderr:2}[e];if(c[e]&&n.with({t:o,channel:e})[c[e]]?.(t),p.stdio.length&&f===e){const e=p.stdio.at(-1);e[1]===r&&(e[2]+=`\n${t}`)}else p.stdio.push([o,r,t]);f=e,m(o)}}))),m.flush();const{success:w,code:g}=await u.status;if(!w&&a)throw new EvalError(`${t} exited with non-zero code ${g}:\n${p.stdout}\n${p.stderr}`);return{success:w,code:g,...p}}($,{bin:t,log:i,callback:f,buffering:m,throw:g,stdin:"piped"===l(a)?a:null,stdout:"piped"===l(c)?c:null,stderr:"piped"===l(u)?u:null})}function l(e){return["inherit","null"].includes(`${e}`)?`${e}`:"piped"}export{i as command};
1
+ var e=class e{constructor({level:t,format:n,output:r,tags:o,...i}={}){"granted"===globalThis.Deno?.permissions.querySync?.({name:"env",variable:"LOG_LEVEL"}).state&&(t??=globalThis.Deno?.env.get("LOG_LEVEL")),this.#e=r||null===r?r:console,this.#t=e.level.log,this.#n=e.format.text,this.#r={date:!1,time:!1,delta:!0,caller:{file:!1,name:!1,line:!1,fileformat:null}},this.level(t).format(n).options(i),this.tags=o??{}}#e;#r;options(e){if(!e)return structuredClone(this.#r);if("date"in e&&(this.#r.date=e.date),"time"in e&&(this.#r.time=e.time),"delta"in e&&(this.#r.delta=e.delta),"caller"in e)switch(!0){case!0===e.caller:this.#r.caller={file:!0,name:!0,line:!0,fileformat:this.#r.caller.fileformat};break;case!1===e.caller:this.#r.caller={file:!1,name:!1,line:!1,fileformat:this.#r.caller.fileformat};break;case"object"==typeof e.caller:"file"in e.caller&&(this.#r.caller.file=e.caller.file),"name"in e.caller&&(this.#r.caller.name=e.caller.name),"line"in e.caller&&(this.#r.caller.line=e.caller.line),"fileformat"in e.caller&&void 0!==e.caller.fileformat&&(this.#r.caller.fileformat=e.caller.fileformat)}return this}#t;level(t){return arguments.length?(Number.isNaN(Number.parseInt(`${t}`))||(t=Number.parseInt(`${t}`)),"string"==typeof t&&t in e.level&&(t=e.level[t]),"number"==typeof t&&(this.#t=t>=0?t:e.level.disabled),this):this.#t}static level=Object.freeze({disabled:NaN,probe:NaN,error:0,warn:1,info:2,ok:2,log:3,debug:4,wdebug:4,trace:5});error(...t){return this.#t>=e.level.error&&this.#e?.error(...this.#n(this,{level:"error",content:t,options:this.#r})),this}warn(...t){return this.#t>=e.level.warn&&this.#e?.warn(...this.#n(this,{level:"warn",content:t,options:this.#r})),this}info(...t){return this.#t>=e.level.info&&this.#e?.info(...this.#n(this,{level:"info",content:t,options:this.#r})),this}ok(...t){return this.#t>=e.level.info&&this.#e?.info(...this.#n(this,{level:"ok",content:t,options:this.#r})),this}log(...t){return this.#t>=e.level.log&&this.#e?.log(...this.#n(this,{level:"log",content:t,options:this.#r})),this}debug(...t){return this.#t>=e.level.debug&&this.#e?.debug(...this.#n(this,{level:"debug",content:t,options:this.#r})),this}wdebug(...t){return this.#t>=e.level.debug&&this.#e?.debug(...this.#n(this,{level:"wdebug",content:t,options:this.#r})),this}trace(...t){return this.#t>=e.level.trace&&this.#e?.debug(...this.#n(this,{level:"trace",content:t,options:this.#r})),this}probe(...e){return this.#e?.debug(...this.#n(this,{level:"probe",content:e,options:this.#r})),this}tags;with(t={}){return new e({level:this.#t,format:this.#n,output:this.#e,...this.#r,tags:{...this.tags,...t}})}#o(e=3){const t=Error,n=t.prepareStackTrace;t.prepareStackTrace=(e,t)=>t;const{stack:r}=new Error;t.prepareStackTrace=n;return r.map((e=>({file:e.getFileName(),name:e.getFunctionName(),line:e.getLineNumber(),column:e.getColumnNumber()})))[e]}static inspect(e){return globalThis.Deno?.inspect(e,{colors:!0,depth:1/0,strAbbreviateSize:1/0})??e}#n;format(t){return this.#n=("string"==typeof t?e.format[t]:t)??e.format.text,this}static format={text(t,{level:n,content:r,options:o}){const i={error:"red",warn:"orange",info:"cyan",ok:"green",log:"white",debug:"gray",wdebug:"yellow",trace:"gray",probe:"magenta"}[n],l=[`%c ${n.replace("wdebug","debug").toLocaleUpperCase().padEnd(5)} │%c`],s=[`color: black; background-color: ${i}`,""];if(o.date||o.time||o.delta){const e=(new Date).toISOString(),t=[];if(o.delta){const e=performance.now()/1e3;let n=e.toPrecision(4);e<1&&(n=e.toPrecision(2)),t.push(`+${n}`)}o.date&&o.time?t.push(e):o.date?t.push(e.slice(0,e.indexOf("T"))):o.time&&t.push(e.slice(e.indexOf("T")+1,-1)),l.push(`%c ${t.join(" ¦ ").trim()} %c`),s.push(`color: black; background-color: ${i}`,"")}if(o.caller.file||o.caller.name||o.caller.line){const e=t.#o();if(e){const t=[];if(o.caller.file){let n=`${e.file}`;Array.isArray(o.caller.fileformat)&&(n=n.replace(o.caller.fileformat[0],o.caller.fileformat[1])),t.push(n)}o.caller.name&&e.name&&t.push(e.name),o.caller.line&&t.push(e.line,e.column),l.push(`%c ${t.join(":").trim()} %c`),s.push("color: black; background-color: gray","")}}const a=[];for(const[n,r]of Object.entries(t.tags))a.push(`${n}:${e.inspect(r)}`);return l.push(`%c ${a.join(" ").trim()} %c`),s.push("background-color: black",""),[l.join(""),...s,...r.map(e.inspect)]},json(e,{level:t,content:n,options:r}){const o={level:t,timestamp:Date.now(),tags:e.tags,content:n},i={};if(r.date||r.time){const e=new Date(o.timestamp).toISOString();r.date&&(i.date=e.slice(0,e.indexOf("T"))),r.time&&(i.time=e.slice(e.indexOf("T")+1,-1))}if(r.delta&&(i.delta=performance.now()/1e3),r.caller.file||r.caller.name||r.caller.line){const t=e.#o();if(t){if(i.caller={},r.caller.file){let e=`${t.file}`;Array.isArray(r.caller.fileformat)&&(e=e.replace(r.caller.fileformat[0],r.caller.fileformat[1])),i.caller.file=e}r.caller.name&&t.name&&(i.caller.name=t.name),r.caller.line&&(i.caller.line=[t.line,t.column])}}return[JSON.stringify({...o,...i})]}}};TransformStream;function t(e){let t=0;for(const n of e)t+=n.length;const n=new Uint8Array(t);let r=0;for(const t of e)n.set(t,r),r+=t.length;return n}function n(e){const t=e.length,n=new Uint8Array(t);n[0]=0;let r=0,o=1;for(;o<t;)e[o]===e[r]?(r++,n[o]=r,o++):0===r?(n[o]=0,o++):r=n[r-1];return n}TransformStream,TransformStream,TransformStream,TransformStream;var r=class extends TransformStream{#i="";constructor(e={allowCR:!1}){super({transform:(t,n)=>{for(t=this.#i+t;;){const r=t.indexOf("\n"),o=e.allowCR?t.indexOf("\r"):-1;if(-1!==o&&o!==t.length-1&&(-1===r||r-1>o)){n.enqueue(t.slice(0,o)),t=t.slice(o+1);continue}if(-1===r)break;const i="\r"===t[r-1]?r-1:r;n.enqueue(t.slice(0,i)),t=t.slice(r+1)}this.#i=t},flush:t=>{if(""===this.#i)return;const n=e.allowCR&&this.#i.endsWith("\r")?this.#i.slice(0,-1):this.#i;t.enqueue(n)}})}};new TextDecoder;var o=new TextEncoder,i=new TextDecoder;function l(t,n,{logger:l=new e,stdin:a=null,stdout:c="debug",stderr:u="error",env:d,cwd:h,raw:f,callback:p,buffering:m,sync:g,throw:w,dryrun:b,winext:v="",os:y=Deno.build.os}={}){"windows"===y&&(t=`${t}${v}`),l=l.with({bin:t}),p&&"piped"!==s(a)&&(a="piped");const T=new Deno.Command(t,{args:n,stdin:g?"null":s(a),stdout:s(c),stderr:s(u),env:d,cwd:h,windowsRawArguments:f});if(b){l.wdebug(`dryrun: ${t} not executed`);const e={success:!0,code:0,stdio:[],stdin:"",stdout:"",stderr:""};return g?e:Promise.resolve(e)}return g?function(e,{bin:t,log:n,throw:r,stdout:o,stderr:l}){const a=Date.now(),c=e.outputSync(),{success:u,code:d}=c,h=Date.now()-a,f={get stdio(){return[[h,1,this.stdout],[h,2,this.stderr]]},stdin:"",stdout:"piped"===s(o)?i.decode(c.stdout):"",stderr:"piped"===s(l)?i.decode(c.stderr):""};for(const{channel:e,mode:t}of[{channel:"stdout",mode:o},{channel:"stderr",mode:l}])"piped"===s(t)&&f[e]&&n.with({t:h,channel:e})[t]?.(f[e]);if(!u&&r)throw new EvalError(`${t} exited with non-zero code ${d}:\n${f.stdout}\n${f.stderr}`);return{success:u,code:d,...f}}(T,{bin:t,log:l,throw:w,stdout:c,stderr:u}):async function(e,{bin:t,log:n,callback:i=({close:e})=>e?.(),buffering:l=250,throw:a,...c}){const u=e.spawn(),d=Date.now(),h={stdio:[],get stdin(){return this.stdio.filter((([e,t])=>0===t)).map((([e,t,n])=>n)).join("\n")},get stdout(){return this.stdio.filter((([e,t])=>1===t)).map((([e,t,n])=>n)).join("\n")},get stderr(){return this.stdio.filter((([e,t])=>2===t)).map((([e,t,n])=>n)).join("\n")}},f={};let p="";const m=function(e,t){let n=null,r=null;const o=(...i)=>{o.clear(),r=()=>{o.clear(),e.call(o,...i)},n=setTimeout(r,t)};return o.clear=()=>{"number"==typeof n&&(clearTimeout(n),n=null,r=null)},o.flush=()=>{r?.()},Object.defineProperty(o,"pending",{get:()=>"number"==typeof n}),o}((async e=>{n.with({t:e}).trace("debounced"),p="",await i({stdio:h,i:h.stdin.length,...f})}),l);if("piped"===s(c.stdin)){const e=u.stdin.getWriter();Object.assign(f,{async write(t,r=!0){const i=Date.now()-d;c.stdin&&n.with({t:i,channel:"stdin"})[c.stdin]?.(t),h.stdio.push([i,0,t]),r&&!t.endsWith("\n")&&(t+="\n"),await e.write(o.encode(t)),p="stdin",e.releaseLock()},async close(){try{e.releaseLock(),await u.stdin.close(),n.with({t:Date.now()-d,closed:"stdin"}).trace()}catch{}},async wait(e=1e3){const t=Date.now()-d;n.with({t:t,waiting:e}).trace(),await function(e,t={}){const{signal:n,persistent:r=!0}=t;return n?.aborted?Promise.reject(n.reason):new Promise(((t,o)=>{const i=()=>{clearTimeout(l),o(n?.reason)},l=setTimeout((()=>{n?.removeEventListener("abort",i),t()}),e);if(n?.addEventListener("abort",i,{once:!0}),!1===r)try{Deno.unrefTimer(l)}catch(e){if(!(e instanceof ReferenceError))throw e;console.error("`persistent` option is only available in Deno")}}))}(e),m(t)}}),m(Date.now()-d)}await Promise.all(["stdout","stderr"].filter((e=>"piped"===s(c[e]))).map((async e=>{for await(const t of u[e].pipeThrough(new TextDecoderStream).pipeThrough(new r)){const r=Date.now()-d,o={stdout:1,stderr:2}[e];if(c[e]&&n.with({t:r,channel:e})[c[e]]?.(t),h.stdio.length&&p===e){const e=h.stdio.at(-1);e[1]===o&&(e[2]+=`\n${t}`)}else h.stdio.push([r,o,t]);p=e,m(r)}}))),m.flush();const{success:g,code:w}=await u.status;if(!g&&a)throw new EvalError(`${t} exited with non-zero code ${w}:\n${h.stdout}\n${h.stderr}`);return{success:g,code:w,...h}}(T,{bin:t,log:l,callback:p,buffering:m,throw:w,stdin:"piped"===s(a)?a:null,stdout:"piped"===s(c)?c:null,stderr:"piped"===s(u)?u:null})}function s(e){return["inherit","null"].includes(`${e}`)?`${e}`:"piped"}export{l as command};
2
2
  /**
3
- * Logger library
3
+ * Logger class.
4
4
  *
5
- * It is intended to supersed {@link https://developer.mozilla.org/en-US/docs/Web/API/console | console} by providing:
6
- * - Colored output
7
- * - Log levels
8
- * - Tags
9
- * - Timestamps
10
- * - Delta
11
- * - Caller information
12
- * - Log formatters
5
+ * This class provides a simple and efficient logging framework intended to supersed the native {@link https://developer.mozilla.org/en-US/docs/Web/API/console | console} by providing additional features and metadata.
6
+ *
7
+ * It supports out-of-the-box colored output, a log level mechanism (that honor `LOG_LEVEL` environment variable), a tag system, and a variety of options to customize the output.
8
+ * A neat addition is the ability to display caller information (file, name, line) which can be especially useful for debugging.
13
9
  *
14
10
  * @example
15
11
  * ```ts
16
12
  * import { Logger } from "./mod.ts"
17
13
  *
18
14
  * // Configure logger
19
- * const tags = { foo: true, bar: "string" }
20
- * const options = { date: true, time: true, delta: true, caller: { file: true, fileformat: /.*\/(?<file>libs\/.*)$/, name: true, line: true } }
21
- * const log = new Logger({ level: Logger.level.debug, options, tags })
15
+ * const tags = { foo: "bar" }
16
+ * const log = new Logger({ level: "trace", tags, date: true, time: true, delta:true, caller:true })
22
17
  *
23
18
  * // Print logs
24
- * log.error("🍱 bento")
25
- * log.warn("🍜 ramen")
26
- * log.info("🍣 sushi")
27
- * log.log("🍥 narutomaki")
28
- * log.debug("🍡 dango")
19
+ * log
20
+ * .error("🍱 bento")
21
+ * .warn("🍜 ramen")
22
+ * .ok("🍚 gohan")
23
+ * .info("🍣 sushi")
24
+ * .log("🍥 narutomaki")
25
+ * .debug("🍡 dango")
26
+ * .wdebug("🍵 matcha")
27
+ * .trace("🍙 onigiri")
28
+ * .probe("🥟 gyoza")
29
+ *
30
+ * // Create a new inherited logger with additional tags
31
+ * log.with({ bar: "true" }).log("🍶 sake")
32
+ * ```
33
+ *
34
+ * @author Simon Lecoq (lowlighter)
35
+ * @license MIT
36
+ * @module
37
+ */
38
+ /**
39
+ * Run a command.
40
+ *
41
+ * This is a wrapper around {@link https://docs.deno.com/api/deno/~/Deno.Command | Deno.command} that provides a better handling of stdio for interactive processes.
42
+ *
43
+ * Like `Deno.command`, the `env`, `cwd`, and `raw` (alias for `windowsRawArguments`) options are supported.
44
+ *
45
+ * The `stdin`, `stdout` and `stderr` options can be either set to an allowed {@link https://docs.deno.com/api/deno/~/Deno.Command | Deno.command} values (`"inherit"`, `"null"`, `"piped"`), or either to a supported log level of {@link Logger}.
46
+ * In the later case, the content will be always be "piped" and logged to the specified level of the provided {@link Logger} instance.
47
+ *
48
+ * Set `winext` option to automatically append an extension to the binary path on Windows (like `.cmd` or `.exe`).
49
+ * This is useful when the binary path isn't automatically resolved on Windows.
50
+ *
51
+ * Pass a `callback` option to interact with the process stdin and stdout.
52
+ * It is called each time data is received on of the piped channels, after input buffering.
53
+ * It will receive an object with the current stdio content, the current command index (based on the content written to stdin), along with a few additional functions:
54
+ * - `write(content: string, newline?: boolean): Promise<void>` encodes and writes content to stdin.
55
+ * - A newline is automatically appended by default but can be toggled off by passing `false` as second argument.
56
+ * - `close(): Promise<void>` closes stdin.
57
+ * - Note that you **need** to eventually call this method to prevent most processes from hanging as they're waiting for more input.
58
+ * - `wait(dt: number): Promise<void>` waits for a given amount of time before calling the callback again.
59
+ * - It is especially useful for polling, like checking if a specific line has been written to stdio or not.
60
+ *
61
+ * The `buffering` option is used to merge messages that are received relatively closely.
62
+ * Setting this option to a low value will also increase the rate at which the `callback` is called.
63
+ *
64
+ * Resulting object contains the same properties as {@link https://docs.deno.com/api/deno/~/Deno.CommandStatus | Deno.CommandStatus}
65
+ * with an additional `stdio` property that contains an array of ordered tuples with the delta timestamp since process start, the channel idenfitier (0:stdin, 1:stdout, 2:stderr) and the content.
66
+ * This offers a proper history of exchanged content.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * import { command } from "./command.ts"
71
+ * import { Logger } from "jsr:@libs/logger"
72
+ * await command("deno", ["version"], { env: { NO_COLOR: "true" }, cwd: "/tmp", raw: true })
73
+ * await command("deno", ["version"], { stdout: "piped" })
74
+ * await command("deno", ["version"], { logger: new Logger(), stdout: "debug" })
75
+ * await command("deno", ["version"], { winext: ".exe" })
76
+ * ```
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * import { command } from "./command.ts"
81
+ * const { stdout } = await command("deno", ["repl"], {
82
+ * env: { NO_COLOR: "true" },
83
+ * callback: ({ i, write, close }) => i === 0 ? write("console.log('hello')") : close(),
84
+ * })
85
+ * console.assert(stdout.includes("hello"))
29
86
  * ```
30
87
  *
31
88
  * @author Simon Lecoq (lowlighter)
32
89
  * @license MIT
90
+ * @module
33
91
  */
package/command.ts CHANGED
@@ -4,11 +4,12 @@ import { Logger, type loglevel } from "@libs/logger"
4
4
  import { TextLineStream } from "@std/streams"
5
5
  import { debounce } from "@std/async/debounce"
6
6
  import { delay } from "@std/async/delay"
7
+ export type { Logger, loglevel, Promisable }
7
8
 
8
9
  /** Run options. */
9
10
  export type options = {
10
- /** Logger. */
11
- log?: Logger
11
+ /** Logger instance. */
12
+ logger?: Logger
12
13
  /** Environment variables. */
13
14
  env?: Deno.CommandOptions["env"]
14
15
  /** Current working directory. */
@@ -80,45 +81,82 @@ const encoder = new TextEncoder()
80
81
  /** Text decoder */
81
82
  const decoder = new TextDecoder()
82
83
 
84
+ /**
85
+ * Asynchronous version of {@link command}.
86
+ *
87
+ * Note that stdin is not usable in sync mode and will always be empty.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { command } from "./command.ts"
92
+ * command("deno", ["version"], { sync: true })
93
+ * ```
94
+ * @example
95
+ * ```
96
+ * import { command } from "./command.ts"
97
+ * try {
98
+ * command("deno", ["eval", "Deno.exit(1)"], { sync: true, throw: true })
99
+ * }
100
+ * catch (error) {
101
+ * console.log(error)
102
+ * }
103
+ * ```
104
+ */
83
105
  export function command(bin: string, args: string[], options?: options & { sync?: false }): Promise<result>
106
+ /**
107
+ * Synchronous version of {@link command}.
108
+ *
109
+ * @example
110
+ * ```
111
+ * import { command } from "./command.ts"
112
+ * try {
113
+ * await command("deno", ["eval", "Deno.exit(1)"], { throw: true })
114
+ * }
115
+ * catch (error) {
116
+ * console.log(error)
117
+ * }
118
+ * ```
119
+ */
84
120
  export function command(bin: string, args: string[], options?: options & { sync: true }): result
85
121
  /**
86
122
  * Run a command.
87
123
  *
88
- * This is a wrapper around `Deno.command` that provides a better handling of stdio for interactive processes.
124
+ * This is a wrapper around {@link https://docs.deno.com/api/deno/~/Deno.Command | Deno.command} that provides a better handling of stdio for interactive processes.
89
125
  *
90
- * `stdin`, `stdout` and `stderr` can be set to allowed `Deno.command` values (`"inherit"`, `"null"`, `"piped"`),
91
- * or can be set to a supported log level of `@libs/logger` library. In the later case, the content will be piped and
92
- * logged to the specified level.
126
+ * Like `Deno.command`, the `env`, `cwd`, and `raw` (alias for `windowsRawArguments`) options are supported.
93
127
  *
94
- * Like `Deno.command`, it is possible to pass `env`, `cwd`, and `raw` (alias for `windowsRawArguments`).
128
+ * The `stdin`, `stdout` and `stderr` options can be either set to an allowed {@link https://docs.deno.com/api/deno/~/Deno.Command | Deno.command} values (`"inherit"`, `"null"`, `"piped"`), or either to a supported log level of {@link Logger}.
129
+ * In the later case, the content will be always be "piped" and logged to the specified level of the provided {@link Logger} instance.
95
130
  *
96
- * You can pass the `sync` option to run the process synchronously. Note that stdin is not usable in sync mode and will
97
- * always be empty.
131
+ * Set `winext` option to automatically append an extension to the binary path on Windows (like `.cmd` or `.exe`).
132
+ * This is useful when the binary path isn't automatically resolved on Windows.
98
133
  *
99
- * You can pass the `throw` option to throw an error if the process exits with a non-zero code rather than returning a result.
134
+ * Pass a `callback` option to interact with the process stdin and stdout.
135
+ * It is called each time data is received on of the piped channels, after input buffering.
136
+ * It will receive an object with the current stdio content, the current command index (based on the content written to stdin), along with a few additional functions:
137
+ * - `write(content: string, newline?: boolean): Promise<void>` encodes and writes content to stdin.
138
+ * - A newline is automatically appended by default but can be toggled off by passing `false` as second argument.
139
+ * - `close(): Promise<void>` closes stdin.
140
+ * - Note that you **need** to eventually call this method to prevent most processes from hanging as they're waiting for more input.
141
+ * - `wait(dt: number): Promise<void>` waits for a given amount of time before calling the callback again.
142
+ * - It is especially useful for polling, like checking if a specific line has been written to stdio or not.
100
143
  *
101
- * You can pass the `winext` option to append an extension to the binary path on Windows (like `.cmd` or `.exe`) when it won't
102
- * automatically be resolved.
144
+ * The `buffering` option is used to merge messages that are received relatively closely.
145
+ * Setting this option to a low value will also increase the rate at which the `callback` is called.
103
146
  *
104
- * Resulted object contains the same properties as `Deno.CommandStatus` with additional `stdio` property that contains an array
105
- * of ordered tuples with the delta timestamp since process start, the channel (0:stdin, 1:stdout, 2:stderr) and the content.
106
- * This allows to easily track the order of messages and have a proper history rather than a merged output.
147
+ * Resulting object contains the same properties as {@link https://docs.deno.com/api/deno/~/Deno.CommandStatus | Deno.CommandStatus}
148
+ * with an additional `stdio` property that contains an array of ordered tuples with the delta timestamp since process start, the channel idenfitier (0:stdin, 1:stdout, 2:stderr) and the content.
149
+ * This offers a proper history of exchanged content.
107
150
  *
108
- * Additionally, you can buffer output using the `buffering` option to merge messages that are received relatively closely.
109
- *
110
- * Finally, you can pass a `callback` option to interact with the process stdin and stdout.
111
- * This callback receives an object with the current stdio content, the current command index (based on the content written to stdin),
112
- * along with a few additional methods:
113
- * - `write(content: string, newline?: boolean): Promise<void>` encodes and writes content to stdin. It automatically appends a newline
114
- * by default but can be toggled off by setting `newline` to `false`.
115
- * - `close(): Promise<void>` closes stdin. Note that you **need** to eventually call this method to prevent most processes from hanging as
116
- * they're waiting for more input.
117
- * - `wait(dt: number): Promise<void>` waits for a given amount of time before calling the callback again. This can be useful for polling,
118
- * like checking if a specific line has been written to stdio or not.
119
- *
120
- * The callback is called at each new line from piped channels, except that it is debounced by the `buffering` option which makes it more
121
- * likely to send input when the process is actually expecting it.
151
+ * @example
152
+ * ```ts
153
+ * import { command } from "./command.ts"
154
+ * import { Logger } from "jsr:@libs/logger"
155
+ * await command("deno", ["version"], { env: { NO_COLOR: "true" }, cwd: "/tmp", raw: true })
156
+ * await command("deno", ["version"], { stdout: "piped" })
157
+ * await command("deno", ["version"], { logger: new Logger(), stdout: "debug" })
158
+ * await command("deno", ["version"], { winext: ".exe" })
159
+ * ```
122
160
  *
123
161
  * @example
124
162
  * ```ts
@@ -130,13 +168,11 @@ export function command(bin: string, args: string[], options?: options & { sync:
130
168
  * console.assert(stdout.includes("hello"))
131
169
  * ```
132
170
  *
133
- * @example
134
- * ```ts
135
- * import { command } from "./command.ts"
136
- * command("deno", ["eval", "Deno.exit(1)"], { throw: true, sync: true })
137
- * ```
171
+ * @author Simon Lecoq (lowlighter)
172
+ * @license MIT
173
+ * @module
138
174
  */
139
- export function command(bin: string, args: string[], { log = new Logger(), stdin = null, stdout = "debug", stderr = "error", env, cwd, raw, callback, buffering, sync, throw: _throw, dryrun, winext = "", os = Deno.build.os } = {} as options): Promisable<result> {
175
+ export function command(bin: string, args: string[], { logger: log = new Logger(), stdin = null, stdout = "debug", stderr = "error", env, cwd, raw, callback, buffering, sync, throw: _throw, dryrun, winext = "", os = Deno.build.os } = {} as options): Promisable<result> {
140
176
  if (os === "windows") {
141
177
  bin = `${bin}${winext}`
142
178
  }
@@ -146,7 +182,7 @@ export function command(bin: string, args: string[], { log = new Logger(), stdin
146
182
  }
147
183
  const command = new Deno.Command(bin, { args, stdin: !sync ? handle(stdin) : "null", stdout: handle(stdout), stderr: handle(stderr), env, cwd, windowsRawArguments: raw })
148
184
  if (dryrun) {
149
- log.debug(`dryrun: ${bin} not executed`)
185
+ log.wdebug(`dryrun: ${bin} not executed`)
150
186
  const result = { success: true, code: 0, stdio: [], stdin: "", stdout: "", stderr: "" }
151
187
  return sync ? result : Promise.resolve(result)
152
188
  }
@@ -208,7 +244,7 @@ async function spawn(
208
244
  const options = {} as Pick<Arg<callback>, "write" | "close" | "wait">
209
245
  let last = ""
210
246
  const debounced = debounce(async (t: number) => {
211
- log.with({ t }).debug("debounced")
247
+ log.with({ t }).trace("debounced")
212
248
  last = ""
213
249
  await callback({ stdio, i: stdio.stdin.length, ...options })
214
250
  }, buffering)
@@ -233,14 +269,14 @@ async function spawn(
233
269
  try {
234
270
  writer.releaseLock()
235
271
  await process.stdin.close()
236
- log.with({ t: Date.now() - start, closed: "stdin" }).debug()
272
+ log.with({ t: Date.now() - start, closed: "stdin" }).trace()
237
273
  } catch {
238
274
  // Ignore
239
275
  }
240
276
  },
241
277
  async wait(dt = 1000) {
242
278
  const t = Date.now() - start
243
- log.with({ t, waiting: dt }).debug()
279
+ log.with({ t, waiting: dt }).trace()
244
280
  await delay(dt)
245
281
  debounced(t)
246
282
  },
package/command_test.ts CHANGED
@@ -49,7 +49,7 @@ for (const sync of [false, true]) {
49
49
  for (const mode of ["inherit", "piped", null, "debug", "log", "info", "warn", "error"] as const) {
50
50
  test("deno")(`command() supports stdio set to "${mode}" in "${sync ? "sync" : "async"}" mode`, async () => {
51
51
  const result = await command("deno", ["--version"], {
52
- log: new Logger({ level: Logger.level.disabled }),
52
+ logger: new Logger({ level: Logger.level.disabled }),
53
53
  env: { NO_COLOR: "true" },
54
54
  stdin: mode,
55
55
  stdout: mode,
package/deno.jsonc CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "icon": "⏯️",
3
3
  "name": "@libs/run",
4
- "version": "1.0.2",
4
+ "version": "2.0.0",
5
5
  "description": "Utilities to run subprocess.",
6
6
  "keywords": [
7
7
  "subprocess",
@@ -28,8 +28,8 @@
28
28
  "@std/async/delay": "jsr:@std/async@0.224.2/delay",
29
29
  "@std/async/debounce": "jsr:@std/async@0.224.2/debounce",
30
30
  "@std/streams": "jsr:@std/streams@0.224.5",
31
- "@libs/logger": "jsr:@libs/logger@1",
32
- "@libs/testing": "jsr:@libs/testing@1",
31
+ "@libs/logger": "jsr:@libs/logger@2",
32
+ "@libs/testing": "jsr:@libs/testing@2",
33
33
  "@libs/typing": "jsr:@libs/typing@2"
34
34
  },
35
35
  "test:permissions": {
@@ -42,11 +42,12 @@
42
42
  },
43
43
  "tasks": {
44
44
  "test": "deno test --allow-run=deno,node,bun,npx --no-prompt --coverage --clean --trace-leaks --doc",
45
- "dev": "deno fmt && deno task test --filter='/^\\[deno\\]/' && deno coverage --exclude=.js --detailed && deno lint && deno publish --dry-run --quiet --allow-dirty",
46
- "dev:future": "DENO_FUTURE=1 && deno task dev",
47
- "coverage": "deno task test --filter='/^\\[deno\\]/' --quiet && deno coverage --exclude=.js",
48
- "ci": "deno fmt --check && deno task test --filter='/^\\[node|bun \\]/' --quiet && deno coverage --exclude=.js && deno lint",
49
- "ci:coverage": "deno task coverage --html && sleep 1 && mkdir -p ../coverage && rm -rf ../coverage/run && mv coverage/html ../coverage/run"
45
+ "dev": "deno fmt && deno task test --filter='/^\\[deno\\]/' && deno coverage --exclude=.js --detailed && deno task lint",
46
+ "test:deno": "deno fmt --check && deno task test --filter='/^\\[deno\\]/' --quiet && deno coverage --exclude=.js && deno lint",
47
+ "test:deno-future": "DENO_FUTURE=1 && deno task test:deno",
48
+ "test:others": "deno fmt --check && deno task test --filter='/^\\[node|bun \\]/' --quiet && deno coverage --exclude=.js && deno lint",
49
+ "coverage:html": "deno task test --filter='/^\\[deno\\]/' --quiet && deno coverage --exclude=.js --html && sleep 1",
50
+ "lint": "deno fmt --check && deno lint && deno doc --lint mod.ts && deno publish --dry-run --quiet --allow-dirty"
50
51
  },
51
52
  "lint": {
52
53
  "rules": {
package/deno.lock CHANGED
@@ -2,36 +2,48 @@
2
2
  "version": "3",
3
3
  "packages": {
4
4
  "specifiers": {
5
- "jsr:@libs/logger@1": "jsr:@libs/logger@1.1.2",
6
- "jsr:@libs/testing@1": "jsr:@libs/testing@1.1.0",
7
- "jsr:@libs/typing@2": "jsr:@libs/typing@2.3.0",
8
- "jsr:@std/assert@^0.225.3": "jsr:@std/assert@0.225.3",
5
+ "jsr:@libs/logger": "jsr:@libs/logger@2.0.0",
6
+ "jsr:@libs/logger@2": "jsr:@libs/logger@2.0.0",
7
+ "jsr:@libs/testing@2": "jsr:@libs/testing@2.0.0",
8
+ "jsr:@libs/typing@2": "jsr:@libs/typing@2.6.0",
9
+ "jsr:@std/assert@0.226.0": "jsr:@std/assert@0.226.0",
10
+ "jsr:@std/assert@1.0.0-rc.2": "jsr:@std/assert@1.0.0-rc.2",
9
11
  "jsr:@std/async@0.224.2": "jsr:@std/async@0.224.2",
10
12
  "jsr:@std/bytes@^1.0.0-rc.3": "jsr:@std/bytes@1.0.1",
11
13
  "jsr:@std/bytes@^1.0.1-rc.3": "jsr:@std/bytes@1.0.1",
12
- "jsr:@std/expect@0.224.3": "jsr:@std/expect@0.224.3",
14
+ "jsr:@std/expect@0.224.5": "jsr:@std/expect@0.224.5",
15
+ "jsr:@std/http@0.224.5": "jsr:@std/http@0.224.5",
13
16
  "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.1",
14
17
  "jsr:@std/io@^0.224.1": "jsr:@std/io@0.224.3",
15
- "jsr:@std/path@0.225.1": "jsr:@std/path@0.225.1",
18
+ "jsr:@std/path@0.225.2": "jsr:@std/path@0.225.2",
16
19
  "jsr:@std/streams@0.224.5": "jsr:@std/streams@0.224.5"
17
20
  },
18
21
  "jsr": {
19
- "@libs/logger@1.1.2": {
20
- "integrity": "7d718f54c9a5d1c00f570051bee0217e3b282e8c2131144ec55c794a8ebd5d4f"
22
+ "@libs/logger@2.0.0": {
23
+ "integrity": "f92b53802c94651a07abcd4995387c290f08433ad34030079779337d0bfbfe7e"
21
24
  },
22
- "@libs/testing@1.1.0": {
23
- "integrity": "961fed690ee11a6f0f2cf8ed759b9b3a5c40ca24d319ba6c18cbbdaf73f1b29b",
25
+ "@libs/testing@2.0.0": {
26
+ "integrity": "a1cb40f127ec8ab3a37814beb13ff604b5aacbf9da633b413b243b7fcbd2309a",
24
27
  "dependencies": [
25
28
  "jsr:@libs/run@1",
26
- "jsr:@std/expect@0.224.3",
27
- "jsr:@std/path@0.225.1"
29
+ "jsr:@libs/typing@2",
30
+ "jsr:@std/assert@0.226.0",
31
+ "jsr:@std/expect@0.224.5",
32
+ "jsr:@std/http@0.224.5",
33
+ "jsr:@std/path@0.225.2"
28
34
  ]
29
35
  },
30
- "@libs/typing@2.3.0": {
31
- "integrity": "8a03098b2d63763bc3b45b58f6e847d9c085405db83330c588aecb6b670587d0"
36
+ "@libs/typing@2.6.0": {
37
+ "integrity": "07a1ee52fa6d769d68d95d7310bac7dd608932c91760a0bc8a95ce6de3630603"
32
38
  },
33
- "@std/assert@0.225.3": {
34
- "integrity": "b3c2847aecf6955b50644cdb9cf072004ea3d1998dd7579fc0acb99dbb23bd4f",
39
+ "@std/assert@0.226.0": {
40
+ "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3",
41
+ "dependencies": [
42
+ "jsr:@std/internal@^1.0.0"
43
+ ]
44
+ },
45
+ "@std/assert@1.0.0-rc.2": {
46
+ "integrity": "0484eab1d76b55fca1c3beaff485a274e67dd3b9f065edcbe70030dfc0b964d3",
35
47
  "dependencies": [
36
48
  "jsr:@std/internal@^1.0.0"
37
49
  ]
@@ -42,13 +54,16 @@
42
54
  "@std/bytes@1.0.1": {
43
55
  "integrity": "e57c9b243932b95a4c3672f8a038cdadea7492efeeb6b8a774844fee70426815"
44
56
  },
45
- "@std/expect@0.224.3": {
46
- "integrity": "eacd71aaa426472bf04ddde7e0765b0b360d3ffa33ad2ca05a96fd7052caea41",
57
+ "@std/expect@0.224.5": {
58
+ "integrity": "ae6e57ee6f23a2fd5ae130cb84448eef182354cb902e1a2a98d64c72ac3a5a1c",
47
59
  "dependencies": [
48
- "jsr:@std/assert@^0.225.3",
60
+ "jsr:@std/assert@1.0.0-rc.2",
49
61
  "jsr:@std/internal@^1.0.0"
50
62
  ]
51
63
  },
64
+ "@std/http@0.224.5": {
65
+ "integrity": "b03b5d1529f6c423badfb82f6640f9f2557b4034cd7c30655ba5bb447ff750a4"
66
+ },
52
67
  "@std/internal@1.0.1": {
53
68
  "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6"
54
69
  },
@@ -58,8 +73,8 @@
58
73
  "jsr:@std/bytes@^1.0.1-rc.3"
59
74
  ]
60
75
  },
61
- "@std/path@0.225.1": {
62
- "integrity": "8c3220635a73730eb51fe43de9e10b79e2724a5bb8638b9355d35ae012fd9429"
76
+ "@std/path@0.225.2": {
77
+ "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506"
63
78
  },
64
79
  "@std/streams@0.224.5": {
65
80
  "integrity": "bcde7818dd5460d474cdbd674b15f6638b9cd73cd64e52bd852fba2bd4d8ec91",
@@ -73,8 +88,8 @@
73
88
  "remote": {},
74
89
  "workspace": {
75
90
  "dependencies": [
76
- "jsr:@libs/logger@1",
77
- "jsr:@libs/testing@1",
91
+ "jsr:@libs/logger@2",
92
+ "jsr:@libs/testing@2",
78
93
  "jsr:@libs/typing@2",
79
94
  "jsr:@std/async@0.224.2",
80
95
  "jsr:@std/streams@0.224.5"
package/mod.mjs CHANGED
@@ -1,33 +1,91 @@
1
- var e=class e{constructor({level:t,format:n,output:o,tags:r,options:s}={}){if("granted"===globalThis.Deno?.permissions.querySync?.({name:"env",variable:"LOG_LEVEL"}).state){const n=globalThis.Deno?.env.get("LOG_LEVEL")??"";n in e.level&&(t??=e.level[n]),Number.isNaN(Number.parseInt(n))||(t??=Number.parseInt(n))}this.level=t??e.level.log,this.#e=o||null===o?o:console,this.#t=n??e.format.text,this.tags=r??{},this.options={date:!1,time:!1,delta:!0,...s,caller:!1!==s?.caller&&{file:!1,name:!1,line:!1,...s?.caller}}}level;#t;#e;tags;options;error(...t){return this.level>=e.level.error&&this.#e?.error(...this.#t(this,{level:e.level.error,content:t})),this}warn(...t){return this.level>=e.level.warn&&this.#e?.warn(...this.#t(this,{level:e.level.warn,content:t})),this}info(...t){return this.level>=e.level.info&&this.#e?.info(...this.#t(this,{level:e.level.info,content:t})),this}log(...t){return this.level>=e.level.log&&this.#e?.log(...this.#t(this,{level:e.level.log,content:t})),this}debug(...t){return this.level>=e.level.debug&&this.#e?.debug(...this.#t(this,{level:e.level.debug,content:t})),this}with(t={}){return new e({level:this.level,format:this.#t,output:this.#e,options:{...this.options},tags:{...this.tags,...t}})}#n(e=3){const t=Error,n=t.prepareStackTrace;t.prepareStackTrace=(e,t)=>t;const{stack:o}=new Error;t.prepareStackTrace=n;const r=o[e];return{file:r.getFileName(),name:r.getFunctionName(),line:r.getLineNumber(),column:r.getColumnNumber()}}static level=Object.freeze({disabled:NaN,error:0,warn:1,info:2,log:3,debug:4});static format={text(t,{level:n=0,content:o}){const r=["red","orange","cyan","white","gray"][n],s=[`%c ${Object.keys(e.level).find((t=>e.level[t]===n)).toLocaleUpperCase().padEnd(5)} │%c`],i=[`color: black; background-color: ${r}`,""];if(t.options.date||t.options.time||t.options.delta){const e=(new Date).toISOString(),n=[];if(t.options.delta){const e=performance.now()/1e3;let t=e.toPrecision(4);e<1&&(t=e.toPrecision(2)),n.push(`+${t}`)}t.options.date&&t.options.time?n.push(e):t.options.date?n.push(e.slice(0,e.indexOf("T"))):t.options.time&&n.push(e.slice(e.indexOf("T")+1,-1)),s.push(`%c ${n.join(" ¦ ").trim()} %c`),i.push(`color: black; background-color: ${r}`,"")}if(t.options.caller){const e=t.#n();if(e){const n=[];t.options.caller.file&&n.push(`${e.file.replace(t.options.caller.fileformat,"$<file>")}`),t.options.caller.name&&e.name&&n.push(e.name),t.options.caller.line&&n.push(e.line,e.column),s.push(`%c ${n.join(":").trim()} %c`),i.push("color: black; background-color: gray","")}}{const n=[];for(const[o,r]of Object.entries(t.tags))n.push(`${o}:${e.inspect(r)}`);s.push(`%c ${n.join(" ").trim()} %c`),i.push("background-color: black","")}return[s.join(""),...i,...o.map(e.inspect)]},json(t,{level:n=0,content:o}){const r={level:Object.keys(e.level).find((t=>e.level[t]===n)),timestamp:Date.now(),tags:t.tags,content:o},s={};if(t.options.date||t.options.time){const e=new Date(r.timestamp).toISOString();t.options.date&&(s.date=e.slice(0,e.indexOf("T"))),t.options.time&&(s.time=e.slice(e.indexOf("T")+1,-1))}if(t.options.delta&&(s.delta=performance.now()/1e3),t.options.caller){const e=t.#n();e&&(s.caller={},t.options.caller.file&&(s.caller.file=`${e.file.replace(t.options.caller.fileformat,"$<file>")}`),t.options.caller.name&&e.name&&(s.caller.name=e.name),t.options.caller.line&&(s.caller.line=[e.line,e.column]))}return[JSON.stringify({...r,...s})]}};static inspect(e){return globalThis.Deno?.inspect(e,{colors:!0,depth:1/0,strAbbreviateSize:1/0})??e}};TransformStream;function t(e){let t=0;for(const n of e)t+=n.length;const n=new Uint8Array(t);let o=0;for(const t of e)n.set(t,o),o+=t.length;return n}function n(e){const t=e.length,n=new Uint8Array(t);n[0]=0;let o=0,r=1;for(;r<t;)e[r]===e[o]?(o++,n[r]=o,r++):0===o?(n[r]=0,r++):o=n[o-1];return n}TransformStream,TransformStream,TransformStream,TransformStream;var o=class extends TransformStream{#o="";constructor(e={allowCR:!1}){super({transform:(t,n)=>{for(t=this.#o+t;;){const o=t.indexOf("\n"),r=e.allowCR?t.indexOf("\r"):-1;if(-1!==r&&r!==t.length-1&&(-1===o||o-1>r)){n.enqueue(t.slice(0,r)),t=t.slice(r+1);continue}if(-1===o)break;const s="\r"===t[o-1]?o-1:o;n.enqueue(t.slice(0,s)),t=t.slice(o+1)}this.#o=t},flush:t=>{if(""===this.#o)return;const n=e.allowCR&&this.#o.endsWith("\r")?this.#o.slice(0,-1):this.#o;t.enqueue(n)}})}};new TextDecoder;var r=new TextEncoder,s=new TextDecoder;function i(t,n,{log:i=new e,stdin:a=null,stdout:c="debug",stderr:u="error",env:d,cwd:p,raw:h,callback:f,buffering:m,sync:w,throw:g,dryrun:b,winext:v="",os:T=Deno.build.os}={}){"windows"===T&&(t=`${t}${v}`),i=i.with({bin:t}),f&&"piped"!==l(a)&&(a="piped");const $=new Deno.Command(t,{args:n,stdin:w?"null":l(a),stdout:l(c),stderr:l(u),env:d,cwd:p,windowsRawArguments:h});if(b){i.debug(`dryrun: ${t} not executed`);const e={success:!0,code:0,stdio:[],stdin:"",stdout:"",stderr:""};return w?e:Promise.resolve(e)}return w?function(e,{bin:t,log:n,throw:o,stdout:r,stderr:i}){const a=Date.now(),c=e.outputSync(),{success:u,code:d}=c,p=Date.now()-a,h={get stdio(){return[[p,1,this.stdout],[p,2,this.stderr]]},stdin:"",stdout:"piped"===l(r)?s.decode(c.stdout):"",stderr:"piped"===l(i)?s.decode(c.stderr):""};for(const{channel:e,mode:t}of[{channel:"stdout",mode:r},{channel:"stderr",mode:i}])"piped"===l(t)&&h[e]&&n.with({t:p,channel:e})[t]?.(h[e]);if(!u&&o)throw new EvalError(`${t} exited with non-zero code ${d}:\n${h.stdout}\n${h.stderr}`);return{success:u,code:d,...h}}($,{bin:t,log:i,throw:g,stdout:c,stderr:u}):async function(e,{bin:t,log:n,callback:s=(({close:e})=>e?.()),buffering:i=250,throw:a,...c}){const u=e.spawn(),d=Date.now(),p={stdio:[],get stdin(){return this.stdio.filter((([e,t])=>0===t)).map((([e,t,n])=>n)).join("\n")},get stdout(){return this.stdio.filter((([e,t])=>1===t)).map((([e,t,n])=>n)).join("\n")},get stderr(){return this.stdio.filter((([e,t])=>2===t)).map((([e,t,n])=>n)).join("\n")}},h={};let f="";const m=function(e,t){let n=null,o=null;const r=(...s)=>{r.clear(),o=()=>{r.clear(),e.call(r,...s)},n=setTimeout(o,t)};return r.clear=()=>{"number"==typeof n&&(clearTimeout(n),n=null,o=null)},r.flush=()=>{o?.()},Object.defineProperty(r,"pending",{get:()=>"number"==typeof n}),r}((async e=>{n.with({t:e}).debug("debounced"),f="",await s({stdio:p,i:p.stdin.length,...h})}),i);if("piped"===l(c.stdin)){const e=u.stdin.getWriter();Object.assign(h,{async write(t,o=!0){const s=Date.now()-d;c.stdin&&n.with({t:s,channel:"stdin"})[c.stdin]?.(t),p.stdio.push([s,0,t]),o&&!t.endsWith("\n")&&(t+="\n"),await e.write(r.encode(t)),f="stdin",e.releaseLock()},async close(){try{e.releaseLock(),await u.stdin.close(),n.with({t:Date.now()-d,closed:"stdin"}).debug()}catch{}},async wait(e=1e3){const t=Date.now()-d;n.with({t:t,waiting:e}).debug(),await function(e,t={}){const{signal:n,persistent:o=!0}=t;return n?.aborted?Promise.reject(n.reason):new Promise(((t,r)=>{const s=()=>{clearTimeout(i),r(n?.reason)},i=setTimeout((()=>{n?.removeEventListener("abort",s),t()}),e);if(n?.addEventListener("abort",s,{once:!0}),!1===o)try{Deno.unrefTimer(i)}catch(e){if(!(e instanceof ReferenceError))throw e;console.error("`persistent` option is only available in Deno")}}))}(e),m(t)}}),m(Date.now()-d)}await Promise.all(["stdout","stderr"].filter((e=>"piped"===l(c[e]))).map((async e=>{for await(const t of u[e].pipeThrough(new TextDecoderStream).pipeThrough(new o)){const o=Date.now()-d,r={stdout:1,stderr:2}[e];if(c[e]&&n.with({t:o,channel:e})[c[e]]?.(t),p.stdio.length&&f===e){const e=p.stdio.at(-1);e[1]===r&&(e[2]+=`\n${t}`)}else p.stdio.push([o,r,t]);f=e,m(o)}}))),m.flush();const{success:w,code:g}=await u.status;if(!w&&a)throw new EvalError(`${t} exited with non-zero code ${g}:\n${p.stdout}\n${p.stderr}`);return{success:w,code:g,...p}}($,{bin:t,log:i,callback:f,buffering:m,throw:g,stdin:"piped"===l(a)?a:null,stdout:"piped"===l(c)?c:null,stderr:"piped"===l(u)?u:null})}function l(e){return["inherit","null"].includes(`${e}`)?`${e}`:"piped"}export{i as command};
1
+ var e=class e{constructor({level:t,format:n,output:r,tags:o,...i}={}){"granted"===globalThis.Deno?.permissions.querySync?.({name:"env",variable:"LOG_LEVEL"}).state&&(t??=globalThis.Deno?.env.get("LOG_LEVEL")),this.#e=r||null===r?r:console,this.#t=e.level.log,this.#n=e.format.text,this.#r={date:!1,time:!1,delta:!0,caller:{file:!1,name:!1,line:!1,fileformat:null}},this.level(t).format(n).options(i),this.tags=o??{}}#e;#r;options(e){if(!e)return structuredClone(this.#r);if("date"in e&&(this.#r.date=e.date),"time"in e&&(this.#r.time=e.time),"delta"in e&&(this.#r.delta=e.delta),"caller"in e)switch(!0){case!0===e.caller:this.#r.caller={file:!0,name:!0,line:!0,fileformat:this.#r.caller.fileformat};break;case!1===e.caller:this.#r.caller={file:!1,name:!1,line:!1,fileformat:this.#r.caller.fileformat};break;case"object"==typeof e.caller:"file"in e.caller&&(this.#r.caller.file=e.caller.file),"name"in e.caller&&(this.#r.caller.name=e.caller.name),"line"in e.caller&&(this.#r.caller.line=e.caller.line),"fileformat"in e.caller&&void 0!==e.caller.fileformat&&(this.#r.caller.fileformat=e.caller.fileformat)}return this}#t;level(t){return arguments.length?(Number.isNaN(Number.parseInt(`${t}`))||(t=Number.parseInt(`${t}`)),"string"==typeof t&&t in e.level&&(t=e.level[t]),"number"==typeof t&&(this.#t=t>=0?t:e.level.disabled),this):this.#t}static level=Object.freeze({disabled:NaN,probe:NaN,error:0,warn:1,info:2,ok:2,log:3,debug:4,wdebug:4,trace:5});error(...t){return this.#t>=e.level.error&&this.#e?.error(...this.#n(this,{level:"error",content:t,options:this.#r})),this}warn(...t){return this.#t>=e.level.warn&&this.#e?.warn(...this.#n(this,{level:"warn",content:t,options:this.#r})),this}info(...t){return this.#t>=e.level.info&&this.#e?.info(...this.#n(this,{level:"info",content:t,options:this.#r})),this}ok(...t){return this.#t>=e.level.info&&this.#e?.info(...this.#n(this,{level:"ok",content:t,options:this.#r})),this}log(...t){return this.#t>=e.level.log&&this.#e?.log(...this.#n(this,{level:"log",content:t,options:this.#r})),this}debug(...t){return this.#t>=e.level.debug&&this.#e?.debug(...this.#n(this,{level:"debug",content:t,options:this.#r})),this}wdebug(...t){return this.#t>=e.level.debug&&this.#e?.debug(...this.#n(this,{level:"wdebug",content:t,options:this.#r})),this}trace(...t){return this.#t>=e.level.trace&&this.#e?.debug(...this.#n(this,{level:"trace",content:t,options:this.#r})),this}probe(...e){return this.#e?.debug(...this.#n(this,{level:"probe",content:e,options:this.#r})),this}tags;with(t={}){return new e({level:this.#t,format:this.#n,output:this.#e,...this.#r,tags:{...this.tags,...t}})}#o(e=3){const t=Error,n=t.prepareStackTrace;t.prepareStackTrace=(e,t)=>t;const{stack:r}=new Error;t.prepareStackTrace=n;return r.map((e=>({file:e.getFileName(),name:e.getFunctionName(),line:e.getLineNumber(),column:e.getColumnNumber()})))[e]}static inspect(e){return globalThis.Deno?.inspect(e,{colors:!0,depth:1/0,strAbbreviateSize:1/0})??e}#n;format(t){return this.#n=("string"==typeof t?e.format[t]:t)??e.format.text,this}static format={text(t,{level:n,content:r,options:o}){const i={error:"red",warn:"orange",info:"cyan",ok:"green",log:"white",debug:"gray",wdebug:"yellow",trace:"gray",probe:"magenta"}[n],l=[`%c ${n.replace("wdebug","debug").toLocaleUpperCase().padEnd(5)} │%c`],s=[`color: black; background-color: ${i}`,""];if(o.date||o.time||o.delta){const e=(new Date).toISOString(),t=[];if(o.delta){const e=performance.now()/1e3;let n=e.toPrecision(4);e<1&&(n=e.toPrecision(2)),t.push(`+${n}`)}o.date&&o.time?t.push(e):o.date?t.push(e.slice(0,e.indexOf("T"))):o.time&&t.push(e.slice(e.indexOf("T")+1,-1)),l.push(`%c ${t.join(" ¦ ").trim()} %c`),s.push(`color: black; background-color: ${i}`,"")}if(o.caller.file||o.caller.name||o.caller.line){const e=t.#o();if(e){const t=[];if(o.caller.file){let n=`${e.file}`;Array.isArray(o.caller.fileformat)&&(n=n.replace(o.caller.fileformat[0],o.caller.fileformat[1])),t.push(n)}o.caller.name&&e.name&&t.push(e.name),o.caller.line&&t.push(e.line,e.column),l.push(`%c ${t.join(":").trim()} %c`),s.push("color: black; background-color: gray","")}}const a=[];for(const[n,r]of Object.entries(t.tags))a.push(`${n}:${e.inspect(r)}`);return l.push(`%c ${a.join(" ").trim()} %c`),s.push("background-color: black",""),[l.join(""),...s,...r.map(e.inspect)]},json(e,{level:t,content:n,options:r}){const o={level:t,timestamp:Date.now(),tags:e.tags,content:n},i={};if(r.date||r.time){const e=new Date(o.timestamp).toISOString();r.date&&(i.date=e.slice(0,e.indexOf("T"))),r.time&&(i.time=e.slice(e.indexOf("T")+1,-1))}if(r.delta&&(i.delta=performance.now()/1e3),r.caller.file||r.caller.name||r.caller.line){const t=e.#o();if(t){if(i.caller={},r.caller.file){let e=`${t.file}`;Array.isArray(r.caller.fileformat)&&(e=e.replace(r.caller.fileformat[0],r.caller.fileformat[1])),i.caller.file=e}r.caller.name&&t.name&&(i.caller.name=t.name),r.caller.line&&(i.caller.line=[t.line,t.column])}}return[JSON.stringify({...o,...i})]}}};TransformStream;function t(e){let t=0;for(const n of e)t+=n.length;const n=new Uint8Array(t);let r=0;for(const t of e)n.set(t,r),r+=t.length;return n}function n(e){const t=e.length,n=new Uint8Array(t);n[0]=0;let r=0,o=1;for(;o<t;)e[o]===e[r]?(r++,n[o]=r,o++):0===r?(n[o]=0,o++):r=n[r-1];return n}TransformStream,TransformStream,TransformStream,TransformStream;var r=class extends TransformStream{#i="";constructor(e={allowCR:!1}){super({transform:(t,n)=>{for(t=this.#i+t;;){const r=t.indexOf("\n"),o=e.allowCR?t.indexOf("\r"):-1;if(-1!==o&&o!==t.length-1&&(-1===r||r-1>o)){n.enqueue(t.slice(0,o)),t=t.slice(o+1);continue}if(-1===r)break;const i="\r"===t[r-1]?r-1:r;n.enqueue(t.slice(0,i)),t=t.slice(r+1)}this.#i=t},flush:t=>{if(""===this.#i)return;const n=e.allowCR&&this.#i.endsWith("\r")?this.#i.slice(0,-1):this.#i;t.enqueue(n)}})}};new TextDecoder;var o=new TextEncoder,i=new TextDecoder;function l(t,n,{logger:l=new e,stdin:a=null,stdout:c="debug",stderr:u="error",env:d,cwd:h,raw:f,callback:p,buffering:m,sync:g,throw:w,dryrun:b,winext:v="",os:y=Deno.build.os}={}){"windows"===y&&(t=`${t}${v}`),l=l.with({bin:t}),p&&"piped"!==s(a)&&(a="piped");const T=new Deno.Command(t,{args:n,stdin:g?"null":s(a),stdout:s(c),stderr:s(u),env:d,cwd:h,windowsRawArguments:f});if(b){l.wdebug(`dryrun: ${t} not executed`);const e={success:!0,code:0,stdio:[],stdin:"",stdout:"",stderr:""};return g?e:Promise.resolve(e)}return g?function(e,{bin:t,log:n,throw:r,stdout:o,stderr:l}){const a=Date.now(),c=e.outputSync(),{success:u,code:d}=c,h=Date.now()-a,f={get stdio(){return[[h,1,this.stdout],[h,2,this.stderr]]},stdin:"",stdout:"piped"===s(o)?i.decode(c.stdout):"",stderr:"piped"===s(l)?i.decode(c.stderr):""};for(const{channel:e,mode:t}of[{channel:"stdout",mode:o},{channel:"stderr",mode:l}])"piped"===s(t)&&f[e]&&n.with({t:h,channel:e})[t]?.(f[e]);if(!u&&r)throw new EvalError(`${t} exited with non-zero code ${d}:\n${f.stdout}\n${f.stderr}`);return{success:u,code:d,...f}}(T,{bin:t,log:l,throw:w,stdout:c,stderr:u}):async function(e,{bin:t,log:n,callback:i=({close:e})=>e?.(),buffering:l=250,throw:a,...c}){const u=e.spawn(),d=Date.now(),h={stdio:[],get stdin(){return this.stdio.filter((([e,t])=>0===t)).map((([e,t,n])=>n)).join("\n")},get stdout(){return this.stdio.filter((([e,t])=>1===t)).map((([e,t,n])=>n)).join("\n")},get stderr(){return this.stdio.filter((([e,t])=>2===t)).map((([e,t,n])=>n)).join("\n")}},f={};let p="";const m=function(e,t){let n=null,r=null;const o=(...i)=>{o.clear(),r=()=>{o.clear(),e.call(o,...i)},n=setTimeout(r,t)};return o.clear=()=>{"number"==typeof n&&(clearTimeout(n),n=null,r=null)},o.flush=()=>{r?.()},Object.defineProperty(o,"pending",{get:()=>"number"==typeof n}),o}((async e=>{n.with({t:e}).trace("debounced"),p="",await i({stdio:h,i:h.stdin.length,...f})}),l);if("piped"===s(c.stdin)){const e=u.stdin.getWriter();Object.assign(f,{async write(t,r=!0){const i=Date.now()-d;c.stdin&&n.with({t:i,channel:"stdin"})[c.stdin]?.(t),h.stdio.push([i,0,t]),r&&!t.endsWith("\n")&&(t+="\n"),await e.write(o.encode(t)),p="stdin",e.releaseLock()},async close(){try{e.releaseLock(),await u.stdin.close(),n.with({t:Date.now()-d,closed:"stdin"}).trace()}catch{}},async wait(e=1e3){const t=Date.now()-d;n.with({t:t,waiting:e}).trace(),await function(e,t={}){const{signal:n,persistent:r=!0}=t;return n?.aborted?Promise.reject(n.reason):new Promise(((t,o)=>{const i=()=>{clearTimeout(l),o(n?.reason)},l=setTimeout((()=>{n?.removeEventListener("abort",i),t()}),e);if(n?.addEventListener("abort",i,{once:!0}),!1===r)try{Deno.unrefTimer(l)}catch(e){if(!(e instanceof ReferenceError))throw e;console.error("`persistent` option is only available in Deno")}}))}(e),m(t)}}),m(Date.now()-d)}await Promise.all(["stdout","stderr"].filter((e=>"piped"===s(c[e]))).map((async e=>{for await(const t of u[e].pipeThrough(new TextDecoderStream).pipeThrough(new r)){const r=Date.now()-d,o={stdout:1,stderr:2}[e];if(c[e]&&n.with({t:r,channel:e})[c[e]]?.(t),h.stdio.length&&p===e){const e=h.stdio.at(-1);e[1]===o&&(e[2]+=`\n${t}`)}else h.stdio.push([r,o,t]);p=e,m(r)}}))),m.flush();const{success:g,code:w}=await u.status;if(!g&&a)throw new EvalError(`${t} exited with non-zero code ${w}:\n${h.stdout}\n${h.stderr}`);return{success:g,code:w,...h}}(T,{bin:t,log:l,callback:p,buffering:m,throw:w,stdin:"piped"===s(a)?a:null,stdout:"piped"===s(c)?c:null,stderr:"piped"===s(u)?u:null})}function s(e){return["inherit","null"].includes(`${e}`)?`${e}`:"piped"}export{l as command};
2
2
  /**
3
- * Logger library
3
+ * Logger class.
4
4
  *
5
- * It is intended to supersed {@link https://developer.mozilla.org/en-US/docs/Web/API/console | console} by providing:
6
- * - Colored output
7
- * - Log levels
8
- * - Tags
9
- * - Timestamps
10
- * - Delta
11
- * - Caller information
12
- * - Log formatters
5
+ * This class provides a simple and efficient logging framework intended to supersed the native {@link https://developer.mozilla.org/en-US/docs/Web/API/console | console} by providing additional features and metadata.
6
+ *
7
+ * It supports out-of-the-box colored output, a log level mechanism (that honor `LOG_LEVEL` environment variable), a tag system, and a variety of options to customize the output.
8
+ * A neat addition is the ability to display caller information (file, name, line) which can be especially useful for debugging.
13
9
  *
14
10
  * @example
15
11
  * ```ts
16
12
  * import { Logger } from "./mod.ts"
17
13
  *
18
14
  * // Configure logger
19
- * const tags = { foo: true, bar: "string" }
20
- * const options = { date: true, time: true, delta: true, caller: { file: true, fileformat: /.*\/(?<file>libs\/.*)$/, name: true, line: true } }
21
- * const log = new Logger({ level: Logger.level.debug, options, tags })
15
+ * const tags = { foo: "bar" }
16
+ * const log = new Logger({ level: "trace", tags, date: true, time: true, delta:true, caller:true })
22
17
  *
23
18
  * // Print logs
24
- * log.error("🍱 bento")
25
- * log.warn("🍜 ramen")
26
- * log.info("🍣 sushi")
27
- * log.log("🍥 narutomaki")
28
- * log.debug("🍡 dango")
19
+ * log
20
+ * .error("🍱 bento")
21
+ * .warn("🍜 ramen")
22
+ * .ok("🍚 gohan")
23
+ * .info("🍣 sushi")
24
+ * .log("🍥 narutomaki")
25
+ * .debug("🍡 dango")
26
+ * .wdebug("🍵 matcha")
27
+ * .trace("🍙 onigiri")
28
+ * .probe("🥟 gyoza")
29
+ *
30
+ * // Create a new inherited logger with additional tags
31
+ * log.with({ bar: "true" }).log("🍶 sake")
32
+ * ```
33
+ *
34
+ * @author Simon Lecoq (lowlighter)
35
+ * @license MIT
36
+ * @module
37
+ */
38
+ /**
39
+ * Run a command.
40
+ *
41
+ * This is a wrapper around {@link https://docs.deno.com/api/deno/~/Deno.Command | Deno.command} that provides a better handling of stdio for interactive processes.
42
+ *
43
+ * Like `Deno.command`, the `env`, `cwd`, and `raw` (alias for `windowsRawArguments`) options are supported.
44
+ *
45
+ * The `stdin`, `stdout` and `stderr` options can be either set to an allowed {@link https://docs.deno.com/api/deno/~/Deno.Command | Deno.command} values (`"inherit"`, `"null"`, `"piped"`), or either to a supported log level of {@link Logger}.
46
+ * In the later case, the content will be always be "piped" and logged to the specified level of the provided {@link Logger} instance.
47
+ *
48
+ * Set `winext` option to automatically append an extension to the binary path on Windows (like `.cmd` or `.exe`).
49
+ * This is useful when the binary path isn't automatically resolved on Windows.
50
+ *
51
+ * Pass a `callback` option to interact with the process stdin and stdout.
52
+ * It is called each time data is received on of the piped channels, after input buffering.
53
+ * It will receive an object with the current stdio content, the current command index (based on the content written to stdin), along with a few additional functions:
54
+ * - `write(content: string, newline?: boolean): Promise<void>` encodes and writes content to stdin.
55
+ * - A newline is automatically appended by default but can be toggled off by passing `false` as second argument.
56
+ * - `close(): Promise<void>` closes stdin.
57
+ * - Note that you **need** to eventually call this method to prevent most processes from hanging as they're waiting for more input.
58
+ * - `wait(dt: number): Promise<void>` waits for a given amount of time before calling the callback again.
59
+ * - It is especially useful for polling, like checking if a specific line has been written to stdio or not.
60
+ *
61
+ * The `buffering` option is used to merge messages that are received relatively closely.
62
+ * Setting this option to a low value will also increase the rate at which the `callback` is called.
63
+ *
64
+ * Resulting object contains the same properties as {@link https://docs.deno.com/api/deno/~/Deno.CommandStatus | Deno.CommandStatus}
65
+ * with an additional `stdio` property that contains an array of ordered tuples with the delta timestamp since process start, the channel idenfitier (0:stdin, 1:stdout, 2:stderr) and the content.
66
+ * This offers a proper history of exchanged content.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * import { command } from "./command.ts"
71
+ * import { Logger } from "jsr:@libs/logger"
72
+ * await command("deno", ["version"], { env: { NO_COLOR: "true" }, cwd: "/tmp", raw: true })
73
+ * await command("deno", ["version"], { stdout: "piped" })
74
+ * await command("deno", ["version"], { logger: new Logger(), stdout: "debug" })
75
+ * await command("deno", ["version"], { winext: ".exe" })
76
+ * ```
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * import { command } from "./command.ts"
81
+ * const { stdout } = await command("deno", ["repl"], {
82
+ * env: { NO_COLOR: "true" },
83
+ * callback: ({ i, write, close }) => i === 0 ? write("console.log('hello')") : close(),
84
+ * })
85
+ * console.assert(stdout.includes("hello"))
29
86
  * ```
30
87
  *
31
88
  * @author Simon Lecoq (lowlighter)
32
89
  * @license MIT
90
+ * @module
33
91
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowlighter/run",
3
- "version": "1.0.2",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "scripts": {},
6
6
  "dependencies": {},