@frontstackdev/cli 0.0.0-canary-20250317140553 → 0.0.0-canary-20250317183353

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,7 @@ Expecting one of '${n.join("', '")}'`);return this._lifeCycleHooks[e]?this._life
21
21
  `),this._exit(0,"commander.version",e)}),this}description(e,t){return e===void 0&&t===void 0?this._description:(this._description=e,t&&(this._argsDescription=t),this)}summary(e){return e===void 0?this._summary:(this._summary=e,this)}alias(e){if(e===void 0)return this._aliases[0];let t=this;if(this.commands.length!==0&&this.commands[this.commands.length-1]._executableHandler&&(t=this.commands[this.commands.length-1]),e===t._name)throw new Error("Command alias can't be the same as its name");const n=this.parent?._findCommand(e);if(n){const r=[n.name()].concat(n.aliases()).join("|");throw new Error(`cannot add alias '${e}' to command '${this.name()}' as already have command '${r}'`)}return t._aliases.push(e),this}aliases(e){return e===void 0?this._aliases:(e.forEach(t=>this.alias(t)),this)}usage(e){if(e===void 0){if(this._usage)return this._usage;const t=this.registeredArguments.map(n=>ze(n));return[].concat(this.options.length||this._helpOption!==null?"[options]":[],this.commands.length?"[command]":[],this.registeredArguments.length?t:[]).join(" ")}return this._usage=e,this}name(e){return e===void 0?this._name:(this._name=e,this)}nameFromFilename(e){return this._name=E.basename(e,E.extname(e)),this}executableDir(e){return e===void 0?this._executableDir:(this._executableDir=e,this)}helpInformation(e){const t=this.createHelp();return t.helpWidth===void 0&&(t.helpWidth=e&&e.error?this._outputConfiguration.getErrHelpWidth():this._outputConfiguration.getOutHelpWidth()),t.formatHelp(this,t)}_getHelpContext(e){e=e||{};const t={error:!!e.error};let n;return t.error?n=l(r=>this._outputConfiguration.writeErr(r),"write"):n=l(r=>this._outputConfiguration.writeOut(r),"write"),t.write=e.write||n,t.command=this,t}outputHelp(e){let t;typeof e=="function"&&(t=e,e=void 0);const n=this._getHelpContext(e);this._getCommandAndAncestors().reverse().forEach(o=>o.emit("beforeAllHelp",n)),this.emit("beforeHelp",n);let r=this.helpInformation(n);if(t&&(r=t(r),typeof r!="string"&&!Buffer.isBuffer(r)))throw new Error("outputHelp callback must return a string or a Buffer");n.write(r),this._getHelpOption()?.long&&this.emit(this._getHelpOption().long),this.emit("afterHelp",n),this._getCommandAndAncestors().forEach(o=>o.emit("afterAllHelp",n))}helpOption(e,t){return typeof e=="boolean"?(e?this._helpOption=this._helpOption??void 0:this._helpOption=null,this):(e=e??"-h, --help",t=t??"display help for command",this._helpOption=this.createOption(e,t),this)}_getHelpOption(){return this._helpOption===void 0&&this.helpOption(void 0,void 0),this._helpOption}addHelpOption(e){return this._helpOption=e,this}help(e){this.outputHelp(e);let t=O.exitCode||0;t===0&&e&&typeof e!="function"&&e.error&&(t=1),this._exit(t,"commander.help","(outputHelp)")}addHelpText(e,t){const n=["beforeAll","before","after","afterAll"];if(!n.includes(e))throw new Error(`Unexpected value for position to addHelpText.
22
22
  Expecting one of '${n.join("', '")}'`);const r=`${e}Help`;return this.on(r,o=>{let a;typeof t=="function"?a=t({error:o.error,command:o.command}):a=t,a&&o.write(`${a}
23
23
  `)}),this}_outputHelpIfRequested(e){const t=this._getHelpOption();t&&e.find(r=>t.is(r))&&(this.outputHelp(),this._exit(0,"commander.helpDisplayed","(outputHelp)"))}},l(S,"Command"),S);function ue(i){return i.map(e=>{if(!e.startsWith("--inspect"))return e;let t,n="127.0.0.1",r="9229",o;return(o=e.match(/^(--inspect(-brk)?)$/))!==null?t=o[1]:(o=e.match(/^(--inspect(-brk|-port)?)=([^:]+)$/))!==null?(t=o[1],/^\d+$/.test(o[3])?r=o[3]:n=o[3]):(o=e.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/))!==null&&(t=o[1],n=o[3],r=o[4]),t&&r!=="0"?`${t}=${n}:${parseInt(r)+1}`:e})}l(ue,"incrementNodeInspectorPort"),oe.Command=Ze;const{Argument:he}=F,{Command:J}=oe,{CommanderError:Xe,InvalidArgumentError:pe}=j,{Help:et}=U,{Option:fe}=L;A.program=new J,A.createCommand=i=>new J(i),A.createOption=(i,e)=>new fe(i,e),A.createArgument=(i,e)=>new he(i,e),A.Command=J,A.Option=fe,A.Argument=he,A.Help=et,A.CommanderError=Xe,A.InvalidArgumentError=pe,A.InvalidOptionArgumentError=pe;const{program:kn,createCommand:jn,createArgument:xn,createOption:Pn,CommanderError:Rn,InvalidArgumentError:Hn,InvalidOptionArgumentError:Nn,Command:tt,Argument:In,Option:Vn,Help:Fn}=A,nt=/"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/,rt=/"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/,it=/^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;function ot(i,e){if(i==="__proto__"||i==="constructor"&&e&&typeof e=="object"&&"prototype"in e){st(i);return}return e}l(ot,"jsonParseTransform");function st(i){console.warn(`[destr] Dropping "${i}" key to prevent prototype pollution.`)}l(st,"warnKeyDropped");function at(i,e={}){if(typeof i!="string")return i;const t=i.trim();if(i[0]==='"'&&i.endsWith('"')&&!i.includes("\\"))return t.slice(1,-1);if(t.length<=9){const n=t.toLowerCase();if(n==="true")return!0;if(n==="false")return!1;if(n==="undefined")return;if(n==="null")return null;if(n==="nan")return Number.NaN;if(n==="infinity")return Number.POSITIVE_INFINITY;if(n==="-infinity")return Number.NEGATIVE_INFINITY}if(!it.test(i)){if(e.strict)throw new SyntaxError("[destr] Invalid JSON");return i}try{if(nt.test(i)||rt.test(i)){if(e.strict)throw new Error("[destr] Possible prototype pollution");return JSON.parse(i,ot)}return JSON.parse(i)}catch(n){if(e.strict)throw n;return i}}l(at,"destr");const ct=/#/g,lt=/&/g,ut=/\//g,ht=/=/g,B=/\+/g,pt=/%5e/gi,ft=/%60/gi,dt=/%7c/gi,mt=/%20/gi;function gt(i){return encodeURI(""+i).replace(dt,"|")}l(gt,"encode");function K(i){return gt(typeof i=="string"?i:JSON.stringify(i)).replace(B,"%2B").replace(mt,"+").replace(ct,"%23").replace(lt,"%26").replace(ft,"`").replace(pt,"^").replace(ut,"%2F")}l(K,"encodeQueryValue");function z(i){return K(i).replace(ht,"%3D")}l(z,"encodeQueryKey");function de(i=""){try{return decodeURIComponent(""+i)}catch{return""+i}}l(de,"decode");function _t(i){return de(i.replace(B," "))}l(_t,"decodeQueryKey");function yt(i){return de(i.replace(B," "))}l(yt,"decodeQueryValue");function Ot(i=""){const e={};i[0]==="?"&&(i=i.slice(1));for(const t of i.split("&")){const n=t.match(/([^=]+)=?(.*)/)||[];if(n.length<2)continue;const r=_t(n[1]);if(r==="__proto__"||r==="constructor")continue;const o=yt(n[2]||"");e[r]===void 0?e[r]=o:Array.isArray(e[r])?e[r].push(o):e[r]=[e[r],o]}return e}l(Ot,"parseQuery");function bt(i,e){return(typeof e=="number"||typeof e=="boolean")&&(e=String(e)),e?Array.isArray(e)?e.map(t=>`${z(i)}=${K(t)}`).join("&"):`${z(i)}=${K(e)}`:z(i)}l(bt,"encodeQueryItem");function wt(i){return Object.keys(i).filter(e=>i[e]!==void 0).map(e=>bt(e,i[e])).filter(Boolean).join("&")}l(wt,"stringifyQuery");const At=/^[\s\w\0+.-]{2,}:([/\\]{1,2})/,Ct=/^[\s\w\0+.-]{2,}:([/\\]{2})?/,Et=/^([/\\]\s*){2,}[^/\\]/,$t=/^\.?\//;function me(i,e={}){return typeof e=="boolean"&&(e={acceptRelative:e}),e.strict?At.test(i):Ct.test(i)||(e.acceptRelative?Et.test(i):!1)}l(me,"hasProtocol");function vt(i="",e){return i.endsWith("/")}l(vt,"hasTrailingSlash");function St(i="",e){return(vt(i)?i.slice(0,-1):i)||"/"}l(St,"withoutTrailingSlash");function Tt(i="",e){return i.endsWith("/")?i:i+"/"}l(Tt,"withTrailingSlash");function kt(i,e){if(xt(e)||me(i))return i;const t=St(e);return i.startsWith(t)?i:Rt(t,i)}l(kt,"withBase");function jt(i,e){const t=Ht(i),n={...Ot(t.search),...e};return t.search=wt(n),Nt(t)}l(jt,"withQuery");function xt(i){return!i||i==="/"}l(xt,"isEmptyURL");function Pt(i){return i&&i!=="/"}l(Pt,"isNonEmptyURL");function Rt(i,...e){let t=i||"";for(const n of e.filter(r=>Pt(r)))if(t){const r=n.replace($t,"");t=Tt(t)+r}else t=n;return t}l(Rt,"joinURL");const ge=Symbol.for("ufo:protocolRelative");function Ht(i="",e){const t=i.match(/^[\s\0]*(blob:|data:|javascript:|vbscript:)(.*)/i);if(t){const[,h,f=""]=t;return{protocol:h.toLowerCase(),pathname:f,href:h+f,auth:"",host:"",search:"",hash:""}}if(!me(i,{acceptRelative:!0}))return _e(i);const[,n="",r,o=""]=i.replace(/\\/g,"/").match(/^[\s\0]*([\w+.-]{2,}:)?\/\/([^/@]+@)?(.*)/)||[];let[,a="",u=""]=o.match(/([^#/?]*)(.*)?/)||[];n==="file:"&&(u=u.replace(/\/(?=[A-Za-z]:)/,""));const{pathname:c,search:p,hash:s}=_e(u);return{protocol:n.toLowerCase(),auth:r?r.slice(0,Math.max(0,r.length-1)):"",host:a,pathname:c,search:p,hash:s,[ge]:!n}}l(Ht,"parseURL");function _e(i=""){const[e="",t="",n=""]=(i.match(/([^#?]*)(\?[^#]*)?(#.*)?/)||[]).splice(1);return{pathname:e,search:t,hash:n}}l(_e,"parsePath");function Nt(i){const e=i.pathname||"",t=i.search?(i.search.startsWith("?")?"":"?")+i.search:"",n=i.hash||"",r=i.auth?i.auth+"@":"",o=i.host||"";return(i.protocol||i[ge]?(i.protocol||"")+"//":"")+r+o+e+t+n}l(Nt,"stringifyParsedURL");const te=class te extends Error{constructor(e,t){super(e,t),this.name="FetchError",t?.cause&&!this.cause&&(this.cause=t.cause)}};l(te,"FetchError");let Q=te;function It(i){const e=i.error?.message||i.error?.toString()||"",t=i.request?.method||i.options?.method||"GET",n=i.request?.url||String(i.request)||"/",r=`[${t}] ${JSON.stringify(n)}`,o=i.response?`${i.response.status} ${i.response.statusText}`:"<no response>",a=`${r}: ${o}${e?` ${e}`:""}`,u=new Q(a,i.error?{cause:i.error}:void 0);for(const c of["request","options","response"])Object.defineProperty(u,c,{get(){return i[c]}});for(const[c,p]of[["data","_data"],["status","status"],["statusCode","status"],["statusText","statusText"],["statusMessage","statusText"]])Object.defineProperty(u,c,{get(){return i.response&&i.response[p]}});return u}l(It,"createFetchError");const Vt=new Set(Object.freeze(["PATCH","POST","PUT","DELETE"]));function ye(i="GET"){return Vt.has(i.toUpperCase())}l(ye,"isPayloadMethod");function Ft(i){if(i===void 0)return!1;const e=typeof i;return e==="string"||e==="number"||e==="boolean"||e===null?!0:e!=="object"?!1:Array.isArray(i)?!0:i.buffer?!1:i.constructor&&i.constructor.name==="Object"||typeof i.toJSON=="function"}l(Ft,"isJSONSerializable");const Dt=new Set(["image/svg","application/xml","application/xhtml","application/html"]),Lt=/^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i;function qt(i=""){if(!i)return"json";const e=i.split(";").shift()||"";return Lt.test(e)?"json":Dt.has(e)||e.startsWith("text/")?"text":"blob"}l(qt,"detectResponseType");function Ut(i,e,t=globalThis.Headers){const n={...e,...i};if(e?.params&&i?.params&&(n.params={...e?.params,...i?.params}),e?.query&&i?.query&&(n.query={...e?.query,...i?.query}),e?.headers&&i?.headers){n.headers=new t(e?.headers||{});for(const[r,o]of new t(i?.headers||{}))n.headers.set(r,o)}return n}l(Ut,"mergeFetchOptions");const Mt=new Set([408,409,425,429,500,502,503,504]),Wt=new Set([101,204,205,304]);function Oe(i={}){const{fetch:e=globalThis.fetch,Headers:t=globalThis.Headers,AbortController:n=globalThis.AbortController}=i;async function r(u){const c=u.error&&u.error.name==="AbortError"&&!u.options.timeout||!1;if(u.options.retry!==!1&&!c){let s;typeof u.options.retry=="number"?s=u.options.retry:s=ye(u.options.method)?0:1;const h=u.response&&u.response.status||500;if(s>0&&(Array.isArray(u.options.retryStatusCodes)?u.options.retryStatusCodes.includes(h):Mt.has(h))){const f=u.options.retryDelay||0;return f>0&&await new Promise(_=>setTimeout(_,f)),o(u.request,{...u.options,retry:s-1})}}const p=It(u);throw Error.captureStackTrace&&Error.captureStackTrace(p,o),p}l(r,"onError");const o=l(async function(c,p={}){const s={request:c,options:Ut(p,i.defaults,t),response:void 0,error:void 0};s.options.method=s.options.method?.toUpperCase(),s.options.onRequest&&await s.options.onRequest(s),typeof s.request=="string"&&(s.options.baseURL&&(s.request=kt(s.request,s.options.baseURL)),(s.options.query||s.options.params)&&(s.request=jt(s.request,{...s.options.params,...s.options.query}))),s.options.body&&ye(s.options.method)&&(Ft(s.options.body)?(s.options.body=typeof s.options.body=="string"?s.options.body:JSON.stringify(s.options.body),s.options.headers=new t(s.options.headers||{}),s.options.headers.has("content-type")||s.options.headers.set("content-type","application/json"),s.options.headers.has("accept")||s.options.headers.set("accept","application/json")):("pipeTo"in s.options.body&&typeof s.options.body.pipeTo=="function"||typeof s.options.body.pipe=="function")&&("duplex"in s.options||(s.options.duplex="half")));let h;if(!s.options.signal&&s.options.timeout){const _=new n;h=setTimeout(()=>_.abort(),s.options.timeout),s.options.signal=_.signal}try{s.response=await e(s.request,s.options)}catch(_){return s.error=_,s.options.onRequestError&&await s.options.onRequestError(s),await r(s)}finally{h&&clearTimeout(h)}if(s.response.body&&!Wt.has(s.response.status)&&s.options.method!=="HEAD"){const _=(s.options.parseResponse?"json":s.options.responseType)||qt(s.response.headers.get("content-type")||"");switch(_){case"json":{const m=await s.response.text(),b=s.options.parseResponse||at;s.response._data=b(m);break}case"stream":{s.response._data=s.response.body;break}default:s.response._data=await s.response[_]()}}return s.options.onResponse&&await s.options.onResponse(s),!s.options.ignoreResponseError&&s.response.status>=400&&s.response.status<600?(s.options.onResponseError&&await s.options.onResponseError(s),await r(s)):s.response},"$fetchRaw2"),a=l(async function(c,p){return(await o(c,p))._data},"$fetch2");return a.raw=o,a.native=(...u)=>e(...u),a.create=(u={})=>Oe({...i,defaults:{...i.defaults,...u}}),a}l(Oe,"createFetch");const Y=function(){if(typeof globalThis<"u")return globalThis;if(typeof self<"u")return self;if(typeof window<"u")return window;if(typeof global<"u")return global;throw new Error("unable to locate global object")}(),Gt=Y.fetch||(()=>Promise.reject(new Error("[ofetch] global.fetch is not supported!"))),Jt=Y.Headers,Bt=Y.AbortController,q=Oe({fetch:Gt,Headers:Jt,AbortController:Bt}),Kt={apiRoot:process.env.FRONTSTACK_CLI_API_ROOT||(process.env.FRONTSTACK_CLI_DEV_MODE=="1"?"https://backend.frontstack.test/":"https://backend.frontstack.dev/")},D=Kt.apiRoot,zt=`${D}login`,Qt=`${D}api/fetch-api/spec.yaml`,be=3008,we=`http://localhost:${be}`,Ae=`${zt}?redirectUrl=${we}/callback`,Yt=".frontstack-local/",$=g.join(process.cwd(),Yt),Z="api_session.jwt",Ce="project.json",x=l(()=>{const i=l(()=>{console.info("Not auth.login implemented yet")},"login"),e=l(async()=>{try{return a(),0}catch(h){console.log(h)}},"logout"),t=l(async(h=void 0)=>{try{return process.env.NODE_TLS_REJECT_UNAUTHORIZED=process.env.FRONTSTACK_CLI_DEV_MODE?"0":"1",await q(`${D}profile`,{method:"GET",headers:{"Content-Type":"application/json",Authorization:`Bearer ${h||r()}`}})}catch(f){return console.log(f),1}},"getProfile"),n=l(async h=>{try{if(h==null)throw new Error(`You have no project selected
