@fui-org/fui-cli 2.1.2 → 2.1.3
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.
|
@@ -72,7 +72,7 @@ ${t}`,e.severity==="block"?"hard":"soft")}function Va(e,t){let n=Ra(t.schema),o=
|
|
|
72
72
|
${u.message}
|
|
73
73
|
likely the login borrowed from ${a} has no access to this database (other group); pass --borrow-from <project/alias> or use fui db add`)}let l=await xo(e,n,{alias:t.apiName,connectionName:t.connectionName,dbToken:c,apiDomain:o.apiDomain,sqlServer:r.SQLServer,database:r.DatabaseName,userToken:o.userToken,makeDefault:t.makeDefault,test:!1});return{notes:[...o.note?[o.note]:[],...l.notes,`sql server: ${r.SQLServer} database: ${r.DatabaseName} login borrowed from ${a} (not printed)`]}}async function ic(e,t){let n=await Bn(e),o=await _r(e.store,n,t),{apiDomain:s,userToken:r}=o.config;return{projectId:n,call:(i,a)=>Un(s,r,i,a),target:{ModuleID:0,APIName:t??o.config.apiName??o.alias}}}async function ac(e,t,n){let{projectId:o,call:s,target:r}=await ic(e,n),a=(await s(t==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",r)).data??[];return await Lr(e.store.paths,o,r.APIName,t==="system"?{system:a}:{functions:a}),{apiName:r.APIName,rows:a}}async function cc(e,t,n){let{projectId:o,call:s,target:r}=await ic(e,n),i=t.kind==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",a;if(t.kind==="system"){if(t.mode==="new"){let d=((await s(i,r)).data??[]).find((p)=>Number(p.SysRight)===t.sysRight);if(d)throw new h(`SystemRight ${t.sysRight} of "${r.APIName}" is already defined ("${String(d.Note??"")}"); edit: fui right system update ${t.sysRight} --note ...; remove: fui right system delete ${t.sysRight}`)}await s("SM_SystemRight_Update",{...r,SysRight:t.sysRight,Note:t.note??null,Delete:t.mode==="delete"?1:0});let u=`SystemRight ${t.sysRight}${t.note&&t.mode!=="delete"?` (${t.note})`:""}`;a=`ok ${t.mode==="new"?"created":t.mode==="update"?"saved":"deleted"} ${u} of "${r.APIName}"`}else await s("SM_FunctionRight_Update",{...r,FunctionID:t.functionId,FunctionCode:t.functionCode,FunctionName:t.functionName??null,Delete:t.mode==="delete"?1:0}),a=t.mode==="new"?`ok created FunctionRight code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:t.mode==="update"?`ok saved FunctionRight ID ${t.functionId}: code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:`ok deleted FunctionRight ID ${t.functionId} and its user assignments`;let c=await s(i,r).catch(()=>null),l=c?.data??[];if(c)await Lr(e.store.paths,o,r.APIName,t.kind==="system"?{system:l}:{functions:l});return{apiName:r.APIName,notes:[a,`current list (${l.length}):`],rows:l}}var ls={"api-domain":{type:"string"},"user-token":{type:"string"},"user-token-env":{type:"string"}};async function us(e){return{apiDomain:f(e.values,"api-domain"),userToken:await Te(f(e.values,"user-token"),f(e.values,"user-token-env"),"--user-token")}}function Jn(e){b(`${e.join(`
|
|
74
74
|
`)}
|
|
75
|
-
`)}var cm={name:"db user-token",usage:"fui db user-token [--db ref] (--from <project/alias> | --token - | --token-env VAR) no source: list fingerprints",summary:"Save a userToken (Bearer for spAPI_* calls) into one connection; never prints tokens",options:{from:{type:"string"},token:{type:"string"},"token-env":{type:"string"}},async run(e){let t=await Te(f(e.values,"token"),f(e.values,"token-env"),"--token"),n=f(e.values,"from");if(n&&t)throw new h("pass --from or --token/--token-env, not both");let o=await nc(await X(e),{db:f(e.values,"db"),userToken:t,tokenSource:n});Jn(o.notes)}},lm={name:"db token-rebuild",usage:"fui db token-rebuild [--db ref] --uid <login> (--pwd - | --pwd-env VAR)",summary:"Rebuild a connection's dbToken after a UID/PWD change (sqlServer/database from the saved connection), test, save",options:{uid:{type:"string"},pwd:{type:"string"},"pwd-env":{type:"string"}},async run(e){let t=f(e.values,"uid");if(!t)throw new h("missing --uid");let n=await Te(f(e.values,"pwd"),f(e.values,"pwd-env"),"--pwd");if(!n)throw new h("missing --pwd - or --pwd-env VAR");Jn((await tc(await X(e),{db:f(e.values,"db"),uid:t,password:n})).notes)}},um={name:"db alias list",usage:"fui db alias list [--search text] [--api-domain host] [--user-token-env VAR]",summary:"Databases you may manage on the acc system (APIName, SQLServer, DatabaseName); read-only",options:{search:{type:"string"},...ls},async run(e){let t=await oc(await X(e),{...await us(e),search:f(e.values,"search")});for(let n of t.notes)T(`# ${n}`);if(t.rows.length>0)b(z(t.rows,e.render))}},dm={name:"db alias new",usage:"fui db alias new <apiName> --module-name N --sql-server S --database D --username U (--password - | --password-env VAR) [--module-id N]",summary:"Register a new database as a tAPI alias on acc (ask the user first; app login, never db_owner)",options:{"module-name":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},username:{type:"string"},password:{type:"string"},"password-env":{type:"string"},"module-id":{type:"string"},...ls},async run(e){let{values:t}=e,n=e.positionals[0],o=f(t,"module-name"),s=f(t,"sql-server"),r=f(t,"database"),i=f(t,"username");if(!n||!o||!s||!r||!i)throw new h("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Te(f(t,"password"),f(t,"password-env"),"--password");if(!a)throw new h("missing --password - or --password-env VAR");let c=f(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new h("--module-id must be an integer");let u=await rc(await X(e),{...await us(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});Jn(u.notes)}},pm={name:"db add-by-name",usage:"fui db add-by-name <apiName> [-p P] [--name connectionName] [--borrow-from <project/alias>] [--default] [--api-domain host] [--user-token-env VAR]",summary:"Connect by alias: look up SQLServer/Database on acc, borrow a SQL login, test, save, fetch schema",options:{name:{type:"string"},"borrow-from":{type:"string"},default:{type:"boolean"},...ls},async run(e){let t=e.positionals[0];if(!t)throw new h("missing <apiName>; see: fui db alias list");let n=await sc(await X(e),{...await us(e),apiName:t,connectionName:f(e.values,"name"),borrowFrom:f(e.values,"borrow-from"),makeDefault:e.values.default===!0});Jn(n.notes)}};function dc(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function pc(e){return e.map((t)=>({functionId:String(t.FunctionID??""),code:String(t.FunctionCode??""),name:String(t.FunctionName??""),enabled:t.Enable===!1||t.Enable===0?"no":"yes"}))}var mc={"api-name":{type:"string"}};function cs(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new h(`${t} must be ${o}`);return s}function lc(e,t,n){if(e!==void 0&&e.length>t)throw new h(`${n} is at most ${t} characters`);return e}function uc(e){return{name:`right ${e} list`,usage:`fui right ${e} list [-p P] [--api-name alias]`,summary:e==="system"?"SystemRight levels (0-9, 9 = Project Admin) of a project on acc; refreshes _db/rights.json":"FunctionRight codes of a project on acc (vueData.user.FunctionRight); refreshes _db/rights.json",options:mc,async run(t){let{apiName:n,rows:o}=await ac(await X(t),e,f(t.values,"api-name"));if(T(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){T(`# none defined; create one: fui right ${e} new ...`);return}b(z(e==="system"?dc(o):pc(o),t.render))}}}function cn(e,t){let n=e==="system"?`fui right system ${t} <0-9>${t==="delete"?"":" [--note text]"} [--api-name alias]`:t==="new"?"fui right function new --code N [--name text] [--api-name alias]":t==="update"?"fui right function update <functionId> --code N [--name text] [--api-name alias]":"fui right function delete <functionId> [--code N] [--api-name alias]",o={system:{new:"Define a SystemRight level that does not exist yet (server pre-check; 9 is always Project Admin)",update:"Change the note of an existing SystemRight level",delete:"DELETE a SystemRight level (ask the user first; the server refuses while users hold it)"},function:{new:"Create a FunctionRight code (the server assigns FunctionID; duplicates rejected)",update:"Edit an existing FunctionRight by its FunctionID",delete:"DELETE a FunctionRight and every user assignment of it (ask the user first)"}};return{name:`right ${e} ${t}`,usage:n,summary:o[e][t],options:{...mc,note:{type:"string"},code:{type:"string"},name:{type:"string"}},async run(s){let{values:r,positionals:i}=s,a;if(e==="system")a={kind:e,mode:t,sysRight:cs(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:lc(f(r,"note"),50,"--note")};else{let l=f(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:cs(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:cs(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:lc(f(r,"name"),100,"--name")}}let c=await cc(await X(s),a,f(r,"api-name"));Jn(c.notes),b(z(e==="system"?dc(c.rows):pc(c.rows),s.render))}}}var hc=[cm,lm,um,dm,pm,uc("system"),cn("system","new"),cn("system","update"),cn("system","delete"),uc("function"),cn("function","new"),cn("function","update"),cn("function","delete")];import{mkdir as Sm}from"node:fs/promises";import{execFile as hm,spawn as fm}from"node:child_process";import{realpathSync as gm}from"node:fs";import{join as fc,sep as kt}from"node:path";var le="2.1.
|
|
75
|
+
`)}var cm={name:"db user-token",usage:"fui db user-token [--db ref] (--from <project/alias> | --token - | --token-env VAR) no source: list fingerprints",summary:"Save a userToken (Bearer for spAPI_* calls) into one connection; never prints tokens",options:{from:{type:"string"},token:{type:"string"},"token-env":{type:"string"}},async run(e){let t=await Te(f(e.values,"token"),f(e.values,"token-env"),"--token"),n=f(e.values,"from");if(n&&t)throw new h("pass --from or --token/--token-env, not both");let o=await nc(await X(e),{db:f(e.values,"db"),userToken:t,tokenSource:n});Jn(o.notes)}},lm={name:"db token-rebuild",usage:"fui db token-rebuild [--db ref] --uid <login> (--pwd - | --pwd-env VAR)",summary:"Rebuild a connection's dbToken after a UID/PWD change (sqlServer/database from the saved connection), test, save",options:{uid:{type:"string"},pwd:{type:"string"},"pwd-env":{type:"string"}},async run(e){let t=f(e.values,"uid");if(!t)throw new h("missing --uid");let n=await Te(f(e.values,"pwd"),f(e.values,"pwd-env"),"--pwd");if(!n)throw new h("missing --pwd - or --pwd-env VAR");Jn((await tc(await X(e),{db:f(e.values,"db"),uid:t,password:n})).notes)}},um={name:"db alias list",usage:"fui db alias list [--search text] [--api-domain host] [--user-token-env VAR]",summary:"Databases you may manage on the acc system (APIName, SQLServer, DatabaseName); read-only",options:{search:{type:"string"},...ls},async run(e){let t=await oc(await X(e),{...await us(e),search:f(e.values,"search")});for(let n of t.notes)T(`# ${n}`);if(t.rows.length>0)b(z(t.rows,e.render))}},dm={name:"db alias new",usage:"fui db alias new <apiName> --module-name N --sql-server S --database D --username U (--password - | --password-env VAR) [--module-id N]",summary:"Register a new database as a tAPI alias on acc (ask the user first; app login, never db_owner)",options:{"module-name":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},username:{type:"string"},password:{type:"string"},"password-env":{type:"string"},"module-id":{type:"string"},...ls},async run(e){let{values:t}=e,n=e.positionals[0],o=f(t,"module-name"),s=f(t,"sql-server"),r=f(t,"database"),i=f(t,"username");if(!n||!o||!s||!r||!i)throw new h("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Te(f(t,"password"),f(t,"password-env"),"--password");if(!a)throw new h("missing --password - or --password-env VAR");let c=f(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new h("--module-id must be an integer");let u=await rc(await X(e),{...await us(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});Jn(u.notes)}},pm={name:"db add-by-name",usage:"fui db add-by-name <apiName> [-p P] [--name connectionName] [--borrow-from <project/alias>] [--default] [--api-domain host] [--user-token-env VAR]",summary:"Connect by alias: look up SQLServer/Database on acc, borrow a SQL login, test, save, fetch schema",options:{name:{type:"string"},"borrow-from":{type:"string"},default:{type:"boolean"},...ls},async run(e){let t=e.positionals[0];if(!t)throw new h("missing <apiName>; see: fui db alias list");let n=await sc(await X(e),{...await us(e),apiName:t,connectionName:f(e.values,"name"),borrowFrom:f(e.values,"borrow-from"),makeDefault:e.values.default===!0});Jn(n.notes)}};function dc(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function pc(e){return e.map((t)=>({functionId:String(t.FunctionID??""),code:String(t.FunctionCode??""),name:String(t.FunctionName??""),enabled:t.Enable===!1||t.Enable===0?"no":"yes"}))}var mc={"api-name":{type:"string"}};function cs(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new h(`${t} must be ${o}`);return s}function lc(e,t,n){if(e!==void 0&&e.length>t)throw new h(`${n} is at most ${t} characters`);return e}function uc(e){return{name:`right ${e} list`,usage:`fui right ${e} list [-p P] [--api-name alias]`,summary:e==="system"?"SystemRight levels (0-9, 9 = Project Admin) of a project on acc; refreshes _db/rights.json":"FunctionRight codes of a project on acc (vueData.user.FunctionRight); refreshes _db/rights.json",options:mc,async run(t){let{apiName:n,rows:o}=await ac(await X(t),e,f(t.values,"api-name"));if(T(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){T(`# none defined; create one: fui right ${e} new ...`);return}b(z(e==="system"?dc(o):pc(o),t.render))}}}function cn(e,t){let n=e==="system"?`fui right system ${t} <0-9>${t==="delete"?"":" [--note text]"} [--api-name alias]`:t==="new"?"fui right function new --code N [--name text] [--api-name alias]":t==="update"?"fui right function update <functionId> --code N [--name text] [--api-name alias]":"fui right function delete <functionId> [--code N] [--api-name alias]",o={system:{new:"Define a SystemRight level that does not exist yet (server pre-check; 9 is always Project Admin)",update:"Change the note of an existing SystemRight level",delete:"DELETE a SystemRight level (ask the user first; the server refuses while users hold it)"},function:{new:"Create a FunctionRight code (the server assigns FunctionID; duplicates rejected)",update:"Edit an existing FunctionRight by its FunctionID",delete:"DELETE a FunctionRight and every user assignment of it (ask the user first)"}};return{name:`right ${e} ${t}`,usage:n,summary:o[e][t],options:{...mc,note:{type:"string"},code:{type:"string"},name:{type:"string"}},async run(s){let{values:r,positionals:i}=s,a;if(e==="system")a={kind:e,mode:t,sysRight:cs(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:lc(f(r,"note"),50,"--note")};else{let l=f(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:cs(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:cs(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:lc(f(r,"name"),100,"--name")}}let c=await cc(await X(s),a,f(r,"api-name"));Jn(c.notes),b(z(e==="system"?dc(c.rows):pc(c.rows),s.render))}}}var hc=[cm,lm,um,dm,pm,uc("system"),cn("system","new"),cn("system","update"),cn("system","delete"),uc("function"),cn("function","new"),cn("function","update"),cn("function","delete")];import{mkdir as Sm}from"node:fs/promises";import{execFile as hm,spawn as fm}from"node:child_process";import{realpathSync as gm}from"node:fs";import{join as fc,sep as kt}from"node:path";var le="2.1.3";var _o="@fui-org/fui-cli",wm=3600000,ym=5000;function gc(e=process.env){return fc(rt(e),"..","update.json")}async function Kn(e=process.env){try{let t=await Yt(gc(e));if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function ut(e,t=process.env){await L(gc(t),`${JSON.stringify(e,null,2)}
|
|
76
76
|
`)}var bm=`${kt}node_modules${kt}@fui-org${kt}fui-cli${kt}`,km=[[`${kt}install${kt}global`,"bun",!1],[`${kt}lib`,"npm",!1],[`${kt}npm`,"npm",!0]];function Lo(e=ht()){let t;try{t=gm(e)}catch{return null}for(let[n,o,s]of km){let r=`${n}${bm}`,i=t.indexOf(r);if(i>=0){let a=t.slice(0,s?i+n.length:i);return{dir:t.slice(0,i+r.length-1),manager:o,prefix:a}}}return null}function Fo(e=ht()){return Lo(e)?.dir??null}function Mo(e,t){return e==="bun"?`bun add -g ${_o}@${t}`:`npm install -g ${_o}@${t}`}function ds(e,t,n=process.env){return new Promise((o)=>{hm(e,t,{env:n,shell:process.platform==="win32",maxBuffer:16777216,windowsHide:!0},(s,r,i)=>o({ok:!s,output:`${r}${i}`.trim()}))})}async function ps(e=process.env){let t=e.FUI_AUTO_UPDATE?.trim().toLowerCase();if(t)return!["0","off","false","no"].includes(t);try{return(await gt(e))["auto-update"]!=="off"}catch{return!0}}async function ms(e=process.env){let t=(e.FUI_UPDATE_REGISTRY||"https://registry.npmjs.org").replace(/\/+$/,""),n=await fetch(`${t}/${_o.replace("/","%2F")}/latest`,{signal:AbortSignal.timeout(ym),headers:{accept:"application/json"}});if(!n.ok)throw Error(`registry answered HTTP ${n.status}`);let o=await n.json();if(typeof o.version!=="string")throw Error("registry answer has no version");return o.version}function hs(e,t=le){return Ai(e,t)===1}async function vm(e,t){let n=`${_o}@${t}`;if(e.manager==="npm")return ds("npm",["install","-g","--prefix",e.prefix,n]);return ds("bun",["add","-g",n],{...process.env,BUN_INSTALL:e.prefix})}async function fs(e=process.env){let t=await Kn(e),n=new Date().toISOString(),o;try{o=await ms(e)}catch(i){throw await ut({...t,checkedAt:n,error:i.message},e),i}if(!hs(o))return await ut({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"current"};let s=Lo();if(!s)return await ut({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"manual",detail:`${Mo("bun",o)} (or ${Mo("npm",o)})`};let r=await vm(s,o);if(!r.ok)return await ut({...t,checkedAt:n,latest:o,error:r.output.slice(-500)},e),{latest:o,action:"failed",detail:r.output};return await ut({checkedAt:n,latest:o,updatedFrom:le,updatedTo:o,noticeShown:!1,skillsVersion:t.skillsVersion,skillsAgents:t.skillsAgents},e),await $m(e),{latest:o,action:"installed"}}async function $m(e){let t=Fo();if(!t)return;await ds(process.execPath,[fc(t,"dist","fui.js"),"skill","install"],{...e,FUI_AUTO_SKILLS:"off",FUI_AUTO_UPDATE:"off"})}async function wc(e=process.env){if(!Fo()||!await ps(e))return null;let t=await Kn(e),n=null;if(t.updatedTo===le&&t.noticeShown===!1)n=`# fui updated ${t.updatedFrom} -> ${le}`,t.noticeShown=!0,await ut(t,e);let o=t.checkedAt?Date.parse(t.checkedAt):0;if(Date.now()-o<wm)return n;return await ut({...t,checkedAt:new Date().toISOString()},e),fm(process.execPath,[ht(),"update","--background"],{stdio:"ignore",env:{...e,FUI_AUTO_UPDATE:"off"},detached:!0,windowsHide:!0}).unref(),n}function yc(e){let t=e[0];if(!t)throw new h(`missing <key>; keys: ${wo.join(", ")}`);if(!wo.includes(t))throw new h(`unknown key "${t}"; keys: ${wo.join(", ")}`);return t}async function jm(){let e=await ps(),t=process.env.FUI_AUTO_UPDATE?.trim(),n=(await gt())["auto-update"],o=t?"env":n?"config":"default",s=t?"FUI_AUTO_UPDATE":n?rt():"built-in (global bun or npm installs only)";return{key:"auto-update",value:e?"on":"off",source:o,from:s}}var Im={name:"config",usage:"fui config",summary:"Show settings with their effective value and source",async run({workdir:e,render:t}){let{base:n,token:o}=await yo();T(`# config file: ${rt()}`),b(z([{key:"workdir",value:e.path,source:e.source,from:e.detail},{key:"api-base",value:n.value??"",source:n.source,from:n.detail},{key:"api-token",value:o.value?"(set)":"",source:o.source,from:o.detail},await jm()],t))}},Cm={name:"config set",usage:"fui config set workdir <path> | api-base <url> | api-token - | auto-update on|off",summary:"Save a setting to the fui config file (api-token only from stdin)",async run({positionals:e}){let t=yc(e),n=e[1];if(!n)throw new h(`missing <value> for ${t}`);let o;if(t==="workdir")o=An(n),await Sm(o,{recursive:!0});else if(t==="auto-update"){if(n!=="on"&&n!=="off")throw new h("auto-update takes on or off");o=n}else if(t==="api-token")o=await Te(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let s=await gt();s[t]=o;let r=await $r(s),i={workdir:"FUI_WORKDIR","api-base":"FUI_API_BASE","api-token":"FUI_API_TOKEN","auto-update":"FUI_AUTO_UPDATE"}[t];if(process.env[i])T(`# note: ${i} is set in this shell and overrides the config file`);b(`set ${t}${t==="api-token"?"":`=${o}`} in ${r}
|
|
77
77
|
`)}},Rm={name:"config unset",usage:"fui config unset <workdir|api-base|api-token|auto-update>",summary:"Remove a setting from the fui config file",async run({positionals:e}){let t=yc(e),n=await gt();delete n[t];let o=await $r(n);b(`unset ${t} in ${o}
|
|
78
78
|
`)}},bc=[Im,Cm,Rm];var Tm={name:"db list",usage:"fui db list [-p project]",summary:"List connections across the workspace (no secrets)",async run({store:e,values:t,render:n}){let o=f(t,"project"),s=o?[jo(o)]:await e.listProjects(),r=[];for(let i of s){let a;try{a=await e.load(i)}catch(c){T(`# ${i}: ${c.message}`);continue}for(let[c,l]of Object.entries(a.connections)){let u="invalid",d=l.database??"";try{if(l.dbToken){let p=tn(l.dbToken);u=p.engine,d||=p.database}}catch{}r.push({db:`${i}/${c}`,engine:u,default:a.default===c?"*":"",apiDomain:l.apiDomain??"",database:d,userToken:l.userToken?"yes":"no",apiName:l.apiName??c})}}if(r.length===0){T("no connections; add one: fui db add <apiName> -p <project> --token -");return}b(z(r,n))}},Pm={name:"db add",usage:`fui db add <apiName> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
|
package/dist/fui.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{enableCompileCache as a}from"node:module";a?.();var{run:e}=await import("./fui-
|
|
2
|
+
import{enableCompileCache as a}from"node:module";a?.();var{run:e}=await import("./fui-hk5frcn2.js");await e();
|
package/package.json
CHANGED
|
@@ -35,8 +35,10 @@ async function runFui($: Engine, args: string[], session: string | undefined) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Runs in the background: the numbers land on the line, never in the model's context.
|
|
38
|
-
async function remoteDiff($: Engine,
|
|
39
|
-
const
|
|
38
|
+
async function remoteDiff($: Engine, current: FuiTarget): Promise<FuiDiff | undefined> {
|
|
39
|
+
const args = ["remote", "diff", "--stat", "--json"];
|
|
40
|
+
if (current.project && current.module) args.push("-p", current.project, "-m", current.module);
|
|
41
|
+
const { exitCode, stdout } = await runFui($, args, session).catch(() => ({
|
|
40
42
|
exitCode: 1,
|
|
41
43
|
stdout: "",
|
|
42
44
|
}));
|
|
@@ -100,9 +102,7 @@ async function projectUrl($: Engine, session: SessionTarget) {
|
|
|
100
102
|
return httpsUrl(firstDomain(info?.pDomain));
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
async function
|
|
104
|
-
const state = (await readJson($, await sessionFile($, command))) as { target?: SessionTarget } | undefined;
|
|
105
|
-
const session = state?.target;
|
|
105
|
+
async function describe($: Engine, session: SessionTarget | undefined, lastCommand?: string): Promise<FuiTarget> {
|
|
106
106
|
const found: FuiTarget = { lastCommand };
|
|
107
107
|
if (session?.projectId) {
|
|
108
108
|
found.project = session.projectId;
|
|
@@ -111,10 +111,33 @@ async function sync($: Engine, command: string, lastCommand: string) {
|
|
|
111
111
|
found.projectUrl = await projectUrl($, session);
|
|
112
112
|
found.moduleUrl = session.moduleId ? await moduleUrl($, session) : undefined;
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
return found;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function readSessionTarget($: Engine, command: string) {
|
|
118
|
+
const state = (await readJson($, await sessionFile($, command))) as { target?: SessionTarget } | undefined;
|
|
119
|
+
return state?.target;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// A folder holding _db or _projectInfo.json is a fui project; <project>/modules/<module> inside it is a module.
|
|
123
|
+
async function folderTarget($: Engine, cwd: string): Promise<SessionTarget | undefined> {
|
|
124
|
+
const parts = cwd.replace(/\/+$/, "").split("/");
|
|
125
|
+
for (let end = parts.length; end > 1; end--) {
|
|
126
|
+
const projectDir = parts.slice(0, end).join("/");
|
|
127
|
+
if (!(await $.fs.exists(`${projectDir}/_db`)) && !(await $.fs.exists(`${projectDir}/_projectInfo.json`))) continue;
|
|
128
|
+
const info = (await readJson($, `${projectDir}/_projectInfo.json`)) as { ProjectID?: unknown } | undefined;
|
|
129
|
+
const projectId = typeof info?.ProjectID === "string" ? info.ProjectID : parts[end - 1];
|
|
130
|
+
const workspaceRoot = parts.slice(0, end - 1).join("/");
|
|
131
|
+
const moduleId = parts[end] === "modules" ? parts[end + 1] : undefined;
|
|
132
|
+
if (moduleId) return { projectId, moduleId, moduleDir: `${projectDir}/modules/${moduleId}`, workspaceRoot };
|
|
133
|
+
const active = await readSessionTarget($, "");
|
|
134
|
+
return active?.projectId === projectId ? active : { projectId, workspaceRoot };
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
115
137
|
}
|
|
116
138
|
|
|
117
139
|
let session: string | undefined;
|
|
140
|
+
let isPinned = false;
|
|
118
141
|
let isStale = false;
|
|
119
142
|
let running: Promise<void> | undefined;
|
|
120
143
|
|
|
@@ -129,7 +152,8 @@ function refreshDiff($: Engine) {
|
|
|
129
152
|
do {
|
|
130
153
|
isStale = false;
|
|
131
154
|
await showDiff($, "pending");
|
|
132
|
-
|
|
155
|
+
const current: FuiTarget | null = await read($, target);
|
|
156
|
+
if (current?.moduleDir) await showDiff($, await remoteDiff($, current));
|
|
133
157
|
} while (isStale);
|
|
134
158
|
})().finally(() => {
|
|
135
159
|
running = undefined;
|
|
@@ -137,8 +161,21 @@ function refreshDiff($: Engine) {
|
|
|
137
161
|
}
|
|
138
162
|
|
|
139
163
|
export const register: Register = (on) => {
|
|
164
|
+
on("session.start", async ($, e, next) => {
|
|
165
|
+
const started = await next(e);
|
|
166
|
+
const folder = await folderTarget($, e.cwd);
|
|
167
|
+
isPinned = folder !== undefined;
|
|
168
|
+
if (folder) {
|
|
169
|
+
await update($, target, () => null);
|
|
170
|
+
const found = await describe($, folder);
|
|
171
|
+
await update($, target, () => found);
|
|
172
|
+
refreshDiff($);
|
|
173
|
+
}
|
|
174
|
+
return started;
|
|
175
|
+
});
|
|
176
|
+
|
|
140
177
|
on("prompt.submit", async ($, e, next) => {
|
|
141
|
-
await update($, target, () => null);
|
|
178
|
+
if (!isPinned) await update($, target, () => null);
|
|
142
179
|
return next(e);
|
|
143
180
|
});
|
|
144
181
|
|
|
@@ -164,7 +201,12 @@ export const register: Register = (on) => {
|
|
|
164
201
|
if (!call) return result;
|
|
165
202
|
|
|
166
203
|
session = sessionKey(e.command);
|
|
167
|
-
|
|
204
|
+
const found = await describe(
|
|
205
|
+
$,
|
|
206
|
+
await readSessionTarget($, e.command),
|
|
207
|
+
[call[1], call[2]].filter(Boolean).join(" "),
|
|
208
|
+
);
|
|
209
|
+
await update($, target, () => found);
|
|
168
210
|
if (!(call[1] === "remote" && call[2] === "diff")) refreshDiff($);
|
|
169
211
|
return result;
|
|
170
212
|
});
|