diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..7a5c00b --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,56 @@ +name: Deploy Docs + +on: + push: + branches: + - "main" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "github-pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build docs + run: npm run docs + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 00cd25f..a07ed22 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ modpack.lock modpack.json coverage/ + +build/ + +docs/ diff --git a/.prettierrc b/.prettierrc index 0d8e632..0e17c3e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -14,7 +14,7 @@ "objectWrap": "preserve", "bracketSameLine": false, "singleAttributePerLine": false, - "parser": "babel", + "parser": "typescript", "overrides": [ { "files": [ diff --git a/.vscode/settings.json b/.vscode/settings.json index 00d665f..ddd3cd7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "cSpell.words": [ "logm", "modloader", + "modloaders", "modpack", "modrinth" ] diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e2ac661..0000000 --- a/docs/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/assets/hierarchy.js b/docs/assets/hierarchy.js deleted file mode 100644 index 88636f0..0000000 --- a/docs/assets/hierarchy.js +++ /dev/null @@ -1 +0,0 @@ -window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzwMKVNfWAgCbHgqm" \ No newline at end of file diff --git a/docs/assets/highlight.css b/docs/assets/highlight.css deleted file mode 100644 index 3f96598..0000000 --- a/docs/assets/highlight.css +++ /dev/null @@ -1,64 +0,0 @@ -:root { - --light-hl-0: #6F42C1; - --dark-hl-0: #B392F0; - --light-hl-1: #24292E; - --dark-hl-1: #E1E4E8; - --light-hl-2: #032F62; - --dark-hl-2: #9ECBFF; - --light-hl-3: #005CC5; - --dark-hl-3: #79B8FF; - --light-hl-4: #D73A49; - --dark-hl-4: #F97583; - --light-hl-5: #B31D28; - --dark-hl-5: #FDAEB7; - --light-code-background: #fff; - --dark-code-background: #24292e; -} - -@media (prefers-color-scheme: light) { :root { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --code-background: var(--light-code-background); -} } - -@media (prefers-color-scheme: dark) { :root { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --code-background: var(--dark-code-background); -} } - -:root[data-theme='light'] { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --code-background: var(--light-code-background); -} - -:root[data-theme='dark'] { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --code-background: var(--dark-code-background); -} - -.hl-0 { color: var(--hl-0); } -.hl-1 { color: var(--hl-1); } -.hl-2 { color: var(--hl-2); } -.hl-3 { color: var(--hl-3); } -.hl-4 { color: var(--hl-4); } -.hl-5 { color: var(--hl-5); } -pre, code { background: var(--code-background); } diff --git a/docs/assets/icons.js b/docs/assets/icons.js deleted file mode 100644 index 3ae8f55..0000000 --- a/docs/assets/icons.js +++ /dev/null @@ -1,18 +0,0 @@ -(function() { - addIcons(); - function addIcons() { - if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); - const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); - svg.innerHTML = `MMNEPVFCICPMFPCPTTAAATR`; - svg.style.display = "none"; - if (location.protocol === "file:") updateUseElements(); - } - - function updateUseElements() { - document.querySelectorAll("use").forEach(el => { - if (el.getAttribute("href").includes("#icon-")) { - el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); - } - }); - } -})() \ No newline at end of file diff --git a/docs/assets/icons.svg b/docs/assets/icons.svg deleted file mode 100644 index 10db10b..0000000 --- a/docs/assets/icons.svg +++ /dev/null @@ -1 +0,0 @@ -MMNEPVFCICPMFPCPTTAAATR \ No newline at end of file diff --git a/docs/assets/main.js b/docs/assets/main.js deleted file mode 100644 index 64b80ab..0000000 --- a/docs/assets/main.js +++ /dev/null @@ -1,60 +0,0 @@ -"use strict"; -window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings.","hierarchy_expand":"Expand","hierarchy_collapse":"Collapse","folder":"Folder","search_index_not_available":"The search index is not available","search_no_results_found_for_0":"No results found for {0}","kind_1":"Project","kind_2":"Module","kind_4":"Namespace","kind_8":"Enumeration","kind_16":"Enumeration Member","kind_32":"Variable","kind_64":"Function","kind_128":"Class","kind_256":"Interface","kind_512":"Constructor","kind_1024":"Property","kind_2048":"Method","kind_4096":"Call Signature","kind_8192":"Index Signature","kind_16384":"Constructor Signature","kind_32768":"Parameter","kind_65536":"Type Literal","kind_131072":"Type Parameter","kind_262144":"Accessor","kind_524288":"Get Signature","kind_1048576":"Set Signature","kind_2097152":"Type Alias","kind_4194304":"Reference","kind_8388608":"Document"}; -"use strict";(()=>{var Ke=Object.create;var he=Object.defineProperty;var Ge=Object.getOwnPropertyDescriptor;var Ze=Object.getOwnPropertyNames;var Xe=Object.getPrototypeOf,Ye=Object.prototype.hasOwnProperty;var et=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var tt=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Ze(e))!Ye.call(t,i)&&i!==n&&he(t,i,{get:()=>e[i],enumerable:!(r=Ge(e,i))||r.enumerable});return t};var nt=(t,e,n)=>(n=t!=null?Ke(Xe(t)):{},tt(e||!t||!t.__esModule?he(n,"default",{value:t,enumerable:!0}):n,t));var ye=et((me,ge)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=(function(e){return function(n){e.console&&console.warn&&console.warn(n)}})(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,l],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. -`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(oc?d+=2:a==c&&(n+=r[l+1]*i[d+1],l+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}if(s.str.length==0&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),f=s.str.charAt(1),p;f in s.node.edges?p=s.node.edges[f]:(p=new t.TokenSet,s.node.edges[f]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),c=0;c1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},(function(e,n){typeof define=="function"&&define.amd?define(n):typeof me=="object"?ge.exports=n():e.lunr=n()})(this,function(){return t})})()});var M,G={getItem(){return null},setItem(){}},K;try{K=localStorage,M=K}catch{K=G,M=G}var S={getItem:t=>M.getItem(t),setItem:(t,e)=>M.setItem(t,e),disableWritingLocalStorage(){M=G},disable(){localStorage.clear(),M=G},enable(){M=K}};window.TypeDoc||={disableWritingLocalStorage(){S.disableWritingLocalStorage()},disableLocalStorage:()=>{S.disable()},enableLocalStorage:()=>{S.enable()}};window.translations||={copy:"Copy",copied:"Copied!",normally_hidden:"This member is normally hidden due to your filter settings.",hierarchy_expand:"Expand",hierarchy_collapse:"Collapse",search_index_not_available:"The search index is not available",search_no_results_found_for_0:"No results found for {0}",folder:"Folder",kind_1:"Project",kind_2:"Module",kind_4:"Namespace",kind_8:"Enumeration",kind_16:"Enumeration Member",kind_32:"Variable",kind_64:"Function",kind_128:"Class",kind_256:"Interface",kind_512:"Constructor",kind_1024:"Property",kind_2048:"Method",kind_4096:"Call Signature",kind_8192:"Index Signature",kind_16384:"Constructor Signature",kind_32768:"Parameter",kind_65536:"Type Literal",kind_131072:"Type Parameter",kind_262144:"Accessor",kind_524288:"Get Signature",kind_1048576:"Set Signature",kind_2097152:"Type Alias",kind_4194304:"Reference",kind_8388608:"Document"};var pe=[];function X(t,e){pe.push({selector:e,constructor:t})}var Z=class{alwaysVisibleMember=null;constructor(){this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){pe.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!rt(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function rt(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var fe=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var Ie=nt(ye(),1);async function R(t){let e=Uint8Array.from(atob(t),s=>s.charCodeAt(0)),r=new Blob([e]).stream().pipeThrough(new DecompressionStream("deflate")),i=await new Response(r).text();return JSON.parse(i)}var Y="closing",ae="tsd-overlay";function it(){let t=Math.abs(window.innerWidth-document.documentElement.clientWidth);document.body.style.overflow="hidden",document.body.style.paddingRight=`${t}px`}function st(){document.body.style.removeProperty("overflow"),document.body.style.removeProperty("padding-right")}function xe(t,e){t.addEventListener("animationend",()=>{t.classList.contains(Y)&&(t.classList.remove(Y),document.getElementById(ae)?.remove(),t.close(),st())}),t.addEventListener("cancel",n=>{n.preventDefault(),ve(t)}),e?.closeOnClick&&document.addEventListener("click",n=>{t.open&&!t.contains(n.target)&&ve(t)},!0)}function Ee(t){if(t.open)return;let e=document.createElement("div");e.id=ae,document.body.appendChild(e),t.showModal(),it()}function ve(t){if(!t.open)return;document.getElementById(ae)?.classList.add(Y),t.classList.add(Y)}var I=class{el;app;constructor(e){this.el=e.el,this.app=e.app}};var be=document.head.appendChild(document.createElement("style"));be.dataset.for="filters";var le={};function we(t){for(let e of t.split(/\s+/))if(le.hasOwnProperty(e)&&!le[e])return!0;return!1}var ee=class extends I{key;value;constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),be.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } -`,this.app.updateIndexVisibility()}fromLocalStorage(){let e=S.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){S.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),le[`tsd-is-${this.el.name}`]=this.value,this.app.filterChanged(),this.app.updateIndexVisibility()}};var Le=0;async function Se(t,e){if(!window.searchData)return;let n=await R(window.searchData);t.data=n,t.index=Ie.Index.load(n.index),e.innerHTML=""}function _e(){let t=document.getElementById("tsd-search-trigger"),e=document.getElementById("tsd-search"),n=document.getElementById("tsd-search-input"),r=document.getElementById("tsd-search-results"),i=document.getElementById("tsd-search-script"),s=document.getElementById("tsd-search-status");if(!(t&&e&&n&&r&&i&&s))throw new Error("Search controls missing");let o={base:document.documentElement.dataset.base};o.base.endsWith("/")||(o.base+="/"),i.addEventListener("error",()=>{let a=window.translations.search_index_not_available;Pe(s,a)}),i.addEventListener("load",()=>{Se(o,s)}),Se(o,s),ot({trigger:t,searchEl:e,results:r,field:n,status:s},o)}function ot(t,e){let{field:n,results:r,searchEl:i,status:s,trigger:o}=t;xe(i,{closeOnClick:!0});function a(){Ee(i),n.setSelectionRange(0,n.value.length)}o.addEventListener("click",a),n.addEventListener("input",fe(()=>{at(r,n,s,e)},200)),n.addEventListener("keydown",l=>{if(r.childElementCount===0||l.ctrlKey||l.metaKey||l.altKey)return;let d=n.getAttribute("aria-activedescendant"),f=d?document.getElementById(d):null;if(f){let p=!1,v=!1;switch(l.key){case"Home":case"End":case"ArrowLeft":case"ArrowRight":v=!0;break;case"ArrowDown":case"ArrowUp":p=l.shiftKey;break}(p||v)&&ke(n)}if(!l.shiftKey)switch(l.key){case"Enter":f?.querySelector("a")?.click();break;case"ArrowUp":Te(r,n,f,-1),l.preventDefault();break;case"ArrowDown":Te(r,n,f,1),l.preventDefault();break}});function c(){ke(n)}n.addEventListener("change",c),n.addEventListener("blur",c),n.addEventListener("click",c),document.body.addEventListener("keydown",l=>{if(l.altKey||l.metaKey||l.shiftKey)return;let d=l.ctrlKey&&l.key==="k",f=!l.ctrlKey&&!ut()&&l.key==="/";(d||f)&&(l.preventDefault(),a())})}function at(t,e,n,r){if(!r.index||!r.data)return;t.innerHTML="",n.innerHTML="",Le+=1;let i=e.value.trim(),s;if(i){let a=i.split(" ").map(c=>c.length?`*${c}*`:"").join(" ");s=r.index.search(a).filter(({ref:c})=>{let l=r.data.rows[Number(c)].classes;return!l||!we(l)})}else s=[];if(s.length===0&&i){let a=window.translations.search_no_results_found_for_0.replace("{0}",` "${te(i)}" `);Pe(n,a);return}for(let a=0;ac.score-a.score);let o=Math.min(10,s.length);for(let a=0;a`,f=Ce(c.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(f+=` (score: ${s[a].score.toFixed(2)})`),c.parent&&(f=` - ${Ce(c.parent,i)}.${f}`);let p=document.createElement("li");p.id=`tsd-search:${Le}-${a}`,p.role="option",p.ariaSelected="false",p.classList.value=c.classes??"";let v=document.createElement("a");v.tabIndex=-1,v.href=r.base+c.url,v.innerHTML=d+`${f}`,p.append(v),t.appendChild(p)}}function Te(t,e,n,r){let i;if(r===1?i=n?.nextElementSibling||t.firstElementChild:i=n?.previousElementSibling||t.lastElementChild,i!==n){if(!i||i.role!=="option"){console.error("Option missing");return}i.ariaSelected="true",i.scrollIntoView({behavior:"smooth",block:"nearest"}),e.setAttribute("aria-activedescendant",i.id),n?.setAttribute("aria-selected","false")}}function ke(t){let e=t.getAttribute("aria-activedescendant");(e?document.getElementById(e):null)?.setAttribute("aria-selected","false"),t.setAttribute("aria-activedescendant","")}function Ce(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(te(t.substring(s,o)),`${te(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(te(t.substring(s))),i.join("")}var lt={"&":"&","<":"<",">":">","'":"'",'"':"""};function te(t){return t.replace(/[&<>"'"]/g,e=>lt[e])}function Pe(t,e){t.innerHTML=e?`
${e}
`:""}var ct=["button","checkbox","file","hidden","image","radio","range","reset","submit"];function ut(){let t=document.activeElement;return t?t.isContentEditable||t.tagName==="TEXTAREA"||t.tagName==="SEARCH"?!0:t.tagName==="INPUT"&&!ct.includes(t.type):!1}var D="mousedown",Me="mousemove",$="mouseup",ne={x:0,y:0},Qe=!1,ce=!1,dt=!1,F=!1,Oe=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(Oe?"is-mobile":"not-mobile");Oe&&"ontouchstart"in document.documentElement&&(dt=!0,D="touchstart",Me="touchmove",$="touchend");document.addEventListener(D,t=>{ce=!0,F=!1;let e=D=="touchstart"?t.targetTouches[0]:t;ne.y=e.pageY||0,ne.x=e.pageX||0});document.addEventListener(Me,t=>{if(ce&&!F){let e=D=="touchstart"?t.targetTouches[0]:t,n=ne.x-(e.pageX||0),r=ne.y-(e.pageY||0);F=Math.sqrt(n*n+r*r)>10}});document.addEventListener($,()=>{ce=!1});document.addEventListener("click",t=>{Qe&&(t.preventDefault(),t.stopImmediatePropagation(),Qe=!1)});var re=class extends I{active;className;constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener($,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(D,n=>this.onDocumentPointerDown(n)),document.addEventListener($,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){F||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!F&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var ue=new Map,de=class{open;accordions=[];key;constructor(e,n){this.key=e,this.open=n}add(e){this.accordions.push(e),e.open=this.open,e.addEventListener("toggle",()=>{this.toggle(e.open)})}toggle(e){for(let n of this.accordions)n.open=e;S.setItem(this.key,e.toString())}},ie=class extends I{constructor(e){super(e);let n=this.el.querySelector("summary"),r=n.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)});let i=`tsd-accordion-${n.dataset.key??n.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`,s;if(ue.has(i))s=ue.get(i);else{let o=S.getItem(i),a=o?o==="true":this.el.open;s=new de(i,a),ue.set(i,s)}s.add(this.el)}};function He(t){let e=S.getItem("tsd-theme")||"os";t.value=e,Ae(e),t.addEventListener("change",()=>{S.setItem("tsd-theme",t.value),Ae(t.value)})}function Ae(t){document.documentElement.dataset.theme=t}var se;function Ne(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Re),Re())}async function Re(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let e=await R(window.navigationData);se=document.documentElement.dataset.base,se.endsWith("/")||(se+="/"),t.innerHTML="";for(let n of e)Be(n,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Be(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',De(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let c=a.appendChild(document.createElement("ul"));c.className="tsd-nested-navigation";for(let l of t.children)Be(l,c,i)}else De(t,r,t.class)}function De(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));if(r.href=se+t.path,n&&(r.className=n),location.pathname===r.pathname&&!r.href.includes("#")&&(r.classList.add("current"),r.ariaCurrent="page"),t.kind){let i=window.translations[`kind_${t.kind}`].replaceAll('"',""");r.innerHTML=``}r.appendChild(Fe(t.text,document.createElement("span")))}else{let r=e.appendChild(document.createElement("span")),i=window.translations.folder.replaceAll('"',""");r.innerHTML=``,r.appendChild(Fe(t.text,document.createElement("span")))}}function Fe(t,e){let n=t.split(/(?<=[^A-Z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[_-])(?=[^_-])/);for(let r=0;r{let i=r.target;for(;i.parentElement&&i.parentElement.tagName!="LI";)i=i.parentElement;i.dataset.dropdown&&(i.dataset.dropdown=String(i.dataset.dropdown!=="true"))});let t=new Map,e=new Set;for(let r of document.querySelectorAll(".tsd-full-hierarchy [data-refl]")){let i=r.querySelector("ul");t.has(r.dataset.refl)?e.add(r.dataset.refl):i&&t.set(r.dataset.refl,i)}for(let r of e)n(r);function n(r){let i=t.get(r).cloneNode(!0);i.querySelectorAll("[id]").forEach(s=>{s.removeAttribute("id")}),i.querySelectorAll("[data-dropdown]").forEach(s=>{s.dataset.dropdown="false"});for(let s of document.querySelectorAll(`[data-refl="${r}"]`)){let o=gt(),a=s.querySelector("ul");s.insertBefore(o,a),o.dataset.dropdown=String(!!a),a||s.appendChild(i.cloneNode(!0))}}}function pt(){let t=document.getElementById("tsd-hierarchy-script");t&&(t.addEventListener("load",Ve),Ve())}async function Ve(){let t=document.querySelector(".tsd-panel.tsd-hierarchy:has(h4 a)");if(!t||!window.hierarchyData)return;let e=+t.dataset.refl,n=await R(window.hierarchyData),r=t.querySelector("ul"),i=document.createElement("ul");if(i.classList.add("tsd-hierarchy"),ft(i,n,e),r.querySelectorAll("li").length==i.querySelectorAll("li").length)return;let s=document.createElement("span");s.classList.add("tsd-hierarchy-toggle"),s.textContent=window.translations.hierarchy_expand,t.querySelector("h4 a")?.insertAdjacentElement("afterend",s),s.insertAdjacentText("beforebegin",", "),s.addEventListener("click",()=>{s.textContent===window.translations.hierarchy_expand?(r.insertAdjacentElement("afterend",i),r.remove(),s.textContent=window.translations.hierarchy_collapse):(i.insertAdjacentElement("afterend",r),i.remove(),s.textContent=window.translations.hierarchy_expand)})}function ft(t,e,n){let r=e.roots.filter(i=>mt(e,i,n));for(let i of r)t.appendChild(je(e,i,n))}function je(t,e,n,r=new Set){if(r.has(e))return;r.add(e);let i=t.reflections[e],s=document.createElement("li");if(s.classList.add("tsd-hierarchy-item"),e===n){let o=s.appendChild(document.createElement("span"));o.textContent=i.name,o.classList.add("tsd-hierarchy-target")}else{for(let a of i.uniqueNameParents||[]){let c=t.reflections[a],l=s.appendChild(document.createElement("a"));l.textContent=c.name,l.href=oe+c.url,l.className=c.class+" tsd-signature-type",s.append(document.createTextNode("."))}let o=s.appendChild(document.createElement("a"));o.textContent=t.reflections[e].name,o.href=oe+i.url,o.className=i.class+" tsd-signature-type"}if(i.children){let o=s.appendChild(document.createElement("ul"));o.classList.add("tsd-hierarchy");for(let a of i.children){let c=je(t,a,n,r);c&&o.appendChild(c)}}return r.delete(e),s}function mt(t,e,n){if(e===n)return!0;let r=new Set,i=[t.reflections[e]];for(;i.length;){let s=i.pop();if(!r.has(s)){r.add(s);for(let o of s.children||[]){if(o===n)return!0;i.push(t.reflections[o])}}}return!1}function gt(){let t=document.createElementNS("http://www.w3.org/2000/svg","svg");return t.setAttribute("width","20"),t.setAttribute("height","20"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.innerHTML='',t}X(re,"a[data-toggle]");X(ie,".tsd-accordion");X(ee,".tsd-filter-item input[type=checkbox]");var qe=document.getElementById("tsd-theme");qe&&He(qe);var yt=new Z;Object.defineProperty(window,"app",{value:yt});_e();Ne();$e();"virtualKeyboard"in navigator&&(navigator.virtualKeyboard.overlaysContent=!0);})(); -/*! Bundled license information: - -lunr/lunr.js: - (** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 - * Copyright (C) 2020 Oliver Nightingale - * @license MIT - *) - (*! - * lunr.utils - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Set - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.tokenizer - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Pipeline - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Vector - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.stemmer - * Copyright (C) 2020 Oliver Nightingale - * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt - *) - (*! - * lunr.stopWordFilter - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.trimmer - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.TokenSet - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Index - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Builder - * Copyright (C) 2020 Oliver Nightingale - *) -*/ diff --git a/docs/assets/navigation.js b/docs/assets/navigation.js deleted file mode 100644 index 205eb25..0000000 --- a/docs/assets/navigation.js +++ /dev/null @@ -1 +0,0 @@ -window.navigationData = "eJyN0k0KwjAQBeC7ZC0WRLvoARRFEQRX4iKkUx3aTkIygiDe3R9UGhtjtn1vPugju4tgOLMoxEqXRqp6TpUWA2EkH+8fkRhsJRW4rJMPj9w291KNVIpiNMmvgw+z1KqusIGg8Q5jwNowanLB+1cWO58Tcozo5DHmABwcpDqReh5nfsO38rFP9UfxnPAsXwiBlQwzZDyQtrA5NeDCXqiZQC+cphj4yBOYJSog9+NXvUoKFh3O7yRwG5BlC1P8M12nloC+XsFftduLsMbq1vDWgZ1q++vx9Uo9cH8DEhlX7w==" \ No newline at end of file diff --git a/docs/assets/search.js b/docs/assets/search.js deleted file mode 100644 index c9733cc..0000000 --- a/docs/assets/search.js +++ /dev/null @@ -1 +0,0 @@ -window.searchData = "eJytmtuOm0gQht+F3FqT6QM+zAMkyirRSpGSm9FohQBPSGzwAs7uajTvvt0YcJWrGpdJrhJM1f838HV1F8xLVFf/NNHD40v0oyiz6EHHy0VUJvs8eog+VdkhSX98KLdVtIiO9c79VpRtXm+TNG/egtN339r9zsWku6RpcqcXRa+LQVLdaztqdv8IxN70gUBxER2SOi/bi5EFjH7mdVNUpcjrHDvbLsubtC4OrdQSx8+2LTKRWxc22yQ5tt+qWmQ0hs42O9TV9zxtv9Q7kWEffqx3v2LaVMc6zaWep+hftNwVaV42srlwjp1tt6+yXZVkuewxwujZlm1SP+ftp0Hq6w0T8pQ6juI3zM9+MEWZp3WybWcMZkidNxhYVz9W6Y9tsWOf/XBOXlEnCh0Su1rlxmEFjJ7zMq+TNmdLDraCofPM2qpN2NmIjYaweSZpdSzb5rrLGDfPJssPeZnlZVrkArOLaLElJOzPbmFhzfpTcr6y+r/PRxYvKPXGhdXHMFzDiAImfx+LvL3qMUTNsmjc7Sqve4xhs0yei7Z4LquandvIB0bOsqrzJOP3UshnDJtl0q887wLVCjn1sX1lk9pBbD+URTuBLjgtx3db7QKr3qXcmzGUHzscXWh3W5WdQZK2xU/2jhFTkjLbPMmyj+FNBTF24df2FTLT91PMc7bX0ZcZfw7OAM71ykSQPN1A70Kf6VTvIjCaWNKJ17VVXWDHNxHEaaKJEJhcaZCIm6RBkmAS7F0oI9O9i8BsunchhoLeRWA62bsQz+u9i8Byonchhr+hxkz2LsTweu8isJT3LsT/xt5FPhhB7xIajLR3EQzmr8LFiLyHyFuslrDdaLkXUNtjmXZZb3HA5I4Ay5L+C2mKOrAlbYvGZfDzcQf2+VCbC7zV5o8GPHoq7k/fKnm5a6CqfcTNwlM3GofcKn1a+/2+dOpWg6hbDXq0rjnAMKmFq/v7Q/ulyet3VR2Am8Rw4k8LNwGz/N/o4WXcMzxE+s7cbVzktsh3mX+3O2xc0mq/P/U2WZUeu/8+9WFf3TpU1T74FP32Plo83i9MfLe066enxeOQ3J3ofhg0zr90icodqYW5v4stylMkT6E87Y70Qq/u1CpGiZokapRo3JFhDA3JMyjPuiPL5FmSZ1Fe7I5iJi8meTHKW7qjJZO3JHlLlLdyRysmb0XyVihv7Y7WTN6a5K1RnkPnccPkbUjeBj94z4G65x49ZUZdQNNRw2LDcIPBUR4HpTlWFWVHYXiUCWGnKD8KA6Q8F8qwxhQihSlSHg5l2WRKksIoKU+IitlkipPCPCmPiVqyyZQphaFSnhW1YpMpWAqTpTwwas0mU7oUxkt3eG24ZE350pgv7ZHRbD3TFDB9UZm60qTYZKY4YcB0BxhLp6aEaUyY9sxoljBNCdOYMO2Z0SxhmhKmMWHaM6NZwjQlTGPCtGdGs4RpSpjGhGnPjGYJ05QwjQnTnhnNEqYpYRoTZjwzmiXMUMIMJsyE1j1D+TKYLxNc+gzFy1wsfiawihlm+cNwGRtaOClaBqNlQmugoWAZDJYJLYOGYmUwVia0EhoKlcFQmdBiaChSBiNlQuuhoUAZDJQNroiW8mQxTza4IloKlMVAWc+IYaudpURZTJTt9lNstbOUKXuxp+qYYqudZfZVmCrrSTFstbOUK4u5sp4Vw1Y7S8mymCzraTFstbOULYvZsp4Xw1Y7S+mymC7riTFstbOUL4v5ij0yhq12MQUsxoDFnhnDVruYEtb/1DUcrtNo8+zDqfFw/ULf57/4/3S/OfT6VuclcnQ9vLy+npsPdwT6D3/OG+G3wmcpV8tHKVeuhVLjOycgtAJCa6nQ8M4Y6GzOOm4JEOn07xXPIvFZIz4lmVgkNXyIPEspoOU2ehIR/IERSC2B1EooBV7SAgDAPeqvz8r0+o+IYFAAACUDYPiMAx4auEladpPAF2wwGHBhSnZFg85Id316OwRuFhC1MgwG0e/NxX23QOq262SmjAF3zcp4GNXGFz9ADuBlZTNwkNufXrJsi8tbB+CIZZNxkDzNbKoIyoSV4fact+zlaqAkZaXtr7ToXgoBMQXEjEyMLadAR8sqc4FmAASsn9my4fg1oho+7YIBAUEtg58hFWCw7kclAwx9sgajAlNSy54dh4ACCCjZ3QYfL846YMnZ9JcnQzNAE1ieZZPm9P4QXBi4rn5ASiZ08bEb3HJQHbSs2DA4KTB9lQwB+BHuLARGs+yvUFZPT+9tj01eb6uaTGPwJGPZHev/5AVcItDQssfH7GIAmVo2gYe/iwEigCMtuxrw9fGsA57Zqr/XMgBCH7fAzYKk9qha2VBDn/GAOLh+dd+Ly55I/2drQAvUQSWrg8yI4FM9Zalh/yUoP25zfygObidQOoXHp9fX/wGI+PsX"; \ No newline at end of file diff --git a/docs/assets/style.css b/docs/assets/style.css deleted file mode 100644 index 44328e9..0000000 --- a/docs/assets/style.css +++ /dev/null @@ -1,1633 +0,0 @@ -@layer typedoc { - :root { - --dim-toolbar-contents-height: 2.5rem; - --dim-toolbar-border-bottom-width: 1px; - --dim-header-height: calc( - var(--dim-toolbar-border-bottom-width) + - var(--dim-toolbar-contents-height) - ); - - /* 0rem For mobile; unit is required for calculation in `calc` */ - --dim-container-main-margin-y: 0rem; - - --dim-footer-height: 3.5rem; - - --modal-animation-duration: 0.2s; - } - - :root { - /* Light */ - --light-color-background: #f2f4f8; - --light-color-background-secondary: #eff0f1; - /* Not to be confused with [:active](https://developer.mozilla.org/en-US/docs/Web/CSS/:active) */ - --light-color-background-active: #d6d8da; - --light-color-background-warning: #e6e600; - --light-color-warning-text: #222; - --light-color-accent: #c5c7c9; - --light-color-active-menu-item: var(--light-color-background-active); - --light-color-text: #222; - --light-color-contrast-text: #000; - --light-color-text-aside: #5e5e5e; - - --light-color-icon-background: var(--light-color-background); - --light-color-icon-text: var(--light-color-text); - - --light-color-comment-tag-text: var(--light-color-text); - --light-color-comment-tag: var(--light-color-background); - - --light-color-link: #1f70c2; - --light-color-focus-outline: #3584e4; - - --light-color-ts-keyword: #056bd6; - --light-color-ts-project: #b111c9; - --light-color-ts-module: var(--light-color-ts-project); - --light-color-ts-namespace: var(--light-color-ts-project); - --light-color-ts-enum: #7e6f15; - --light-color-ts-enum-member: var(--light-color-ts-enum); - --light-color-ts-variable: #4760ec; - --light-color-ts-function: #572be7; - --light-color-ts-class: #1f70c2; - --light-color-ts-interface: #108024; - --light-color-ts-constructor: var(--light-color-ts-class); - --light-color-ts-property: #9f5f30; - --light-color-ts-method: #be3989; - --light-color-ts-reference: #ff4d82; - --light-color-ts-call-signature: var(--light-color-ts-method); - --light-color-ts-index-signature: var(--light-color-ts-property); - --light-color-ts-constructor-signature: var( - --light-color-ts-constructor - ); - --light-color-ts-parameter: var(--light-color-ts-variable); - /* type literal not included as links will never be generated to it */ - --light-color-ts-type-parameter: #a55c0e; - --light-color-ts-accessor: #c73c3c; - --light-color-ts-get-signature: var(--light-color-ts-accessor); - --light-color-ts-set-signature: var(--light-color-ts-accessor); - --light-color-ts-type-alias: #d51270; - /* reference not included as links will be colored with the kind that it points to */ - --light-color-document: #000000; - - --light-color-alert-note: #0969d9; - --light-color-alert-tip: #1a7f37; - --light-color-alert-important: #8250df; - --light-color-alert-warning: #9a6700; - --light-color-alert-caution: #cf222e; - - --light-external-icon: url("data:image/svg+xml;utf8,"); - --light-color-scheme: light; - } - - :root { - /* Dark */ - --dark-color-background: #2b2e33; - --dark-color-background-secondary: #1e2024; - /* Not to be confused with [:active](https://developer.mozilla.org/en-US/docs/Web/CSS/:active) */ - --dark-color-background-active: #5d5d6a; - --dark-color-background-warning: #bebe00; - --dark-color-warning-text: #222; - --dark-color-accent: #9096a2; - --dark-color-active-menu-item: var(--dark-color-background-active); - --dark-color-text: #f5f5f5; - --dark-color-contrast-text: #ffffff; - --dark-color-text-aside: #dddddd; - - --dark-color-icon-background: var(--dark-color-background-secondary); - --dark-color-icon-text: var(--dark-color-text); - - --dark-color-comment-tag-text: var(--dark-color-text); - --dark-color-comment-tag: var(--dark-color-background); - - --dark-color-link: #00aff4; - --dark-color-focus-outline: #4c97f2; - - --dark-color-ts-keyword: #3399ff; - --dark-color-ts-project: #e358ff; - --dark-color-ts-module: var(--dark-color-ts-project); - --dark-color-ts-namespace: var(--dark-color-ts-project); - --dark-color-ts-enum: #f4d93e; - --dark-color-ts-enum-member: var(--dark-color-ts-enum); - --dark-color-ts-variable: #798dff; - --dark-color-ts-function: #a280ff; - --dark-color-ts-class: #8ac4ff; - --dark-color-ts-interface: #6cff87; - --dark-color-ts-constructor: var(--dark-color-ts-class); - --dark-color-ts-property: #ff984d; - --dark-color-ts-method: #ff4db8; - --dark-color-ts-reference: #ff4d82; - --dark-color-ts-call-signature: var(--dark-color-ts-method); - --dark-color-ts-index-signature: var(--dark-color-ts-property); - --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor); - --dark-color-ts-parameter: var(--dark-color-ts-variable); - /* type literal not included as links will never be generated to it */ - --dark-color-ts-type-parameter: #e07d13; - --dark-color-ts-accessor: #ff6060; - --dark-color-ts-get-signature: var(--dark-color-ts-accessor); - --dark-color-ts-set-signature: var(--dark-color-ts-accessor); - --dark-color-ts-type-alias: #ff6492; - /* reference not included as links will be colored with the kind that it points to */ - --dark-color-document: #ffffff; - - --dark-color-alert-note: #0969d9; - --dark-color-alert-tip: #1a7f37; - --dark-color-alert-important: #8250df; - --dark-color-alert-warning: #9a6700; - --dark-color-alert-caution: #cf222e; - - --dark-external-icon: url("data:image/svg+xml;utf8,"); - --dark-color-scheme: dark; - } - - @media (prefers-color-scheme: light) { - :root { - --color-background: var(--light-color-background); - --color-background-secondary: var( - --light-color-background-secondary - ); - --color-background-active: var(--light-color-background-active); - --color-background-warning: var(--light-color-background-warning); - --color-warning-text: var(--light-color-warning-text); - --color-accent: var(--light-color-accent); - --color-active-menu-item: var(--light-color-active-menu-item); - --color-text: var(--light-color-text); - --color-contrast-text: var(--light-color-contrast-text); - --color-text-aside: var(--light-color-text-aside); - - --color-icon-background: var(--light-color-icon-background); - --color-icon-text: var(--light-color-icon-text); - - --color-comment-tag-text: var(--light-color-text); - --color-comment-tag: var(--light-color-background); - - --color-link: var(--light-color-link); - --color-focus-outline: var(--light-color-focus-outline); - - --color-ts-keyword: var(--light-color-ts-keyword); - --color-ts-project: var(--light-color-ts-project); - --color-ts-module: var(--light-color-ts-module); - --color-ts-namespace: var(--light-color-ts-namespace); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-enum-member: var(--light-color-ts-enum-member); - --color-ts-variable: var(--light-color-ts-variable); - --color-ts-function: var(--light-color-ts-function); - --color-ts-class: var(--light-color-ts-class); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-constructor: var(--light-color-ts-constructor); - --color-ts-property: var(--light-color-ts-property); - --color-ts-method: var(--light-color-ts-method); - --color-ts-reference: var(--light-color-ts-reference); - --color-ts-call-signature: var(--light-color-ts-call-signature); - --color-ts-index-signature: var(--light-color-ts-index-signature); - --color-ts-constructor-signature: var( - --light-color-ts-constructor-signature - ); - --color-ts-parameter: var(--light-color-ts-parameter); - --color-ts-type-parameter: var(--light-color-ts-type-parameter); - --color-ts-accessor: var(--light-color-ts-accessor); - --color-ts-get-signature: var(--light-color-ts-get-signature); - --color-ts-set-signature: var(--light-color-ts-set-signature); - --color-ts-type-alias: var(--light-color-ts-type-alias); - --color-document: var(--light-color-document); - - --color-alert-note: var(--light-color-alert-note); - --color-alert-tip: var(--light-color-alert-tip); - --color-alert-important: var(--light-color-alert-important); - --color-alert-warning: var(--light-color-alert-warning); - --color-alert-caution: var(--light-color-alert-caution); - - --external-icon: var(--light-external-icon); - --color-scheme: var(--light-color-scheme); - } - } - - @media (prefers-color-scheme: dark) { - :root { - --color-background: var(--dark-color-background); - --color-background-secondary: var( - --dark-color-background-secondary - ); - --color-background-active: var(--dark-color-background-active); - --color-background-warning: var(--dark-color-background-warning); - --color-warning-text: var(--dark-color-warning-text); - --color-accent: var(--dark-color-accent); - --color-active-menu-item: var(--dark-color-active-menu-item); - --color-text: var(--dark-color-text); - --color-contrast-text: var(--dark-color-contrast-text); - --color-text-aside: var(--dark-color-text-aside); - - --color-icon-background: var(--dark-color-icon-background); - --color-icon-text: var(--dark-color-icon-text); - - --color-comment-tag-text: var(--dark-color-text); - --color-comment-tag: var(--dark-color-background); - - --color-link: var(--dark-color-link); - --color-focus-outline: var(--dark-color-focus-outline); - - --color-ts-keyword: var(--dark-color-ts-keyword); - --color-ts-project: var(--dark-color-ts-project); - --color-ts-module: var(--dark-color-ts-module); - --color-ts-namespace: var(--dark-color-ts-namespace); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-enum-member: var(--dark-color-ts-enum-member); - --color-ts-variable: var(--dark-color-ts-variable); - --color-ts-function: var(--dark-color-ts-function); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-constructor: var(--dark-color-ts-constructor); - --color-ts-property: var(--dark-color-ts-property); - --color-ts-method: var(--dark-color-ts-method); - --color-ts-reference: var(--dark-color-ts-reference); - --color-ts-call-signature: var(--dark-color-ts-call-signature); - --color-ts-index-signature: var(--dark-color-ts-index-signature); - --color-ts-constructor-signature: var( - --dark-color-ts-constructor-signature - ); - --color-ts-parameter: var(--dark-color-ts-parameter); - --color-ts-type-parameter: var(--dark-color-ts-type-parameter); - --color-ts-accessor: var(--dark-color-ts-accessor); - --color-ts-get-signature: var(--dark-color-ts-get-signature); - --color-ts-set-signature: var(--dark-color-ts-set-signature); - --color-ts-type-alias: var(--dark-color-ts-type-alias); - --color-document: var(--dark-color-document); - - --color-alert-note: var(--dark-color-alert-note); - --color-alert-tip: var(--dark-color-alert-tip); - --color-alert-important: var(--dark-color-alert-important); - --color-alert-warning: var(--dark-color-alert-warning); - --color-alert-caution: var(--dark-color-alert-caution); - - --external-icon: var(--dark-external-icon); - --color-scheme: var(--dark-color-scheme); - } - } - - :root[data-theme="light"] { - --color-background: var(--light-color-background); - --color-background-secondary: var(--light-color-background-secondary); - --color-background-active: var(--light-color-background-active); - --color-background-warning: var(--light-color-background-warning); - --color-warning-text: var(--light-color-warning-text); - --color-icon-background: var(--light-color-icon-background); - --color-accent: var(--light-color-accent); - --color-active-menu-item: var(--light-color-active-menu-item); - --color-text: var(--light-color-text); - --color-contrast-text: var(--light-color-contrast-text); - --color-text-aside: var(--light-color-text-aside); - --color-icon-text: var(--light-color-icon-text); - - --color-comment-tag-text: var(--light-color-text); - --color-comment-tag: var(--light-color-background); - - --color-link: var(--light-color-link); - --color-focus-outline: var(--light-color-focus-outline); - - --color-ts-keyword: var(--light-color-ts-keyword); - --color-ts-project: var(--light-color-ts-project); - --color-ts-module: var(--light-color-ts-module); - --color-ts-namespace: var(--light-color-ts-namespace); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-enum-member: var(--light-color-ts-enum-member); - --color-ts-variable: var(--light-color-ts-variable); - --color-ts-function: var(--light-color-ts-function); - --color-ts-class: var(--light-color-ts-class); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-constructor: var(--light-color-ts-constructor); - --color-ts-property: var(--light-color-ts-property); - --color-ts-method: var(--light-color-ts-method); - --color-ts-reference: var(--light-color-ts-reference); - --color-ts-call-signature: var(--light-color-ts-call-signature); - --color-ts-index-signature: var(--light-color-ts-index-signature); - --color-ts-constructor-signature: var( - --light-color-ts-constructor-signature - ); - --color-ts-parameter: var(--light-color-ts-parameter); - --color-ts-type-parameter: var(--light-color-ts-type-parameter); - --color-ts-accessor: var(--light-color-ts-accessor); - --color-ts-get-signature: var(--light-color-ts-get-signature); - --color-ts-set-signature: var(--light-color-ts-set-signature); - --color-ts-type-alias: var(--light-color-ts-type-alias); - --color-document: var(--light-color-document); - - --color-note: var(--light-color-note); - --color-tip: var(--light-color-tip); - --color-important: var(--light-color-important); - --color-warning: var(--light-color-warning); - --color-caution: var(--light-color-caution); - - --external-icon: var(--light-external-icon); - --color-scheme: var(--light-color-scheme); - } - - :root[data-theme="dark"] { - --color-background: var(--dark-color-background); - --color-background-secondary: var(--dark-color-background-secondary); - --color-background-active: var(--dark-color-background-active); - --color-background-warning: var(--dark-color-background-warning); - --color-warning-text: var(--dark-color-warning-text); - --color-icon-background: var(--dark-color-icon-background); - --color-accent: var(--dark-color-accent); - --color-active-menu-item: var(--dark-color-active-menu-item); - --color-text: var(--dark-color-text); - --color-contrast-text: var(--dark-color-contrast-text); - --color-text-aside: var(--dark-color-text-aside); - --color-icon-text: var(--dark-color-icon-text); - - --color-comment-tag-text: var(--dark-color-text); - --color-comment-tag: var(--dark-color-background); - - --color-link: var(--dark-color-link); - --color-focus-outline: var(--dark-color-focus-outline); - - --color-ts-keyword: var(--dark-color-ts-keyword); - --color-ts-project: var(--dark-color-ts-project); - --color-ts-module: var(--dark-color-ts-module); - --color-ts-namespace: var(--dark-color-ts-namespace); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-enum-member: var(--dark-color-ts-enum-member); - --color-ts-variable: var(--dark-color-ts-variable); - --color-ts-function: var(--dark-color-ts-function); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-constructor: var(--dark-color-ts-constructor); - --color-ts-property: var(--dark-color-ts-property); - --color-ts-method: var(--dark-color-ts-method); - --color-ts-reference: var(--dark-color-ts-reference); - --color-ts-call-signature: var(--dark-color-ts-call-signature); - --color-ts-index-signature: var(--dark-color-ts-index-signature); - --color-ts-constructor-signature: var( - --dark-color-ts-constructor-signature - ); - --color-ts-parameter: var(--dark-color-ts-parameter); - --color-ts-type-parameter: var(--dark-color-ts-type-parameter); - --color-ts-accessor: var(--dark-color-ts-accessor); - --color-ts-get-signature: var(--dark-color-ts-get-signature); - --color-ts-set-signature: var(--dark-color-ts-set-signature); - --color-ts-type-alias: var(--dark-color-ts-type-alias); - --color-document: var(--dark-color-document); - - --color-note: var(--dark-color-note); - --color-tip: var(--dark-color-tip); - --color-important: var(--dark-color-important); - --color-warning: var(--dark-color-warning); - --color-caution: var(--dark-color-caution); - - --external-icon: var(--dark-external-icon); - --color-scheme: var(--dark-color-scheme); - } - - html { - color-scheme: var(--color-scheme); - @media (prefers-reduced-motion: no-preference) { - scroll-behavior: smooth; - } - } - - *:focus-visible, - .tsd-accordion-summary:focus-visible svg { - outline: 2px solid var(--color-focus-outline); - } - - .always-visible, - .always-visible .tsd-signatures { - display: inherit !important; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - line-height: 1.2; - } - - h1 { - font-size: 1.875rem; - margin: 0.67rem 0; - } - - h2 { - font-size: 1.5rem; - margin: 0.83rem 0; - } - - h3 { - font-size: 1.25rem; - margin: 1rem 0; - } - - h4 { - font-size: 1.05rem; - margin: 1.33rem 0; - } - - h5 { - font-size: 1rem; - margin: 1.5rem 0; - } - - h6 { - font-size: 0.875rem; - margin: 2.33rem 0; - } - - dl, - menu, - ol, - ul { - margin: 1em 0; - } - - dd { - margin: 0 0 0 34px; - } - - .container { - max-width: 1700px; - padding: 0 2rem; - } - - /* Footer */ - footer { - border-top: 1px solid var(--color-accent); - padding-top: 1rem; - padding-bottom: 1rem; - max-height: var(--dim-footer-height); - } - footer > p { - margin: 0 1em; - } - - .container-main { - margin: var(--dim-container-main-margin-y) auto; - /* toolbar, footer, margin */ - min-height: calc( - 100svh - var(--dim-header-height) - var(--dim-footer-height) - - 2 * var(--dim-container-main-margin-y) - ); - } - - @keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - @keyframes fade-out { - from { - opacity: 1; - visibility: visible; - } - to { - opacity: 0; - } - } - @keyframes pop-in-from-right { - from { - transform: translate(100%, 0); - } - to { - transform: translate(0, 0); - } - } - @keyframes pop-out-to-right { - from { - transform: translate(0, 0); - visibility: visible; - } - to { - transform: translate(100%, 0); - } - } - body { - background: var(--color-background); - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; - font-size: 16px; - color: var(--color-text); - margin: 0; - } - - a { - color: var(--color-link); - text-decoration: none; - } - a:hover { - text-decoration: underline; - } - a.external[target="_blank"] { - background-image: var(--external-icon); - background-position: top 3px right; - background-repeat: no-repeat; - padding-right: 13px; - } - a.tsd-anchor-link { - color: var(--color-text); - } - :target { - scroll-margin-block: calc(var(--dim-header-height) + 0.5rem); - } - - code, - pre { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - padding: 0.2em; - margin: 0; - font-size: 0.875rem; - border-radius: 0.8em; - } - - pre { - position: relative; - white-space: pre-wrap; - word-wrap: break-word; - padding: 10px; - border: 1px solid var(--color-accent); - margin-bottom: 8px; - } - pre code { - padding: 0; - font-size: 100%; - } - pre > button { - position: absolute; - top: 10px; - right: 10px; - opacity: 0; - transition: opacity 0.1s; - box-sizing: border-box; - } - pre:hover > button, - pre > button.visible, - pre > button:focus-visible { - opacity: 1; - } - - blockquote { - margin: 1em 0; - padding-left: 1em; - border-left: 4px solid gray; - } - - img { - max-width: 100%; - } - - * { - scrollbar-width: thin; - scrollbar-color: var(--color-accent) var(--color-icon-background); - } - - *::-webkit-scrollbar { - width: 0.75rem; - } - - *::-webkit-scrollbar-track { - background: var(--color-icon-background); - } - - *::-webkit-scrollbar-thumb { - background-color: var(--color-accent); - border-radius: 999rem; - border: 0.25rem solid var(--color-icon-background); - } - - dialog { - border: none; - outline: none; - padding: 0; - background-color: var(--color-background); - } - dialog::backdrop { - display: none; - } - #tsd-overlay { - background-color: rgba(0, 0, 0, 0.5); - position: fixed; - z-index: 9999; - top: 0; - left: 0; - right: 0; - bottom: 0; - animation: fade-in var(--modal-animation-duration) forwards; - } - #tsd-overlay.closing { - animation-name: fade-out; - } - - .tsd-typography { - line-height: 1.333em; - } - .tsd-typography ul { - list-style: square; - padding: 0 0 0 20px; - margin: 0; - } - .tsd-typography .tsd-index-panel h3, - .tsd-index-panel .tsd-typography h3, - .tsd-typography h4, - .tsd-typography h5, - .tsd-typography h6 { - font-size: 1em; - } - .tsd-typography h5, - .tsd-typography h6 { - font-weight: normal; - } - .tsd-typography p, - .tsd-typography ul, - .tsd-typography ol { - margin: 1em 0; - } - .tsd-typography table { - border-collapse: collapse; - border: none; - } - .tsd-typography td, - .tsd-typography th { - padding: 6px 13px; - border: 1px solid var(--color-accent); - } - .tsd-typography thead, - .tsd-typography tr:nth-child(even) { - background-color: var(--color-background-secondary); - } - - .tsd-alert { - padding: 8px 16px; - margin-bottom: 16px; - border-left: 0.25em solid var(--alert-color); - } - .tsd-alert blockquote > :last-child, - .tsd-alert > :last-child { - margin-bottom: 0; - } - .tsd-alert-title { - color: var(--alert-color); - display: inline-flex; - align-items: center; - } - .tsd-alert-title span { - margin-left: 4px; - } - - .tsd-alert-note { - --alert-color: var(--color-alert-note); - } - .tsd-alert-tip { - --alert-color: var(--color-alert-tip); - } - .tsd-alert-important { - --alert-color: var(--color-alert-important); - } - .tsd-alert-warning { - --alert-color: var(--color-alert-warning); - } - .tsd-alert-caution { - --alert-color: var(--color-alert-caution); - } - - .tsd-breadcrumb { - margin: 0; - margin-top: 1rem; - padding: 0; - color: var(--color-text-aside); - } - .tsd-breadcrumb a { - color: var(--color-text-aside); - text-decoration: none; - } - .tsd-breadcrumb a:hover { - text-decoration: underline; - } - .tsd-breadcrumb li { - display: inline; - } - .tsd-breadcrumb li:after { - content: " / "; - } - - .tsd-comment-tags { - display: flex; - flex-direction: column; - } - dl.tsd-comment-tag-group { - display: flex; - align-items: center; - overflow: hidden; - margin: 0.5em 0; - } - dl.tsd-comment-tag-group dt { - display: flex; - margin-right: 0.5em; - font-size: 0.875em; - font-weight: normal; - } - dl.tsd-comment-tag-group dd { - margin: 0; - } - code.tsd-tag { - padding: 0.25em 0.4em; - border: 0.1em solid var(--color-accent); - margin-right: 0.25em; - font-size: 70%; - } - h1 code.tsd-tag:first-of-type { - margin-left: 0.25em; - } - - dl.tsd-comment-tag-group dd:before, - dl.tsd-comment-tag-group dd:after { - content: " "; - } - dl.tsd-comment-tag-group dd pre, - dl.tsd-comment-tag-group dd:after { - clear: both; - } - dl.tsd-comment-tag-group p { - margin: 0; - } - - .tsd-panel.tsd-comment .lead { - font-size: 1.1em; - line-height: 1.333em; - margin-bottom: 2em; - } - .tsd-panel.tsd-comment .lead:last-child { - margin-bottom: 0; - } - - .tsd-filter-visibility h4 { - font-size: 1rem; - padding-top: 0.75rem; - padding-bottom: 0.5rem; - margin: 0; - } - .tsd-filter-item:not(:last-child) { - margin-bottom: 0.5rem; - } - .tsd-filter-input { - display: flex; - width: -moz-fit-content; - width: fit-content; - align-items: center; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; - } - .tsd-filter-input input[type="checkbox"] { - cursor: pointer; - position: absolute; - width: 1.5em; - height: 1.5em; - opacity: 0; - } - .tsd-filter-input input[type="checkbox"]:disabled { - pointer-events: none; - } - .tsd-filter-input svg { - cursor: pointer; - width: 1.5em; - height: 1.5em; - margin-right: 0.5em; - border-radius: 0.33em; - /* Leaving this at full opacity breaks event listeners on Firefox. - Don't remove unless you know what you're doing. */ - opacity: 0.99; - } - .tsd-filter-input input[type="checkbox"]:focus-visible + svg { - outline: 2px solid var(--color-focus-outline); - } - .tsd-checkbox-background { - fill: var(--color-accent); - } - input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { - stroke: var(--color-text); - } - .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { - fill: var(--color-background); - stroke: var(--color-accent); - stroke-width: 0.25rem; - } - .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { - stroke: var(--color-accent); - } - - .settings-label { - font-weight: bold; - text-transform: uppercase; - display: inline-block; - } - - .tsd-filter-visibility .settings-label { - margin: 0.75rem 0 0.5rem 0; - } - - .tsd-theme-toggle .settings-label { - margin: 0.75rem 0.75rem 0 0; - } - - .tsd-hierarchy h4 label:hover span { - text-decoration: underline; - } - - .tsd-hierarchy { - list-style: square; - margin: 0; - } - .tsd-hierarchy-target { - font-weight: bold; - } - .tsd-hierarchy-toggle { - color: var(--color-link); - cursor: pointer; - } - - .tsd-full-hierarchy:not(:last-child) { - margin-bottom: 1em; - padding-bottom: 1em; - border-bottom: 1px solid var(--color-accent); - } - .tsd-full-hierarchy, - .tsd-full-hierarchy ul { - list-style: none; - margin: 0; - padding: 0; - } - .tsd-full-hierarchy ul { - padding-left: 1.5rem; - } - .tsd-full-hierarchy a { - padding: 0.25rem 0 !important; - font-size: 1rem; - display: inline-flex; - align-items: center; - color: var(--color-text); - } - .tsd-full-hierarchy svg[data-dropdown] { - cursor: pointer; - } - .tsd-full-hierarchy svg[data-dropdown="false"] { - transform: rotate(-90deg); - } - .tsd-full-hierarchy svg[data-dropdown="false"] ~ ul { - display: none; - } - - .tsd-panel-group.tsd-index-group { - margin-bottom: 0; - } - .tsd-index-panel .tsd-index-list { - list-style: none; - line-height: 1.333em; - margin: 0; - padding: 0.25rem 0 0 0; - overflow: hidden; - display: grid; - grid-template-columns: repeat(3, 1fr); - column-gap: 1rem; - grid-template-rows: auto; - } - @media (max-width: 1024px) { - .tsd-index-panel .tsd-index-list { - grid-template-columns: repeat(2, 1fr); - } - } - @media (max-width: 768px) { - .tsd-index-panel .tsd-index-list { - grid-template-columns: repeat(1, 1fr); - } - } - .tsd-index-panel .tsd-index-list li { - -webkit-page-break-inside: avoid; - -moz-page-break-inside: avoid; - -ms-page-break-inside: avoid; - -o-page-break-inside: avoid; - page-break-inside: avoid; - } - - .tsd-flag { - display: inline-block; - padding: 0.25em 0.4em; - border-radius: 4px; - color: var(--color-comment-tag-text); - background-color: var(--color-comment-tag); - text-indent: 0; - font-size: 75%; - line-height: 1; - font-weight: normal; - } - - .tsd-anchor { - position: relative; - top: -100px; - } - - .tsd-member { - position: relative; - } - .tsd-member .tsd-anchor + h3 { - display: flex; - align-items: center; - margin-top: 0; - margin-bottom: 0; - border-bottom: none; - } - - .tsd-navigation.settings { - margin: 0; - margin-bottom: 1rem; - } - .tsd-navigation > a, - .tsd-navigation .tsd-accordion-summary { - width: calc(100% - 0.25rem); - display: flex; - align-items: center; - } - .tsd-navigation a, - .tsd-navigation summary > span, - .tsd-page-navigation a { - display: flex; - width: calc(100% - 0.25rem); - align-items: center; - padding: 0.25rem; - color: var(--color-text); - text-decoration: none; - box-sizing: border-box; - } - .tsd-navigation a.current, - .tsd-page-navigation a.current { - background: var(--color-active-menu-item); - color: var(--color-contrast-text); - } - .tsd-navigation a:hover, - .tsd-page-navigation a:hover { - text-decoration: underline; - } - .tsd-navigation ul, - .tsd-page-navigation ul { - margin-top: 0; - margin-bottom: 0; - padding: 0; - list-style: none; - } - .tsd-navigation li, - .tsd-page-navigation li { - padding: 0; - max-width: 100%; - } - .tsd-navigation .tsd-nav-link { - display: none; - } - .tsd-nested-navigation { - margin-left: 3rem; - } - .tsd-nested-navigation > li > details { - margin-left: -1.5rem; - } - .tsd-small-nested-navigation { - margin-left: 1.5rem; - } - .tsd-small-nested-navigation > li > details { - margin-left: -1.5rem; - } - - .tsd-page-navigation-section > summary { - padding: 0.25rem; - } - .tsd-page-navigation-section > summary > svg { - margin-right: 0.25rem; - } - .tsd-page-navigation-section > div { - margin-left: 30px; - } - .tsd-page-navigation ul { - padding-left: 1.75rem; - } - - #tsd-sidebar-links a { - margin-top: 0; - margin-bottom: 0.5rem; - line-height: 1.25rem; - } - #tsd-sidebar-links a:last-of-type { - margin-bottom: 0; - } - - a.tsd-index-link { - padding: 0.25rem 0 !important; - font-size: 1rem; - line-height: 1.25rem; - display: inline-flex; - align-items: center; - color: var(--color-text); - } - .tsd-accordion-summary { - list-style-type: none; /* hide marker on non-safari */ - outline: none; /* broken on safari, so just hide it */ - display: flex; - align-items: center; - gap: 0.25rem; - box-sizing: border-box; - } - .tsd-accordion-summary::-webkit-details-marker { - display: none; /* hide marker on safari */ - } - .tsd-accordion-summary, - .tsd-accordion-summary a { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - - cursor: pointer; - } - .tsd-accordion-summary a { - width: calc(100% - 1.5rem); - } - .tsd-accordion-summary > * { - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; - } - /* - * We need to be careful to target the arrow indicating whether the accordion - * is open, but not any other SVGs included in the details element. - */ - .tsd-accordion:not([open]) > .tsd-accordion-summary > svg:first-child { - transform: rotate(-90deg); - } - .tsd-index-content > :not(:first-child) { - margin-top: 0.75rem; - } - .tsd-index-summary { - margin-top: 1.5rem; - margin-bottom: 0.75rem; - display: flex; - align-content: center; - } - - .tsd-no-select { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - } - .tsd-kind-icon { - margin-right: 0.5rem; - width: 1.25rem; - height: 1.25rem; - min-width: 1.25rem; - min-height: 1.25rem; - } - .tsd-signature > .tsd-kind-icon { - margin-right: 0.8rem; - } - - .tsd-panel { - margin-bottom: 2.5rem; - } - .tsd-panel.tsd-member { - margin-bottom: 4rem; - } - .tsd-panel:empty { - display: none; - } - .tsd-panel > h1, - .tsd-panel > h2, - .tsd-panel > h3 { - margin: 1.5rem -1.5rem 0.75rem -1.5rem; - padding: 0 1.5rem 0.75rem 1.5rem; - } - .tsd-panel > h1.tsd-before-signature, - .tsd-panel > h2.tsd-before-signature, - .tsd-panel > h3.tsd-before-signature { - margin-bottom: 0; - border-bottom: none; - } - - .tsd-panel-group { - margin: 2rem 0; - } - .tsd-panel-group.tsd-index-group { - margin: 2rem 0; - } - .tsd-panel-group.tsd-index-group details { - margin: 2rem 0; - } - .tsd-panel-group > .tsd-accordion-summary { - margin-bottom: 1rem; - } - - #tsd-search[open] { - animation: fade-in var(--modal-animation-duration) ease-out forwards; - } - #tsd-search[open].closing { - animation-name: fade-out; - } - - /* Avoid setting `display` on closed dialog */ - #tsd-search[open] { - display: flex; - flex-direction: column; - padding: 1rem; - width: 32rem; - max-width: 90vw; - max-height: calc(100vh - env(keyboard-inset-height, 0px) - 25vh); - /* Anchor dialog to top */ - margin-top: 10vh; - border-radius: 6px; - will-change: max-height; - } - #tsd-search-input { - box-sizing: border-box; - width: 100%; - padding: 0 0.625rem; /* 10px */ - outline: 0; - border: 2px solid var(--color-accent); - background-color: transparent; - color: var(--color-text); - border-radius: 4px; - height: 2.5rem; - flex: 0 0 auto; - font-size: 0.875rem; - transition: border-color 0.2s, background-color 0.2s; - } - #tsd-search-input:focus-visible { - background-color: var(--color-background-active); - border-color: transparent; - color: var(--color-contrast-text); - } - #tsd-search-input::placeholder { - color: inherit; - opacity: 0.8; - } - #tsd-search-results { - margin: 0; - padding: 0; - list-style: none; - flex: 1 1 auto; - display: flex; - flex-direction: column; - overflow-y: auto; - } - #tsd-search-results:not(:empty) { - margin-top: 0.5rem; - } - #tsd-search-results > li { - background-color: var(--color-background); - line-height: 1.5; - box-sizing: border-box; - border-radius: 4px; - } - #tsd-search-results > li:nth-child(even) { - background-color: var(--color-background-secondary); - } - #tsd-search-results > li:is(:hover, [aria-selected="true"]) { - background-color: var(--color-background-active); - color: var(--color-contrast-text); - } - /* It's important that this takes full size of parent `li`, to capture a click on `li` */ - #tsd-search-results > li > a { - display: flex; - align-items: center; - padding: 0.5rem 0.25rem; - box-sizing: border-box; - width: 100%; - } - #tsd-search-results > li > a > .text { - flex: 1 1 auto; - min-width: 0; - overflow-wrap: anywhere; - } - #tsd-search-results > li > a .parent { - color: var(--color-text-aside); - } - #tsd-search-results > li > a mark { - color: inherit; - background-color: inherit; - font-weight: bold; - } - #tsd-search-status { - flex: 1; - display: grid; - place-content: center; - text-align: center; - overflow-wrap: anywhere; - } - #tsd-search-status:not(:empty) { - min-height: 6rem; - } - - .tsd-signature { - margin: 0 0 1rem 0; - padding: 1rem 0.5rem; - border: 1px solid var(--color-accent); - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 14px; - overflow-x: auto; - } - - .tsd-signature-keyword { - color: var(--color-ts-keyword); - font-weight: normal; - } - - .tsd-signature-symbol { - color: var(--color-text-aside); - font-weight: normal; - } - - .tsd-signature-type { - font-style: italic; - font-weight: normal; - } - - .tsd-signatures { - padding: 0; - margin: 0 0 1em 0; - list-style-type: none; - } - .tsd-signatures .tsd-signature { - margin: 0; - border-color: var(--color-accent); - border-width: 1px 0; - transition: background-color 0.1s; - } - .tsd-signatures .tsd-index-signature:not(:last-child) { - margin-bottom: 1em; - } - .tsd-signatures .tsd-index-signature .tsd-signature { - border-width: 1px; - } - .tsd-description .tsd-signatures .tsd-signature { - border-width: 1px; - } - - ul.tsd-parameter-list, - ul.tsd-type-parameter-list { - list-style: square; - margin: 0; - padding-left: 20px; - } - ul.tsd-parameter-list > li.tsd-parameter-signature, - ul.tsd-type-parameter-list > li.tsd-parameter-signature { - list-style: none; - margin-left: -20px; - } - ul.tsd-parameter-list h5, - ul.tsd-type-parameter-list h5 { - font-size: 16px; - margin: 1em 0 0.5em 0; - } - .tsd-sources { - margin-top: 1rem; - font-size: 0.875em; - } - .tsd-sources a { - color: var(--color-text-aside); - text-decoration: underline; - } - .tsd-sources ul { - list-style: none; - padding: 0; - } - - .tsd-page-toolbar { - position: sticky; - z-index: 1; - top: 0; - left: 0; - width: 100%; - color: var(--color-text); - background: var(--color-background-secondary); - border-bottom: var(--dim-toolbar-border-bottom-width) - var(--color-accent) solid; - transition: transform 0.3s ease-in-out; - } - .tsd-page-toolbar a { - color: var(--color-text); - } - .tsd-toolbar-contents { - display: flex; - align-items: center; - height: var(--dim-toolbar-contents-height); - margin: 0 auto; - } - .tsd-toolbar-contents > .title { - font-weight: bold; - margin-right: auto; - } - #tsd-toolbar-links { - display: flex; - align-items: center; - gap: 1.5rem; - margin-right: 1rem; - } - - .tsd-widget { - box-sizing: border-box; - display: inline-block; - opacity: 0.8; - height: 2.5rem; - width: 2.5rem; - transition: opacity 0.1s, background-color 0.1s; - text-align: center; - cursor: pointer; - border: none; - background-color: transparent; - } - .tsd-widget:hover { - opacity: 0.9; - } - .tsd-widget:active { - opacity: 1; - background-color: var(--color-accent); - } - #tsd-toolbar-menu-trigger { - display: none; - } - - .tsd-member-summary-name { - display: inline-flex; - align-items: center; - padding: 0.25rem; - text-decoration: none; - } - - .tsd-anchor-icon { - display: inline-flex; - align-items: center; - margin-left: 0.5rem; - color: var(--color-text); - vertical-align: middle; - } - - .tsd-anchor-icon svg { - width: 1em; - height: 1em; - visibility: hidden; - } - - .tsd-member-summary-name:hover > .tsd-anchor-icon svg, - .tsd-anchor-link:hover > .tsd-anchor-icon svg, - .tsd-anchor-icon:focus-visible svg { - visibility: visible; - } - - .deprecated { - text-decoration: line-through !important; - } - - .warning { - padding: 1rem; - color: var(--color-warning-text); - background: var(--color-background-warning); - } - - .tsd-kind-project { - color: var(--color-ts-project); - } - .tsd-kind-module { - color: var(--color-ts-module); - } - .tsd-kind-namespace { - color: var(--color-ts-namespace); - } - .tsd-kind-enum { - color: var(--color-ts-enum); - } - .tsd-kind-enum-member { - color: var(--color-ts-enum-member); - } - .tsd-kind-variable { - color: var(--color-ts-variable); - } - .tsd-kind-function { - color: var(--color-ts-function); - } - .tsd-kind-class { - color: var(--color-ts-class); - } - .tsd-kind-interface { - color: var(--color-ts-interface); - } - .tsd-kind-constructor { - color: var(--color-ts-constructor); - } - .tsd-kind-property { - color: var(--color-ts-property); - } - .tsd-kind-method { - color: var(--color-ts-method); - } - .tsd-kind-reference { - color: var(--color-ts-reference); - } - .tsd-kind-call-signature { - color: var(--color-ts-call-signature); - } - .tsd-kind-index-signature { - color: var(--color-ts-index-signature); - } - .tsd-kind-constructor-signature { - color: var(--color-ts-constructor-signature); - } - .tsd-kind-parameter { - color: var(--color-ts-parameter); - } - .tsd-kind-type-parameter { - color: var(--color-ts-type-parameter); - } - .tsd-kind-accessor { - color: var(--color-ts-accessor); - } - .tsd-kind-get-signature { - color: var(--color-ts-get-signature); - } - .tsd-kind-set-signature { - color: var(--color-ts-set-signature); - } - .tsd-kind-type-alias { - color: var(--color-ts-type-alias); - } - - /* if we have a kind icon, don't color the text by kind */ - .tsd-kind-icon ~ span { - color: var(--color-text); - } - - /* mobile */ - @media (max-width: 769px) { - #tsd-toolbar-menu-trigger { - display: inline-block; - /* temporary fix to vertically align, for compatibility */ - line-height: 2.5; - } - #tsd-toolbar-links { - display: none; - } - - .container-main { - display: flex; - } - .col-content { - float: none; - max-width: 100%; - width: 100%; - } - .col-sidebar { - position: fixed !important; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - z-index: 1024; - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - padding: 1.5rem 1.5rem 0 0; - width: 75vw; - visibility: hidden; - background-color: var(--color-background); - transform: translate(100%, 0); - } - .col-sidebar > *:last-child { - padding-bottom: 20px; - } - .overlay { - content: ""; - display: block; - position: fixed; - z-index: 1023; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.75); - visibility: hidden; - } - - .to-has-menu .overlay { - animation: fade-in 0.4s; - } - - .to-has-menu .col-sidebar { - animation: pop-in-from-right 0.4s; - } - - .from-has-menu .overlay { - animation: fade-out 0.4s; - } - - .from-has-menu .col-sidebar { - animation: pop-out-to-right 0.4s; - } - - .has-menu body { - overflow: hidden; - } - .has-menu .overlay { - visibility: visible; - } - .has-menu .col-sidebar { - visibility: visible; - transform: translate(0, 0); - display: flex; - flex-direction: column; - gap: 1.5rem; - max-height: 100vh; - padding: 1rem 2rem; - } - .has-menu .tsd-navigation { - max-height: 100%; - } - .tsd-navigation .tsd-nav-link { - display: flex; - } - } - - /* one sidebar */ - @media (min-width: 770px) { - .container-main { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); - grid-template-areas: "sidebar content"; - --dim-container-main-margin-y: 2rem; - } - - .tsd-breadcrumb { - margin-top: 0; - } - - .col-sidebar { - grid-area: sidebar; - } - .col-content { - grid-area: content; - padding: 0 1rem; - } - } - @media (min-width: 770px) and (max-width: 1399px) { - .col-sidebar { - max-height: calc( - 100vh - var(--dim-header-height) - var(--dim-footer-height) - - 2 * var(--dim-container-main-margin-y) - ); - overflow: auto; - position: sticky; - top: calc( - var(--dim-header-height) + var(--dim-container-main-margin-y) - ); - } - .site-menu { - margin-top: 1rem; - } - } - - /* two sidebars */ - @media (min-width: 1200px) { - .container-main { - grid-template-columns: - minmax(0, 1fr) minmax(0, 2.5fr) minmax( - 0, - 20rem - ); - grid-template-areas: "sidebar content toc"; - } - - .col-sidebar { - display: contents; - } - - .page-menu { - grid-area: toc; - padding-left: 1rem; - } - .site-menu { - grid-area: sidebar; - } - - .site-menu { - margin-top: 0rem; - } - - .page-menu, - .site-menu { - max-height: calc( - 100vh - var(--dim-header-height) - var(--dim-footer-height) - - 2 * var(--dim-container-main-margin-y) - ); - overflow: auto; - position: sticky; - top: calc( - var(--dim-header-height) + var(--dim-container-main-margin-y) - ); - } - } -} diff --git a/docs/assets/typedoc-github-style.css b/docs/assets/typedoc-github-style.css deleted file mode 100644 index 7107051..0000000 --- a/docs/assets/typedoc-github-style.css +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Define colors - */ - -:root { - /* GitHub "Light default" */ - --light-color-background: #ffffff; - --light-color-background-secondary: #f6f8fa; - --light-color-background-navbar: #f6f8fa; - --light-color-background-overlay: #c8d1da66; - - --light-color-accent: #d1d9e0; - - --light-color-text: #1f2328; - --light-color-text-aside: #59636e; - - --light-color-link: #0969da; - - --light-color-warning-border: #f7ebba; - --light-color-background-warning: #fff8c5; - - --light-color-alert-note: #0969da; - --light-color-alert-tip: #1a7f37; - --light-color-alert-important: #8250df; - --light-color-alert-warning: #9a6700; - --light-color-alert-caution: #cf222e; - - /* GitHub "Dark default" */ - --dark-color-background: #0d1117; - --dark-color-background-secondary: #151b23; - --dark-color-background-navbar: #010409; - --dark-color-background-overlay: #21283066; - - --dark-color-accent: #383e48; - - --dark-color-text: #f0f6fc; - --dark-color-text-aside: #9198a1; - - --dark-color-link: #4493f8; - - --dark-color-warning-border: #3a2d12; - --dark-color-background-warning: #282215; - - --dark-color-alert-note: #1f6feb; - --dark-color-alert-tip: #238636; - --dark-color-alert-important: #8957e5; - --dark-color-alert-warning: #9e6a03; - --dark-color-alert-caution: #da3633; - - /* Link colors */ - --color-warning-text: var(--color-text); - --color-contrast-text: var(--color-text); - --color-icon-background: var(--color-background); - --color-focus-outline: var(--color-accent); -} - -@media (prefers-color-scheme: light) { - :root { - --color-background-navbar: var(--light-color-background-navbar); - --color-background-overlay: var(--light-color-background-overlay); - --color-warning-border: var(--light-color-warning-border); - } -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background-navbar: var(--dark-color-background-navbar); - --color-background-overlay: var(--dark-color-background-overlay); - --color-warning-border: var(--dark-color-warning-border); - } -} - -:root[data-theme='light'] { - --color-background-navbar: var(--light-color-background-navbar); - --color-background-overlay: var(--light-color-background-overlay); - --color-warning-border: var(--light-color-warning-border); -} - -:root[data-theme='dark'] { - --color-background-navbar: var(--dark-color-background-navbar); - --color-background-overlay: var(--dark-color-background-overlay); - --color-warning-border: var(--dark-color-warning-border); -} - -/* - * Define fonts - */ - -:root { - --font-family-text: - -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; - --font-family-code: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; -} - -body { - font-family: var(--font-family-text); -} - -/* - * Links - */ - -.tsd-accordion-details a, -.tsd-accordion a, -.tsd-page-toolbar a.title { - color: var(--color-text); - text-decoration: none; -} - -.tsd-accordion-details a:hover, -.tsd-page-toolbar a.title:hover, -.tsd-accordion a:hover, -.tsd-anchor-icon { - color: var(--color-text-aside); -} - -.tsd-kind-class { - color: var(--color-link); - text-decoration: underline; - text-underline-offset: 3px; -} - -.tsd-index-link, -.tsd-page-navigation a:hover { - text-decoration: none; -} - -.tsd-index-link:hover { - text-decoration: underline; - text-underline-offset: 3px; -} - -a code, -.tsd-sources a, -.tsd-page-navigation a:hover { - color: var(--color-link); -} - -a.external[target='_blank'] { - background-image: none; - padding-right: 0px; -} - -/* - * Tables - */ - -table { - margin: 1em 0; -} - -.tsd-typography th, -.tsd-typography td { - padding: 8px; - text-align: left; -} - -.tsd-typography th { - background-color: var(--color-background); - color: var(--color-text); -} - -.tsd-typography tr:nth-child(2n) { - background-color: var(--color-background-code); -} - -/* - * Horizontal line - */ - -.tsd-typography hr { - color: var(--color-accent); -} - -/* - * Buttons - */ - -button { - background-color: var(--color-background-navbar); - color: var(--color-text); - border: 1px solid var(--color-accent); - border-radius: 6px; - padding: 8px 16px; - cursor: pointer; - transition: background-color 0.1s ease-in-out; -} - -button:hover { - background-color: var(--color-accent); -} - -pre > button { - background-color: transparent; - transition: background-color 0.1s ease-in-out; - border: none; - opacity: 1; - top: 8px; - padding: 4px 8px; -} - -/* - * Checkbox - */ - -.tsd-filter-input input[type='checkbox'], -.tsd-filter-input svg { - width: 1em; - height: 1em; -} - -.tsd-filter-input svg { - border-radius: 2px; -} - -.tsd-checkbox-background { - fill: var(--color-background); - stroke: var(--color-accent); - stroke-width: 6px; -} - -input[type='checkbox']:checked ~ svg .tsd-checkbox-background { - fill: var(--color-accent); -} - -.tsd-checkbox-checkmark { - color: var(--color-text); -} - -/* - * Select - */ - -select { - background-color: var(--color-background); - border: 1px solid var(--color-accent); - border-radius: 6px; - padding: 8px; - font-family: inherit; -} - -/* - * Code blocks - */ - -code, -pre { - border: none; - border-radius: 6px; - margin: 1em 0; - background-color: var(--color-background-secondary); - color: var(--color-text); - font-family: var(--font-family-code); -} - -code.tsd-tag { - background-color: var(--color-accent); - border: unset; -} - -/* - * Warnings - */ - -.warning { - border-style: solid; - border-width: 1px; - border-color: var(--color-warning-border); - border-radius: 6px; -} - -/* - * Topbar - */ - -.tsd-page-toolbar { - background-color: var(--color-background-navbar); - border-bottom-color: var(--color-accent); -} - -.tsd-toolbar-contents a.title:hover { - color: var(--color-text); -} - -/* - * Search - */ - -#tsd-search-trigger { - width: unset; - border: unset; - background-color: unset; - transition: opacity 0.15s ease-in-out; -} - -#tsd-search-trigger:hover { - opacity: 1; -} - -#tsd-search-input, -#tsd-search-input:focus-visible { - background-color: transparent; - border: 1px solid var(--color-focus-outline); -} - -#tsd-search-status:not(:empty) { - min-height: unset; - padding-top: 1.5rem; - padding-bottom: 0.5rem; -} - -#tsd-search-results > li:is(:hover, [aria-selected='true']) { - background-color: color-mix(in srgb, var(--color-text-aside), #0000 88%); -} - -#tsd-search-results > li > a:hover { - text-decoration: unset; -} - -#tsd-overlay { - background-color: var(--color-background-overlay); -} - -/* - * Baseboard - */ - -footer { - border-top-color: var(--color-accent); -} - -/* - * Side navigations - */ - -.site-menu { - padding: 1rem 0; -} - -.tsd-navigation a { - color: var(--color-text); - border-radius: 6px; - padding: 6px; -} - -.tsd-navigation a, -.tsd-navigation a:hover { - text-decoration: none; -} - -.tsd-navigation a:hover:not(.current) { - background-color: color-mix(in srgb, var(--color-text-aside), #0000 88%); -} - -.tsd-navigation a.current { - background-color: color-mix(in srgb, var(--color-text-aside), #0000 92%); -} - -/* - * Type definition groups - */ - -.tsd-index-panel, -.tsd-member-group { - background-color: var(--color-background); - padding: 16px; - border: 1px var(--color-accent) solid; - border-radius: 6px; -} - -.tsd-panel > h1, -.tsd-panel > h2, -.tsd-panel > h3 { - margin-top: 0px; -} - -.tsd-panel-group.tsd-index-group details { - margin: 0px; -} - -.tsd-member-group .tsd-member:last-child { - margin-bottom: 0px; -} - -.tsd-signature { - border: 1px solid var(--color-accent); - border-radius: 6px; -} - -.tsd-signatures .tsd-signature { - border-color: var(--color-accent); - border-radius: 0px; -} - -.tsd-description .tsd-signatures .tsd-signature { - border-radius: 6px; -} - -.tsd-full-hierarchy:not(:last-child) { - border-bottom: var(--color-accent); -} - -/* - * Footer - */ - -footer p { - font-size: 1rem; - text-align: center; - color: var(--color-text-aside); -} - -/* - * Fix collapsed margin - */ - -.tsd-accordion-summary > h3 { - margin-top: 0px; - margin-bottom: 0px; -} - -.tsd-page-navigation:not([open]) > .tsd-accordion-summary { - margin-bottom: 0px; -} - -/* - * Fix collapse arrows position - */ - -.tsd-accordion-summary svg { - transition: transform 0.1s ease-in-out; - margin-top: auto; - margin-bottom: auto; -} diff --git a/docs/functions/generateGitignoreRules.html b/docs/functions/generateGitignoreRules.html deleted file mode 100644 index 071806a..0000000 --- a/docs/functions/generateGitignoreRules.html +++ /dev/null @@ -1,5 +0,0 @@ -generateGitignoreRules | modpack-lock
modpack-lock
    Preparing search index...

    Function generateGitignoreRules

    • Generate .gitignore rules for files not hosted on Modrinth and write them to .gitignore file

      -

      Parameters

      • lockfile: Lockfile

        The lockfile object

        -
      • workingDir: string

        The working directory

        -
      • options: Options | InitOptions = {}

        The options object

        -

      Returns Promise<void>

    diff --git a/docs/functions/generateJson.html b/docs/functions/generateJson.html deleted file mode 100644 index eccf37f..0000000 --- a/docs/functions/generateJson.html +++ /dev/null @@ -1,7 +0,0 @@ -generateJson | modpack-lock
    modpack-lock
      Preparing search index...

      Function generateJson

      diff --git a/docs/functions/generateLicense.html b/docs/functions/generateLicense.html deleted file mode 100644 index 2e87ce0..0000000 --- a/docs/functions/generateLicense.html +++ /dev/null @@ -1,7 +0,0 @@ -generateLicense | modpack-lock
      modpack-lock
        Preparing search index...

        Function generateLicense

        • Write a license to a file

          -

          Parameters

          • modpackInfo: ModpackInfo

            The modpack information

            -
          • workingDir: string

            The path to write the license to

            -
          • options: InitOptions = {}

            The initialization options object

            -
          • licenseTextOverride: string = null

            The license text to override the default license text with

            -

          Returns Promise<string>

          The license text or null if the license text could not be generated

          -
        diff --git a/docs/functions/generateLockfile.html b/docs/functions/generateLockfile.html deleted file mode 100644 index e0b5e7a..0000000 --- a/docs/functions/generateLockfile.html +++ /dev/null @@ -1,5 +0,0 @@ -generateLockfile | modpack-lock
        modpack-lock
          Preparing search index...

          Function generateLockfile

          diff --git a/docs/functions/generateModpackFiles.html b/docs/functions/generateModpackFiles.html deleted file mode 100644 index 0f51445..0000000 --- a/docs/functions/generateModpackFiles.html +++ /dev/null @@ -1,6 +0,0 @@ -generateModpackFiles | modpack-lock
          modpack-lock
            Preparing search index...

            Function generateModpackFiles

            • Generate the modpack files (lockfile, JSON, and optionally license, gitignore, and readme)

              -

              Parameters

              • modpackInfo: ModpackInfo

                The modpack information

                -
              • workingDir: string

                The directory to generate the files in

                -
              • options: Options | InitOptions = {}

                The options object

                -

              Returns Promise<Lockfile>

              The lockfile object

              -
            diff --git a/docs/functions/generateReadmeFiles.html b/docs/functions/generateReadmeFiles.html deleted file mode 100644 index e244222..0000000 --- a/docs/functions/generateReadmeFiles.html +++ /dev/null @@ -1,5 +0,0 @@ -generateReadmeFiles | modpack-lock
            modpack-lock
              Preparing search index...

              Function generateReadmeFiles

              diff --git a/docs/functions/getLockfile.html b/docs/functions/getLockfile.html deleted file mode 100644 index 1363f45..0000000 --- a/docs/functions/getLockfile.html +++ /dev/null @@ -1,4 +0,0 @@ -getLockfile | modpack-lock
              modpack-lock
                Preparing search index...

                Function getLockfile

                • Get the lockfile file if it exists

                  -

                  Parameters

                  • directoryPath: string

                    The path to the directory to scan

                    -

                  Returns Lockfile

                  The JSON object if the file exists, otherwise null

                  -
                diff --git a/docs/functions/getModpackInfo.html b/docs/functions/getModpackInfo.html deleted file mode 100644 index 43662b6..0000000 --- a/docs/functions/getModpackInfo.html +++ /dev/null @@ -1,4 +0,0 @@ -getModpackInfo | modpack-lock
                modpack-lock
                  Preparing search index...

                  Function getModpackInfo

                  • Get the modpack info from the JSON file if it exists

                    -

                    Parameters

                    • directoryPath: string

                      The path to the directory to scan

                      -

                    Returns Promise<ModpackInfo>

                    The modpack info JSON object if the file exists, otherwise null

                    -
                  diff --git a/docs/functions/promptUserForInfo.html b/docs/functions/promptUserForInfo.html deleted file mode 100644 index 940e009..0000000 --- a/docs/functions/promptUserForInfo.html +++ /dev/null @@ -1,4 +0,0 @@ -promptUserForInfo | modpack-lock
                  modpack-lock
                    Preparing search index...

                    Function promptUserForInfo

                    diff --git a/docs/hierarchy.html b/docs/hierarchy.html deleted file mode 100644 index 70f06f7..0000000 --- a/docs/hierarchy.html +++ /dev/null @@ -1 +0,0 @@ -modpack-lock
                    modpack-lock
                      Preparing search index...

                      modpack-lock

                      Hierarchy Summary

                      diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index b8abb8f..0000000 --- a/docs/index.html +++ /dev/null @@ -1,185 +0,0 @@ -modpack-lock
                      modpack-lock
                        Preparing search index...

                        modpack-lock

                        NPM: npmjs.com/package/modpack-lock -Source: github.com/nickesc/modpack-lock -Tests: github.com/nickesc/modpack-lock/actions/workflows/modpack-lock-tests.yml

                        -

                        modpack-lock

                        - -

                        Creates a modpack lockfile for files hosted on Modrinth (mods, resource packs, shaders and datapacks).

                        - -

                        Many mod and pack authors request that modpack creators link to Modrinth or CurseForge downloads rather than re-hosting files. This makes it difficult to track content files in version control when pushing to a remote server.

                        -

                        This script generates a modpack.lock file containing a plaintext representation of the modpack's contents. This object contains the metadata for the content available on Modrinth, including hashes, versions, names, download URLs and more. An optional modpack.json file can also be created to store your modpack's metadata (name, version, modloader, dependencies, etc.) alongside the lockfile. This setup allows for easy diffing and clear version history.

                        -
                        -

                        While an .mrpack file could be used to track changes to the modpack, it is a large, binary file that cannot be diffed and can contain large amounts of duplicate data from the rest of the repository.

                        -
                        -

                        Using the scripts field in modpack.json, you can also define reusable, tracked shell commands for common modpack tasks (like publishing, generating assets or CI/CD workflows).

                        - -

                        To install the script globally with npm:

                        -
                        npm install -g modpack-lock
                        -
                        - -

                        Alternatively, you can run it using npx:

                        -
                        npx modpack-lock
                        -
                        - - -

                        To generate a lockfile for your modpack, run:

                        -
                        modpack-lock
                        -
                        - -

                        The script will:

                        -
                          -
                        • Scan target directory's mods, resourcepacks, datapacks, and shaderpacks directories for .jar and .zip files
                        • -
                        • Calculate SHA1 hashes for each file
                        • -
                        • Query the Modrinth API for matching versions
                        • -
                        • Generate a modpack.lock file (and update modpack.json dependencies if present)
                        • -
                        -

                        Use flags to generate README.md files for each category or update the .gitignore to ignore content hosted on Modrinth.

                        -
                        Usage: modpack-lock [options] [command]
                        -
                        -Creates a modpack lockfile for files hosted on Modrinth (mods, resource packs, shaders and datapacks)
                        -
                        -Options:
                        -  -p, --path <path>       Path to the modpack directory
                        -  -d, --dry-run           Dry-run mode - no files will be written
                        -
                        -GENERATION
                        -  -g, --gitignore         Update the .gitignore file to ignore content hosted on Modrinth
                        -  -r, --readme            Generate README.md files for each category
                        -
                        -LOGGING
                        -  -q, --quiet             Quiet mode - only show errors and warnings
                        -  -s, --silent            Silent mode - no output
                        -
                        -INFORMATION
                        -  -V                      output the version number
                        -  -h, --help              display help for modpack-lock
                        -
                        -Commands:
                        -  init [options]          This utility will walk you through creating a modpack.json file. It only covers the most common items, and tries to guess sensible defaults.
                        -  run [options] <script>  Run a script defined in the modpack.json file's 'scripts' field
                        -
                        - -
                        Tip

                        - -

                        You can generate summary files for each category by running modpack-lock -r. This will generate a README.md file in each of the content folders, detailing the scanned files and important attribution information for them.

                        -
                        - -

                        To initialize a new modpack, run:

                        -
                        modpack-lock init
                        -
                        - -

                        This command will:

                        -
                          -
                        • Prompt the user for the modpack's metadata (name, version, author, etc.)
                        • -
                        • Generate the lockfile
                        • -
                        • Generate a modpack.json file that stores your modpack's metadata (name, version, author, etc.), including a list of dependencies
                        • -
                        -

                        The interactive mode will prompt you for each field. Set their initial values using the available option flags. Use --noninteractive with the required options to skip the interactive-prompt and use the provided values.

                        -
                        Usage: modpack-lock init [options]
                        -
                        -This utility will walk you through creating a modpack.json file. It only covers the most common items, and tries to guess sensible defaults.
                        -
                        -Options:
                        -  -f, --folder <path>                                Path to the modpack directory
                        -  -n, --noninteractive                               Non-interactive mode - must provide options for required fields
                        -  --add-license                                      Add the LICENSE file to the modpack
                        -  --add-gitignore                                    Update the .gitignore file to ignore content hosted on Modrinth
                        -  --add-readme                                       Generate README.md files for each category
                        -
                        -MODPACK INFORMATION
                        -  --name <name>                                      Modpack name; defaults to the directory name
                        -  --version <version>                                Modpack version; defaults to 1.0.0
                        -  --id <id>                                          Modpack slug/ID; defaults to the directory name slugified
                        -  --description <description>                        Modpack description
                        -  --author <author>                                  Modpack author; required
                        -  --projectUrl <projectUrl>                          Modpack URL; defaults to a guessed Modrinth project URL
                        -  --sourceUrl <sourceUrl>                            Modpack source code URL; defaults to a guessed GitHub repository URL
                        -  --license <license>                                Modpack license, popular licenses fetched from GitHub; defaults to MIT in interactive mode
                        -  --modloader <modloader>                            Modpack modloader, list of loaders fetched from Modrinth; required
                        -  --targetModloaderVersion <targetModloaderVersion>  Target modloader version
                        -  --targetMinecraftVersion <targetMinecraftVersion>  Target Minecraft version, list of versions fetched from Modrinth; required
                        -
                        -INFORMATION
                        -  -h, --help                                         display help for modpack-lock init
                        -
                        - - -

                        To run a script defined in modpack.json, run:

                        -
                        modpack-lock run <script>
                        -
                        - -

                        This command takes the name of the script as its first argument:

                        -
                          -
                        • It searches for a scripts fiels in modpack.json in the current directory by default.
                        • -
                        • Use the -f option to specify a different path to the modpack directory.
                        • -
                        • For debug logging, use the -D option.
                        • -
                        -
                        -

                        To pass additional arguments and options to the script, write them after a -- separator:

                        -
                        modpack-lock run <script> -- [options] <args>
                        -
                        - -
                        -

                        The scripts field in modpack.json is a key-value pair of script names and their corresponding shell commands. The scripts field is optional and is omitted by default.

                        -
                        Usage: modpack-lock run [options] <script>
                        -
                        -Run a script defined in the modpack.json file's 'scripts' field
                        -
                        -Arguments:
                        -  script               The name of the script to run
                        -
                        -Options:
                        -  -f, --folder <path>  Path to the modpack directory
                        -  -D, --debug          Debug mode -- show more information about how the command is being parsed
                        -
                        -INFORMATION
                        -  -h, --help           display help for modpack-lock run
                        -
                        - - -

                        For programmatic usage, modpack-lock exports these functions:

                        -
                          -
                        • getModpackInfo()
                        • -
                        • getLockfile()
                        • -
                        • generateJson()
                        • -
                        • generateGitignoreRules()
                        • -
                        • generateReadmeFiles()
                        • -
                        • generateLicense()
                        • -
                        • generateLockfile()
                        • -
                        • generateModpackFiles()
                        • -
                        • promptUserForInfo()
                        • -
                        -

                        See the API documentation for full details.

                        - - -

                        The lockfile contains metadata about Modrinth-hosted files found in modpack directories:

                        -
                        {
                        "version": "1.0.1",
                        "generated": "2026-01-06T03:00:00.000Z",
                        "total": 7,
                        "counts": {
                        "mods": 1,
                        "resourcepacks": 3,
                        "datapacks": 1,
                        "shaderpacks": 2
                        },
                        "dependencies": {
                        "mods": [
                        {
                        "path": "mods/example-mod.jar",
                        "version": { ... }
                        }
                        ],
                        "resourcepacks": [ ... ],
                        "datapacks": [ ... ],
                        "shaderpacks": [ ... ]
                        }
                        } -
                        - - -

                        The JSON file contains your modpack metadata and a dependency list:

                        -
                        {
                        "name": "My Modpack",
                        "version": "1.0.0",
                        "id": "my-modpack",
                        "description": "",
                        "author": "name",
                        "projectUrl": "",
                        "sourceUrl": "",
                        "license": "",
                        "modloader": "modloader",
                        "targetModloaderVersion": "",
                        "targetMinecraftVersion": "x.y.z",
                        "scripts": {
                        "example": "echo 'example script'"
                        },
                        "dependencies": {
                        "mods": [ ... ],
                        "resourcepacks": [ ... ],
                        "datapacks": [ ... ],
                        "shaderpacks": [ ... ]
                        }
                        } -
                        - -
                        Important

                        - -

                        Use modpack-lock -g to automatically update your .gitignore file with rules to ignore modpack contents, with exceptions for any files that are not hosted by Modrinth:

                        -
                        # .gitignore
                        -
                        -# modpack-lock:start
                        -mods/*.jar
                        -resourcepacks/*.zip
                        -datapacks/*.zip
                        -shaderpacks/*.zip
                        -*/**/*.disabled
                        -
                        -## Exceptions
                        -!mods/example.jar
                        -# modpack-lock:end
                        -
                        - -

                        This section is managed by modpack-lock and will be updated automatically when you run modpack-lock -g. Changes made inside this section will be overwritten, but any changes you make outside of this section will be preserved.

                        -
                        - -

                        This project is licensed under the MIT License - see the LICENSE file for more details.

                        -

                        GitHub License

                        -
                        diff --git a/docs/interfaces/InitOptions.html b/docs/interfaces/InitOptions.html deleted file mode 100644 index dde5d0b..0000000 --- a/docs/interfaces/InitOptions.html +++ /dev/null @@ -1,36 +0,0 @@ -InitOptions | modpack-lock
                        modpack-lock
                          Preparing search index...

                          Interface InitOptions

                          Contains options for the initialization of the modpack files.

                          -
                          interface InitOptions {
                              folder: string;
                              noninteractive: boolean;
                              addLicense: boolean;
                              addGitignore: boolean;
                              addReadme: boolean;
                              name: string;
                              version: string;
                              id: string;
                              description: string;
                              author: string;
                              projectUrl: string;
                              sourceUrl: string;
                              license: string;
                              modloader: string;
                              targetModloaderVersion: string;
                              targetMinecraftVersion: string;
                              _init: boolean;
                          }
                          Index

                          Properties

                          folder: string

                          The folder to generate the modpack files in

                          -
                          noninteractive: boolean

                          Whether to run the interactive mode

                          -
                          addLicense: boolean

                          Whether to add the license file to the modpack

                          -
                          addGitignore: boolean

                          Whether to generate .gitignore rules

                          -
                          addReadme: boolean

                          Whether to generate README.md files

                          -
                          name: string

                          The name of the modpack

                          -
                          version: string

                          The version of the modpack

                          -
                          id: string

                          The slug/ID of the modpack

                          -
                          description: string

                          The description of the modpack

                          -
                          author: string

                          The author of the modpack

                          -
                          projectUrl: string

                          The modpack's project URL

                          -
                          sourceUrl: string

                          The modpack's source code URL

                          -
                          license: string

                          The modpack's license

                          -
                          modloader: string

                          The modpack's modloader

                          -
                          targetModloaderVersion: string

                          The target modloader version

                          -
                          targetMinecraftVersion: string

                          The target Minecraft version

                          -
                          _init: boolean

                          Internal boolean added to indicate options come from the init command.

                          -
                          diff --git a/docs/interfaces/Lockfile.html b/docs/interfaces/Lockfile.html deleted file mode 100644 index 2099cdf..0000000 --- a/docs/interfaces/Lockfile.html +++ /dev/null @@ -1,20 +0,0 @@ -Lockfile | modpack-lock
                          modpack-lock
                            Preparing search index...

                            Interface Lockfile

                            Contains information about the modpack dependencies and their versions.

                            -
                            interface Lockfile {
                                version: string;
                                generated: string;
                                total: number;
                                counts: {
                                    mods: number;
                                    resourcepacks: number;
                                    datapacks: number;
                                    shaderpacks: number;
                                };
                                dependencies: {
                                    mods: any[];
                                    resourcepacks: any[];
                                    datapacks: any[];
                                    shaderpacks: any[];
                                };
                            }
                            Index

                            Properties

                            version: string

                            The version of the modpack

                            -
                            generated: string

                            The date and time the lockfile was generated

                            -
                            total: number

                            The total number of files in the modpack

                            -
                            counts: {
                                mods: number;
                                resourcepacks: number;
                                datapacks: number;
                                shaderpacks: number;
                            }

                            The counts object

                            -

                            Type Declaration

                            • mods: number

                              The mods count

                              -
                            • resourcepacks: number

                              The resourcepacks count

                              -
                            • datapacks: number

                              The datapacks count

                              -
                            • shaderpacks: number

                              The shaderpacks count

                              -
                            dependencies: {
                                mods: any[];
                                resourcepacks: any[];
                                datapacks: any[];
                                shaderpacks: any[];
                            }

                            The dependencies object

                            -

                            Type Declaration

                            • mods: any[]

                              The mods object

                              -
                            • resourcepacks: any[]

                              The resourcepacks object

                              -
                            • datapacks: any[]

                              The datapacks object

                              -
                            • shaderpacks: any[]

                              The shaderpacks object

                              -
                            diff --git a/docs/interfaces/ModpackInfo.html b/docs/interfaces/ModpackInfo.html deleted file mode 100644 index 98f70ff..0000000 --- a/docs/interfaces/ModpackInfo.html +++ /dev/null @@ -1,24 +0,0 @@ -ModpackInfo | modpack-lock
                            modpack-lock
                              Preparing search index...

                              Interface ModpackInfo

                              Contains information about the modpack that is not dependent on the lockfile.

                              -
                              interface ModpackInfo {
                                  name: string;
                                  version: string;
                                  description: string;
                                  id: string;
                                  author: string;
                                  projectUrl: string;
                                  sourceUrl: string;
                                  license: string;
                                  modloader: string;
                                  targetModloaderVersion: string;
                                  targetMinecraftVersion: string;
                              }
                              Index

                              Properties

                              name: string

                              The name of the modpack (Required)

                              -
                              version: string

                              The version of the modpack (Required)

                              -
                              description: string

                              The description of the modpack

                              -
                              id: string

                              The slug/ID of the modpack (Required)

                              -
                              author: string

                              The author of the modpack (Required)

                              -
                              projectUrl: string

                              The project URL of the modpack

                              -
                              sourceUrl: string

                              The source code URL of the modpack

                              -
                              license: string

                              The license of the modpack

                              -
                              modloader: string

                              The modloader of the modpack (Required)

                              -
                              targetModloaderVersion: string

                              The target modloader version of the modpack

                              -
                              targetMinecraftVersion: string

                              The target Minecraft version of the modpack (Required)

                              -
                              diff --git a/docs/interfaces/Options.html b/docs/interfaces/Options.html deleted file mode 100644 index 6fe4120..0000000 --- a/docs/interfaces/Options.html +++ /dev/null @@ -1,14 +0,0 @@ -Options | modpack-lock
                              modpack-lock
                                Preparing search index...

                                Interface Options

                                Contains options for the generation of the modpack files.

                                -
                                interface Options {
                                    dryRun: boolean;
                                    quiet: boolean;
                                    silent: boolean;
                                    gitignore: boolean;
                                    readme: boolean;
                                    licenseFile: boolean;
                                }
                                Index

                                Properties

                                dryRun: boolean

                                Whether to dry run the generation

                                -
                                quiet: boolean

                                Whether to quiet the console output

                                -
                                silent: boolean

                                Whether to silent the console output

                                -
                                gitignore: boolean

                                Whether to generate a .gitignore file

                                -
                                readme: boolean

                                Whether to generate README.md files

                                -
                                licenseFile: boolean

                                Whether to generate a license file

                                -
                                diff --git a/docs/media/LICENSE b/docs/media/LICENSE deleted file mode 100644 index 90b5e58..0000000 --- a/docs/media/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 N. Escobar - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/modules.html b/docs/modules.html deleted file mode 100644 index b4fb24c..0000000 --- a/docs/modules.html +++ /dev/null @@ -1 +0,0 @@ -modpack-lock
                                modpack-lock
                                  Preparing search index...
                                  diff --git a/docs/sitemap.xml b/docs/sitemap.xml deleted file mode 100644 index 684c124..0000000 --- a/docs/sitemap.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - https://nickesc.github.io/modpack-lock/index.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/modules.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/hierarchy.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/interfaces/ModpackInfo.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/interfaces/Lockfile.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/interfaces/Options.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/interfaces/InitOptions.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/getModpackInfo.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/getLockfile.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/generateGitignoreRules.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/generateJson.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/generateLicense.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/generateLockfile.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/generateReadmeFiles.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/generateModpackFiles.html - 2026-02-15T00:19:06.741Z - - - https://nickesc.github.io/modpack-lock/functions/promptUserForInfo.html - 2026-02-15T00:19:06.741Z - - diff --git a/eslint.config.js b/eslint.config.js index 4e750d6..35c4c6c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,19 +1,12 @@ import js from "@eslint/js"; -import jsdoc from "eslint-plugin-jsdoc"; import globals from "globals"; import {defineConfig} from "eslint/config"; export default defineConfig([ { - files: ["**/*.{js,mjs,cjs}"], + files: ["**/*.{ts}", "test/**/*.{ts,js}"], plugins: {js}, extends: ["js/recommended"], languageOptions: {globals: globals.node}, - ignores: ["dist/**/*.js", "coverage/**/*.js", "node_modules/**/*.js", "docs/**/*"], - }, - { - files: ["**/*.{js,mjs,cjs}"], - ...jsdoc.configs["flat/recommended"], - ignores: ["dist/**/*.js", "coverage/**/*.js", "node_modules/**/*.js", "docs/**/*"], }, ]); diff --git a/package-lock.json b/package-lock.json index 48f469c..dab9153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,21 +9,25 @@ "version": "0.6.2", "license": "MIT", "dependencies": { + "@sindresorhus/slugify": "^3.0.0", "commander": "^14.0.3", "prompts": "^2.4.2", "slugify": "^1.6.6" }, "bin": { - "modpack-lock": "src/cli.js" + "modpack-lock": "build/cli.js" }, "devDependencies": { "@eslint/js": "^10.0.1", + "@types/node": "^25.3.3", + "@types/prompts": "^2.4.9", "@vitest/coverage-v8": "^4.0.18", "eslint": "^10.0.0", - "eslint-plugin-jsdoc": "^62.7.1", "globals": "^17.3.0", + "tsx": "^4.21.0", "typedoc": "^0.28.17", "typedoc-github-theme": "^0.3.1", + "typescript": "^5.9.3", "unzipper": "^0.12.3", "vitest": "^4.0.16" }, @@ -91,33 +95,6 @@ "node": ">=18" } }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.84.0.tgz", - "integrity": "sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.8", - "@typescript-eslint/types": "^8.54.0", - "comment-parser": "1.4.5", - "esquery": "^1.7.0", - "jsdoc-type-pratt-parser": "~7.1.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@es-joy/resolve.exports": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", - "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -1233,14 +1210,41 @@ "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/base62": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", - "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", - "dev": true, + "node_modules/@sindresorhus/slugify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-3.0.0.tgz", + "integrity": "sha512-SCrKh1zS96q+CuH5GumHcyQEVPsM4Ve8oE0E6tw7AAhGq50K8ojbTUOQnX/j9Mhcv/AXiIsbCfquovyGOo5fGw==", "license": "MIT", + "dependencies": { + "@sindresorhus/transliterate": "^2.0.0", + "escape-string-regexp": "^5.0.0" + }, "engines": { - "node": ">=18" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/slugify/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-2.3.1.tgz", + "integrity": "sha512-gVaaGtKYMYAMmI8buULVH3A2TXVJ98QiwGwI7ddrWGuGidGC2uRt4FHs22+8iROJ0QTzju9CuMjlVsrvpqsdhA==", + "license": "MIT", + "engines": { + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1302,6 +1306,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", + "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/prompts": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz", + "integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "kleur": "^3.0.3" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -1309,20 +1334,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@vitest/coverage-v8": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", @@ -1505,16 +1516,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1587,16 +1588,6 @@ "node": ">=20" } }, - "node_modules/comment-parser": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.5.tgz", - "integrity": "sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -1785,35 +1776,6 @@ } } }, - "node_modules/eslint-plugin-jsdoc": { - "version": "62.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.7.1.tgz", - "integrity": "sha512-4Zvx99Q7d1uggYBUX/AIjvoyqXhluGbbKrRmG8SQTLprPFg6fa293tVJH1o1GQwNe3lUydd8ZHzn37OaSncgSQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.84.0", - "@es-joy/resolve.exports": "1.2.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.5", - "debug": "^4.4.3", - "escape-string-regexp": "^4.0.0", - "espree": "^11.1.0", - "esquery": "^1.7.0", - "html-entities": "^2.6.0", - "object-deep-merge": "^2.0.0", - "parse-imports-exports": "^0.2.4", - "semver": "^7.7.4", - "spdx-expression-parse": "^4.0.0", - "to-valid-identifier": "^1.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" - } - }, "node_modules/eslint-scope": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.0.tgz", @@ -2092,6 +2054,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2135,23 +2110,6 @@ "node": ">=8" } }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2285,16 +2243,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.1.1.tgz", - "integrity": "sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2514,13 +2462,6 @@ "dev": true, "license": "MIT" }, - "node_modules/object-deep-merge": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", - "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", - "dev": true, - "license": "MIT" - }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -2582,23 +2523,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-imports-exports": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", - "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-statements": "1.0.11" - } - }, - "node_modules/parse-statements": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", - "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", - "dev": true, - "license": "MIT" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2741,17 +2665,14 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/reserved-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", - "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, "node_modules/rollup": { @@ -2874,31 +2795,6 @@ "node": ">=0.10.0" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -2980,21 +2876,24 @@ "node": ">=14.0.0" } }, - "node_modules/to-valid-identifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", - "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", "dependencies": { - "@sindresorhus/base62": "^1.0.0", - "reserved-identifiers": "^1.0.0" + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" }, "engines": { - "node": ">=20" + "node": ">=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "fsevents": "~2.3.3" } }, "node_modules/type-check": { @@ -3053,7 +2952,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3069,6 +2967,13 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package.json b/package.json index 42ef4b6..16e35bf 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ "license": "MIT", "author": "N. Escobar (https://nickesc.github.io/)", "type": "module", - "main": "src/modpack-lock.js", + "main": "build/modpack-lock.js", "bin": { - "modpack-lock": "src/cli.js" + "modpack-lock": "build/cli.js" }, "keywords": [ "modrinth", @@ -27,30 +27,36 @@ "node": ">=22.0.0" }, "scripts": { + "build": "tsc", "test": "vitest --run", - "start": "node src/cli.js", + "start": "tsx src/cli.ts", "docs": "typedoc", - "modpack-lock": "node src/cli.js" + "modpack-lock": "tsx src/cli.ts", + "prepare": "npm run build" }, "files": [ "README.md", "LICENSE", "package.json", - "src/**/*.js" + "build/**/*" ], "dependencies": { + "@sindresorhus/slugify": "^3.0.0", "commander": "^14.0.3", "prompts": "^2.4.2", "slugify": "^1.6.6" }, "devDependencies": { "@eslint/js": "^10.0.1", + "@types/node": "^25.3.3", + "@types/prompts": "^2.4.9", "@vitest/coverage-v8": "^4.0.18", "eslint": "^10.0.0", - "eslint-plugin-jsdoc": "^62.7.1", "globals": "^17.3.0", + "tsx": "^4.21.0", "typedoc": "^0.28.17", "typedoc-github-theme": "^0.3.1", + "typescript": "^5.9.3", "unzipper": "^0.12.3", "vitest": "^4.0.16" } diff --git a/src/cli.js b/src/cli.ts similarity index 66% rename from src/cli.js rename to src/cli.ts index 412e379..e2f4c22 100644 --- a/src/cli.js +++ b/src/cli.ts @@ -1,18 +1,28 @@ #!/usr/bin/env NODE_OPTIONS=--no-warnings node import {Command} from "commander"; -import slugify from "slugify"; +import slugify from "@sindresorhus/slugify"; import path from "path"; -import {spawn} from "child_process"; +import {ChildProcess, spawn} from "child_process"; import {generateLockfile, printLockfileSummary} from "./generate_lockfile.js"; import {generateReadmeFiles} from "./generate_readme.js"; import {generateGitignoreRules} from "./generate_gitignore.js"; import {generateModpackFiles} from "./modpack-lock.js"; -import {promptUserForInfo, promptUserAboutOptionalFiles} from "./modpack_info.js"; +import {promptUserForInfo, promptUserAboutOptionalFiles} from "./user_prompts.js"; import {getModpackInfo} from "./directory_scanning.js"; import * as config from "./config/index.js"; import pkg from "../package.json" with {type: "json"}; import {logm, styleText} from "./logger.js"; +import type { + Jsonfile, + Options, + InitOptions, + Lockfile, + ModpackInfo, + OptionalFileOptions, + RunOptions, +} from "./types/index.js"; +import type prompts from "prompts"; const modpackLock = new Command("modpack-lock"); @@ -20,22 +30,18 @@ const modpackLock = new Command("modpack-lock"); * Merge modpack info with priority: options > existingInfo > defaults * Preserves all fields from existingInfo */ -function mergeModpackInfo(existingInfo, options, defaults) { - const result = {}; - for (const [key, defaultValue] of Object.entries(defaults)) { - result[key] = options[key] || existingInfo?.[key] || defaultValue; +function mergeModpackInfo(existingInfo: Jsonfile | null, options: InitOptions, defaults: ModpackInfo): Jsonfile { + const mergedInfo: Jsonfile = { + ...defaults, + ...(existingInfo ?? {}), + }; + + for (const key of config.MODPACK_INFO_FIELDS) { + const defaultValue = defaults[key]; + mergedInfo[key] = options[key] || existingInfo?.[key] || defaultValue; } - // Then, add any fields from existingInfo that aren't in defaults - if (existingInfo) { - for (const [key, value] of Object.entries(existingInfo)) { - if (!(key in defaults)) { - result[key] = value; - } - } - } - - return result; + return mergedInfo; } modpackLock @@ -55,15 +61,15 @@ modpackLock .optionsGroup(config.headings.information) .helpOption("-h, --help", `display help for ${pkg.name}`) .version(pkg.version, "-V") - .action(async (options) => { + .action(async (options: Options) => { try { - const currDir = options.path || process.cwd(); + const currDir: string = options.path || process.cwd(); logm.quietFromOptions(options); - const modpackInfo = await getModpackInfo(currDir); + const modpackInfo: Jsonfile | null = await getModpackInfo(currDir); if (modpackInfo) { - const lockfile = await generateModpackFiles(modpackInfo, currDir, options); + const lockfile: Lockfile = await generateModpackFiles(modpackInfo, currDir, options); printLockfileSummary(lockfile); } else { // Warn if license option is passed but no modpack.json exists @@ -74,7 +80,7 @@ modpackLock } // Generate lockfile - const lockfile = await generateLockfile(currDir, options); + const lockfile: Lockfile = await generateLockfile(currDir, options); if (options.gitignore || options.readme) { logm.header("Generating Optional Files"); @@ -123,52 +129,60 @@ modpackLock .option("--targetMinecraftVersion ", config.infoFields.targetMinecraftVersion.option) .optionsGroup(config.headings.information) .helpOption("-h, --help", `display help for ${pkg.name} init`) - .action(async (options) => { + .action(async (options: InitOptions) => { options._init = true; - const currDir = options.folder || process.cwd(); + const currDir: string = options.folder || process.cwd(); - let existingInfo = await getModpackInfo(currDir); + let existingInfo: Jsonfile | null = await getModpackInfo(currDir); if (options.noninteractive) { logm.quiet(); - if ( - (!options.author && !existingInfo?.author) || - (!options.modloader && !existingInfo?.modloader) || - (!options.targetMinecraftVersion && !existingInfo?.targetMinecraftVersion) - ) { - logm.error("Must provide options for required fields"); + const defaultName: string = path.basename(currDir); + + const author: string = options.author || existingInfo?.author || ""; + const modloader: string = options.modloader || existingInfo?.modloader || ""; + const targetMinecraftVersion: string = + options.targetMinecraftVersion || existingInfo?.targetMinecraftVersion || ""; + + if (!author || !modloader || !targetMinecraftVersion) { + logm.error( + `Must provide options for required fields:`, + styleText( + ["bold"], + `${author ? "" : "author"} ${modloader ? "" : "modloader"} ${targetMinecraftVersion ? "" : "targetMinecraftVersion"}`, + ), + ); process.exitCode = 1; return; - } else { - const defaultName = path.basename(currDir); - const defaults = { - name: defaultName, - version: config.DEFAULT_MODPACK_VERSION, - id: defaultName, - description: "", - author: options.author, // Required, no default - projectUrl: "", - sourceUrl: "", - license: "", - modloader: options.modloader, // Required, no default - targetModloaderVersion: "", - targetMinecraftVersion: options.targetMinecraftVersion, // Required, no default - }; - - const modpackInfo = mergeModpackInfo(existingInfo, options, defaults); - modpackInfo.id = slugify(modpackInfo.id, config.SLUGIFY_OPTIONS); - - options.readme = options.addReadme; - options.gitignore = options.addGitignore; - options.licenseFile = options.addLicense; + } - // generate the modpack files - try { - await generateModpackFiles(modpackInfo, currDir, options); - } catch (error) { - logm.error(error); - process.exitCode = 1; - } + const defaults: ModpackInfo = { + name: defaultName, + version: config.DEFAULT_MODPACK_VERSION, + id: "", // Required, defaults after merge if not provided + description: "", + author: author, // Required, no default + projectUrl: "", + sourceUrl: "", + license: "", + modloader: modloader, // Required, no default + targetModloaderVersion: "", + targetMinecraftVersion: targetMinecraftVersion, // Required, no default + }; + + const modpackInfo: Jsonfile = mergeModpackInfo(existingInfo, options, defaults); + modpackInfo.id = slugify(modpackInfo.id || modpackInfo.name || defaultName, config.SLUGIFY_OPTIONS); + + options.readme = options.addReadme || false; + options.gitignore = options.addGitignore || false; + options.licenseFile = options.addLicense || false; + + // generate the modpack files + try { + await generateModpackFiles(modpackInfo, currDir, options); + } catch (error) { + logm.error(error); + process.exitCode = 1; } } else { logm.info(logm.label("modpack-lock"), styleText(["bold", "italic", "blueBright"], "init")); @@ -197,29 +211,32 @@ modpackLock ); logm.newline(); try { - const defaults = { + const defaults: ModpackInfo = { name: path.basename(currDir), version: config.DEFAULT_MODPACK_VERSION, - id: undefined, - description: undefined, - author: undefined, - projectUrl: undefined, - sourceUrl: undefined, + id: "", + description: "", + author: "", + projectUrl: "", + sourceUrl: "", license: config.DEFAULT_MODPACK_LICENSE, - modloader: undefined, - targetModloaderVersion: undefined, - targetMinecraftVersion: undefined, + modloader: "", + targetModloaderVersion: "", + targetMinecraftVersion: "", }; const mergedDefaults = mergeModpackInfo(existingInfo, options, defaults); // prompt user for modpack information - const userAnswers = await promptUserForInfo(mergedDefaults); + const userAnswers: prompts.Answers = await promptUserForInfo(mergedDefaults); // Preserve extra fields (e.g. scripts) from existing modpack.json - const modpackInfo = {...mergedDefaults, ...userAnswers}; + const modpackInfo: Jsonfile = {...mergedDefaults, ...userAnswers}; // prompt user if they want to add the license text - const optionalFiles = await promptUserAboutOptionalFiles(modpackInfo, options); + const optionalFiles: prompts.Answers = await promptUserAboutOptionalFiles( + modpackInfo, + options, + ); logm.newline(); @@ -227,7 +244,7 @@ modpackLock options.readme = optionalFiles.addReadme; options.gitignore = optionalFiles.addGitignore; options.licenseFile = optionalFiles.addLicense; - const lockfile = await generateModpackFiles(modpackInfo, currDir, options); + const lockfile: Lockfile = await generateModpackFiles(modpackInfo, currDir, options); printLockfileSummary(lockfile); } catch (error) { @@ -248,15 +265,15 @@ modpackLock .helpOption("-h, --help", `display help for ${pkg.name} run`) .allowExcessArguments(true) .allowUnknownOption(true) - .action(async (script, options, command) => { + .action(async (script: string, options: RunOptions, command: Command) => { options._run = true; try { if (options.debug) { logm.debug("COMMAND:", command); } - const currDir = options.folder || process.cwd(); - const modpackInfo = await getModpackInfo(currDir); + const currDir: string = options.folder || process.cwd(); + const modpackInfo: Jsonfile | null = await getModpackInfo(currDir); // verify neccecary files and information exist if (!modpackInfo) { @@ -270,9 +287,9 @@ modpackLock } // build the full command - const scriptCommand = modpackInfo.scripts[script]; - const args = command.args ? command.args.slice(1) : []; - const fullCommand = `${scriptCommand} ${args.join(" ")}`; + const scriptCommand: string = modpackInfo.scripts[script]; + const args: string[] = command.args ? command.args.slice(1) : []; + const fullCommand: string = `${scriptCommand} ${args.join(" ")}`; // debug logging if (options.debug) { @@ -285,20 +302,20 @@ modpackLock } // spawn the command - const child = spawn(fullCommand, [], { + const child: ChildProcess = spawn(fullCommand, [], { shell: true, stdio: "inherit", cwd: currDir, }); // preserve exit code on completion - const exitCode = await new Promise((resolve) => { - child.on("close", (code) => { + const exitCode: number = await new Promise((resolve) => { + child.on("close", (code: number) => { resolve(code || 0); }); }); process.exitCode = exitCode; - } catch (error) { + } catch (error: any) { logm.error(error.message); process.exitCode = 1; } diff --git a/src/config/api.js b/src/config/api.js deleted file mode 100644 index 49b5f16..0000000 --- a/src/config/api.js +++ /dev/null @@ -1,41 +0,0 @@ -import pkg from "../../package.json" with {type: "json"}; -import * as constants from "./constants.js"; - -/** User-Agent header for Modrinth API requests */ -export const PACKAGE_USER_AGENT = `${constants.AUTHOR_USERNAME}/${pkg.name}/${pkg.version}`; - -/** Modrinth API base URL */ -export const MODRINTH_API_BASE = "https://api.modrinth.com/v2"; - -/** Modrinth version files endpoint */ -export const MODRINTH_VERSION_FILES_ENDPOINT = `${MODRINTH_API_BASE}/version_files`; - -/** Modrinth projects endpoint */ -export const MODRINTH_PROJECTS_ENDPOINT = `${MODRINTH_API_BASE}/projects`; - -/** Modrinth users endpoint */ -export const MODRINTH_USERS_ENDPOINT = `${MODRINTH_API_BASE}/users`; - -/** Modrinth Minecraft versions endpoint */ -export const MODRINTH_MINECRAFT_VERSIONS_ENDPOINT = `${MODRINTH_API_BASE}/tag/game_version`; - -/** Modrinth Modloaders endpoint */ -export const MODRINTH_MODLOADERS_ENDPOINT = `${MODRINTH_API_BASE}/tag/loader`; - -/** Batch size for Modrinth API requests */ -export const BATCH_SIZE = 100; - -/** GitHub API base URL */ -export const GITHUB_API_BASE = "https://api.github.com"; - -/** GitHub licenses endpoint */ -export const GITHUB_LICENSES_ENDPOINT = `${GITHUB_API_BASE}/licenses`; - -/** GitHub featured licenses endpoint */ -export const GITHUB_FEATURED_LICENSES_ENDPOINT = `${GITHUB_LICENSES_ENDPOINT}?featured=true`; - -/** GitHub license endpoint */ -export const GITHUB_LICENSE_ENDPOINT = (license) => `${GITHUB_API_BASE}/licenses/${license}`; - -/** GitHub Accept request header */ -export const GITHUB_ACCEPT_HEADER = "application/vnd.github+json"; diff --git a/src/config/api.ts b/src/config/api.ts new file mode 100644 index 0000000..c0ebc54 --- /dev/null +++ b/src/config/api.ts @@ -0,0 +1,41 @@ +import pkg from "../../package.json" with {type: "json"}; +import * as constants from "./constants.js"; + +/** User-Agent header for Modrinth API requests */ +export const PACKAGE_USER_AGENT: string = `${constants.AUTHOR_USERNAME}/${pkg.name}/${pkg.version}`; + +/** Modrinth API base URL */ +export const MODRINTH_API_BASE: string = "https://api.modrinth.com/v2"; + +/** Modrinth version files endpoint */ +export const MODRINTH_VERSION_FILES_ENDPOINT: string = `${MODRINTH_API_BASE}/version_files`; + +/** Modrinth projects endpoint */ +export const MODRINTH_PROJECTS_ENDPOINT: string = `${MODRINTH_API_BASE}/projects`; + +/** Modrinth users endpoint */ +export const MODRINTH_USERS_ENDPOINT: string = `${MODRINTH_API_BASE}/users`; + +/** Modrinth Minecraft versions endpoint */ +export const MODRINTH_MINECRAFT_VERSIONS_ENDPOINT: string = `${MODRINTH_API_BASE}/tag/game_version`; + +/** Modrinth Modloaders endpoint */ +export const MODRINTH_MODLOADERS_ENDPOINT: string = `${MODRINTH_API_BASE}/tag/loader`; + +/** Batch size for Modrinth API requests */ +export const BATCH_SIZE: number = 100; + +/** GitHub API base URL */ +export const GITHUB_API_BASE: string = "https://api.github.com"; + +/** GitHub licenses endpoint */ +export const GITHUB_LICENSES_ENDPOINT: string = `${GITHUB_API_BASE}/licenses`; + +/** GitHub featured licenses endpoint */ +export const GITHUB_FEATURED_LICENSES_ENDPOINT: string = `${GITHUB_LICENSES_ENDPOINT}?featured=true`; + +/** GitHub license endpoint */ +export const GITHUB_LICENSE_ENDPOINT: (license: string) => string = (license) => `${GITHUB_API_BASE}/licenses/${license}`; + +/** GitHub Accept request header */ +export const GITHUB_ACCEPT_HEADER: string = "application/vnd.github+json"; diff --git a/src/config/constants.js b/src/config/constants.js deleted file mode 100644 index 001e391..0000000 --- a/src/config/constants.js +++ /dev/null @@ -1,41 +0,0 @@ -import pkg from "../../package.json" with {type: "json"}; - -/** Author username */ -export const AUTHOR_USERNAME = "nickesc"; - -/** Lockfile format version -- increment on changes to the format */ -export const LOCKFILE_VERSION = "1.0.1"; - -/** Required fields for the modpack information */ -export const MODPACK_INFO_REQUIRED_FIELDS = [ - "name", // - "version", - "id", - "author", - "modloader", - "targetMinecraftVersion", -]; - -/** Dependency categories, corresponds to folders in Minecraft profile */ -export const DEPENDENCY_CATEGORIES = [ - "mods", // - "resourcepacks", - "shaderpacks", - "datapacks", -]; - -/** Minecraft version types */ -export const MINECRAFT_VERSION_TYPES = [ - "release", // - "alpha", - "beta", - "snapshot", -]; - -const gitignoreMarker = (mode) => `# ${pkg.name}:${mode}`; - -/** Gitignore section start marker */ -export const GITIGNORE_START_MARKER = gitignoreMarker("start"); - -/** Gitignore section end marker */ -export const GITIGNORE_END_MARKER = gitignoreMarker("end"); diff --git a/src/config/constants.ts b/src/config/constants.ts new file mode 100644 index 0000000..d785f19 --- /dev/null +++ b/src/config/constants.ts @@ -0,0 +1,50 @@ +import pkg from "../../package.json" with {type: "json"}; +import type {DependencyCategory, MinecraftVersionType, ModpackInfo} from "../types/index.js"; + +/** Author username */ +export const AUTHOR_USERNAME: string = "nickesc"; + +/** Lockfile format version -- increment on changes to the format */ +export const LOCKFILE_VERSION: string = "1.0.1"; + +/** Required fields for the modpack information */ +export const MODPACK_INFO_REQUIRED_FIELDS: (keyof ModpackInfo)[] = [ + "name", // + "version", + "id", + "author", + "modloader", + "targetMinecraftVersion", +]; + +export const MODPACK_INFO_FIELDS: (keyof ModpackInfo)[] = [ + ...MODPACK_INFO_REQUIRED_FIELDS, // + "projectUrl", + "sourceUrl", + "license", + "targetModloaderVersion", +]; + +/** Dependency categories, corresponds to folders in Minecraft profile */ +export const DEPENDENCY_CATEGORIES: DependencyCategory[] = [ + "mods", // + "resourcepacks", + "shaderpacks", + "datapacks", +]; + +/** Minecraft version types */ +export const MINECRAFT_VERSION_TYPE_ORDER: MinecraftVersionType[] = [ + "release", // + "alpha", + "beta", + "snapshot", +]; + +const gitignoreMarker: (mode: string) => string = (mode) => `# ${pkg.name}:${mode}`; + +/** Gitignore section start marker */ +export const GITIGNORE_START_MARKER: string = gitignoreMarker("start"); + +/** Gitignore section end marker */ +export const GITIGNORE_END_MARKER: string = gitignoreMarker("end"); diff --git a/src/config/defaults.js b/src/config/defaults.ts similarity index 73% rename from src/config/defaults.js rename to src/config/defaults.ts index ea2d1bf..9e030af 100644 --- a/src/config/defaults.js +++ b/src/config/defaults.ts @@ -1,22 +1,24 @@ -export const DEFAULT_MODPACK_VERSION = "1.0.0"; +import type {Choice} from "prompts"; -export const DEFAULT_MODPACK_LICENSE = "mit"; +export const DEFAULT_MODPACK_VERSION: string = "1.0.0"; -export const DEFAULT_PROJECT_URL = (id) => { +export const DEFAULT_MODPACK_LICENSE: string = "mit"; + +export const DEFAULT_PROJECT_URL: (id: string) => string = (id) => { return `https://modrinth.com/modpack/${id}`; }; -export const DEFAULT_SOURCE_URL = (id, author) => { +export const DEFAULT_SOURCE_URL: (id: string, author: string) => string = (id, author) => { return `https://github.com/${author}/${id}`; }; /** All-Rights-Reserved license option */ -export const ALL_RIGHTS_RESERVED_LICENSE = {title: "All-Rights-Reserved", value: "all-rights-reserved"}; +export const ALL_RIGHTS_RESERVED_LICENSE: Choice = {title: "All-Rights-Reserved", value: "all-rights-reserved"}; /** Other option */ -export const OTHER_OPTION = {title: "Other", value: "other"}; +export const OTHER_OPTION: Choice = {title: "Other", value: "other"}; /** Fallback licenses */ -export const FALLBACK_LICENSES = [ +export const FALLBACK_LICENSES: Choice[] = [ {title: "MIT", value: "mit"}, {title: "Apache-2.0", value: "apache-2.0"}, {title: "GPL-3.0", value: "gpl-3.0"}, @@ -24,7 +26,7 @@ export const FALLBACK_LICENSES = [ ]; /** Fallback modloaders */ -export const FALLBACK_MODLOADERS = [ +export const FALLBACK_MODLOADERS: Choice[] = [ {title: "fabric", value: "fabric"}, {title: "forge", value: "forge"}, {title: "neoforge", value: "neoforge"}, @@ -37,7 +39,7 @@ export const FALLBACK_MODLOADERS = [ ]; /** Fallback target Minecraft versions */ -export const FALLBACK_TARGET_MINECRAFT_VERSIONS = [ +export const FALLBACK_TARGET_MINECRAFT_VERSIONS: Choice[] = [ {title: "1.21.x", value: "1.21.x"}, {title: "1.20.x", value: "1.20.x"}, {title: "1.19.x", value: "1.19.x"}, diff --git a/src/config/files.js b/src/config/files.js deleted file mode 100644 index 0913f2b..0000000 --- a/src/config/files.js +++ /dev/null @@ -1,14 +0,0 @@ -/** Machine-readable/lockfile name */ -export const MODPACK_LOCKFILE_NAME = "modpack.lock"; - -/** Human-readable/JSON file name */ -export const MODPACK_JSON_NAME = "modpack.json"; - -/** License file name */ -export const MODPACK_LICENSE_NAME = "LICENSE"; - -/** Gitignore file name */ -export const GITIGNORE_NAME = ".gitignore"; - -/** README.md file name */ -export const README_NAME = "README.md"; diff --git a/src/config/files.ts b/src/config/files.ts new file mode 100644 index 0000000..2bb76e5 --- /dev/null +++ b/src/config/files.ts @@ -0,0 +1,14 @@ +/** Machine-readable/lockfile name */ +export const MODPACK_LOCKFILE_NAME: string = "modpack.lock"; + +/** Human-readable/JSON file name */ +export const MODPACK_JSON_NAME: string = "modpack.json"; + +/** License file name */ +export const MODPACK_LICENSE_NAME: string = "LICENSE"; + +/** Gitignore file name */ +export const GITIGNORE_NAME: string = ".gitignore"; + +/** README.md file name */ +export const README_NAME: string = "README.md"; diff --git a/src/config/index.js b/src/config/index.ts similarity index 100% rename from src/config/index.js rename to src/config/index.ts diff --git a/src/config/options.js b/src/config/options.js deleted file mode 100644 index bdf515b..0000000 --- a/src/config/options.js +++ /dev/null @@ -1,18 +0,0 @@ -import {logm} from "../logger.js"; - -/** Options for slugify */ -export const SLUGIFY_OPTIONS = { - lower: true, - strict: true, - separator: "-", - locale: "en", - trim: true, -}; - -/** Options for prompts */ -export const PROMPTS_OPTIONS = { - onCancel: () => { - logm.warn("Modpack initialization was interrupted"); - process.exit(1); - }, -}; diff --git a/src/config/options.ts b/src/config/options.ts new file mode 100644 index 0000000..337bd28 --- /dev/null +++ b/src/config/options.ts @@ -0,0 +1,23 @@ +import {logm} from "../logger.js"; +import type {Options as slugifyOptions} from "@sindresorhus/slugify"; + +/** Options for slugify */ +export const SLUGIFY_OPTIONS: slugifyOptions = { + lowercase: true, + separator: "-", + locale: "en", + transliterate: true, + preserveTrailingDash: false, + preserveLeadingUnderscore: false, + decamelize: false, +}; + +/** Options for prompts */ +export const PROMPTS_OPTIONS: { + onCancel: () => void; +} = { + onCancel: () => { + logm.warn("Modpack initialization was interrupted"); + process.exit(1); + }, +}; diff --git a/src/config/strings.js b/src/config/strings.ts similarity index 73% rename from src/config/strings.js rename to src/config/strings.ts index 55abbc9..e5cbf11 100644 --- a/src/config/strings.js +++ b/src/config/strings.ts @@ -2,7 +2,24 @@ import * as defaults from "./defaults.js"; import * as files from "./files.js"; import pkg from "../../package.json" with {type: "json"}; -export const infoFields = { +interface InfoField { + prompt: string; + option: string; +} + +export const infoFields: { + name: InfoField; + version: InfoField; + id: InfoField; + description: InfoField; + author: InfoField; + projectUrl: InfoField; + sourceUrl: InfoField; + license: InfoField; + modloader: InfoField; + targetModloaderVersion: InfoField; + targetMinecraftVersion: InfoField; +} = { name: { prompt: "modpack name", option: "Modpack name; defaults to the directory name", @@ -49,7 +66,11 @@ export const infoFields = { }, }; -export const fileFields = { +export const fileFields: { + addLicense: InfoField; + addGitignore: InfoField; + addReadme: InfoField; +} = { addLicense: { prompt: `Add the ${files.MODPACK_LICENSE_NAME} file`, option: `Add the ${files.MODPACK_LICENSE_NAME} file to the modpack`, @@ -64,7 +85,13 @@ export const fileFields = { }, }; -export const headings = { +export const headings: { + options: string; + generation: string; + logging: string; + packInfo: string; + information: string; +} = { options: "Options:", generation: "GENERATION", logging: "LOGGING", @@ -72,11 +99,11 @@ export const headings = { information: "INFORMATION", }; -export const dryRunText = (filename, location) => { +export const dryRunText: (filename: string, location: string) => string = (filename, location) => { return `[DRY RUN] Would write ${filename} to: ${location}`; }; /** All-Rights-Reserved license text */ -export const ARR_LICENSE_TEXT = "Copyright (c) [year] [fullname]\n" + "\n" + "All rights reserved.\n"; +export const ARR_LICENSE_TEXT: string = "Copyright (c) [year] [fullname]\n" + "\n" + "All rights reserved.\n"; export {pkg}; diff --git a/src/config/types.js b/src/config/types.js deleted file mode 100644 index 082bd60..0000000 --- a/src/config/types.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @typedef {Object} ModpackInfo - * Contains information about the modpack that is not dependent on the lockfile. - * @property {string} name - The name of the modpack (Required) - * @property {string} version - The version of the modpack (Required) - * @property {string} description - The description of the modpack - * @property {string} id - The slug/ID of the modpack (Required) - * @property {string} author - The author of the modpack (Required) - * @property {string} projectUrl - The project URL of the modpack - * @property {string} sourceUrl - The source code URL of the modpack - * @property {string} license - The license of the modpack - * @property {string} modloader - The modloader of the modpack (Required) - * @property {string} targetModloaderVersion - The target modloader version of the modpack - * @property {string} targetMinecraftVersion - The target Minecraft version of the modpack (Required) - */ - -/** - * @typedef {Object} Lockfile - * Contains information about the modpack dependencies and their versions. - * @property {string} version - The version of the modpack - * @property {string} generated - The date and time the lockfile was generated - * @property {number} total - The total number of files in the modpack - * @property {Object} counts - The counts object - * @property {number} counts.mods - The mods count - * @property {number} counts.resourcepacks - The resourcepacks count - * @property {number} counts.datapacks - The datapacks count - * @property {number} counts.shaderpacks - The shaderpacks count - * @property {Object} dependencies - The dependencies object - * @property {Array} dependencies.mods - The mods object - * @property {Array} dependencies.resourcepacks - The resourcepacks object - * @property {Array} dependencies.datapacks - The datapacks object - * @property {Array} dependencies.shaderpacks - The shaderpacks object - */ - -/** - * @typedef {Object} Options - * Contains options for the generation of the modpack files. - * @property {boolean} dryRun - Whether to dry run the generation - * @property {boolean} quiet - Whether to quiet the console output - * @property {boolean} silent - Whether to silent the console output - * @property {boolean} gitignore - Whether to generate a .gitignore file - * @property {boolean} readme - Whether to generate README.md files - * @property {boolean} licenseFile - Whether to generate a license file - */ - -/** - * @typedef {Object} InitOptions - * Contains options for the initialization of the modpack files. - * @property {string} folder - The folder to generate the modpack files in - * @property {boolean} noninteractive - Whether to run the interactive mode - * @property {boolean} addLicense - Whether to add the license file to the modpack - * @property {boolean} addGitignore - Whether to generate .gitignore rules - * @property {boolean} addReadme - Whether to generate README.md files - * @property {string} name - The name of the modpack - * @property {string} version - The version of the modpack - * @property {string} id - The slug/ID of the modpack - * @property {string} description - The description of the modpack - * @property {string} author - The author of the modpack - * @property {string} projectUrl - The modpack's project URL - * @property {string} sourceUrl - The modpack's source code URL - * @property {string} license - The modpack's license - * @property {string} modloader - The modpack's modloader - * @property {string} targetModloaderVersion - The target modloader version - * @property {string} targetMinecraftVersion - The target Minecraft version - * @property {boolean} _init - Internal boolean added to indicate options come from the `init` command. - */ - -/** - * @typedef {Object} RunOptions - * Contains options for the running scripts defined in modpack.json. - * @property {string} folder - The folder to look for modpack.json in - * @property {boolean} debug - Whether to print debug information about the executed script - * @property {boolean} _run - Internal boolean added to indicate options come from the `run` command. - */ - -export {}; diff --git a/src/directory_scanning.js b/src/directory_scanning.ts similarity index 61% rename from src/directory_scanning.js rename to src/directory_scanning.ts index a7656c1..dd6e35a 100644 --- a/src/directory_scanning.js +++ b/src/directory_scanning.ts @@ -3,19 +3,17 @@ import crypto from "crypto"; import path from "path"; import * as config from "./config/index.js"; import {logm} from "./logger.js"; - -/** - * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo - * @typedef {import('./config/types.js').Lockfile} Lockfile - */ +import type {Lockfile, Jsonfile} from "./types/index.js"; +import type {ContentDirectory} from "./types/index.js"; +import type {ContentFile} from "./types/index.js"; /** * Get the directories to scan for modpack files - * @param {string} directoryPath - The path to the directory to scan - * @returns {Array} The directories to scan + * @param directoryPath - The path to the directory to scan + * @returns The directories to scan */ -export function getScanDirectories(directoryPath) { - const scanDirectories = []; +export function getScanDirectories(directoryPath: string): ContentDirectory[] { + const scanDirectories: ContentDirectory[] = []; for (const category of config.DEPENDENCY_CATEGORIES) { scanDirectories.push({name: category, path: path.join(directoryPath, category)}); } @@ -25,7 +23,7 @@ export function getScanDirectories(directoryPath) { /** * Calculate SHA1 hash of a file */ -async function calculateSHA1(filePath) { +async function calculateSHA1(filePath: string): Promise { const fileBuffer = await fs.readFile(filePath); return crypto.createHash("sha1").update(fileBuffer).digest("hex"); } @@ -33,8 +31,8 @@ async function calculateSHA1(filePath) { /** * Find all files in a directory */ -async function findFiles(dirPath) { - const files = []; +async function findFiles(dirPath: string): Promise { + const files: string[] = []; try { const entries = await fs.readdir(dirPath, {withFileTypes: true}); @@ -45,8 +43,8 @@ async function findFiles(dirPath) { files.push(fullPath); } } - } catch (error) { - if (error.code !== "ENOENT") { + } catch (error: any) { + if (error?.code !== "ENOENT") { logm.warn(`Could not read directory ${dirPath}: ${error.message}`); } } @@ -58,9 +56,9 @@ async function findFiles(dirPath) { /** * Scan a directory and return file info with hashes */ -export async function scanDirectory(dirInfo, workspaceRoot) { +export async function scanDirectory(dirInfo: ContentDirectory, workspaceRoot: string): Promise { const files = await findFiles(dirInfo.path); - const fileEntries = []; + const fileEntries: ContentFile[] = []; for (const filePath of files) { try { @@ -73,8 +71,8 @@ export async function scanDirectory(dirInfo, workspaceRoot) { hash: hash, category: dirInfo.name, }); - } catch (error) { - logm.warn(`Could not hash file ${filePath}: ${error.message}`); + } catch (error: any) { + logm.error(`Could not hash file ${filePath}: ${error.message}`); } } @@ -84,14 +82,14 @@ export async function scanDirectory(dirInfo, workspaceRoot) { /** * Scan for existing JSON file and return the JSON object if it exists */ -async function getJsonFile(directoryPath, filename) { +async function getJsonFile(directoryPath: string, filename: string): Promise { const jsonPath = path.join(directoryPath, filename); // try to read the file try { const fileContent = await fs.readFile(jsonPath, "utf-8"); return JSON.parse(fileContent); - } catch (error) { - if (error.code !== "ENOENT") { + } catch (error: any) { + if (error?.code !== "ENOENT") { throw new Error(`Error: Could not read file ${jsonPath}: ${error.message}`, {cause: error}); } else { return null; @@ -101,18 +99,18 @@ async function getJsonFile(directoryPath, filename) { /** * Get the modpack info from the JSON file if it exists - * @param {string} directoryPath - The path to the directory to scan - * @returns {Promise} The modpack info JSON object if the file exists, otherwise null + * @param directoryPath - The path to the directory to scan + * @returns The Jsonfile object if the file exists, otherwise null */ -export async function getModpackInfo(directoryPath) { +export async function getModpackInfo(directoryPath: string): Promise { return getJsonFile(directoryPath, config.MODPACK_JSON_NAME); } /** * Get the lockfile file if it exists - * @param {string} directoryPath - The path to the directory to scan - * @returns {Lockfile|null} The JSON object if the file exists, otherwise null + * @param directoryPath - The path to the directory to scan + * @returns The Lockfile object if the file exists, otherwise null */ -export async function getLockfile(directoryPath) { +export async function getLockfile(directoryPath: string): Promise { return getJsonFile(directoryPath, config.MODPACK_LOCKFILE_NAME); } diff --git a/src/generate_gitignore.js b/src/generate_gitignore.ts similarity index 67% rename from src/generate_gitignore.js rename to src/generate_gitignore.ts index 870218f..f85341a 100644 --- a/src/generate_gitignore.js +++ b/src/generate_gitignore.ts @@ -2,24 +2,23 @@ import fs from "fs/promises"; import path from "path"; import * as config from "./config/index.js"; import {logm} from "./logger.js"; - -/** - * @typedef {import('./config/types.js').Options} Options - * @typedef {import('./config/types.js').InitOptions} InitOptions - * @typedef {import('./config/types.js').Lockfile} Lockfile - */ +import type {Lockfile, Options, InitOptions} from "./types/index.js"; /** * Generate .gitignore rules for files not hosted on Modrinth and write them to .gitignore file - * @param {Lockfile} lockfile - The lockfile object - * @param {string} workingDir - The working directory - * @param {Options | InitOptions} options - The options object + * @param lockfile - The lockfile object + * @param workingDir - The working directory + * @param options - The options object */ -export async function generateGitignoreRules(lockfile, workingDir, options = {}) { +export async function generateGitignoreRules( + lockfile: Lockfile, + workingDir: string, + options: Options | InitOptions = {}, +): Promise { logm.quietFromOptions(options); - const rules = []; - const exceptions = []; + const rules: string[] = []; + const exceptions: string[] = []; // Base ignore patterns for each category for (const category of config.DEPENDENCY_CATEGORIES) { @@ -42,37 +41,37 @@ export async function generateGitignoreRules(lockfile, workingDir, options = {}) rules.push(...exceptions); } - const rulesContent = rules.join("\n"); - const gitignorePath = path.join(workingDir, config.GITIGNORE_NAME); + const rulesContent: string = rules.join("\n"); + const gitignorePath: string = path.join(workingDir, config.GITIGNORE_NAME); // Read existing .gitignore file if it exists - let existingContent = ""; + let existingContent: string = ""; try { existingContent = await fs.readFile(gitignorePath, "utf-8"); - } catch (error) { + } catch (error: any) { // File doesn't exist, that's okay - we'll create it - if (error.code !== "ENOENT") { + if (error?.code !== "ENOENT") { logm.warn(`Could not read ${config.GITIGNORE_NAME} file: ${error.message}`); return; } } // Find markers in existing content - const startMarkerIndex = existingContent.indexOf(config.GITIGNORE_START_MARKER); - const endMarkerIndex = existingContent.indexOf(config.GITIGNORE_END_MARKER); + const startMarkerIndex: number = existingContent.indexOf(config.GITIGNORE_START_MARKER); + const endMarkerIndex: number = existingContent.indexOf(config.GITIGNORE_END_MARKER); - let newContent; + let newContent: string; if (startMarkerIndex !== -1 && endMarkerIndex !== -1 && endMarkerIndex > startMarkerIndex) { // Both markers exist, replace content between them - const beforeSection = existingContent.substring(0, startMarkerIndex); - const afterSection = existingContent.substring(endMarkerIndex + config.GITIGNORE_END_MARKER.length); + const beforeSection: string = existingContent.substring(0, startMarkerIndex); + const afterSection: string = existingContent.substring(endMarkerIndex + config.GITIGNORE_END_MARKER.length); // Remove trailing newlines from before section and leading newlines from after section - const beforeTrimmed = beforeSection.replace(/\n+$/, ""); - const afterTrimmed = afterSection.replace(/^\n+/, ""); + const beforeTrimmed: string = beforeSection.replace(/\n+$/, ""); + const afterTrimmed: string = afterSection.replace(/^\n+/, ""); - const parts = [beforeTrimmed]; + const parts: string[] = [beforeTrimmed]; if (beforeTrimmed) parts.push(""); // Add separator if there's content before parts.push(config.GITIGNORE_START_MARKER, rulesContent, config.GITIGNORE_END_MARKER); if (afterTrimmed) { @@ -83,7 +82,7 @@ export async function generateGitignoreRules(lockfile, workingDir, options = {}) newContent = parts.join("\n"); } else if (startMarkerIndex !== -1 || endMarkerIndex !== -1) { // Only one marker exists, append to end - const trimmed = existingContent.replace(/\n+$/, ""); + const trimmed: string = existingContent.replace(/\n+$/, ""); newContent = [trimmed, "", config.GITIGNORE_START_MARKER, rulesContent, config.GITIGNORE_END_MARKER].join("\n"); } else { // No markers exist, append to end @@ -92,7 +91,7 @@ export async function generateGitignoreRules(lockfile, workingDir, options = {}) newContent = [config.GITIGNORE_START_MARKER, rulesContent, config.GITIGNORE_END_MARKER].join("\n"); } else { // File has content, append with newline - const trimmed = existingContent.replace(/\n+$/, ""); + const trimmed: string = existingContent.replace(/\n+$/, ""); newContent = [trimmed, "", config.GITIGNORE_START_MARKER, rulesContent, config.GITIGNORE_END_MARKER].join( "\n", ); @@ -106,7 +105,7 @@ export async function generateGitignoreRules(lockfile, workingDir, options = {}) try { await fs.writeFile(gitignorePath, newContent, "utf-8"); logm.generated(config.GITIGNORE_NAME, gitignorePath); - } catch (error) { + } catch (error: any) { logm.warn(`Could not write ${config.GITIGNORE_NAME} file: ${error.message}`); } } diff --git a/src/generate_json.js b/src/generate_json.js deleted file mode 100644 index bd38ad4..0000000 --- a/src/generate_json.js +++ /dev/null @@ -1,106 +0,0 @@ -import fs from "fs/promises"; -import path from "path"; -import {getProjects} from "./modrinth_interactions.js"; -import * as config from "./config/index.js"; -import {logm} from "./logger.js"; - -/** - * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo - * @typedef {import('./config/types.js').Options} Options - * @typedef {import('./config/types.js').InitOptions} InitOptions - * @typedef {import('./config/types.js').Lockfile} Lockfile - */ - -/** - * Create a JSON object from the modpack information and dependencies - */ -function createModpackJson(modpackInfo, dependencies) { - return { - ...modpackInfo, - dependencies: dependencies, - }; -} - -/** - * Write modpack.json to disk - */ -async function writeJson(jsonObject, outputPath) { - const content = JSON.stringify(jsonObject, null, 2); - await fs.writeFile(path.join(outputPath, config.MODPACK_JSON_NAME), content, "utf-8"); - logm.generated(config.MODPACK_JSON_NAME, path.join(outputPath, config.MODPACK_JSON_NAME)); -} - -/** - * Generate a modpack.json file - * @param {ModpackInfo} modpackInfo - The modpack information - * @param {Lockfile} lockfile - The lockfile - * @param {string} workingDir - The path to write the JSON object to - * @param {Options | InitOptions} options - The options object - * @returns {Promise} The JSON file's object - */ -export default async function generateJson(modpackInfo, lockfile, workingDir, options = {}) { - logm.quietFromOptions(options); - - // Validate modpack info - for (const field of config.MODPACK_INFO_REQUIRED_FIELDS) { - if (!modpackInfo[field]) { - throw new Error(`Modpack info is missing required field: ${field}`); - } - } - - const projectIds = {}; - const packDependencies = {}; - for (const category of config.DEPENDENCY_CATEGORIES) { - projectIds[category] = new Set(); - packDependencies[category] = []; - } - - // Collect project IDs from lockfile - if (lockfile) - if (lockfile.dependencies) { - for (const [category, entries] of Object.entries(lockfile.dependencies)) { - for (const entry of entries) { - if (entry.version && entry.version.project_id) { - projectIds[category].add(entry.version.project_id); - } else { - packDependencies[category].push(entry.path); - } - } - } - const allProjectIds = new Set(); - for (const category of config.DEPENDENCY_CATEGORIES) { - for (const projectId of projectIds[category]) { - allProjectIds.add(projectId); - } - } - - // Fetch projects from Modrinth - const projects = await getProjects(Array.from(allProjectIds)); - const projectsMap = {}; - for (const project of projects) { - projectsMap[project.id] = project.slug; - } - - // Add projects to dependencies by category - for (const category of config.DEPENDENCY_CATEGORIES) { - for (const projectId of projectIds[category]) { - const projectSlug = projectsMap[projectId]; - if (projectSlug) { - packDependencies[category].push(projectSlug); - } - } - } - } - - // Create modpack JSON object - const jsonObject = createModpackJson(modpackInfo, packDependencies); - - // Write modpack JSON object to disk - if (options.dryRun) { - logm.debug(config.dryRunText(config.MODPACK_JSON_NAME, path.join(workingDir, config.MODPACK_JSON_NAME))); - } else { - await writeJson(jsonObject, workingDir); - } - - return jsonObject; -} diff --git a/src/generate_json.ts b/src/generate_json.ts new file mode 100644 index 0000000..f0436f3 --- /dev/null +++ b/src/generate_json.ts @@ -0,0 +1,122 @@ +import fs from "fs/promises"; +import path from "path"; +import {getProjects} from "./modrinth_interactions.js"; +import * as config from "./config/index.js"; +import {logm} from "./logger.js"; +import type {Lockfile, Jsonfile, Options, InitOptions, DependencyCategory} from "./types/index.js"; + +/** + * Create a JSON object from the modpack information and dependencies + */ +function createModpackJson(modpackInfo: Jsonfile, dependencies: Record): Jsonfile { + return { + ...modpackInfo, + dependencies: dependencies, + }; +} + +/** + * Write modpack.json to disk + */ +async function writeJson(jsonObject: Jsonfile, outputPath: string): Promise { + const content: string = JSON.stringify(jsonObject, null, 2); + await fs.writeFile(path.join(outputPath, config.MODPACK_JSON_NAME), content, "utf-8"); + logm.generated(config.MODPACK_JSON_NAME, path.join(outputPath, config.MODPACK_JSON_NAME)); +} + +/** + * Generate a modpack.json file + * @param modpackInfo - The modpack information + * @param lockfile - The lockfile + * @param workingDir - The path to write the JSON object to + * @param options - The options object + * @returns The JSON file's object + */ +export default async function generateJson( + modpackInfo: Jsonfile, + lockfile: Lockfile, + workingDir: string, + options: Options | InitOptions = {}, +): Promise { + logm.quietFromOptions(options); + + // Validate modpack info + const missingFields: string[] = []; + for (const field of config.MODPACK_INFO_REQUIRED_FIELDS) { + if (!modpackInfo[field]) { + missingFields.push(field); + } + } + + if (missingFields.length > 0) { + throw new Error(`Modpack info is missing required fields: ${missingFields.join(", ")}`); + } + + //TODO: consider changing these to partial records and only initializing the categories that are present in the lockfile + const projectIds: Record> = { + mods: new Set(), + resourcepacks: new Set(), + datapacks: new Set(), + shaderpacks: new Set(), + }; + const packDependencies: Record = { + mods: [], + resourcepacks: [], + datapacks: [], + shaderpacks: [], + }; + + // Collect project IDs from lockfile + if (lockfile) + if (lockfile.dependencies) { + for (const category of config.DEPENDENCY_CATEGORIES) { + if (lockfile.dependencies[category]) { + // TODO: consider initializing the categories with an empty array/set here + for (const entry of lockfile.dependencies[category]) { + if (entry.version && entry.version.project_id) { + projectIds[category].add(entry.version.project_id); + } else { + packDependencies[category].push(entry.path); + } + } + } + } + const allProjectIds: Set = new Set(); + for (const category of config.DEPENDENCY_CATEGORIES) { + for (const projectId of projectIds[category]) { + allProjectIds.add(projectId); + } + } + + // Fetch projects from Modrinth + const projects = await getProjects(Array.from(allProjectIds)); + const projectsMap: Record = {}; + for (const project of projects) { + if (project.id || project.slug) { + projectsMap[project.id] = project.slug || project.id; + } + } + + // Add projects to dependencies by category + for (const category of config.DEPENDENCY_CATEGORIES) { + for (const projectId of projectIds[category]) { + const projectSlug: string | undefined = projectsMap[projectId]; + if (projectSlug) { + packDependencies[category].push(projectSlug); + } + } + } + } + + // Create modpack JSON object + const jsonObject: Jsonfile = createModpackJson(modpackInfo, packDependencies); + + // Write modpack JSON object to disk + if (options.dryRun) { + logm.debug(config.dryRunText(config.MODPACK_JSON_NAME, path.join(workingDir, config.MODPACK_JSON_NAME))); + } else { + await writeJson(jsonObject, workingDir); + } + + return jsonObject; +} diff --git a/src/generate_license.js b/src/generate_license.js deleted file mode 100644 index 96ce9b6..0000000 --- a/src/generate_license.js +++ /dev/null @@ -1,54 +0,0 @@ -import fs from "fs/promises"; -import path from "path"; -import {getLicenseText} from "./github_interactions.js"; -import * as config from "./config/index.js"; -import {logm} from "./logger.js"; - -/** - * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo - * @typedef {import('./config/types.js').Options} Options - * @typedef {import('./config/types.js').InitOptions} InitOptions - */ - -async function writeLicense(licenseText, outputPath) { - await fs.writeFile(path.join(outputPath, config.MODPACK_LICENSE_NAME), licenseText, "utf-8"); - logm.generated(config.MODPACK_LICENSE_NAME, path.join(outputPath, config.MODPACK_LICENSE_NAME)); -} - -/** - * Write a license to a file - * @param {ModpackInfo} modpackInfo - The modpack information - * @param {string} workingDir - The path to write the license to - * @param {InitOptions} options - The initialization options object - * @param {string} licenseTextOverride - The license text to override the default license text with - * @returns {Promise | null} The license text or null if the license text could not be generated - */ -export default async function generateLicense(modpackInfo, workingDir, options = {}, licenseTextOverride = null) { - logm.quietFromOptions(options); - - try { - const spdxId = modpackInfo.license; - - let licenseText = licenseTextOverride || (await getLicenseText(spdxId)); - licenseText = licenseText.replace("[year]", new Date().getFullYear()); - licenseText = licenseText.replace("{{year}}", new Date().getFullYear()); - licenseText = licenseText.replace("[fullname]", modpackInfo.author); - licenseText = licenseText.replace("{{fullname}}", modpackInfo.author); - licenseText = licenseText.replace("[organization]", modpackInfo.author); - licenseText = licenseText.replace("{{organization}}", modpackInfo.author); - licenseText = licenseText.replace("[project]", modpackInfo.name); - licenseText = licenseText.replace("{{project}}", modpackInfo.name); - - if (options.dryRun) { - logm.debug( - config.dryRunText(config.MODPACK_LICENSE_NAME, path.join(workingDir, config.MODPACK_LICENSE_NAME)), - ); - } else { - await writeLicense(licenseText, workingDir); - } - return licenseText; - } catch { - logm.warn(`Unable to generate license for: ${modpackInfo.license}`); - return null; - } -} diff --git a/src/generate_license.ts b/src/generate_license.ts new file mode 100644 index 0000000..d4764ab --- /dev/null +++ b/src/generate_license.ts @@ -0,0 +1,65 @@ +import fs from "fs/promises"; +import path from "path"; +import {getLicenseText} from "./github_interactions.js"; +import * as config from "./config/index.js"; +import {logm} from "./logger.js"; +import type {Jsonfile, Options, InitOptions} from "./types/index.js"; + +async function writeLicense(licenseText: string, outputPath: string) { + await fs.writeFile(path.join(outputPath, config.MODPACK_LICENSE_NAME), licenseText, "utf-8"); + logm.generated(config.MODPACK_LICENSE_NAME, path.join(outputPath, config.MODPACK_LICENSE_NAME)); +} + +/** + * Generate a license file + * @param modpackInfo - The modpack information + * @param workingDir - The path to write the license to + * @param options - The options object + * @param licenseTextOverride - Text to override the license's default content + * @returns The license text or null if the license text could not be generated + */ +export default async function generateLicense( + modpackInfo: Jsonfile, + workingDir: string, + options: Options | InitOptions = {}, + licenseTextOverride?: string, +): Promise { + logm.quietFromOptions(options); + + try { + const spdxId = modpackInfo.license; + if (!spdxId) { + throw new Error("License ID is required to generate a license file"); + } + + let licenseText = licenseTextOverride || (await getLicenseText(spdxId)); + if (!licenseText) { + throw new Error(`License text could not be generated for: ${spdxId}`); + } + + licenseText = licenseText.replace("[year]", String(new Date().getFullYear())); + licenseText = licenseText.replace("{{year}}", String(new Date().getFullYear())); + if (modpackInfo.author) { + licenseText = licenseText.replace("[fullname]", modpackInfo.author); + licenseText = licenseText.replace("{{fullname}}", modpackInfo.author); + licenseText = licenseText.replace("[organization]", modpackInfo.author); + licenseText = licenseText.replace("{{organization}}", modpackInfo.author); + } + if (modpackInfo.name) { + licenseText = licenseText.replace("[project]", modpackInfo.name); + licenseText = licenseText.replace("{{project}}", modpackInfo.name); + } + + if (options.dryRun) { + logm.debug( + config.dryRunText(config.MODPACK_LICENSE_NAME, path.join(workingDir, config.MODPACK_LICENSE_NAME)), + ); + } else { + await writeLicense(licenseText, workingDir); + } + return licenseText; + } catch { + logm.warn(`Unable to generate license for: ${modpackInfo.license}`); + return null; + } +} diff --git a/src/generate_lockfile.js b/src/generate_lockfile.ts similarity index 67% rename from src/generate_lockfile.js rename to src/generate_lockfile.ts index 1120bbc..edc53ee 100644 --- a/src/generate_lockfile.js +++ b/src/generate_lockfile.ts @@ -4,17 +4,20 @@ import {getVersionsFromHashes} from "./modrinth_interactions.js"; import {getScanDirectories, scanDirectory} from "./directory_scanning.js"; import * as config from "./config/index.js"; import {logm, styleText} from "./logger.js"; - -/** - * @typedef {import('./config/types.js').Options} Options - * @typedef {import('./config/types.js').InitOptions} InitOptions - * @typedef {import('./config/types.js').Lockfile} Lockfile - */ +import type { + ContentFile, + Lockfile, + Options, + InitOptions, + ContentVersion, + DependencyCategory, + LockfileDependency, +} from "./types/index.js"; /** * Create empty lockfile structure */ -function createEmptyLockfile() { +function createEmptyLockfile(): Lockfile { return { version: config.LOCKFILE_VERSION, generated: new Date().toISOString(), @@ -27,13 +30,13 @@ function createEmptyLockfile() { /** * Create lockfile structure from file info and version data */ -function createLockfile(fileEntries, versionData) { - const lockfile = createEmptyLockfile(); +function createLockfile(fileEntries: ContentFile[], versionData: Record): Lockfile { + const lockfile: Lockfile = createEmptyLockfile(); logm.newline(); // Organize by category for (const fileInfo of fileEntries) { - const version = versionData[fileInfo.hash]; + const version: ContentVersion | undefined = versionData[fileInfo.hash]; lockfile.dependencies[fileInfo.category] ||= []; @@ -46,14 +49,14 @@ function createLockfile(fileEntries, versionData) { logm.warn(`File ${fileInfo.path} not found on Modrinth`); } - lockfile.dependencies[fileInfo.category].push(entry); + lockfile.dependencies[fileInfo.category]?.push(entry); } logm.header("Generating Lockfile"); // Calculate counts for each category for (const [category, entries] of Object.entries(lockfile.dependencies)) { - lockfile.counts[category] = entries.length; + lockfile.counts[category as DependencyCategory] = entries.length; } lockfile.total = fileEntries.length; @@ -64,28 +67,28 @@ function createLockfile(fileEntries, versionData) { /** * Write lockfile to disk */ -async function writeLockfile(lockfile, outputPath) { - const content = JSON.stringify(lockfile, null, 2); +async function writeLockfile(lockfile: Lockfile, outputPath: string): Promise { + const content: string = JSON.stringify(lockfile, null, 2); await fs.writeFile(outputPath, content, "utf-8"); logm.generated(config.MODPACK_LOCKFILE_NAME, outputPath); } /** * Generate the lockfile - * @param {string} workingDir - The working directory - * @param {Options} options - The options object - * @returns {Lockfile} The lockfile object + * @param workingDir - The working directory + * @param options - The options object + * @returns The lockfile object */ -export async function generateLockfile(workingDir, options = {}) { +export async function generateLockfile(workingDir: string, options: Options | InitOptions = {}): Promise { logm.quietFromOptions(options); logm.header("Scanning Directories"); // Scan all directories - const allFileEntries = []; + const allFileEntries: ContentFile[] = []; for (const dirInfo of getScanDirectories(workingDir)) { logm.info(styleText(["cyan"], `${dirInfo.name}/`)); - const fileEntries = await scanDirectory(dirInfo, workingDir); + const fileEntries: ContentFile[] = await scanDirectory(dirInfo, workingDir); logm.info( styleText(["dim"], ` └─ Found`), styleText(["yellow"], `${fileEntries.length}`), @@ -105,8 +108,8 @@ export async function generateLockfile(workingDir, options = {}) { if (allFileEntries.length === 0) { logm.header("GENERATING LOCKFILE"); logm.warn("No files found. Creating empty lockfile."); - const emptyLockfile = createEmptyLockfile(); - const outputPath = path.join(workingDir, config.MODPACK_LOCKFILE_NAME); + const emptyLockfile: Lockfile = createEmptyLockfile(); + const outputPath: string = path.join(workingDir, config.MODPACK_LOCKFILE_NAME); if (options.dryRun) { logm.debug(config.dryRunText(config.MODPACK_LOCKFILE_NAME, outputPath)); } else { @@ -119,10 +122,10 @@ export async function generateLockfile(workingDir, options = {}) { logm.header("Querying Modrinth API"); // Extract all hashes - const hashes = allFileEntries.map((info) => info.hash); + const hashes: string[] = allFileEntries.map((info) => info.hash); // Query Modrinth API - const versionData = await getVersionsFromHashes(hashes); + const versionData: Record = await getVersionsFromHashes(hashes); logm.info(styleText(["dim"], "Found version information for:")); logm.info( @@ -134,10 +137,10 @@ export async function generateLockfile(workingDir, options = {}) { ); // Create lockfile - const lockfile = createLockfile(allFileEntries, versionData); + const lockfile: Lockfile = createLockfile(allFileEntries, versionData); // Write lockfile - const outputPath = path.join(workingDir, config.MODPACK_LOCKFILE_NAME); + const outputPath: string = path.join(workingDir, config.MODPACK_LOCKFILE_NAME); if (options.dryRun) { logm.debug(config.dryRunText(config.MODPACK_LOCKFILE_NAME, outputPath)); } else { @@ -149,9 +152,9 @@ export async function generateLockfile(workingDir, options = {}) { /** * Print a summary of the lockfile contents - * @param {Lockfile} lockfile - The lockfile object + * @param lockfile - The lockfile object */ -export function printLockfileSummary(lockfile) { +export function printLockfileSummary(lockfile: Lockfile): void { logm.header("Lockfile Summary"); if (lockfile.total === 0) { @@ -159,9 +162,12 @@ export function printLockfileSummary(lockfile) { return; } - for (const [category, entries] of Object.entries(lockfile.dependencies)) { - const withVersion = entries.filter((e) => e.version !== null).length; - const withoutVersion = entries.length - withVersion; + for (const [category, entries] of Object.entries(lockfile.dependencies) as [ + DependencyCategory, + LockfileDependency[], + ][]) { + const withVersion: number = entries.filter((e) => e.version !== null).length; + const withoutVersion: number = entries.length - withVersion; logm.info( styleText(["bold"], `${category}:`), entries.length, diff --git a/src/generate_readme.js b/src/generate_readme.ts similarity index 68% rename from src/generate_readme.js rename to src/generate_readme.ts index 57ab603..1d91780 100644 --- a/src/generate_readme.js +++ b/src/generate_readme.ts @@ -4,29 +4,44 @@ import {getProjects, getUsers} from "./modrinth_interactions.js"; import {getScanDirectories} from "./directory_scanning.js"; import * as config from "./config/index.js"; import {logm, styleText} from "./logger.js"; - -/** - * @typedef {import('./config/types.js').Options} Options - * @typedef {import('./config/types.js').InitOptions} InitOptions - * @typedef {import('./config/types.js').Lockfile} Lockfile - */ +import type { + Options, + InitOptions, + Lockfile, + ProjectResponseItem, + UserResponseItem, + LockfileDependency, + ContentDirectory, + DependencyCategory, +} from "./types/index.js"; /** * Generate README.md content for a category */ -function generateCategoryReadme(category, entries, projectsMap, usersMap) { - const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1); - const lines = [`# ${categoryTitle}`, "", "| Name | Author | Version | Dependencies | Dependants |", "|-|-|-|-|-|"]; +function generateCategoryReadme( + category: DependencyCategory, + entries: LockfileDependency[], + projectsMap: Record, + usersMap: Record, +): string { + const categoryTitle: string = category.charAt(0).toUpperCase() + category.slice(1); + const lines: string[] = [ + `# ${categoryTitle}`, + "", + "| Name | Author | Version | Dependencies | Dependents |", + "|-|-|-|-|-|", + ]; // Map category to Modrinth URL path segment - const categoryPathMap = {}; + const categoryPathMap: Record = {}; for (const cat of config.DEPENDENCY_CATEGORIES) { categoryPathMap[cat] = cat === "shaderpacks" ? "shader" : cat.toLowerCase().slice(0, -1); } - const categoryPath = categoryPathMap[category] || "project"; + const categoryPath: string = categoryPathMap[category] || "project"; // Build a set of project_ids present in this category for filtering dependencies - const categoryProjectIds = new Set(); + const categoryProjectIds: Set = new Set(); + for (const entry of entries) { if (entry.version && entry.version.project_id) { categoryProjectIds.add(entry.version.project_id); @@ -35,11 +50,11 @@ function generateCategoryReadme(category, entries, projectsMap, usersMap) { for (const entry of entries) { const version = entry.version; - let nameCell; - let authorCell; - let versionCell; - let dependenciesCell; - let dependantsCell; + let nameCell: string; + let authorCell: string; + let versionCell: string; + let dependenciesCell: string; + let dependentsCell: string; if (version && version.project_id) { const project = projectsMap[version.project_id]; @@ -47,8 +62,8 @@ function generateCategoryReadme(category, entries, projectsMap, usersMap) { // Name column with icon and link if (project) { - const projectName = project.title || project.slug || "Unknown"; - const projectSlug = project.slug || project.id; + const projectName: string = project.title || project.slug || "Unknown"; + const projectSlug: string = project.slug || project.id; const projectUrl = `https://modrinth.com/${categoryPath}/${projectSlug}`; if (project.icon_url) { @@ -58,13 +73,13 @@ function generateCategoryReadme(category, entries, projectsMap, usersMap) { } } else { // Project not found, use filename - const fileName = path.basename(entry.path); + const fileName: string = path.basename(entry.path); nameCell = fileName; } // Author column with avatar and link if (author) { - const authorName = author.username || "Unknown"; + const authorName: string = author.username || "Unknown"; const authorUrl = `https://modrinth.com/user/${authorName}`; if (author.avatar_url) { @@ -81,13 +96,13 @@ function generateCategoryReadme(category, entries, projectsMap, usersMap) { // Dependencies column - only show dependencies that are present in this category if (version.dependencies && Array.isArray(version.dependencies) && version.dependencies.length > 0) { - const dependencyLinks = []; + const dependencyLinks: string[] = []; for (const dep of version.dependencies) { if (dep.project_id && categoryProjectIds.has(dep.project_id)) { const depProject = projectsMap[dep.project_id]; if (depProject) { - const depProjectName = depProject.title || depProject.slug || "Unknown"; - const depProjectSlug = depProject.slug || depProject.id; + const depProjectName: string = depProject.title || depProject.slug || "Unknown"; + const depProjectSlug: string = depProject.slug || depProject.id; const depUrl = `https://modrinth.com/${categoryPath}/${depProjectSlug}`; if (depProject.icon_url) { dependencyLinks.push( @@ -104,8 +119,8 @@ function generateCategoryReadme(category, entries, projectsMap, usersMap) { dependenciesCell = "-"; } - // Dependants column - find all entries in the same category that depend on this project - const dependants = []; + // Dependents column - find all entries in the same category that depend on this project + const dependents: string[] = []; for (const catEntry of entries) { // Skip if this is the same entry (same project_id) if (catEntry.version && catEntry.version.project_id === version.project_id) { @@ -118,51 +133,55 @@ function generateCategoryReadme(category, entries, projectsMap, usersMap) { if (hasDependency) { const depProject = projectsMap[catEntry.version.project_id]; if (depProject) { - const depProjectName = depProject.title || depProject.slug || "Unknown"; - const depProjectSlug = depProject.slug || depProject.id; + const depProjectName: string = depProject.title || depProject.slug || "Unknown"; + const depProjectSlug: string = depProject.slug || depProject.id; const depUrl = `https://modrinth.com/${categoryPath}/${depProjectSlug}`; if (depProject.icon_url) { - dependants.push( + dependents.push( `${depProjectName}`, ); } else { - dependants.push(`[${depProjectName}](${depUrl})`); + dependents.push(`[${depProjectName}](${depUrl})`); } } } } } - dependantsCell = dependants.length > 0 ? dependants.join(" ") : "-"; + dependentsCell = dependents.length > 0 ? dependents.join(" ") : "-"; } else { // File not found on Modrinth - const fileName = path.basename(entry.path); + const fileName: string = path.basename(entry.path); nameCell = fileName; authorCell = "Unknown"; versionCell = "-"; dependenciesCell = "-"; - dependantsCell = "-"; + dependentsCell = "-"; } - lines.push(`| ${nameCell} | ${authorCell} | ${versionCell} | ${dependenciesCell} | ${dependantsCell} |`); + lines.push(`| ${nameCell} | ${authorCell} | ${versionCell} | ${dependenciesCell} | ${dependentsCell} |`); } return lines.join("\n") + "\n"; } /** - * Generate the README.md files for each category - * @param {Lockfile} lockfile - The lockfile object - * @param {string} workingDir - The working directory - * @param {Options | InitOptions} options - The options object + * Generate README.md files for each category + * @param lockfile - The lockfile object + * @param workingDir - The working directory + * @param options - The options object */ -export async function generateReadmeFiles(lockfile, workingDir, options = {}) { +export async function generateReadmeFiles( + lockfile: Lockfile, + workingDir: string, + options: Options | InitOptions = {}, +): Promise { logm.quietFromOptions(options); // Collect unique project IDs and author IDs from version data - const projectIds = new Set(); - const authorIds = new Set(); + const projectIds = new Set(); + const authorIds = new Set(); - for (const [, entries] of Object.entries(lockfile.dependencies)) { + for (const entries of Object.values(lockfile.dependencies)) { for (const entry of entries) { if (entry.version && entry.version.project_id) { projectIds.add(entry.version.project_id); @@ -187,24 +206,29 @@ export async function generateReadmeFiles(lockfile, workingDir, options = {}) { const [projects, users] = await Promise.all([getProjects(Array.from(projectIds)), getUsers(Array.from(authorIds))]); // Map projects and users to their IDs - const projectsMap = {}; + const projectsMap: Record = {}; for (const project of projects) { projectsMap[project.id] = project; } - const usersMap = {}; + const usersMap: Record = {}; for (const user of users) { usersMap[user.id] = user; } // Generate README for each category - for (const [category, entries] of Object.entries(lockfile.dependencies)) { + for (const [category, entries] of Object.entries(lockfile.dependencies) as [ + DependencyCategory, + LockfileDependency[], + ][]) { if (entries.length === 0) { continue; } - const readmeContent = generateCategoryReadme(category, entries, projectsMap, usersMap); - const categoryDir = getScanDirectories(workingDir).find((d) => d.name === category); + const readmeContent: string = generateCategoryReadme(category, entries, projectsMap, usersMap); + const categoryDir: ContentDirectory | undefined = getScanDirectories(workingDir).find( + (d) => d.name === category, + ); if (categoryDir) { const readmePath = path.join(categoryDir.path, config.README_NAME); @@ -215,7 +239,7 @@ export async function generateReadmeFiles(lockfile, workingDir, options = {}) { try { await fs.writeFile(readmePath, readmeContent, "utf-8"); logm.generated(config.README_NAME, readmePath); - } catch (error) { + } catch (error: any) { logm.warn(`Could not write ${config.README_NAME} file to ${readmePath}: ${error.message}`); } } diff --git a/src/github_interactions.js b/src/github_interactions.ts similarity index 63% rename from src/github_interactions.js rename to src/github_interactions.ts index 98be871..bb48145 100644 --- a/src/github_interactions.js +++ b/src/github_interactions.ts @@ -1,14 +1,16 @@ import * as config from "./config/index.js"; import {logm} from "./logger.js"; +import type {Choice} from "prompts"; +import type {LicenseResponseItem, LicenseTextResponse} from "./types/index.js"; /** * Fetch a list of the most popular licenses from GitHub - * @param {boolean} featured - If the fetch should be limited to featured licenses - * @returns {Promise>} The list of licenses for use in a prompt + * @param featured - If the fetch should be limited to featured licenses + * @returns The list of licenses for use in a prompt */ -export async function getLicenseList(featured = false) { +export async function getLicenseList(featured: boolean = false): Promise { try { - const url = featured ? config.GITHUB_FEATURED_LICENSES_ENDPOINT : config.GITHUB_LICENSES_ENDPOINT; + const url: string = featured ? config.GITHUB_FEATURED_LICENSES_ENDPOINT : config.GITHUB_LICENSES_ENDPOINT; const response = await fetch(url, { headers: { Accept: config.GITHUB_ACCEPT_HEADER, @@ -19,9 +21,11 @@ export async function getLicenseList(featured = false) { const errorText = await response.text(); throw new Error(`GitHub API error (${response.status}): ${errorText}`); } - let licenseList = await response.json(); + const licenseList: LicenseResponseItem[] = (await response.json()) as LicenseResponseItem[]; - let licenseSpdxIds = licenseList.map((license) => ({title: license.spdx_id, value: license.key})); + let licenseSpdxIds: Choice[] = licenseList.map( + (license: LicenseResponseItem): Choice => ({title: license.spdx_id, value: license.key}), + ); if (!featured) { // get featured licenses and place them at the beginning of the list, removing them from the original list @@ -37,23 +41,22 @@ export async function getLicenseList(featured = false) { return licenseSpdxIds; } catch { logm.warn(`Could not fetch license list. Using fallbacks.`); - const licenses = config.FALLBACK_LICENSES.push(config.ALL_RIGHTS_RESERVED_LICENSE); - licenses.push(config.OTHER_OPTION); + const licenses = [...config.FALLBACK_LICENSES, config.ALL_RIGHTS_RESERVED_LICENSE, config.OTHER_OPTION]; return licenses; } } /** * Fetch specific license information from GitHub - * @param {string} spdxId - The SPDX ID of the license - * @returns {Promise | null} The license text + * @param spdxId - The SPDX ID of the license to fetch + * @returns The license text */ -export async function getLicenseText(spdxId) { +export async function getLicenseText(spdxId: string): Promise { if (spdxId === "all-rights-reserved") { return config.ARR_LICENSE_TEXT; } try { - const url = config.GITHUB_LICENSE_ENDPOINT(spdxId.toLowerCase()); + const url: string = config.GITHUB_LICENSE_ENDPOINT(spdxId.toLowerCase()); const response = await fetch(url, { headers: { Accept: config.GITHUB_ACCEPT_HEADER, @@ -65,7 +68,7 @@ export async function getLicenseText(spdxId) { throw new Error(`GitHub API error (${response.status}): ${errorText}`); } - const json = await response.json(); + const json: LicenseTextResponse = (await response.json()) as LicenseTextResponse; if (json.body) { return json.body; } else { diff --git a/src/logger.js b/src/logger.ts similarity index 56% rename from src/logger.js rename to src/logger.ts index d365497..0fd458c 100644 --- a/src/logger.js +++ b/src/logger.ts @@ -1,13 +1,26 @@ import {styleText} from "node:util"; +import type {InspectColor} from "node:util"; +import type {InitOptions, Options} from "./types/index.js"; class Logger { /** * The styles to apply to the console output. - * @type {Object} */ - styles = { - log: null, - info: null, + styles: { + log: InspectColor[]; + info: InspectColor[]; + debug: InspectColor[]; + warn: InspectColor[]; + error: InspectColor[]; + generated: InspectColor[]; + label: InspectColor[]; + labelDebug: InspectColor[]; + labelWarn: InspectColor[]; + labelError: InspectColor[]; + labelGenerated: InspectColor[]; + } = { + log: [], + info: [], debug: ["magenta"], warn: ["yellow"], error: ["red"], @@ -23,12 +36,20 @@ class Logger { silentConsole = false; lastLogWasNewline = false; - quiet(silent = false) { + /** + * Set the quiet console flag. When set, only errors and warnings are logged to the console. + * @param silent - Whether to also set the silent console flag. When set, no console output is logged. + */ + quiet(silent: boolean = false) { this.quietConsole = true; this.silentConsole = silent; } - quietFromOptions(options) { + /** + * Set the quiet console flag based on the options object. + * @param options - The options object containing the quiet and silent flags. + */ + quietFromOptions(options: InitOptions | Options) { if (options.silent) { this.quietConsole = true; this.silentConsole = true; @@ -37,31 +58,37 @@ class Logger { } } - styleArgs(style, args) { + /** + * Style the arguments with the given style. + * @param style - The style to apply to the arguments. + * @param args - The arguments to style. + * @returns The styled arguments. + */ + styleArgs(style: InspectColor[] | undefined, args: any[]): any[] | "" { if (!style) { return args; } if (args.length === 0) { return ""; } - return args.map((arg) => (typeof arg === "string" ? styleText(style, arg) : arg)); + return args.map((arg: any) => (typeof arg === "string" ? styleText(style, arg) : arg)); } /** * Style the text as a label. - * @param {string} text - The text to style. - * @param {string[]} style - The style to apply to the text. - * @returns {string} The styled text. + * @param text - The text to style. + * @param style - The style to apply to the text. + * @returns The styled text. */ - label(text, style = this.styles.label) { + label(text: string, style: InspectColor[] = this.styles.label) { return styleText(style, String(text).toUpperCase()); } /** * Log a header. - * @param {string} text - The text to log. + * @param text - The text to log. */ - header(text) { + header(text: string) { if (this.quietConsole) { return; } @@ -73,7 +100,12 @@ class Logger { this.lastLogWasNewline = true; } - generated(desc, outputPath) { + /** + * Log a message indicating that a file was generated. + * @param desc - The description of the file that was generated. + * @param outputPath - The path to the file that was generated. + */ + generated(desc: string, outputPath: string) { if (this.quietConsole) { return; } @@ -87,6 +119,9 @@ class Logger { this.lastLogWasNewline = false; } + /** + * Log a newline. Only logs a newline if the last log was not a newline. + */ newline() { if (this.quietConsole) { return; @@ -100,10 +135,10 @@ class Logger { /** * Log a message. - * @param {string} message - The message to log. - * @param {...any} otherMessages - The other messages to log. + * @param message - The message to log. + * @param otherMessages - Other messages to log. */ - log(message, ...otherMessages) { + log(message: any, ...otherMessages: any[]) { if (this.quietConsole) { return; } @@ -113,10 +148,10 @@ class Logger { /** * Log an info message. - * @param {string} message - The message to log. - * @param {...any} otherMessages - The other messages to log. + * @param message - The message to log. + * @param otherMessages - Other messages to log. */ - info(message, ...otherMessages) { + info(message: any, ...otherMessages: any[]) { if (this.quietConsole) { return; } @@ -126,10 +161,10 @@ class Logger { /** * Log a debug message. - * @param {string} message - The message to log. - * @param {...any} otherMessages - The other messages to log. + * @param message - The message to log. + * @param otherMessages - Other messages to log. */ - debug(message, ...otherMessages) { + debug(message: any, ...otherMessages: any[]) { if (this.silentConsole) { return; } @@ -142,10 +177,10 @@ class Logger { /** * Log a warning message. - * @param {string} message - The message to log. - * @param {...any} otherMessages - The other messages to log. + * @param message - The message to log. + * @param otherMessages - Other messages to log. */ - warn(message, ...otherMessages) { + warn(message: any, ...otherMessages: any[]) { if (this.silentConsole) { return; } @@ -158,10 +193,10 @@ class Logger { /** * Log an error message. - * @param {string} message - The message to log. - * @param {...any} otherMessages - The other messages to log. + * @param message - The message to log. + * @param otherMessages - Other messages to log. */ - error(message, ...otherMessages) { + error(message: any, ...otherMessages: any[]) { if (this.silentConsole) { return; } diff --git a/src/modpack-lock.js b/src/modpack-lock.ts similarity index 62% rename from src/modpack-lock.js rename to src/modpack-lock.ts index 2394330..b158620 100644 --- a/src/modpack-lock.js +++ b/src/modpack-lock.ts @@ -4,15 +4,19 @@ import {generateGitignoreRules} from "./generate_gitignore.js"; import generateJson from "./generate_json.js"; import generateLicense from "./generate_license.js"; import {logm} from "./logger.js"; -import {promptUserForInfo} from "./modpack_info.js"; +import {promptUserForInfo} from "./user_prompts.js"; import {getModpackInfo, getLockfile} from "./directory_scanning.js"; - -/** - * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo - * @typedef {import('./config/types.js').Options} Options - * @typedef {import('./config/types.js').InitOptions} InitOptions - * @typedef {import('./config/types.js').Lockfile} Lockfile - */ +import type { + Jsonfile, + Options, + InitOptions, + Lockfile, + ModpackInfo, + DependencyCategory, + LockfileDependency, + ContentVersion, + VersionDependency, +} from "./types/index.js"; /** * @license MIT @@ -22,15 +26,19 @@ import {getModpackInfo, getLockfile} from "./directory_scanning.js"; /** * Generate the modpack files (lockfile, JSON, and optionally license, gitignore, and readme) - * @param {ModpackInfo} modpackInfo - The modpack information - * @param {string} workingDir - The directory to generate the files in - * @param {Options | InitOptions } options - The options object - * @returns {Promise} The lockfile object + * @param modpackInfo - The Jsonfile object containing the modpack information + * @param workingDir - The directory to generate the files in + * @param options - The options object + * @returns The lockfile object */ -async function generateModpackFiles(modpackInfo, workingDir, options = {}) { +async function generateModpackFiles( + modpackInfo: Jsonfile, + workingDir: string, + options: Options | InitOptions = {}, +): Promise { logm.quietFromOptions(options); - const lockfile = await generateLockfile(workingDir, options); + const lockfile: Lockfile = await generateLockfile(workingDir, options); await generateJson(modpackInfo, lockfile, workingDir, options); @@ -67,3 +75,14 @@ export { getLockfile, promptUserForInfo, }; +export type { + Lockfile, // + ModpackInfo, + Jsonfile, + Options, + InitOptions, + DependencyCategory, + LockfileDependency, + ContentVersion, + VersionDependency, +}; diff --git a/src/modrinth_interactions.js b/src/modrinth_interactions.ts similarity index 51% rename from src/modrinth_interactions.js rename to src/modrinth_interactions.ts index 8394905..d75ec54 100644 --- a/src/modrinth_interactions.js +++ b/src/modrinth_interactions.ts @@ -1,10 +1,18 @@ import * as config from "./config/index.js"; import {logm} from "./logger.js"; +import type {Choice} from "prompts"; +import type { + ContentVersion, + ProjectResponseItem, + MinecraftVersionResponseItem, + ModloaderResponseItem, + UserResponseItem, +} from "./types/index.js"; /** * Split an array into chunks of specified size */ -function chunkArray(array, size) { +function chunkArray(array: any[], size: number) { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); @@ -13,9 +21,11 @@ function chunkArray(array, size) { } /** - * Query Modrinth API for version information from hashes + * Fetch version information for multiple file hashes from the Modrinth API + * @param hashes - An array of hashes to fetch version information for + * @returns An object with the version information for each hash */ -export async function getVersionsFromHashes(hashes) { +export async function getVersionsFromHashes(hashes: string[]): Promise> { if (hashes.length === 0) { return {}; } @@ -38,23 +48,29 @@ export async function getVersionsFromHashes(hashes) { throw new Error(`Modrinth API error (${response.status}): ${errorText}`); } - return await response.json(); - } catch (error) { - logm.error(`Error fetching version information from hashes: ${error.message}`); - throw error; + return (await response.json()) as Record; + } catch (error: unknown) { + if (error instanceof Error) { + logm.error(`Error fetching version information from hashes: ${error.message}`); + throw error; + } else { + throw new Error(`Unknown error fetching version information from hashes`); + } } } /** - * Fetch multiple projects by their IDs in batches + * Fetch multiple projects by their IDs from the Modrinth API + * @param projectIds - An array of project IDs to fetch + * @returns An array of project objects */ -export async function getProjects(projectIds) { +export async function getProjects(projectIds: string[]): Promise { if (projectIds.length === 0) { return []; } - const chunks = chunkArray(projectIds, config.BATCH_SIZE); - const results = []; + const chunks: string[][] = chunkArray(projectIds, config.BATCH_SIZE); + const results: ProjectResponseItem[] = []; for (const chunk of chunks) { try { @@ -70,11 +86,15 @@ export async function getProjects(projectIds) { throw new Error(`Modrinth API error (${response.status}): ${errorText}`); } - const data = await response.json(); + const data = (await response.json()) as ProjectResponseItem[]; results.push(...data); - } catch (error) { - logm.error(`Error fetching projects: ${error.message}`); - throw error; + } catch (error: any) { + if (error instanceof Error) { + logm.error(`Error fetching projects: ${error.message}`); + throw error; + } else { + throw new Error(`Unknown error fetching projects`); + } } } @@ -82,15 +102,17 @@ export async function getProjects(projectIds) { } /** - * Fetch multiple users by their IDs in batches + * Fetch multiple users by their IDs from the Modrinth API + * @param userIds - An array of user IDs to fetch + * @returns An array of user information objects */ -export async function getUsers(userIds) { +export async function getUsers(userIds: string[]): Promise { if (userIds.length === 0) { return []; } - const chunks = chunkArray(userIds, config.BATCH_SIZE); - const results = []; + const chunks: string[][] = chunkArray(userIds, config.BATCH_SIZE); + const results: UserResponseItem[] = []; for (const chunk of chunks) { try { @@ -106,11 +128,15 @@ export async function getUsers(userIds) { throw new Error(`Modrinth API error (${response.status}): ${errorText}`); } - const data = await response.json(); + const data = (await response.json()) as UserResponseItem[]; results.push(...data); - } catch (error) { - logm.error(`Error fetching users: ${error.message}`); - throw error; + } catch (error: any) { + if (error instanceof Error) { + logm.error(`Error fetching users: ${error.message}`); + throw error; + } else { + throw new Error(`Unknown error fetching users`); + } } } @@ -118,10 +144,10 @@ export async function getUsers(userIds) { } /** - * Fetch Minecraft versions from Modrinth - * @returns {Promise>} The Minecraft versions + * Fetch Minecraft versions from the Modrinth API + * @returns An array of Minecraft versions for use with a prompt */ -export async function getMinecraftVersions() { +export async function getMinecraftVersions(): Promise { try { const url = config.MODRINTH_MINECRAFT_VERSIONS_ENDPOINT; const response = await fetch(url, { @@ -134,18 +160,21 @@ export async function getMinecraftVersions() { throw new Error(`Modrinth API error (${response.status}): ${errorText}`); } - const json = await response.json(); + const json = (await response.json()) as MinecraftVersionResponseItem[]; if (json) { //sort by version type (in the order of the MINECRAFT_VERSION_TYPES array) json.sort((a, b) => { return ( - config.MINECRAFT_VERSION_TYPES.indexOf(a.version_type) - - config.MINECRAFT_VERSION_TYPES.indexOf(b.version_type) + config.MINECRAFT_VERSION_TYPE_ORDER.indexOf(a.version_type) - + config.MINECRAFT_VERSION_TYPE_ORDER.indexOf(b.version_type) ); }); - return json.map((version) => ({title: version.version, value: version.version})); + return json.map((version) => ({ + title: version.version, + value: version.version, + })) as Choice[]; } else { - throw new Error(); + throw new Error("Could not fetch Minecraft versions"); } } catch { logm.warn(`Could not fetch Minecraft versions. Using fallbacks.`); @@ -154,10 +183,10 @@ export async function getMinecraftVersions() { } /** - * Fetch Modloaders from Modrinth - * @returns {Promise>} The Modloaders + * Fetch modloaders from the Modrinth API + * @returns An array of modloaders for use with a prompt */ -export async function getModloaders() { +export async function getModloaders(): Promise { try { const url = config.MODRINTH_MODLOADERS_ENDPOINT; const response = await fetch(url, { @@ -170,11 +199,14 @@ export async function getModloaders() { throw new Error(`Modrinth API error (${response.status}): ${errorText}`); } - const json = await response.json(); + const json = (await response.json()) as ModloaderResponseItem[]; if (json) { - return json.map((loader) => ({title: loader.name, value: loader.name})); + return json.map((loader) => ({ + title: loader.name, + value: loader.name, + })); } else { - throw new Error(); + throw new Error("Could not fetch Modloaders"); } } catch { logm.warn(`Could not fetch Modloaders. Using fallbacks.`); diff --git a/src/types/Jsonfile.ts b/src/types/Jsonfile.ts new file mode 100644 index 0000000..23a3fc5 --- /dev/null +++ b/src/types/Jsonfile.ts @@ -0,0 +1,15 @@ +import type {DependencyCategory, ModpackInfo} from "./index.js"; + +/** + * The modpack.json file's shape; contains the modpack information and dependencies + * @property dependencies - The dependencies of the modpack + * @property scripts - The scripts of the modpack + * @property [key: string] - Any other properties of the modpack + */ +export type Jsonfile = Partial & { + dependencies?: Partial>; + scripts?: { + [key: string]: string; + }; + [key: string]: unknown; +}; diff --git a/src/types/Lockfile.ts b/src/types/Lockfile.ts new file mode 100644 index 0000000..8ccc025 --- /dev/null +++ b/src/types/Lockfile.ts @@ -0,0 +1,41 @@ +import type {ContentVersion} from "./index.js"; + +/** + * Lockfile shape; contains detailed information about the modpack dependencies and their versions. + * @property version - The version of the lockfile + * @property generated - The date and time the lockfile was generated + * @property total - The total number of files in the modpack + * @property counts - An object containing the number of files in each dependency category + * @property dependencies - An object containing arrays with all the modpack's dependencies and their version information + */ +export type Lockfile = { + version: string; + generated: string; + total: number; + counts: Partial>; + dependencies: { + mods?: LockfileDependency[]; + resourcepacks?: LockfileDependency[]; + datapacks?: LockfileDependency[]; + shaderpacks?: LockfileDependency[]; + }; +}; + +/** + * The categories of dependencies in the lockfile + * @property mods - The mods folder + * @property resourcepacks - The resourcepacks folder + * @property datapacks - The datapacks folder + * @property shaderpacks - The shaderpacks folder + */ +export type DependencyCategory = keyof Lockfile["dependencies"]; + +/** + * The shape of a dependency in the lockfile + * @property path - The path to the dependency file in the modpack directory + * @property version - The version of the dependency, or null if the dependency is not found on Modrinth + */ +export type LockfileDependency = { + path: string; + version: ContentVersion | null; +}; diff --git a/src/types/ModpackInfo.ts b/src/types/ModpackInfo.ts new file mode 100644 index 0000000..bf2adb5 --- /dev/null +++ b/src/types/ModpackInfo.ts @@ -0,0 +1,27 @@ +/** + * Contains information about the modpack that is not dependent on the lockfile. + * @property name - The name of the modpack (Required) + * @property version - The modpack version (Required) + * @property description - A description of the modpack + * @property id - The modpack's slug/ID (Required) + * @property author - The author of the modpack (Required) + * @property projectUrl - The project's homepage URL + * @property sourceUrl - The project's source code URL + * @property license - The modpack's license + * @property modloader - The modpack's modloader (Required) + * @property targetModloaderVersion - The target modloader version + * @property targetMinecraftVersion - The target Minecraft version (Required) + */ +export type ModpackInfo = { + name: string; + version: string; + description: string; + id: string; + author: string; + projectUrl: string; + sourceUrl: string; + license: string; + modloader: string; + targetModloaderVersion: string; + targetMinecraftVersion: string; +}; diff --git a/src/types/OptionalFileOptions.ts b/src/types/OptionalFileOptions.ts new file mode 100644 index 0000000..4200855 --- /dev/null +++ b/src/types/OptionalFileOptions.ts @@ -0,0 +1,9 @@ +import type {InitOptions} from "./index.js"; + +/** + * The options available for the user to add optional files to the modpack + * @property addLicense - Whether to add the license text to the modpack + * @property addReadme - Whether to add the README.md file to the modpack + * @property addGitignore - Whether to add the .gitignore file to the modpack + */ +export type OptionalFileOptions = "addLicense" | "addReadme" | "addGitignore"; diff --git a/src/types/contentFiles.ts b/src/types/contentFiles.ts new file mode 100644 index 0000000..0f18560 --- /dev/null +++ b/src/types/contentFiles.ts @@ -0,0 +1,25 @@ +import type {DependencyCategory} from "./index.js"; + +/** + * A directory that contains modpack content (e.g. mods, resourcepacks, datapacks, shaderpacks) + * @property name - The name of the directory + * @property path - The path to the directory + */ +export type ContentDirectory = { + name: DependencyCategory; + path: string; +}; + +/** + * A binary content file in the modpack -- a file tracked by the modpack lockfile + * @property path - The path to the file + * @property fullPath - The full path to the file + * @property hash - The hash of the file + * @property category - The content category of the file (e.g. mods, resourcepacks, datapacks, shaderpacks) + */ +export type ContentFile = { + path: string; + fullPath: string; + hash: string; + category: DependencyCategory; +}; diff --git a/src/types/githubResponses.ts b/src/types/githubResponses.ts new file mode 100644 index 0000000..0ecb237 --- /dev/null +++ b/src/types/githubResponses.ts @@ -0,0 +1,17 @@ +/** + * One of the license objects in a response from the GitHub API + * @property spdx_id - The SPDX ID of the license + * @property key - The key of the license + */ +export type LicenseResponseItem = { + spdx_id: string; + key: string; +}; + +/** + * The response from the GitHub API for a license text + * @property body - The text of the license + */ +export type LicenseTextResponse = { + body: string; +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..8e5212e --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,8 @@ +export type * from "./Lockfile.js"; +export type * from "./Jsonfile.js"; +export type * from "./ModpackInfo.js"; +export type * from "./options.js"; +export type * from "./modrinthResponses.js"; +export type * from "./githubResponses.js"; +export type * from "./contentFiles.js"; +export type * from "./OptionalFileOptions.js"; diff --git a/src/types/modrinthResponses.ts b/src/types/modrinthResponses.ts new file mode 100644 index 0000000..221b08c --- /dev/null +++ b/src/types/modrinthResponses.ts @@ -0,0 +1,140 @@ +/** + * The shape of a dependency on another version of another project + * @property version_id - The ID of the dependency version + * @property project_id - The ID of the project + * @property file_name - The name of the file + * @property dependency_type - The type of dependency + */ +export type VersionDependency = { + version_id: string; + project_id: string; + file_name: string; + dependency_type: string; +}; + +/** + * A metadata for a specific version file of a project on Modrinth. Saved in the lockfile. + * @property id - The ID of the version + * @property project_id - The ID of the project + * @property author_id - The ID of the author + * @property date_published - The date the version was published + * @property downloads - The number of downloads of the version + * @property files - The files of the version + * @property game_versions - The game versions the version is compatible with + * @property loaders - The loaders the version is compatible with + * @property featured - Whether the version is featured + * @property name - The name of the version + * @property version_number - The version number + * @property changelog - The changelog of the version + * @property changelog_url - The URL of the changelog + * @property version_type - The type of version + * @property status - The status of the version + * @property requested_status - The requested status of the version + * @property dependencies - The dependencies for this version + */ +export type ContentVersion = { + id: string; + project_id: string; + author_id: string; + date_published: string; + downloads: number; + files: any[]; + game_versions?: string[]; + loaders?: string[]; + featured?: boolean; + name?: string; + version_number?: string; + changelog?: string; + changelog_url?: string | null; + version_type?: string; + status?: string; + requested_status?: string | null; + dependencies?: VersionDependency[]; +}; + +/** + * The shape of a project response from the Modrinth API + */ +export type ProjectResponseItem = { + id: string; + project_type: string; + team: string; + published: string; + updated: string; + downloads: number; + followers: number; + slug?: string; + client_side?: string; + server_side?: string; + game_versions?: string[]; + organization?: string | null; + title?: string; + description?: string; + body?: string; + body_url?: string | null; + approved?: string; + queued?: string | null; + status?: string; + requested_status?: string | null; + moderator_message?: string | null; + license?: any; + categories?: string[]; + additional_categories?: string[]; + loaders?: string[]; + versions?: string[]; + icon_url?: string; + issues_url?: string; + source_url?: string; + wiki_url?: string; + discord_url?: string; + donation_urls?: string[]; + gallery?: string[]; + color?: number; + thread_id?: string; + monetization_status?: string; +}; + +/** + * The types of Minecraft versions that can be returned from the Modrinth API + */ +export type MinecraftVersionType = "release" | "alpha" | "beta" | "snapshot"; + +/** + * The shape of an item in the Minecraft versions response from the Modrinth API + */ +export type MinecraftVersionResponseItem = { + version: string; + version_type: MinecraftVersionType; + date: string; + major: boolean; +}; + +/** + * The shape of an item in the modloaders response from the Modrinth API + */ +export type ModloaderResponseItem = { + icon: string; + name: string; + supported_project_types: string[]; +}; + +/** + * The shape of an item in the users response from the Modrinth API + */ +export type UserResponseItem = { + username: string; + id: string; + created: string; + role: "admin" | "moderator" | "developer"; + name?: string; + email?: string | null; + bio?: string; + payout_data?: any; + avatar_url?: string; + badges?: number; + auth_providers?: string[]; + email_verified?: boolean | null; + has_password?: boolean | null; + has_totp?: boolean | null; + github_id?: number | null; +}; diff --git a/src/types/options.ts b/src/types/options.ts new file mode 100644 index 0000000..ed1fc24 --- /dev/null +++ b/src/types/options.ts @@ -0,0 +1,71 @@ +/** + * Contains options for the generation of the modpack files. + * @property dryRun - Whether to run in dry-run mode (no files written) + * @property quiet - Whether to quiet the console output + * @property silent - Whether to silence the console output + * @property gitignore - Whether to generate a .gitignore file + * @property readme - Whether to generate README.md files + * @property licenseFile - Whether to generate a license file + * @property path - The modpack root directory + */ +export interface Options { + dryRun?: boolean; + quiet?: boolean; + silent?: boolean; + gitignore?: boolean; + readme?: boolean; + licenseFile?: boolean; + path?: string; +} + +/** + * Contains options for the initialization of the modpack files. + * @property folder - The modpack root directory + * @property noninteractive - Whether to initialize in non-interactive mode + * @property addLicense - Whether to add the license file to the modpack + * @property addGitignore - Whether to generate .gitignore rules + * @property addReadme - Whether to generate README.md files + * @property name - The option to set the modpack name + * @property version - The option to set the version + * @property id - The option to set the slug/ID + * @property description - The option to set the description + * @property author - The option to set the author + * @property projectUrl - The option to set the project URL + * @property sourceUrl - The option to set the source code URL + * @property license - The option to set the license + * @property modloader - The option to set the modloader + * @property targetModloaderVersion - The option to set the target modloader version + * @property targetMinecraftVersion - The option to set the target Minecraft version + * @property _init - Internal boolean added to indicate options come from the `init` command. + */ +export interface InitOptions extends Options { + folder?: string; + noninteractive?: boolean; + addLicense?: boolean; + addGitignore?: boolean; + addReadme?: boolean; + name?: string; + version?: string; + id?: string; + description?: string; + author?: string; + projectUrl?: string; + sourceUrl?: string; + license?: string; + modloader?: string; + targetModloaderVersion?: string; + targetMinecraftVersion?: string; + _init?: boolean; +} + +/** + * Contains options for the running scripts defined in modpack.json. + * @property folder - The modpack root directory + * @property debug - Whether to print debug information + * @property _run - Internal boolean added to indicate options come from the `run` command. + */ +export interface RunOptions extends Options { + folder?: string; + debug?: boolean; + _run?: boolean; +} diff --git a/src/modpack_info.js b/src/user_prompts.ts similarity index 60% rename from src/modpack_info.js rename to src/user_prompts.ts index a2a96e7..b18d52b 100644 --- a/src/modpack_info.js +++ b/src/user_prompts.ts @@ -1,24 +1,21 @@ -import prompts from "prompts"; -import slugify from "slugify"; +import prompts, {type Choice, type InitialReturnValue, type PromptObject} from "prompts"; +import slugify from "@sindresorhus/slugify"; import * as config from "./config/index.js"; import {getLicenseList, getLicenseText} from "./github_interactions.js"; import {getMinecraftVersions, getModloaders} from "./modrinth_interactions.js"; - -/** - * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo - */ +import type {OptionalFileOptions, InitOptions, Jsonfile, ModpackInfo} from "./types/index.js"; /** * Capitalizes a string */ -function capitalize(string) { +function capitalize(string: string) { return `${string.charAt(0).toUpperCase()}${string.slice(1)}`; } /** * Validate that a value is not empty */ -function validateNotEmpty(value, field) { +function validateNotEmpty(value: string, field: string) { if (value === undefined || value?.trim().length === 0) { return `${field} cannot be empty`; } @@ -28,13 +25,17 @@ function validateNotEmpty(value, field) { /** * Returns a required text prompt */ -function requiredText(name, message, initial) { +function requiredText( + name: keyof ModpackInfo | "other", + message: string, + initial: PromptObject["initial"], +): PromptObject { return { type: "text", name: name, message: `${capitalize(message)}`, initial: initial, - validate: (value) => { + validate: (value: string) => { return validateNotEmpty(value, name); }, }; @@ -43,7 +44,11 @@ function requiredText(name, message, initial) { /** * Returns an optional text prompt */ -function optionalText(name, message, initial) { +function optionalText( + name: keyof ModpackInfo | "other", + message: string, + initial: PromptObject["initial"], +): PromptObject { return { type: "text", name: name, @@ -53,9 +58,9 @@ function optionalText(name, message, initial) { } /** - * Get an other answer from the user + * Get the value of an "other" answer from the user */ -async function getOtherAnswer(value, message, initial) { +async function getOtherAnswer(value: string, message: string, initial: PromptObject["initial"]) { if (value && value !== config.OTHER_OPTION.value) { return value; } @@ -65,12 +70,18 @@ async function getOtherAnswer(value, message, initial) { } /** - * Returns a required autocomplete prompt with a fallback to the other option + * Returns a required autocomplete prompt with a fallback to the "other" option */ -function requiredAutocomplete(name, message, initial, choices, defaultValue) { +function requiredAutocomplete( + name: keyof ModpackInfo | "other", + message: string, + initial: PromptObject["initial"], + choices: Choice[], + defaultValue: string, +): PromptObject { initial = initial || defaultValue || config.OTHER_OPTION.value; if (initial && !choices.some((choice) => choice.value === initial)) { - choices.push({title: initial, value: initial}); + choices.push({title: initial as string, value: initial as string}); } return { @@ -79,17 +90,21 @@ function requiredAutocomplete(name, message, initial, choices, defaultValue) { message: `${capitalize(message)}`, initial: initial, choices: choices, - fallback: config.OTHER_OPTION.value, - format: async (value) => { - return await getOtherAnswer(value, ` └─𜰙 Other ${message}`, initial); + //fallback: config.OTHER_OPTION.value, + format: async (value: string) => { + return await getOtherAnswer(value, ` └─𜰙 Other ${message}`, initial as string); }, }; } /** - * Returns an confirmation prompt to generate an optional file + * Returns a confirmation prompt to generate an optional file */ -function fileGenerationConfirm(name, message, showPrompt) { +function fileGenerationConfirm( + name: OptionalFileOptions | "other", + message: string, + showPrompt: boolean, +): PromptObject { return { type: showPrompt ? "confirm" : null, name: name, @@ -99,15 +114,15 @@ function fileGenerationConfirm(name, message, showPrompt) { } /** - * Get user input for modpack information - * @param {ModpackInfo} defaults - The initial/default modpack information - * @returns {Promise} The modpack information from the user + * Prompt the user for modpack information + * @param defaults - The default modpack information, used as the initial value for each prompt + * @returns The answers to the prompts from the user */ -export async function promptUserForInfo(defaults = {}) { +export async function promptUserForInfo(defaults: Jsonfile): Promise> { const licenseList = await getLicenseList(); const minecraftVersions = await getMinecraftVersions(); const modloaders = await getModloaders(); - let answers = await prompts( + let answers: prompts.Answers = await prompts( [ requiredText("name", config.infoFields.name.prompt, defaults.name), requiredText( @@ -115,9 +130,9 @@ export async function promptUserForInfo(defaults = {}) { config.infoFields.version.prompt, defaults.version || config.DEFAULT_MODPACK_VERSION, ), - requiredText("id", config.infoFields.id.prompt, (prev, values) => - slugify(defaults.id || values.name, config.SLUGIFY_OPTIONS), - ), + requiredText("id", config.infoFields.id.prompt, (prev: string, values: any) => { + return slugify(defaults.id || values.name, config.SLUGIFY_OPTIONS); + }), optionalText("description", config.infoFields.description.prompt, defaults.description), requiredText("author", config.infoFields.author.prompt, defaults.author), optionalText( @@ -142,7 +157,7 @@ export async function promptUserForInfo(defaults = {}) { config.infoFields.modloader.prompt, defaults.modloader, modloaders, - config.FALLBACK_MODLOADERS[0].value, + config.FALLBACK_MODLOADERS[0] ? config.FALLBACK_MODLOADERS[0].value : "", ), optionalText( "targetModloaderVersion", @@ -154,7 +169,7 @@ export async function promptUserForInfo(defaults = {}) { config.infoFields.targetMinecraftVersion.prompt, defaults.targetMinecraftVersion, minecraftVersions, - minecraftVersions[0].value, + minecraftVersions[0]?.value || "", ), ], config.PROMPTS_OPTIONS, @@ -165,36 +180,40 @@ export async function promptUserForInfo(defaults = {}) { /** * Prompt the user about adding the license text to the modpack - * @param {ModpackInfo} modpackInfo - The modpack information - * @returns {Promise} The answers from the user + * @param modpackInfo - The Jsonfile object containing the modpack information + * @param options - The default options + * @returns The answers from the user */ -export async function promptUserAboutOptionalFiles(modpackInfo, defaults = {}) { - const licenseText = await getLicenseText(modpackInfo.license); - const answers = await prompts( +export async function promptUserAboutOptionalFiles( + modpackInfo: Jsonfile, + options: InitOptions, +): Promise> { + const licenseText = modpackInfo.license ? await getLicenseText(modpackInfo.license) : null; + const answers: prompts.Answers = await prompts( [ fileGenerationConfirm( "addLicense", `${config.fileFields.addLicense.prompt}?`, - licenseText && defaults.addLicense === undefined, + licenseText && options.addLicense === undefined ? true : false, ), fileGenerationConfirm( "addReadme", `${config.fileFields.addReadme.prompt}?`, - defaults.addReadme === undefined, + options.addReadme === undefined, ), fileGenerationConfirm( "addGitignore", `${config.fileFields.addGitignore.prompt}?`, - defaults.addGitignore === undefined, + options.addGitignore === undefined, ), ], config.PROMPTS_OPTIONS, ); answers.addLicense = - answers.addLicense === undefined ? (licenseText ? defaults.addLicense : false) : answers.addLicense; - answers.addReadme = answers.addReadme === undefined ? defaults.addReadme : answers.addReadme; - answers.addGitignore = answers.addGitignore === undefined ? defaults.addGitignore : answers.addGitignore; + answers.addLicense === undefined ? (licenseText ? options.addLicense : false) : answers.addLicense; + answers.addReadme = answers.addReadme === undefined ? options.addReadme : answers.addReadme; + answers.addGitignore = answers.addGitignore === undefined ? options.addGitignore : answers.addGitignore; return answers; } diff --git a/test/modpack-lock.test.js b/test/modpack-lock.test.js index 73ae06d..0f0d130 100644 --- a/test/modpack-lock.test.js +++ b/test/modpack-lock.test.js @@ -29,7 +29,7 @@ const __dirname = path.dirname(__filename); const WORKSPACE_ZIP = path.resolve(__dirname, "workspace.zip"); const LOCKFILE_NAME = "modpack.lock"; const JSON_NAME = "modpack.json"; -const CLI_PATH = path.resolve(__dirname, "../src/cli.js"); +const CLI_PATH = path.resolve(__dirname, "../src/cli.ts"); const DEPENDENCY_CATEGORIES = ["mods", "resourcepacks", "shaderpacks", "datapacks"]; @@ -123,7 +123,7 @@ async function scanWorkspaceFiles(workspaceDir) { */ async function runCli(args = [], options = {}) { try { - const {stdout, stderr} = await execFileAsync("node", [CLI_PATH, ...args], options); + const {stdout, stderr} = await execFileAsync("tsx", [CLI_PATH, ...args], options); return {stdout, stderr, exitCode: 0}; } catch (error) { return { @@ -973,5 +973,100 @@ describe("CLI", () => { const json = await readModpackJson(existingJsonWorkspace); expect(json.name).toBe("Existing Pack"); }); + + it("fails with an informative error when existing modpack.json is missing required fields", async () => { + const existingJsonWorkspace = await extractWorkspaceFixture(); + + // Missing author, modloader, targetMinecraftVersion + const modpackInfo = { + name: "Incomplete Pack", + version: "1.0.0", + id: "incomplete-pack", + }; + + await fs.writeFile(path.join(existingJsonWorkspace, JSON_NAME), JSON.stringify(modpackInfo, null, 2)); + + const result = await runCli(["--path", existingJsonWorkspace]); + + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("author"); + expect(result.stderr).toContain("modloader"); + expect(result.stderr).toContain("targetMinecraftVersion"); + }); + it("succeeds when existing modpack.json is missing required fields but provided in options during init", async () => { + const existingJsonWorkspace = await extractWorkspaceFixture(); + + // Missing author, modloader, targetMinecraftVersion + const modpackInfo = { + name: "Incomplete Pack", + version: "1.0.0", + id: "incomplete-pack", + }; + + await fs.writeFile(path.join(existingJsonWorkspace, JSON_NAME), JSON.stringify(modpackInfo, null, 2)); + + const result = await runCli([ + "init", + "--noninteractive", + "--folder", + existingJsonWorkspace, + "--author", + "Test Author", + "--modloader", + "fabric", + "--targetMinecraftVersion", + "1.21.1", + ]); + + expect(result.exitCode).toBe(0); + expect(result.stderr).not.toContain("author"); + expect(result.stderr).not.toContain("modloader"); + expect(result.stderr).not.toContain("targetMinecraftVersion"); + expect(result.stderr).not.toContain("targetMinecraftVersion"); + }); + it("succeeds when options are missing but provided in existing modpack.json during init", async () => { + const existingJsonWorkspace = await extractWorkspaceFixture(); + + // Missing author, modloader, targetMinecraftVersion + const modpackInfo = { + name: "Incomplete Pack", + version: "1.0.0", + id: "incomplete-pack", + author: "Test Author", + modloader: "fabric", + targetMinecraftVersion: "1.21.1", + }; + + await fs.writeFile(path.join(existingJsonWorkspace, JSON_NAME), JSON.stringify(modpackInfo, null, 2)); + + const result = await runCli(["init", "--noninteractive", "--folder", existingJsonWorkspace]); + + expect(result.exitCode).toBe(0); + expect(result.stderr).not.toContain("author"); + expect(result.stderr).not.toContain("modloader"); + expect(result.stderr).not.toContain("targetMinecraftVersion"); + expect(result.stderr).not.toContain("targetMinecraftVersion"); + }); + + it("fills and slugifies id during noninteractive init when existing modpack.json is missing it", async () => { + const existingJsonWorkspace = await extractWorkspaceFixture(); + + const modpackInfo = { + name: "Incomplete Pack", + version: "1.0.0", + author: "Test Author", + modloader: "fabric", + targetMinecraftVersion: "1.21.1", + }; + + await fs.writeFile(path.join(existingJsonWorkspace, JSON_NAME), JSON.stringify(modpackInfo, null, 2)); + + const result = await runCli(["init", "--noninteractive", "--folder", existingJsonWorkspace]); + + expect(result.exitCode).toBe(0); + + const json = await readModpackJson(existingJsonWorkspace); + expect(json.id).toBe("incomplete-pack"); + }); }); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8c31fb6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,49 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + "rootDir": "./src", + "outDir": "./build", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "allowJs": true, + "checkJs": true, + //"types": [], + // For nodejs: + "lib": ["esnext"], + "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/typedoc.json b/typedoc.json index 638c195..aa56e8f 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,6 +1,6 @@ { - "tsconfig": "typedoc.ts.json", - "entryPoints": ["src/modpack-lock.js"], + "tsconfig": "tsconfig.json", + "entryPoints": ["src/modpack-lock.ts"], "readme": "README.md", "out": "docs", "darkHighlightTheme": "github-dark", diff --git a/typedoc.ts.json b/typedoc.ts.json deleted file mode 100644 index d852856..0000000 --- a/typedoc.ts.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "module": "NodeNext", - "target": "ES6" - }, - "include": [ - "src/**/*.js" - ] -}