24
- Run ${d.hex("#7c3bed")("frontstack project")} to select a frontstack project`);return process.env.NODE_TLS_REJECT_UNAUTHORIZED=process.env.FRONTSTACK_CLI_DEV_MODE?"0":"1",await q(Qt,{method:"GET",headers:{"Content-Type":"application/yaml",Authorization:`Bearer ${r()}`,"FS-Project":h},parseResponse:l(_=>{try{return ne.load(_)}catch{throw new Error("Invalid API specification. The API specification must be a valid YAML file.")}},"parseResponse")})}catch(f){throw"data"in f&&"message"in f.data?new Error(`Server error: ${f.data.message}`):f}},"getFetchApiSpec"),r=l(()=>{const h=g.join($,Z);return y.existsSync(h)?y.readFileSync(h,"utf8"):null},"getToken"),o=l(h=>(y.existsSync($)||y.mkdirSync($),y.writeFileSync(g.join($,Z),h),0),"writeToken"),a=l(()=>{const h=g.join($,Z);y.existsSync(h)&&y.unlinkSync(h)},"deleteToken"),u=l(async()=>(process.env.NODE_TLS_REJECT_UNAUTHORIZED=process.env.FRONTSTACK_CLI_DEV_MODE?"0":"1",await q(`${D}api/project`,{method:"GET",headers:{"Content-Type":"application/yaml",Authorization:`Bearer ${r()}`},parseResponse:l(h=>JSON.parse(h),"parseResponse")})),"getProjects"),c=l(()=>{const h=g.join($,Ce);if(!y.existsSync(h))return null;try{return JSON.parse(y.readFileSync(h,"utf8")).projectId}catch{console.error("[Error] Corrupt project file - please run 'frontstack project'")}},"getProjectId");return{login:i,logout:e,getProfile:t,getFetchApiSpec:n,getToken:r,writeToken:o,deleteToken:a,getProjects:u,getProject:l(async(h=void 0)=>{try{return h===void 0&&(h=c()),h===void 0?void 0:(process.env.NODE_TLS_REJECT_UNAUTHORIZED=process.env.FRONTSTACK_CLI_DEV_MODE?"0":"1",await q(`${D}api/project/${h}`,{method:"GET",headers:{"Content-Type":"application/yaml",Authorization:`Bearer ${r()}`},parseResponse:l(f=>JSON.parse(f),"parseResponse")}))}catch(f){return{error:f.message}}},"getProject"),getProjectId:c,writeProject:l(h=>(y.existsSync($)||y.mkdirSync($),y.writeFileSync(g.join($,Ce),JSON.stringify({projectId:h})),0),"writeProject")}},"auth"),{writeToken:Zt}=x(),Xt=l(async(i,e)=>{const t=new Set,n=xe.createServer((r,o)=>{const a=we;if(r.url===void 0)return;const c=new URL(r.url,a).searchParams.get("token");c&&(o.writeHead(200,{"Content-Type":"text/html"}),o.end('<html><head><script>// window.close()<\/script></head><body style="font-family: sans-serif;"><div style="height: 100%; width: 100%; display: flex; justify-content: center; align-items: center;"><div style="text-align: center"><h1 style="color: #7c3bed;">Login Successful</h1><p>You can <a href="#" onClick="window.close()">close</a> this browser window and return to the terminal.<p style="font-size: .8rem; color: #666; text-align: left; margin-top: 20px;">&copy; 2024 frontstack</p></div></div></html>'),o.on("finish",async()=>{for(const s of t)s.destroy();Zt(c)!==0&&(console.error("Login failed."),n.close(),process.exit(1)),console.log(`${d.hex("#008000")("[SUCCESS]")} Login successful`),n.close(),process.exit(0)}))}).listen(i,()=>{console.log(`Please continue login in your browser at ${d.hex("#7c3bed")(e)}`)});n.on("connection",r=>{t.add(r),r.on("close",()=>t.delete(r))}),setTimeout(()=>{n.close()},6e4)},"login$1"),en=l(async()=>{await Pe(Ae),await Xt(be,Ae)},"login"),tn=x(),nn=l(async i=>{await tn.logout()!==0&&(console.error("Logout failed."),process.exit(1)),console.log("Logout successful."),process.exit(0)},"logout"),{getFetchApiSpec:rn}=x();C.registerHelper("eq",function(i,e,t){return i===e}),C.registerHelper("uc",function(i){return i.toUpperCase()}),C.registerHelper("contains",function(i,e,t){return i===void 0?!1:i.includes(e)}),C.registerHelper("endsWith",function(i,e,t){return i===void 0?!1:i.endsWith(e)}),C.registerHelper("blockNameFromOperationId",function(i){return i.replace("get","").replace("Block","")});const X=l((i,e=!1)=>i.$ref?`components['schemas']['${i.$ref.split("/").pop()}']`:i.type==="array"?`Array<${X(i.items,!0)}>`:i.type==="object"?`{ ${Object.keys(i.properties).map(n=>`${n}: ${X(i.properties[n])}`).join("; ")} }`:i.type,"getSchemaType");C.registerHelper("getSchemaType",i=>X(i)),C.registerHelper("stringInArray",function(i,e,t){return e==null||e.length===0?!1:t.fn?e.indexOf(i)>-1?t.fn(this):t.inverse(this):e.indexOf(i)>-1});var on=l(async i=>{const e=g.dirname(new URL(import.meta.url).pathname),t=g.join(e,"src/commands/generate/templates"),n=g.join(t,"types.js.hbs"),r=y.readFileSync(n,"utf8"),o=C.compile(r),a=g.join(t,"client.js.hbs"),u=y.readFileSync(a,"utf8"),c=C.compile(u),p=g.join(t,"query.js.hbs"),s=y.readFileSync(p,"utf8");let h;if(h=await rn(i),h.error)throw new Error(`Issue fetching API specification. Check more information below.
24
+ Run ${d.hex("#7c3bed")("frontstack project")} to select a frontstack project`);return process.env.NODE_TLS_REJECT_UNAUTHORIZED=process.env.FRONTSTACK_CLI_DEV_MODE?"0":"1",await q(Qt,{method:"GET",headers:{"Content-Type":"application/yaml",Authorization:`Bearer ${r()}`,"FS-Project":h},parseResponse:l(_=>{try{return ne.load(_)}catch{throw new Error("Invalid API specification. The API specification must be a valid YAML file.")}},"parseResponse")})}catch(f){throw"data"in f&&"message"in f.data?new Error(`Server error: ${f.data.message}`):f}},"getFetchApiSpec"),r=l(()=>{const h=g.join($,Z);return y.existsSync(h)?y.readFileSync(h,"utf8"):null},"getToken"),o=l(h=>(y.existsSync($)||y.mkdirSync($),y.writeFileSync(g.join($,Z),h),0),"writeToken"),a=l(()=>{const h=g.join($,Z);y.existsSync(h)&&y.unlinkSync(h)},"deleteToken"),u=l(async()=>(process.env.NODE_TLS_REJECT_UNAUTHORIZED=process.env.FRONTSTACK_CLI_DEV_MODE?"0":"1",await q(`${D}api/project`,{method:"GET",headers:{"Content-Type":"application/yaml",Authorization:`Bearer ${r()}`},parseResponse:l(h=>JSON.parse(h),"parseResponse")})),"getProjects"),c=l(()=>{const h=g.join($,Ce);if(!y.existsSync(h))return null;try{return JSON.parse(y.readFileSync(h,"utf8")).projectId}catch{console.error("[Error] Corrupt project file - please run 'frontstack project'")}},"getProjectId");return{login:i,logout:e,getProfile:t,getFetchApiSpec:n,getToken:r,writeToken:o,deleteToken:a,getProjects:u,getProject:l(async(h=void 0)=>{try{return h===void 0&&(h=c()),h===void 0?void 0:(process.env.NODE_TLS_REJECT_UNAUTHORIZED=process.env.FRONTSTACK_CLI_DEV_MODE?"0":"1",await q(`${D}api/project/${h}`,{method:"GET",headers:{"Content-Type":"application/yaml",Authorization:`Bearer ${r()}`},parseResponse:l(f=>JSON.parse(f),"parseResponse")}))}catch(f){return{error:f.message}}},"getProject"),getProjectId:c,writeProject:l(h=>(y.existsSync($)||y.mkdirSync($),y.writeFileSync(g.join($,Ce),JSON.stringify({projectId:h})),0),"writeProject")}},"auth"),{writeToken:Zt}=x(),Xt=l(async(i,e)=>{const t=new Set,n=xe.createServer((r,o)=>{const a=we;if(r.url===void 0)return;const c=new URL(r.url,a).searchParams.get("token");c&&(o.writeHead(200,{"Content-Type":"text/html"}),o.end('<html><head><script>// window.close()<\/script></head><body style="font-family: sans-serif;"><div style="height: 100%; width: 100%; display: flex; justify-content: center; align-items: center;"><div style="text-align: center"><h1 style="color: #7c3bed;">Login Successful</h1><p>You can <a href="#" onClick="window.close()">close</a> this browser window and return to the terminal.<p style="font-size: .8rem; color: #666; text-align: left; margin-top: 20px;">&copy; 2024 frontstack</p></div></div></html>'),o.on("finish",async()=>{for(const s of t)s.destroy();Zt(c)!==0&&(console.error("Login failed."),n.close(),process.exit(1)),console.log(`${d.hex("#008000")("[SUCCESS]")} Login successful`),n.close(),process.exit(0)}))}).listen(i,()=>{console.log(`Please continue login in your browser at ${d.hex("#7c3bed")(e)}`)});n.on("connection",r=>{t.add(r),r.on("close",()=>t.delete(r))}),setTimeout(()=>{n.close()},6e4)},"login$1"),en=l(async()=>{await Pe(Ae),await Xt(be,Ae)},"login"),tn=x(),nn=l(async i=>{await tn.logout()!==0&&(console.error("Logout failed."),process.exit(1)),console.log("Logout successful."),process.exit(0)},"logout"),{getFetchApiSpec:rn}=x();C.registerHelper("eq",function(i,e,t){return i===e}),C.registerHelper("uc",function(i){return i.toUpperCase()}),C.registerHelper("and",function(i,e,t){return i&&e}),C.registerHelper("contains",function(i,e,t){return i===void 0?!1:i.includes(e)}),C.registerHelper("endsWith",function(i,e,t){return i===void 0?!1:i.endsWith(e)}),C.registerHelper("blockNameFromOperationId",function(i){return i.replace("get","").replace("Block","")});const X=l((i,e=!1)=>i.$ref?`components['schemas']['${i.$ref.split("/").pop()}']`:i.type==="array"?`Array<${X(i.items,!0)}>`:i.type==="object"?`{ ${Object.keys(i.properties).map(n=>`${n}: ${X(i.properties[n])}`).join("; ")} }`:i.type,"getSchemaType");C.registerHelper("getSchemaType",i=>X(i)),C.registerHelper("stringInArray",function(i,e,t){return e==null||e.length===0?!1:t.fn?e.indexOf(i)>-1?t.fn(this):t.inverse(this):e.indexOf(i)>-1});var on=l(async i=>{const e=g.dirname(new URL(import.meta.url).pathname),t=g.join(e,"src/commands/generate/templates"),n=g.join(t,"types.js.hbs"),r=y.readFileSync(n,"utf8"),o=C.compile(r),a=g.join(t,"client.js.hbs"),u=y.readFileSync(a,"utf8"),c=C.compile(u),p=g.join(t,"query.js.hbs"),s=y.readFileSync(p,"utf8");let h;if(h=await rn(i),h.error)throw new Error(`Issue fetching API specification. Check more information below.
25
25
  `+h.error);let f="";try{f=await Re(h)}catch(b){throw new Error(`Invalid API Specification: ${b.message}`)}const _=o({paths:h.paths,components:h.components}),m=c({components:h.components,paths:h.paths,servers:h.servers});return{_schemaTypes:f,_types:_,_client:m,_query:s,_specFile:h}},"generate");const sn=l(async i=>{const e=i.verbose||!1,t=i.persistOas||!1,{getProject:n}=x(),r=await n();if(!r||"error"in r){console.info(`${d.hex("#b90404")("[ERROR]")} frontstack generate`),console.info(`${d.hex("#b90404")("[ERROR]")} Failed to generate Javascript client`),console.info(" * Are you connected to the internet?"),console.info(` * Are you logged in using the ${d.hex("#7c3bed")("frontstack login")} command?`),console.info(` * Have you selected a project using the ${d.hex("#7c3bed")("frontstack project")} command?`),console.info(`Alternatively, run ${d.hex("#7c3bed")("frontstack generate -v")} for additional output.`);return}let o=r;if(i.env){let w;if(i.env!=="develop"){if(w=r.environments.find(T=>T.environment===i.env),!w){console.info(`${d.hex("#b90404")("[ERROR]")} frontstack generate`),console.info(`${d.hex("#b90404")("[ERROR]")} Environment ${i.env} not found`);return}o=w}}if(!i.env&&r.environments.length>0){const w={type:"select",name:"projectId",message:"Pick an environment (use arrow keys and enter)",choices:[{title:`${r.name} (${r.environment})`,value:r.id},...r.environments.map(k=>({title:(k.id===o?.id?"(\u2714) ":"")+`${k.name} (${k.environment})`,value:k.id}))]},T=await re(w);o=r.environments.find(k=>k.id===T.projectId)??r}let a,u,c,p,s;try{e&&console.log("Generating Javascript client"),{_schemaTypes:a,_types:u,_client:c,_query:p,_specFile:s}=await on(o.id)}catch(w){console.info(`${d.hex("#b90404")("[ERROR]")} frontstack generate`),console.info(`${d.hex("#b90404")("[ERROR]")} Failed to generate Javascript client`),e?console.info(`${d.hex("#b90404")("[ERROR]")} ${w.message}`):(console.info("Are you..."),console.info(` * logged in using the ${d.hex("#7c3bed")("frontstack login")} command?`),console.info(" * connected to the internet?"),console.info(`Alternatively, run ${d.hex("#7c3bed")("frontstack generate -v")} for additional output.`));return}let h=i.output||".frontstack";process.stdout.write("Generating Javascript client"),h.startsWith("/")||(h=g.join(process.cwd(),h)),h.endsWith("/")||(h+="/");const f=g.join(h,"fetch-api.d.ts"),_=g.join(h,"generated-types.d.ts"),m=g.join(h,"generated-client.ts"),b=g.join(h,"query-types.ts");if(e&&(console.log(`
26
26
  Schema types path: ${d.dim(f)}`),console.log(`Types path: ${d.dim(_)}`),console.log(`Client path: ${d.dim(m)}`),console.log(`Target directory: ${d.dim(h)}`)),y.mkdirSync(h,{recursive:!0}),t){const w=ne.dump(s,{indent:2}),T=g.join(h,"fetch-api.spec.yaml");console.log(`
27
27
  Persisting OpenAPI Specification file in ${T}`),y.writeFileSync(g.join(T),w)}y.writeFileSync(g.join(f),a),y.writeFileSync(g.join(_),u),y.writeFileSync(g.join(m),c),y.writeFileSync(g.join(b),p),e?console.log(`Javascript client generated for ${d.hex("#7c3bed")(`${o.name} (${o.environment})`)}`):(process.stdout.clearLine(0),process.stdout.cursorTo(0),process.stdout.write(d.green(`Javascript client generated for ${d.hex("#7c3bed")(`${o.name} (${o.environment})`)}
@@ -4,10 +4,132 @@
4
4
  */
5
5
 
6
6
  import { ofetch } from 'ofetch'
7
- import type { BlockEndpoints, BlockParameters, BlockResponses, RequestOptions, BlockQueryFilters, BlockQuerySorts, BlockMode, Page } from './generated-types'
7
+ import type { BlockEndpoints, BlockParameters, BlockResponses, RequestOptions, BlockQueryFilters, BlockQuerySorts, BlockMode, Page, KeyBlocks, QueryBlocks } from './generated-types'
8
8
  import type { Query } from './query-types'
9
9
 
10
10
  type Method = 'GET' | 'POST' | 'PATCH' | 'DELETE'
11
+ type ContextToken = string
12
+ type Context = any
13
+ type ContextOption = any
14
+
15
+ /**
16
+ * Interface describing the frontstack client API
17
+ */
18
+ export interface FrontstackClient {
19
+ /**
20
+ * Fetch a simple block with a key
21
+ *
22
+ * @param blockName The name of the block to fetch
23
+ * @example 'VariantCard'
24
+ * @param key The key identifier for the block
25
+ * @example '<variant-id>'
26
+ * @param config Further configuration options
27
+ */
28
+ block: <BlockName extends keyof KeyBlocks>(
29
+ blockName: BlockName,
30
+ key: string,
31
+ config?: {
32
+ /**
33
+ * Additional configuration options:
34
+ * - `requestUrl` to provide a URL that will be used to track the origin of the request
35
+ */
36
+ options?: RequestOptions
37
+ }
38
+ ) => Promise<BlockResponses[BlockName]>;
39
+
40
+ /**
41
+ * Fetch a listing block with parameters and optional query filters
42
+ *
43
+ * @param blockName The name of the listing block to fetch
44
+ * @example 'ProductList'
45
+ * @param parameters The parameters to pass to the listing
46
+ * @example { categoryId: '<category-id>' }
47
+ * @param config Further configuration options
48
+ * @example {
49
+ * query: {
50
+ * filter: [{ type: 'equals', field: 'name', value: 'Red Dress' }],
51
+ * sort: [{
52
+ * 'order': 'asc',
53
+ * 'field': 'label'
54
+ * }]
55
+ * }
56
+ * }
57
+ */
58
+ listing: <BlockName extends keyof QueryBlocks>(
59
+ blockName: BlockName,
60
+ parameters: BlockParameters[BlockName],
61
+ config?: {
62
+ /**
63
+ * Query parameters to pass to the listing:
64
+ * - `filter` accepts an array of filter objects to filter results
65
+ * - `sort` accepts a string, a sorting option object, or an array of sorting option objects to sort results
66
+ * - `search` to perform a text search
67
+ * - `limit` to limit the number of results returned
68
+ * - `page` to paginate results
69
+ */
70
+ query?: Query<BlockQueryFilters[BlockName], BlockQuerySorts[BlockName]>
71
+ /**
72
+ * Additional configuration options:
73
+ * - `requestUrl` to provide a URL that will be used to track the origin of the request
74
+ */
75
+ options?: RequestOptions
76
+ }
77
+ ) => Promise<BlockResponses[BlockName]>;
78
+
79
+ /**
80
+ * Fetch a page by its slug
81
+ * @param slug The URL slug of the page (without protocol)
82
+ * @example my-brand.com/uk/women/shoes/running
83
+ *
84
+ * @returns The page data
85
+ * @example { data: { title: 'Running Shoes' }, type: 'ProductCategory', urls: [] }
86
+ */
87
+ page: (
88
+ slug: string | string[],
89
+ config?: {
90
+ options?: RequestOptions
91
+ }
92
+ ) => Promise<Page>;
93
+
94
+ /**
95
+ * Fetch a context by its token
96
+ * @param token The token of the context
97
+ * @example 'ae0d4981-c363-4d5a-a49e-1f053d49f2f7'
98
+ *
99
+ * @returns The context and token
100
+ */
101
+ context: (
102
+ token: ContextToken
103
+ ) => Promise<Context>;
104
+
105
+ /**
106
+ * Fetch a list of contexts
107
+ * @param token The token of the context to fetch
108
+ * @example 'ae0d4981-c363-4d5a-a49e-1f053d49f2f7'
109
+ *
110
+ * @returns The list of contexts and the token
111
+ */
112
+ contextList: (
113
+ token?: ContextToken
114
+ ) => Promise<[ContextOption[], ContextToken]>;
115
+
116
+ /**
117
+ * Update a context
118
+ * @param context The context to update
119
+ * @example { region: 'uk', locale: 'en-GB' }
120
+ * @param token The token of the context to update
121
+ * @example 'ae0d4981-c363-4d5a-a49e-1f053d49f2f7'
122
+ *
123
+ * @returns The updated context
124
+ */
125
+ contextUpdate: (
126
+ context: {
127
+ region: string
128
+ locale: string
129
+ },
130
+ token: ContextToken
131
+ ) => Promise<Context>;
132
+ }
11
133
 
12
134
  const endpoints: BlockEndpoints = {
13
135
  {{#each paths}}
@@ -54,60 +176,39 @@ const servers = {
54
176
  /**
55
177
  * frontstack Client
56
178
  */
57
- const client = {
179
+ const client: FrontstackClient = {
58
180
  /**
59
- * Fetch a block with parameters
60
- *
61
- * @param blockName The name of the block to fetch
62
- * @example 'VariantCard'
63
- * @param blockParameters The parameters to pass to the block
64
- * @example { key: '<variant-id>' }
65
- * @param config Further configuration options
66
- * @example {
67
- * query: {
68
- * filter: [{ type: 'equals', field: 'name', value: 'Red Dress' }],
69
- * sort: [{
70
- * 'order': 'asc',
71
- * 'field': 'label'
72
- * }]
73
- * }
74
- * }
181
+ * Fetch a simple block with a key
75
182
  */
76
- block: async <BlockName extends keyof BlockEndpoints>(
77
- blockName: BlockName,
78
- blockParameters: BlockParameters[BlockName],
79
- config?: (
80
- BlockMode[BlockName] extends 'query' ?
81
- {
82
- /**
83
- * Query parameters to pass to the block:
84
- * - `filter` accepts an array of filter objects to filter results
85
- * - `sort` accepts a string, a sorting option object, or an array of sorting option objects to sort results
86
- * - `search` to perform a text search
87
- * - `limit` to limit the number of results returned
88
- * - `page` to paginate results
89
- */
90
- query?: Query<BlockQueryFilters[BlockName], BlockQuerySorts[BlockName]>
91
- } : {}
92
- ) &
93
- {
94
- /**
95
- * Additional configuration options:
96
- * - `requestUrl` to provide a URL that will be used to track the origin of the request
97
- */
98
- options?: RequestOptions
99
- }
100
- ): Promise<BlockResponses[BlockName]> => {
183
+ block: async (blockName, key, config) => {
101
184
  let endpoint: string = `${servers[0].url}${endpoints[blockName]}`;
102
185
 
103
- // If payload contains `key` replace '{key}' in the endpoint
104
- if('key' in blockParameters && endpoint.includes('{key}')) {
105
- endpoint = endpoint.replace('{key}', (blockParameters as any).key)
106
- delete (blockParameters as any).key
186
+ // Replace '{key}' in the endpoint
187
+ endpoint = endpoint.replace('{key}', key);
188
+
189
+ // No additional parameters needed for blocks
190
+ let payload = { param: {} };
191
+
192
+ let headers: Record<string, string> = {}
193
+
194
+ if(config?.options?.requestUrl !== undefined) {
195
+ headers['fs-request-url'] = config.options.requestUrl
107
196
  }
197
+ if (config?.options?.contextKey !== undefined) {
198
+ headers["fs-token"] = config.options.contextKey;
199
+ }
200
+
201
+ return (await invoke(endpoint, 'POST', payload, headers))._data
202
+ },
203
+
204
+ /**
205
+ * Fetch a listing block with parameters and optional query filters
206
+ */
207
+ listing: async (blockName, parameters, config) => {
208
+ let endpoint: string = `${servers[0].url}${endpoints[blockName]}`;
108
209
 
109
210
  // Merge block parameters with query
110
- let payload = config && 'query' in config ? { param: {...blockParameters}, ...config.query } : { param: {...blockParameters} }
211
+ let payload = config && 'query' in config ? { param: {...parameters}, ...config.query } : { param: {...parameters} }
111
212
 
112
213
  let headers: Record<string, string> = {}
113
214
 
@@ -123,18 +224,8 @@ const client = {
123
224
 
124
225
  /**
125
226
  * Fetch a page by its slug
126
- * @param slug The URL slug of the page (without protocol)
127
- * @example my-brand.com/uk/women/shoes/running
128
- *
129
- * @returns The page data
130
- * @example { data: { title: 'Running Shoes' }, type: 'ProductCategory', urls: [] }
131
227
  */
132
- page: async (
133
- slug: string | string[],
134
- config?: {
135
- options?: RequestOptions
136
- }
137
- ): Promise<Page> => {
228
+ page: async (slug, config) => {
138
229
  let endpoint: string = `${servers[0].url}/page/${slug}`;
139
230
 
140
231
  let headers: Record<string, string> = {}
@@ -151,14 +242,8 @@ const client = {
151
242
 
152
243
  /**
153
244
  * Fetch a context by its token
154
- * @param token The token of the context
155
- * @example 'ae0d4981-c363-4d5a-a49e-1f053d49f2f7'
156
- *
157
- * @returns The context and token
158
245
  */
159
- context: async (
160
- token: ContextToken
161
- ): Promise<Context> => {
246
+ context: async (token) => {
162
247
  let endpoint: string = `${servers[0].url}/context/token`;
163
248
 
164
249
  let headers: Record<string, string> = {
@@ -170,14 +255,8 @@ const client = {
170
255
 
171
256
  /**
172
257
  * Fetch a list of contexts
173
- * @param token The token of the context to fetch
174
- * @example 'ae0d4981-c363-4d5a-a49e-1f053d49f2f7'
175
- *
176
- * @returns The list of contexts and the token
177
258
  */
178
- contextList: async (
179
- token?: ContextToken
180
- ): Promise<[ContextOption[], ContextToken]> => {
259
+ contextList: async (token) => {
181
260
  let endpoint: string = `${servers[0].url}/context`;
182
261
 
183
262
  const requestHeaders: Record<string, string> = {}
@@ -196,17 +275,8 @@ const client = {
196
275
 
197
276
  /**
198
277
  * Update a context
199
- * @param context The context to update
200
- * @example { region: 'uk', locale: 'en-GB' }
201
- * @param token The token of the context to update
202
- * @example 'ae0d4981-c363-4d5a-a49e-1f053d49f2f7'
203
- *
204
- * @returns The updated context
205
278
  */
206
- contextUpdate: async(context: {
207
- region: string
208
- locale: string
209
- }, token: ContextToken): Promise<Context> => {
279
+ contextUpdate: async (context, token) => {
210
280
  let endpoint: string = `${servers[0].url}/context`;
211
281
 
212
282
  let headers: Record<string, string> = {
@@ -23,7 +23,7 @@ type RequestOptions = {
23
23
 
24
24
  /* List of all types used to fetch block parameters */
25
25
 
26
- {{#each paths}}{{#if (endsWith this.post.operationId "Block")}}export type {{this.summary}}Parameters = {
26
+ {{#each paths}}{{#if (and (endsWith this.post.operationId "Block") (contains this.post.tags "query-block"))}}export type {{this.summary}}Parameters = {
27
27
  {{#each this.post.parameters}}
28
28
  {{#if @first}}{{else}}
29
29
  {{/if}}
@@ -51,14 +51,31 @@ type RequestOptions = {
51
51
 
52
52
  type BlockParameters = {
53
53
  {{#each components.schemas}}
54
- {{#if (contains tags "blocks")}}
54
+ {{#if (contains tags "query-block")}}
55
55
  {{@key}}: {{@key}}Parameters
56
56
  {{/if}}
57
57
  {{/each}}
58
58
  }
59
59
 
60
60
  type BlockEndpoints = {
61
- [key in keyof BlockParameters]: string;
61
+ [key in keyof QueryBlocks | keyof KeyBlocks]: string;
62
+ }
63
+
64
+ /* Types for split block/listing methods */
65
+ export type KeyBlocks = {
66
+ {{#each components.schemas}}
67
+ {{#if (contains tags "key-block")}}
68
+ {{@key}}: never
69
+ {{/if}}
70
+ {{/each}}
71
+ }
72
+
73
+ export type QueryBlocks = {
74
+ {{#each components.schemas}}
75
+ {{#if (contains tags "query-block")}}
76
+ {{@key}}: {{@key}}Parameters
77
+ {{/if}}
78
+ {{/each}}
62
79
  }
63
80
 
64
81
  declare global {
@@ -68,22 +85,8 @@ export type {{@key}} = components['schemas']['{{@key}}']
68
85
  {{/if}}
69
86
  {{/each}}
70
87
  }
71
- {{!--
72
-
73
- The loop below would work as well, but it makes all parameters optional, which leads to bad DX
74
- Solve later
75
88
 
76
- {{#each components.schemas}}
77
- {{#if (contains tags "blocks")}}
78
- export type {{@key}} = {
79
- {{#each properties}}
80
- {{@key}}: {{{getSchemaType this}}}
81
- {{/each}}
82
- }
83
- {{/if}}
84
- {{/each}}
85
- --}}
86
- type BlockResponses = {
89
+ export type BlockResponses = {
87
90
  {{#each components.schemas}}
88
91
  {{#if (contains tags "blocks")}}
89
92
  {{@key}}: {{@key}}
@@ -91,17 +94,17 @@ type BlockResponses = {
91
94
  {{/each}}
92
95
  }
93
96
 
94
- type BlockQueryFilters = {
97
+ export type BlockQueryFilters = {
95
98
  {{#each components.schemas}}
96
- {{#if (contains tags "blocks")}}
99
+ {{#if (contains tags "query-block")}}
97
100
  {{@key}}: components['schemas']['{{@key}}QueryOptions']['filter']
98
101
  {{/if}}
99
102
  {{/each}}
100
103
  }
101
104
 
102
- type BlockQuerySorts = {
105
+ export type BlockQuerySorts = {
103
106
  {{#each components.schemas}}
104
- {{#if (contains tags "blocks")}}
107
+ {{#if (contains tags "query-block")}}
105
108
  {{@key}}: components['schemas']['{{@key}}QueryOptions']['sort']
106
109
  {{/if}}
107
110
  {{/each}}
package/dist/version CHANGED
@@ -1 +1 @@
1
- 0.0.0-canary-20250317140553
1
+ 0.0.0-canary-20250317183353
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontstackdev/cli",
3
- "version": "0.0.0-canary-20250317140553",
3
+ "version": "0.0.0-canary-20250317183353",
4
4
  "description": "frontstack CLI for managing projects",
5
5
  "type": "module",
6
6
  "module": "dist/frontstack.mjs",