@fui-org/fui-cli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/dist/fui.js +347 -44
- package/package.json +15 -3
- package/skills/fui/SKILL.md +149 -66
- package/skills/fui-skill/README.md +112 -0
- package/skills/fui-skill/SKILL.md +269 -0
- package/skills/fui-skill/assets/projectdefaultstyle.css +518 -0
- package/skills/fui-skill/design-md/airbnb/DESIGN.md +545 -0
- package/skills/fui-skill/design-md/airbnb/README.md +5 -0
- package/skills/fui-skill/design-md/airtable/DESIGN.md +554 -0
- package/skills/fui-skill/design-md/airtable/README.md +5 -0
- package/skills/fui-skill/design-md/apple/DESIGN.md +562 -0
- package/skills/fui-skill/design-md/apple/README.md +5 -0
- package/skills/fui-skill/design-md/asu/DESIGN.md +179 -0
- package/skills/fui-skill/design-md/asu/README.md +107 -0
- package/skills/fui-skill/design-md/binance/DESIGN.md +634 -0
- package/skills/fui-skill/design-md/binance/README.md +5 -0
- package/skills/fui-skill/design-md/bmw/DESIGN.md +544 -0
- package/skills/fui-skill/design-md/bmw/README.md +5 -0
- package/skills/fui-skill/design-md/bmw-m/DESIGN.md +503 -0
- package/skills/fui-skill/design-md/bmw-m/README.md +5 -0
- package/skills/fui-skill/design-md/bugatti/DESIGN.md +454 -0
- package/skills/fui-skill/design-md/bugatti/README.md +5 -0
- package/skills/fui-skill/design-md/cal/DESIGN.md +542 -0
- package/skills/fui-skill/design-md/cal/README.md +5 -0
- package/skills/fui-skill/design-md/claude/DESIGN.md +589 -0
- package/skills/fui-skill/design-md/claude/README.md +5 -0
- package/skills/fui-skill/design-md/clay/DESIGN.md +541 -0
- package/skills/fui-skill/design-md/clay/README.md +5 -0
- package/skills/fui-skill/design-md/clickhouse/DESIGN.md +544 -0
- package/skills/fui-skill/design-md/clickhouse/README.md +5 -0
- package/skills/fui-skill/design-md/cohere/DESIGN.md +451 -0
- package/skills/fui-skill/design-md/cohere/README.md +5 -0
- package/skills/fui-skill/design-md/coinbase/DESIGN.md +570 -0
- package/skills/fui-skill/design-md/coinbase/README.md +5 -0
- package/skills/fui-skill/design-md/composio/DESIGN.md +506 -0
- package/skills/fui-skill/design-md/composio/README.md +5 -0
- package/skills/fui-skill/design-md/cursor/DESIGN.md +537 -0
- package/skills/fui-skill/design-md/cursor/README.md +5 -0
- package/skills/fui-skill/design-md/elevenlabs/DESIGN.md +504 -0
- package/skills/fui-skill/design-md/elevenlabs/README.md +5 -0
- package/skills/fui-skill/design-md/expo/DESIGN.md +526 -0
- package/skills/fui-skill/design-md/expo/README.md +5 -0
- package/skills/fui-skill/design-md/ferrari/DESIGN.md +531 -0
- package/skills/fui-skill/design-md/ferrari/README.md +5 -0
- package/skills/fui-skill/design-md/figma/DESIGN.md +578 -0
- package/skills/fui-skill/design-md/figma/README.md +5 -0
- package/skills/fui-skill/design-md/framer/DESIGN.md +544 -0
- package/skills/fui-skill/design-md/framer/README.md +5 -0
- package/skills/fui-skill/design-md/fui/DESIGN.md +532 -0
- package/skills/fui-skill/design-md/hashicorp/DESIGN.md +575 -0
- package/skills/fui-skill/design-md/hashicorp/README.md +5 -0
- package/skills/fui-skill/design-md/ibm/DESIGN.md +550 -0
- package/skills/fui-skill/design-md/ibm/README.md +5 -0
- package/skills/fui-skill/design-md/intercom/DESIGN.md +546 -0
- package/skills/fui-skill/design-md/intercom/README.md +5 -0
- package/skills/fui-skill/design-md/kraken/DESIGN.md +125 -0
- package/skills/fui-skill/design-md/kraken/README.md +5 -0
- package/skills/fui-skill/design-md/lamborghini/DESIGN.md +288 -0
- package/skills/fui-skill/design-md/lamborghini/README.md +5 -0
- package/skills/fui-skill/design-md/linear.app/DESIGN.md +548 -0
- package/skills/fui-skill/design-md/linear.app/README.md +5 -0
- package/skills/fui-skill/design-md/lovable/DESIGN.md +298 -0
- package/skills/fui-skill/design-md/lovable/README.md +5 -0
- package/skills/fui-skill/design-md/mastercard/DESIGN.md +365 -0
- package/skills/fui-skill/design-md/mastercard/README.md +5 -0
- package/skills/fui-skill/design-md/meta/DESIGN.md +683 -0
- package/skills/fui-skill/design-md/meta/README.md +5 -0
- package/skills/fui-skill/design-md/minimax/DESIGN.md +746 -0
- package/skills/fui-skill/design-md/minimax/README.md +5 -0
- package/skills/fui-skill/design-md/mintlify/DESIGN.md +852 -0
- package/skills/fui-skill/design-md/mintlify/README.md +5 -0
- package/skills/fui-skill/design-md/miro/DESIGN.md +825 -0
- package/skills/fui-skill/design-md/miro/README.md +5 -0
- package/skills/fui-skill/design-md/mistral.ai/DESIGN.md +773 -0
- package/skills/fui-skill/design-md/mistral.ai/README.md +5 -0
- package/skills/fui-skill/design-md/mongodb/DESIGN.md +767 -0
- package/skills/fui-skill/design-md/mongodb/README.md +5 -0
- package/skills/fui-skill/design-md/nike/DESIGN.md +575 -0
- package/skills/fui-skill/design-md/nike/README.md +5 -0
- package/skills/fui-skill/design-md/notion/DESIGN.md +821 -0
- package/skills/fui-skill/design-md/notion/README.md +5 -0
- package/skills/fui-skill/design-md/nvidia/DESIGN.md +640 -0
- package/skills/fui-skill/design-md/nvidia/README.md +5 -0
- package/skills/fui-skill/design-md/ollama/DESIGN.md +539 -0
- package/skills/fui-skill/design-md/ollama/README.md +5 -0
- package/skills/fui-skill/design-md/opencode.ai/DESIGN.md +521 -0
- package/skills/fui-skill/design-md/opencode.ai/README.md +5 -0
- package/skills/fui-skill/design-md/pinterest/DESIGN.md +597 -0
- package/skills/fui-skill/design-md/pinterest/README.md +5 -0
- package/skills/fui-skill/design-md/playstation/DESIGN.md +661 -0
- package/skills/fui-skill/design-md/playstation/README.md +5 -0
- package/skills/fui-skill/design-md/posthog/DESIGN.md +690 -0
- package/skills/fui-skill/design-md/posthog/README.md +5 -0
- package/skills/fui-skill/design-md/raycast/DESIGN.md +669 -0
- package/skills/fui-skill/design-md/raycast/README.md +5 -0
- package/skills/fui-skill/design-md/renault/DESIGN.md +589 -0
- package/skills/fui-skill/design-md/renault/README.md +5 -0
- package/skills/fui-skill/design-md/replicate/DESIGN.md +616 -0
- package/skills/fui-skill/design-md/replicate/README.md +5 -0
- package/skills/fui-skill/design-md/resend/DESIGN.md +585 -0
- package/skills/fui-skill/design-md/resend/README.md +5 -0
- package/skills/fui-skill/design-md/revolut/DESIGN.md +636 -0
- package/skills/fui-skill/design-md/revolut/README.md +5 -0
- package/skills/fui-skill/design-md/runwayml/DESIGN.md +244 -0
- package/skills/fui-skill/design-md/runwayml/README.md +5 -0
- package/skills/fui-skill/design-md/sanity/DESIGN.md +357 -0
- package/skills/fui-skill/design-md/sanity/README.md +5 -0
- package/skills/fui-skill/design-md/sentry/DESIGN.md +551 -0
- package/skills/fui-skill/design-md/sentry/README.md +5 -0
- package/skills/fui-skill/design-md/shopify/DESIGN.md +516 -0
- package/skills/fui-skill/design-md/shopify/README.md +5 -0
- package/skills/fui-skill/design-md/slack/DESIGN.md +482 -0
- package/skills/fui-skill/design-md/spacex/DESIGN.md +363 -0
- package/skills/fui-skill/design-md/spacex/README.md +5 -0
- package/skills/fui-skill/design-md/spotify/DESIGN.md +246 -0
- package/skills/fui-skill/design-md/spotify/README.md +5 -0
- package/skills/fui-skill/design-md/starbucks/DESIGN.md +580 -0
- package/skills/fui-skill/design-md/starbucks/README.md +5 -0
- package/skills/fui-skill/design-md/stripe/DESIGN.md +487 -0
- package/skills/fui-skill/design-md/stripe/README.md +5 -0
- package/skills/fui-skill/design-md/supabase/DESIGN.md +462 -0
- package/skills/fui-skill/design-md/supabase/README.md +5 -0
- package/skills/fui-skill/design-md/superhuman/DESIGN.md +448 -0
- package/skills/fui-skill/design-md/superhuman/README.md +5 -0
- package/skills/fui-skill/design-md/tesla/DESIGN.md +286 -0
- package/skills/fui-skill/design-md/tesla/README.md +5 -0
- package/skills/fui-skill/design-md/theverge/DESIGN.md +339 -0
- package/skills/fui-skill/design-md/theverge/README.md +5 -0
- package/skills/fui-skill/design-md/together.ai/DESIGN.md +633 -0
- package/skills/fui-skill/design-md/together.ai/README.md +5 -0
- package/skills/fui-skill/design-md/uber/DESIGN.md +636 -0
- package/skills/fui-skill/design-md/uber/README.md +5 -0
- package/skills/fui-skill/design-md/vercel/DESIGN.md +736 -0
- package/skills/fui-skill/design-md/vercel/README.md +5 -0
- package/skills/fui-skill/design-md/vodafone/DESIGN.md +538 -0
- package/skills/fui-skill/design-md/vodafone/README.md +5 -0
- package/skills/fui-skill/design-md/voltagent/DESIGN.md +521 -0
- package/skills/fui-skill/design-md/voltagent/README.md +5 -0
- package/skills/fui-skill/design-md/warp/DESIGN.md +526 -0
- package/skills/fui-skill/design-md/warp/README.md +5 -0
- package/skills/fui-skill/design-md/webflow/DESIGN.md +588 -0
- package/skills/fui-skill/design-md/webflow/README.md +5 -0
- package/skills/fui-skill/design-md/wired/DESIGN.md +497 -0
- package/skills/fui-skill/design-md/wired/README.md +5 -0
- package/skills/fui-skill/design-md/wise/DESIGN.md +544 -0
- package/skills/fui-skill/design-md/wise/README.md +5 -0
- package/skills/fui-skill/design-md/x.ai/DESIGN.md +465 -0
- package/skills/fui-skill/design-md/x.ai/README.md +5 -0
- package/skills/fui-skill/design-md/zapier/DESIGN.md +537 -0
- package/skills/fui-skill/design-md/zapier/README.md +5 -0
- package/skills/fui-skill/examples/component.vue +162 -0
- package/skills/fui-skill/examples/f-table-patterns.json +331 -0
- package/skills/fui-skill/examples/module-patterns.json +973 -0
- package/skills/fui-skill/examples/project-patterns.json +222 -0
- package/skills/fui-skill/metadata.json +75 -0
- package/skills/fui-skill/references/INDEX.md +144 -0
- package/skills/fui-skill/references/advanced-techniques.md +160 -0
- package/skills/fui-skill/references/coding-standards.md +112 -0
- package/skills/fui-skill/references/component-design.md +455 -0
- package/skills/fui-skill/references/component-quickref.md +77 -0
- package/skills/fui-skill/references/component-table.md +276 -0
- package/skills/fui-skill/references/components-dialog.md +192 -0
- package/skills/fui-skill/references/components-display.md +147 -0
- package/skills/fui-skill/references/components-echart.md +391 -0
- package/skills/fui-skill/references/components-input.md +359 -0
- package/skills/fui-skill/references/controls-patterns.md +847 -0
- package/skills/fui-skill/references/controls-styling-vocabulary.md +140 -0
- package/skills/fui-skill/references/db-table-design.md +77 -0
- package/skills/fui-skill/references/db-workflow.md +504 -0
- package/skills/fui-skill/references/default-function.md +415 -0
- package/skills/fui-skill/references/design-modes.md +85 -0
- package/skills/fui-skill/references/echart-templates.md +481 -0
- package/skills/fui-skill/references/fastproject.md +97 -0
- package/skills/fui-skill/references/fsheet.md +218 -0
- package/skills/fui-skill/references/fullstack-workflow.md +351 -0
- package/skills/fui-skill/references/module-data-patterns.md +126 -0
- package/skills/fui-skill/references/module-json-anatomy.md +137 -0
- package/skills/fui-skill/references/module-structure.md +260 -0
- package/skills/fui-skill/references/new-session.md +108 -0
- package/skills/fui-skill/references/pdfmake.md +60 -0
- package/skills/fui-skill/references/permission-system.md +169 -0
- package/skills/fui-skill/references/platform-architecture.md +294 -0
- package/skills/fui-skill/references/project-config.md +335 -0
- package/skills/fui-skill/references/project-provisioning.md +383 -0
- package/skills/fui-skill/references/script-map.md +296 -0
- package/skills/fui-skill/references/sql-clr-functions.md +224 -0
- package/skills/fui-skill/references/system-design.md +116 -0
- package/skills/fui-skill/references/tapi-file-api.md +191 -0
- package/skills/fui-skill/references/tapi-permission-patterns.md +158 -0
- package/skills/fui-skill/references/tapi-reference.md +549 -0
- package/skills/fui-skill/references/tools-registry.md +460 -0
- package/skills/fui-skill/references/ui-crosswindow-patterns.md +317 -0
- package/skills/fui-skill/references/ui-dialog-patterns.md +229 -0
- package/skills/fui-skill/references/ui-layout-patterns.md +176 -0
- package/skills/fui-skill/references/ui-patterns.md +315 -0
- package/skills/fui-skill/references/ui-screenshot-review.md +94 -0
- package/skills/fui-skill/references/ui-table-cell-patterns.md +316 -0
- package/skills/fui-skill/references/ui-templates.md +29 -0
- package/skills/fui-skill/references/verification.md +246 -0
- package/skills/fui-skill/references/watcher-patterns.md +196 -0
- package/skills/fui-skill/references/websocket-realtime.md +254 -0
- package/skills/fui-skill/scripts/component-3.0.js +2549 -0
- package/skills/fui-skill/scripts/component.js +3142 -0
- package/skills/fui-skill/scripts/componentTable-3.0.js +909 -0
- package/skills/fui-skill/scripts/componentTable.js +769 -0
- package/skills/fui-skill/scripts/defaultfunction-3.0.js +781 -0
- package/skills/fui-skill/scripts/defaultfunction.js +966 -0
- package/skills/fui-skill/scripts/fastproject-3.0.js +870 -0
- package/skills/fui-skill/scripts/fastproject.js +828 -0
- package/skills/fui-skill/scripts/fechart.js +890 -0
- package/skills/fui-skill/scripts/fsheet.js +1330 -0
package/dist/fui.js
CHANGED
|
@@ -1,30 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import{parseArgs as
|
|
4
|
-
`),n}function
|
|
5
|
-
`+s.map((i)=>` --db ${i}/${t.alias}`).join(`
|
|
6
|
-
`))}let r=[];for(let s of o)if(Object.keys((await this.load(s)).connections).length>0)r.push(s);if(r.length===1)return r[0];if(r.length===0)throw new a("no DB connections in workspace; run: fui db add <alias> -p <project> --token -");throw new a(`several projects have connections; pass -p <project> or --db <project>/<alias>:
|
|
7
|
-
`+r.map((s)=>` -p ${s}`).join(`
|
|
8
|
-
`))}async resolve(e){let t=await this.resolveProject(e),n=await this.load(t);if(Object.keys(n.connections).length===0)throw new a(`project "${t}" has no DB connections; run: fui db add <alias> -p ${t}`);let r=await this.pickAlias(t,n,e),s=n.connections[r];if(!s.dbToken)throw new a(`connection ${t}/${r} has no dbToken; re-add it`);let{engine:i}=v(s.dbToken);if(i==="mssql"&&!s.apiDomain)throw new a(`connection ${t}/${r} is MSSQL and needs apiDomain; re-add with --api-domain`);return{projectId:t,alias:r,config:s,engine:i,dbToken:s.dbToken}}async pickAlias(e,t,n){let o=Object.keys(t.connections);if(n.db){let{alias:s}=xe(n.db),i=O(t,s);if(!i)throw new a(this.menu(e,t,`no connection "${s}" in project "${e}"`));return i}if(o.length===1)return o[0];if(n.objectName){let s=n.objectName.toLowerCase(),i=[];for(let c of o)if((await this.knownObjectNames(e,c)).has(s))i.push(c);if(i.length===1)return i[0];throw new a(this.menu(e,t,i.length===0?`cannot infer connection for "${n.objectName}"`:`"${n.objectName}" exists in several connections`))}if(n.write)throw new a(this.menu(e,t,`project "${e}" has several connections; writes need --db`));let r=t.default?O(t,t.default):null;if(r)return r;throw new a(this.menu(e,t,`project "${e}" has several connections and no default`))}async knownObjectNames(e,t){let n=new Set,o=Bun.file(this.paths.schemaFile(e,t));if(await o.exists())try{let r=await o.json();for(let s of[r.tables,r.procedures].flatMap((i)=>Array.isArray(i)?i:[])){let i=typeof s==="string"?/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(s):null;if(i)n.add(i[1].toLowerCase())}}catch{}for(let r of await this.localSqlNames(e,t))n.add(r.toLowerCase());return n}async localSqlNames(e,t){let n=[];try{for await(let o of new Bun.Glob("*.sql").scan({cwd:this.paths.connDir(e,t)}))n.push(o.slice(0,-4))}catch(o){if(o.code!=="ENOENT")throw o}return n.sort()}menu(e,t,n){let o=Object.keys(t.connections).map((r)=>` --db ${e}/${r}${t.default===r?" (default)":""}`);return[n,...o].join(`
|
|
9
|
-
`)}}import{join as x}from"path";class te{root;constructor(e){this.root=e}projectDir(e){return x(this.root,e)}dbDir(e){return x(this.projectDir(e),"_db")}connectionsFile(e){return x(this.dbDir(e),"_connections.json")}connDir(e,t){return x(this.dbDir(e),t)}schemaFile(e,t){return x(this.connDir(e,t),"schema.json")}spFile(e,t,n){return x(this.connDir(e,t),`${n}.sql`)}historyRoot(){return x(this.root,"_history")}}var Ne={project:{type:"string",short:"p"},db:{type:"string"},workdir:{type:"string"},json:{type:"boolean"},help:{type:"boolean",short:"h"}};async function Pe(e,t){let n=await be(u(e,"workdir")),o=new te(n.path);return{paths:o,workdir:n,store:new ee(o),values:e,positionals:t,json:e.json===!0}}function u(e,t){let n=e[t];return typeof n==="string"?n:void 0}function _e(e){return e?.code==="EPIPE"}process.stdout.on("error",(e)=>{if(_e(e))process.exit(0);throw e});function f(e){try{process.stdout.write(e)}catch(t){if(_e(t))process.exit(0);throw t}}function h(e){process.stderr.write(e.endsWith(`
|
|
3
|
+
import{parseArgs as Qh}from"util";import{chmod as wu}from"fs/promises";import{homedir as yu}from"os";import{join as St,resolve as bu}from"path";var Oe={Ok:0,Runtime:1,Usage:2,Guard:3};class f extends Error{name="UsageError"}class $e extends Error{severity;logKind;name="GuardError";constructor(e,t="hard",n="guard"){super(e);this.severity=t;this.logKind=n}}class lt extends Error{report;name="ReportedFailure";constructor(e,t){super(e);this.report=t}}function Ms(e){if(e instanceof f)return Oe.Usage;if(e instanceof $e)return Oe.Guard;return Oe.Runtime}var no=["workdir","api-base","api-token","auto-update"];function $n(e){return e.HOME||yu()}function Wt(e,t=Bun.env,n=process.cwd()){let o=e.trim();if(o==="~")return $n(t);if(o.startsWith("~/"))return St($n(t),o.slice(2));return bu(n,o)}function Ze(e=Bun.env){let t=e.XDG_CONFIG_HOME||St($n(e),".config");return St(t,"fui","config.json")}async function ut(e=Bun.env){let t=Ze(e),n=Bun.file(t);if(!await n.exists())return{};try{let o=await n.json();if(o&&typeof o==="object"&&!Array.isArray(o))return o}catch{}throw new f(`${t} is not a JSON object; fix or delete it`)}async function Ko(e,t=Bun.env){let n=Ze(t);return await Bun.write(n,`${JSON.stringify(e,null,2)}
|
|
4
|
+
`),await wu(n,384),n}function ku(e){return e.CLAUDE_CONFIG_DIR?St(e.CLAUDE_CONFIG_DIR,".claude.json"):St($n(e),".claude.json")}function $u(e,t){return[t.command,...Array.isArray(t.args)?t.args:[]].join(" ").includes("fui-mcp")||e.toLowerCase().includes("fui")}function Fs(e,t){if(!e||typeof e!=="object")return;for(let[n,o]of Object.entries(e)){let s=o?.env?.[t];if(typeof s==="string"&&s.trim()&&$u(n,o))return s}return}async function Us(e,t=Bun.env,n=process.cwd()){let o=ku(t),s=Bun.file(o);if(!await s.exists())return null;let r;try{r=await s.json()}catch{return null}let i=Fs(r.projects?.[n]?.mcpServers,e)??Fs(r.mcpServers,e);return i?{value:i,file:o}:null}async function vu(e=Bun.env,t=process.cwd()){let n=await Us("FUI_MCP_WORKDIR",e,t);return n?{path:Wt(n.value,e,t),file:n.file}:null}var Go=St(".fui-mcp","workspaces");async function Bs(e,t=Bun.env,n=process.cwd()){if(e)return{path:Wt(e,t,n),source:"flag",detail:"--workdir"};if(t.FUI_MCP_WORKDIR)return{path:Wt(t.FUI_MCP_WORKDIR,t,n),source:"env",detail:"FUI_MCP_WORKDIR"};let o=await ut(t);if(typeof o.workdir==="string"&&o.workdir)return{path:Wt(o.workdir,t,n),source:"config",detail:Ze(t)};let s=await vu(t,n);if(s)return{path:s.path,source:"mcp",detail:s.file};return{path:St($n(t),Go),source:"default",detail:"built-in"}}async function Ls(e,t,n,o){let s=n[e];if(s)return{value:s,source:"env",detail:e};let i=(await ut(n))[t];if(typeof i==="string"&&i)return{value:i,source:"config",detail:Ze(n)};let a=await Us(e,n,o);if(a)return{value:a.value,source:"mcp",detail:a.file};return{value:void 0,source:"missing",detail:`set ${e} or run: fui config set ${t} ...`}}async function oo(e=Bun.env,t=process.cwd()){let[n,o]=await Promise.all([Ls("FUI_API_BASE","api-base",e,t),Ls("FUI_API_TOKEN","api-token",e,t)]);return{base:n,token:o}}function Hs(e){return e?.code==="EPIPE"}process.stdout.on("error",(e)=>{if(Hs(e))process.exit(0);throw e});function k(e){try{process.stdout.write(e)}catch(t){if(Hs(t))process.exit(0);throw t}}function E(e){process.stderr.write(e.endsWith(`
|
|
10
5
|
`)?e:`${e}
|
|
11
|
-
`)}async function
|
|
12
|
-
`)
|
|
13
|
-
`}
|
|
14
|
-
`)}
|
|
15
|
-
|
|
16
|
-
`}
|
|
17
|
-
|
|
18
|
-
`)}
|
|
19
|
-
|
|
20
|
-
`)}
|
|
21
|
-
`
|
|
22
|
-
`)}
|
|
23
|
-
`
|
|
24
|
-
${n}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`)}},qe=[Kt,Jt];var Be=`
|
|
6
|
+
`)}async function ro(){return Bun.stdin.text()}async function vn(e,t){if(t&&e)throw new f("pass SQL as an argument or with -f, not both");let n;if(t){let o=Bun.file(t);if(!await o.exists())throw new f(`file not found: ${t}`);n=await o.text()}else if(e==="-")n=await ro();else if(e!==void 0)n=e;else throw new f('missing SQL: pass "<sql>", -f <file>, or - for stdin');if(!n.trim())throw new f("SQL is empty");return n}async function Pe(e,t,n){if(e!==void 0&&t!==void 0)throw new f(`pass ${n} or ${n}-env, not both`);if(t!==void 0){let s=Bun.env[t];if(!s)throw new f(`env var ${t} is empty or unset`);return s.trim()}if(e===void 0)return;if(e!=="-")throw new f(`${n} takes "-" (stdin) only; use ${n}-env VAR for env vars`);let o=(await ro()).trim();if(!o)throw new f(`${n}: stdin is empty`);return o}var Ws=60000,qs=400;function Js(e){return e.length<=qs?e:`${e.slice(0,qs)}\u2026`}async function Su(e){let t;try{t=(await e.text()).trim()}catch{return""}if(!t)return"";try{let n=JSON.parse(t);for(let o of["Message","message","error","ExceptionMessage"]){let s=n?.[o];if(typeof s==="string"&&s.trim())return` \u2014 ${Js(s.trim())}`}}catch{}return` \u2014 ${Js(t)}`}function et(e){return e.data||[]}class Vs{base;token;constructor(e,t){this.base=e;this.token=t}async request(e,t){let n;try{n=await fetch(`${this.base}${e}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8",Authorization:`Bearer ${this.token}`},body:t!==void 0?JSON.stringify(t):"{}",signal:AbortSignal.timeout(Ws)})}catch(o){let s=o?.name;if(s==="TimeoutError"||s==="AbortError")throw Error(`FUI API timeout: ${e} did not answer within ${Ws/1000}s`);throw Error(`FUI API network error: ${e} \u2014 ${o?.message??String(o)}`)}if(!n.ok)throw Error(`FUI API error: ${n.status} ${n.statusText}${await Su(n)}`);return await n.json()}async listProjects(){return et(await this.request("/fp/FUI_Dev_ProjectList"))}editProject(e){return this.request("/fp/FUI_Dev_ProjectEdit",{jsonObj:JSON.stringify(e)})}getProjectJson(e){return this.request(`/fp/obj/FUI_Dev_ProjectJson/${e}`,{})}async listProjectUserRights(e){return et(await this.request("/fp/FUI_Dev_UserRightList",{PID:e,MID:""}))}async searchUsers(e,t){return et(await this.request("/fp/FUI_Dev_UserSearch",{textsearch:e,useridlike:t}))}updateProjectUserRight(e,t,n){return this.request("/fp/FUI_Dev_UserRight_Update",{PID:e,MID:"",UserRight:t,UserList:n})}async getRuntimeModuleVersion(e,t,n){return et(await this.request("/fp/FUI_Runtime_ModuleVersionSelect",{domain:e,url1_ID:t,url2_ID:n}))}async listModules(e){return et(await this.request("/fp/FUI_Dev_modulelist",{pid:e}))}editModule(e,t){return this.request("/fp/FUI_Dev_ModuleEdit",{ProjectID:e,ModuleObj:JSON.stringify(t)})}deleteModule(e,t){return this.request("/fp/FUI_Dev_moduleDelete",{ProjectID:e,ModuleID:t})}async getModuleContent(e,t){let o=(await this.request(`/fp/obj/FUI_Dev_moduledata/module-load/${e}/${t}`,{})).data;if(!o.mHeader&&!o.mBody)E(`# warning: module-load ${e}/${t} returned empty mHeader and mBody`);return o}getModuleUI(e,t){return this.request(`/fp/obj/FUI_Dev_moduledata/module-ui/${e}/${t}`,{})}getModuleHtml(e,t){return this.request(`/fp/obj/FUI_Dev_moduledata/module-html/${e}/${t}`,{})}async getModuleScript(e,t){return(await this.request(`/fp/obj/FUI_Dev_moduledata/module-code/${e}/${t}`,{})).data?.FileContent||""}async getModuleCSS(e,t){return(await this.request(`/fp/obj/FUI_Dev_moduledata/module-css/${e}/${t}`,{})).data?.FileContent||""}updateModule(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-save/${e}/${t}`,n)}updateModuleUI(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-ui/${e}/${t}`,{FileContent:n})}updateModuleScript(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-code/${e}/${t}`,{FileContent:n})}updateModuleCSS(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-css/${e}/${t}`,{FileContent:n})}updateProjectData(e,t){return this.request(`/fp/obj/FUI_Dev_editorupdate/project/${e}/-`,{FileContent:JSON.stringify(t)})}async getModuleComponents(e,t){return et(await this.request("/fp/FUI_Dev_ComponentSelect",{ProjectID:e,...t&&{ModuleID:t}}))}updateComponent(e){return this.request("/fp/FUI_Dev_ComponentUpdate",e)}async getModuleDependencies(e,t){return et(await this.request("/fp/FUI_Dev_File/select",{ProjectID:e,ModuleID:t}))}async listFrameworkImportFiles(e){return et(await this.request("/fp/FUI_Dev_File/select",{frameWork:e}))}async listImportFiles(e,t){return et(await this.request("/fp/FUI_Dev_File/select",{...e&&{ProjectID:e},...t&&{ModuleID:t}}))}getFileContent(e){return this.request(`/fp/obj/FUI_Dev_File/getcontent/${e}`,{})}uploadFileContent(e,t){return this.request(`/fp/FUI_Dev_File/uploadcontent/${e}`,{FileContent:t})}insertImportFile(e){return this.request("/fp/FUI_Dev_File/insert",e)}updateImportFile(e,t){return this.request(`/fp/FUI_Dev_File/update/${e}`,t)}deleteImportFile(e){return this.request(`/fp/FUI_Dev_File/delete/${e}`,{})}}async function so(){let{base:e,token:t}=await oo();if(!e.value||!t.value){let n=[!e.value&&"FUI_API_BASE",!t.value&&"FUI_API_TOKEN"].filter(Boolean).join(", ");throw new f(`missing ${n}; set the env var, run fui config set api-base <url> / fui config set api-token -, or configure the Fui MCP server`)}return new Vs(e.value,t.value)}import{mkdir as Iu,rename as Cu}from"fs/promises";import{homedir as Ru}from"os";import{join as zo}from"path";var ju=new TextDecoder("utf-8",{ignoreBOM:!0});async function H(e){return ju.decode(await Bun.file(e).bytes())}async function pe(e){try{return await H(e)}catch{return null}}function Sn(e){return e?.code}var Pu=/^[A-Za-z0-9_-]+$/;function Eu(e=Bun.env){return e.FUI_MCP_SESSION_DIR||zo(e.HOME||Ru(),".fui-mcp","session")}function Tu(e=Bun.env){let t=e.FUI_SESSION||"default";if(!Pu.test(t))throw new f(`invalid FUI_SESSION "${t}": letters, digits, "_" or "-"`);return zo(Eu(e),`cli-${t}.json`)}class jn{file;constructor(e=Tu()){this.file=e}async read(){let e=await pe(this.file);if(!e)return{};try{let t=JSON.parse(e);return t&&typeof t==="object"&&!Array.isArray(t)?t:{}}catch{return{}}}async write(e){await Iu(zo(this.file,".."),{recursive:!0});let t=`${this.file}.${process.pid}.tmp`;await Bun.write(t,JSON.stringify(e,null,2)),await Cu(t,this.file)}async update(e){let t=await this.read();return e(t),await this.write(t),t}async getTarget(){return(await this.read()).target??null}async setTarget(e){let t={projectId:e.projectId,moduleId:e.moduleId,moduleDir:e.moduleDir,workspaceRoot:e.workspaceRoot,...e.target!==void 0?{target:e.target}:{}};await this.update((n)=>{n.target=t})}async clearTarget(){await this.update((e)=>{delete e.target})}async createApproval(e){let t={...e,token:crypto.randomUUID(),createdAt:new Date().toISOString()};return await this.update((n)=>{n.approval=t}),t}async getApproval(){return(await this.read()).approval??null}async clearApproval(){await this.update((e)=>{delete e.approval})}async markSkillLoaded(){await this.update((e)=>{e.skillLoadedAt=new Date().toISOString()})}async wasSkillLoaded(){return Boolean((await this.read()).skillLoadedAt)}async markSpVerified(e){await this.update((t)=>{let n=new Set(t.spVerified??[]);for(let o of e)n.add(o.toLowerCase());t.spVerified=[...n]})}async getVerifiedSpNames(){return new Set((await this.read()).spVerified??[])}}function qt(e=Bun.env){return["1","true","yes"].includes((e.FUI_MCP_STRICT??"").trim().toLowerCase())}import{readdir as Au}from"fs/promises";import{isAbsolute as Nu,join as Yo,relative as _u,sep as Du}from"path";var xu=/^[A-Za-z0-9._ -]+$/,Xo=20;function tt(e,t){if(!e||!xu.test(e)||/^\.+$/.test(e))throw new f(`invalid ${t} ${JSON.stringify(e)}: letters, digits, ".", "_", "-" and spaces only (used as a workspace folder name)`);return e}function Ou(e,t){let n=_u(e,t);if(!n||n.startsWith("..")||Nu(n))return null;let[o,s,r]=n.split(Du);if(!o||o.startsWith("_")||o.startsWith("."))return null;if(s==="modules"&&r)return{projectId:o,moduleId:r,moduleDir:Yo(e,o,"modules",r)};return{projectId:o}}async function Ks(e,t){try{return(await Au(e,{withFileTypes:!0})).filter((o)=>o.isDirectory()&&!(t&&(o.name.startsWith("_")||o.name.startsWith(".")))).map((o)=>o.name).sort()}catch{return[]}}function Gs(e){return e.length>Xo?[...e.slice(0,Xo),` ... and ${e.length-Xo} more`]:e}class Qo{paths;session;cwd;constructor(e,t,n=process.cwd()){this.paths=e;this.session=t;this.cwd=n}cwdTarget(){return Ou(this.paths.root,this.cwd)}async contextProjectId(){return this.cwdTarget()?.projectId??(await this.session.getTarget())?.projectId??void 0}async resolveProject(e){if(e)return{projectId:tt(e,"projectId"),source:"explicit"};let t=this.cwdTarget();if(t)return{projectId:t.projectId,source:"cwd"};let n=await this.session.getTarget();if(n?.projectId)return{projectId:n.projectId,source:"session"};throw new f(await this.noTargetMenu("project"))}async resolveProjectModule(e,t){if(e&&t)return tt(e,"projectId"),tt(t,"moduleId"),{projectId:e,moduleId:t,moduleDir:Yo(this.paths.root,e,"modules",t),source:"explicit"};if(e||t)throw new f("module commands need both -p <project> and -m <module>");let n=this.cwdTarget();if(n?.moduleId&&n.moduleDir)return{projectId:n.projectId,moduleId:n.moduleId,moduleDir:n.moduleDir,source:"cwd"};let o=await this.session.getTarget();if(o?.projectId&&o.moduleId&&o.moduleDir)return{projectId:o.projectId,moduleId:o.moduleId,moduleDir:o.moduleDir,source:"session"};throw new f(await this.noTargetMenu("module"))}async resolveModuleDir(e,t,n){if(e)return e;return(await this.resolveProjectModule(t,n)).moduleDir}async noTargetMenu(e){let t=await Ks(this.paths.root,!0),n=e==="project"?"no project: pass -p <project>, cd into a workspace project, or run: fui use <project>/<module>":"no module: pass -p <project> -m <module>, cd into a module folder, or run: fui use <project>/<module>";if(t.length===0)return`${n}
|
|
7
|
+
workspace ${this.paths.root} has no projects; run: fui project list`;if(e==="module"&&t.length===1){let o=await Ks(Yo(this.paths.root,t[0],"modules"),!1);if(o.length>0)return[n,...Gs(o.map((s)=>` fui use ${t[0]}/${s}`))].join(`
|
|
8
|
+
`)}return[n,...Gs(t.map((o)=>` -p ${o}`))].join(`
|
|
9
|
+
`)}}function Zo(e){return new TextEncoder().encode(e).toBase64()}function In(e){try{return new TextDecoder("utf-8",{fatal:!0}).decode(Uint8Array.fromBase64(e.trim()))}catch{throw new f("token is not valid Base64 UTF-8")}}function Jt(e,t,n,o){return Zo(`Server=${e}; Database=${t}; UID=${n}; PWD=${o}; Encrypt=True; TrustServerCertificate=True;`)}function zs(e){let t=new Map;for(let n of e.split(";")){let o=n.indexOf("=");if(o<=0)continue;t.set(n.slice(0,o).trim().toLowerCase(),n.slice(o+1).trim())}return t}function Mu(e){let t=e.trim();if(/^postgres(?:ql)?:\/\//i.test(t))return"postgres";if(/^(?:mysql|mariadb):\/\//i.test(t))return"mysql";if(/^(?:sqlite|file):/i.test(t))return"sqlite";let n=zs(t);if(n.has("server")||n.has("data source"))return"mssql";throw new f("unsupported connection: expected postgres://, mysql://, mariadb://, sqlite:/file: URL or MSSQL Server=...; string")}function Cn(e){let t=In(e);return{engine:Mu(t),plain:t}}function io(e){let{engine:t,plain:n}=Cn(e);if(t==="mssql"){let s=zs(n);return{engine:t,server:s.get("server")??s.get("data source")??"",database:s.get("database")??s.get("initial catalog")??"",user:s.get("uid")??s.get("user id")??""}}if(t==="sqlite")return{engine:t,server:"",database:n.replace(/^(?:sqlite|file):(?:\/\/)?/i,""),user:""};let o=new URL(n);return{engine:t,server:o.port?`${o.hostname}:${o.port}`:o.hostname,database:decodeURIComponent(o.pathname.replace(/^\//,"")),user:decodeURIComponent(o.username)}}import{join as Ju}from"path";import{mkdir as Fu,readdir as Lu,writeFile as Uu}from"fs/promises";import{basename as Xs,dirname as Qs,extname as Zs,isAbsolute as Bu,join as er,relative as ei,sep as Hu}from"path";var Ys=50;function ao(e=new Date){let t=(n)=>String(n).padStart(2,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}_${t(e.getHours())}${t(e.getMinutes())}${t(e.getSeconds())}`}function Wu(e,t,n={}){let o=ei(e.root,t),r=o!==""&&!o.startsWith("..")&&!Bu(o)?o:er("_external",Xs(t)),i=Zs(r),a=Xs(r,i),c=n.stamp??ao(),l=n.suffix?`_${n.suffix}`:"";return er(e.historyRoot(),Qs(r),`${a}_${c}${l}${i}`)}async function qu(e,t){let n=Zs(e),o=e.slice(0,e.length-n.length);for(let r=1;r<=Ys;r++){let i=r===1?e:`${o}_${r}${n}`;try{return await Uu(i,t,{encoding:"utf-8",flag:"wx"}),i}catch(a){if(Sn(a)!=="EEXIST")throw a}}let s=`${o}_${Ys}${n}`;return await Bun.write(s,t),s}async function he(e,t,n={}){let o=n.content;if(o===void 0)try{o=await H(t)}catch(r){let i=Sn(r);if(i==="ENOENT"||i==="ENOTDIR")return null;throw r}let s=Wu(e,t,n);return await Fu(Qs(s),{recursive:!0}),qu(s,o)}function jt(e,t){let n=ei(e.root,t);return(n.startsWith("..")?t:n).split(Hu).join("/")}async function co(e,t,n={}){let o=n.stamp??ao(),s=new Set(n.skipDirNames??["_preview"]),r=[];async function i(a){let c;try{c=await Lu(a,{withFileTypes:!0})}catch{return}for(let l of c){let u=er(a,l.name);if(l.isDirectory()){if(!s.has(l.name))await i(u)}else if(l.isFile()){let d=await he(e,u,{suffix:n.suffix,stamp:o});if(d)r.push(d)}}}return await i(t),r}var Vu=/^[A-Za-z0-9_-]+$/,Ku=/^[A-Za-z0-9_.-]+$/;function Vt(e){if(!Vu.test(e))throw new f(`invalid alias "${e}": use letters, digits, "_" or "-"`);return e}function tr(e){if(!Ku.test(e)||e.startsWith("_")||e.startsWith("."))throw new f(`invalid project id "${e}"`);return e}function ti(e){let t=e.split("/");if(t.length===1)return{alias:t[0]};if(t.length===2&&t[0]&&t[1])return{project:t[0],alias:t[1]};throw new f(`invalid --db "${e}": use <alias> or <project>/<alias>`)}function me(e,t){let n=t.toLowerCase();return Object.keys(e.connections).find((o)=>o.toLowerCase()===n)??null}class nt{paths;constructor(e){this.paths=e}async load(e){let t=this.paths.connectionsFile(e);if(!await Bun.file(t).exists())return{connections:{}};let o=await H(t);if(!o.trim())return{connections:{}};let s;try{s=JSON.parse(o)}catch(i){throw Error(`${t} is not valid JSON (${i.message}); fix it by hand before writing`)}if(!s||typeof s!=="object"||Array.isArray(s))throw Error(`${t} must be an object { default, connections }`);let r=s;if(r.connections===void 0)return{...r,connections:{}};if(!r.connections||typeof r.connections!=="object"||Array.isArray(r.connections))throw Error(`${t}: "connections" must be an object of alias -> config`);return r}async write(e,t){let n=this.paths.connectionsFile(e);await he(this.paths,n),await Bun.write(n,JSON.stringify(t,null,2))}async save(e,t,n,o){let s=await this.load(e),r=me(s,t)??Vt(t);if(s.connections[r]=n,o||!s.default||!me(s,s.default))s.default=r;return await this.write(e,s),r}async setDefault(e,t){let n=await this.load(e),o=me(n,t);if(!o)throw new f(this.menu(e,n,`no connection "${t}" in project "${e}"`));return n.default=o,await this.write(e,n),o}async loadLegacy(e){let t=await pe(Ju(this.paths.dbDir(e),"config.json"));if(!t)return null;try{let n=JSON.parse(t);if(!n||typeof n!=="object"||Array.isArray(n))return null;let o=n;return typeof o.dbToken==="string"&&o.dbToken?o:null}catch{return null}}async listLegacyProjects(){let e=[];try{for await(let t of new Bun.Glob("*/_db/config.json").scan({cwd:this.paths.root,onlyFiles:!0})){let n=t.split(/[\\/]/)[0];if(!n.startsWith("_"))e.push(n)}}catch(t){if(t.code!=="ENOENT")throw t}return e.sort()}async listProjects(){let e=[],t=new Bun.Glob("*/_db/_connections.json");try{for await(let n of t.scan({cwd:this.paths.root,onlyFiles:!0})){let o=n.split(/[\\/]/)[0];if(!o.startsWith("_"))e.push(o)}}catch(n){if(n.code!=="ENOENT")throw n}return e.sort()}async resolveProject(e){let t=e.db?ti(e.db):void 0,n=t?.project??e.project??Bun.env.FUI_PROJECT??e.contextProject;if(n)return tr(n);let o=await this.listProjects();if(t){let r=[];for(let i of o)if(me(await this.load(i),t.alias))r.push(i);if(r.length===1)return r[0];if(r.length===0)throw new f(`no project has connection "${t.alias}"; run: fui db list`);throw new f(`connection "${t.alias}" exists in several projects; use one of:
|
|
10
|
+
`+r.map((i)=>` --db ${i}/${t.alias}`).join(`
|
|
11
|
+
`))}let s=[];for(let r of o)if(Object.keys((await this.load(r)).connections).length>0)s.push(r);if(s.length===1)return s[0];if(s.length===0)throw new f("no DB connections in workspace; run: fui db add <alias> -p <project> --token -");throw new f(`several projects have connections; pass -p <project> or --db <project>/<alias>:
|
|
12
|
+
`+s.map((r)=>` -p ${r}`).join(`
|
|
13
|
+
`))}async resolve(e){let t=await this.resolveProject(e),n=await this.load(t);if(Object.keys(n.connections).length===0)throw new f(`project "${t}" has no DB connections; run: fui db add <alias> -p ${t}`);let{alias:s,note:r}=await this.pickAlias(t,n,e),i=n.connections[s];if(!i.dbToken)throw new f(`connection ${t}/${s} has no dbToken; re-add it`);let{engine:a}=Cn(i.dbToken);if(a==="mssql"&&!i.apiDomain)throw new f(`connection ${t}/${s} is MSSQL and needs apiDomain; re-add with --api-domain`);return{projectId:t,alias:s,config:i,engine:a,dbToken:i.dbToken,note:r}}async pickAlias(e,t,n){let o=Object.keys(t.connections);if(n.db){let{alias:r}=ti(n.db),i=me(t,r);if(!i)throw new f(this.menu(e,t,`no connection "${r}" in project "${e}"`));return{alias:i,note:null}}if(o.length===1)return{alias:o[0],note:null};if(n.objectName){let r=Array.isArray(n.objectName)?n.objectName:[n.objectName],i=r[0],a=r.map((l)=>l.toLowerCase()),c=[];for(let l of o){let u=await this.knownObjectNames(e,l);if(a.some((d)=>u.has(d)))c.push(l)}if(c.length===1)return{alias:c[0],note:`connection "${c[0]}" (inferred from "${i}")`};throw new f(this.menu(e,t,c.length===0?`cannot infer the connection for "${i}": it is in no connection of project "${e}"`:`"${i}" exists in several connections (${c.join(", ")}); pick one`))}if(n.write)throw new f(this.menu(e,t,`project "${e}" has ${o.length} connections; this command needs --db`));let s=t.default?me(t,t.default):null;if(s)return{alias:s,note:`using default connection "${s}" (${o.length} connections)`};throw new f(this.menu(e,t,`project "${e}" has several connections and no default`))}async knownObjectNames(e,t){let n=new Set,o=Bun.file(this.paths.schemaFile(e,t));if(await o.exists())try{let s=await o.json();for(let r of[s.tables,s.procedures].flatMap((i)=>Array.isArray(i)?i:[])){let i=typeof r==="string"?/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(r):null;if(i)n.add(i[1].toLowerCase())}}catch{}for(let s of await this.localSqlNames(e,t))n.add(s.toLowerCase());return n}async localSqlNames(e,t){let n=[];try{for await(let o of new Bun.Glob("*.sql").scan({cwd:this.paths.connDir(e,t)}))n.push(o.slice(0,-4))}catch(o){if(o.code!=="ENOENT")throw o}return n.sort()}menu(e,t,n){let o=Object.keys(t.connections).map((s)=>` --db ${e}/${s}${t.default===s?" (default)":""}`);return[n,...o].join(`
|
|
14
|
+
`)}}import{join as It}from"path";class Rn{root;constructor(e){this.root=e}projectDir(e){return It(this.root,e)}dbDir(e){return It(this.projectDir(e),"_db")}connectionsFile(e){return It(this.dbDir(e),"_connections.json")}connDir(e,t){return It(this.dbDir(e),t)}schemaFile(e,t){return It(this.connDir(e,t),"schema.json")}spFile(e,t,n){return It(this.connDir(e,t),`${n}.sql`)}historyRoot(){return It(this.root,"_history")}}var ni={project:{type:"string",short:"p"},db:{type:"string"},workdir:{type:"string"},json:{type:"boolean"},"no-header":{type:"boolean"},filter:{type:"string"},where:{type:"string",multiple:!0},fields:{type:"string"},help:{type:"boolean",short:"h"}};async function oi(e,t){let n=await Bs(h(e,"workdir")),o=new Rn(n.path),s=e.json===!0,r={json:s,header:e["no-header"]!==!0,filter:h(e,"filter"),where:Gu(e.where),fields:h(e,"fields")?.split(",").map((c)=>c.trim()).filter(Boolean)},i=new jn,a=new Qo(o,i);return{paths:o,session:i,targets:a,workdir:n,store:new nt(o),values:e,positionals:t,json:s,render:r}}function Gu(e){if(e===void 0)return;return(Array.isArray(e)?e:[e]).map((t)=>{let n=String(t),o=n.indexOf("=");if(o<=0)throw new f(`--where expects column=value, got "${n}"`);return[n.slice(0,o),n.slice(o+1)]})}function h(e,t){let n=e[t];return typeof n==="string"?n:void 0}async function z(e){return{store:e.store,session:e.session,project:h(e.values,"project"),contextProject:await e.targets.contextProjectId()}}function O(e){let t;return{paths:e.paths,session:e.session,targets:e.targets,api:()=>t??=so()}}function zu(e){if(e===null||e===void 0)return"NULL";if(e instanceof Date)return e.toISOString();if(e instanceof Uint8Array)return`0x${e.toHex()}`;return(typeof e==="object"?JSON.stringify(e):String(e)).replace(/\\/g,"\\\\").replace(/\t/g,"\\t").replace(/\r/g,"\\r").replace(/\n/g,"\\n")}function si(e,t){let n=[...new Set(e.flatMap((s)=>Object.keys(s)))],o=e.map((s)=>n.map((r)=>zu(s[r])));return t?[n,...o]:o}function Xu(e,t=!0){if(e.length===0)return"";return`${si(e,t).map((n)=>n.join("\t")).join(`
|
|
15
|
+
`)}
|
|
16
|
+
`}function Yu(e,t=!0){if(e.length===0)return"";let n=si(e,t),o=n[0].map((r,i)=>Math.max(...n.map((a)=>Bun.stringWidth(a[i]))));return`${n.map((r)=>r.map((i,a)=>a===r.length-1?i:i+" ".repeat(o[a]-Bun.stringWidth(i))).join(" ")).join(`
|
|
17
|
+
`)}
|
|
18
|
+
`}function nr(e){return`${JSON.stringify(e,(t,n)=>typeof n==="bigint"?n.toString():n)}
|
|
19
|
+
`}function ri(e){return e===null||e===void 0?"":typeof e==="object"?JSON.stringify(e):String(e)}function Qu(e,{filter:t,where:n,fields:o}){let s=[...new Set(e.flatMap((a)=>Object.keys(a)))],r=(a)=>{let c=s.find((l)=>l.toLowerCase()===a.toLowerCase());if(!c&&e.length>0)throw new f(`unknown column "${a}"; columns: ${s.join(", ")}`);return c},i=e;if(t){let a=t.toLowerCase();i=i.filter((c)=>Object.values(c).some((l)=>ri(l).toLowerCase().includes(a)))}for(let[a,c]of n??[]){let l=r(a);if(l)i=i.filter((u)=>ri(u[l]).toLowerCase()===c.toLowerCase())}if(o?.length){let a=o.map(r).filter((c)=>Boolean(c));i=i.map((c)=>Object.fromEntries(a.map((l)=>[l,c[l]])))}return i}function X(e,t){let{json:n,header:o,isTty:s=process.stdout.isTTY===!0}=t,r=Qu(e,t);if(n)return nr(r);return s?Yu(r,o):Xu(r,o)}function ii(e,t){let n=t>0&&e.length>t;return{rows:n?e.slice(0,t):e,total:e.length,truncated:n}}import{readdir as ed}from"fs/promises";import{join as ui}from"path";function dt(e){return e.trim().replace(/^https?:\/\//i,"").replace(/\/.*$/,"").toLowerCase()}function Ee(e){let t=dt(e);return`${/^(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/.test(t)?"http":"https"}://${t}`}function Zu(e){try{let t=JSON.parse(e),n=t.Message??t.message??t.error??t.ExceptionMessage;if(typeof n==="string"&&n)return n.slice(0,400)}catch{}return e.slice(0,400)}async function ai(e,t,n,o){let s;try{s=await fetch(`${e}${t}`,{method:"POST",headers:{"Content-Type":"application/json",authorization:`Bearer ${n}`},body:JSON.stringify({sqlcmd:o}),signal:AbortSignal.timeout(30000)})}catch(i){if(i instanceof Error&&i.name==="TimeoutError")throw Error("tAPI timed out after 30s; narrow the query with TOP/WHERE");throw i}let r=await s.text();if(!s.ok)throw Error(`tAPI HTTP ${s.status}: ${Zu(r)||s.statusText}`);try{return JSON.parse(r)}catch{throw Error(`tAPI returned non-JSON: ${r.slice(0,400)}`)}}class ot{origin;dbToken;engine="mssql";constructor(e,t){this.origin=e;this.dbToken=t}async query(e){return(await ai(this.origin,"/help/_sql_execute",this.dbToken,e)).data??[]}exec(e){return ai(this.origin,"/help/_sql_execute_NonQuery",this.dbToken,e)}async close(){}}async function or(e,t,n,o,s){let r=`${e}/help/${t}/${n?"auth/":""}${o}`,i={};if(!n&&s)i.authorization=`Bearer ${s}`;let a=await fetch(r,{headers:i,signal:AbortSignal.timeout(15000)});return{url:r,status:a.status,ok:a.ok,text:await a.text()}}var td=20000;async function Pn(e,t,n,o){if(!t)throw new f('no userToken: run fui db user-token first (a developer token with access to the permission database, alias "acc")');let s=await fetch(`${Ee(e)}/acc/${n}`,{method:"POST",headers:{"Content-Type":"application/json",authorization:`Bearer ${t}`},body:JSON.stringify(o),signal:AbortSignal.timeout(td)}),r=await s.json().catch(()=>null);if(!s.ok||r&&r.Message&&!r.data)throw Error(r?.Message??`HTTP ${s.status}: ${s.statusText}`);return r??{}}async function di(e){try{return(await ed(e,{withFileTypes:!0})).filter((t)=>t.isDirectory()&&!t.name.startsWith("_")).map((t)=>t.name)}catch{return[]}}async function rr(e){let t=[];for(let n of await di(e.paths.root)){let o=await e.load(n);for(let[s,r]of Object.entries(o.connections))if(r?.userToken)t.push({projectId:n,alias:s,userToken:r.userToken,apiDomain:r.apiDomain?dt(r.apiDomain):""})}return t}function sr(e){let t=new Map;for(let n of e){let o=t.get(n.userToken)??[];o.push({projectId:n.projectId,alias:n.alias,apiDomain:n.apiDomain??""}),t.set(n.userToken,o)}return[...t.entries()].map(([n,o])=>({token:n,sources:o}))}function ci(e){return e.map((t)=>`${t.projectId}/${t.alias}`).join(", ")}function pi(e){return e.map((t)=>`${t.projectId}/${t.alias} (${t.apiDomain||"?"})`).join(", ")}function mi(e){return new Bun.CryptoHasher("sha256").update(e).digest("hex").slice(-8)}function fi(e,t,n){let o=t.split("/").filter(Boolean);if(o.length===2)return{projectId:o[0],alias:o[1]};if(o.length===1)return{projectId:e,alias:o[0]};throw new f(`${n} must be "<project>/<alias>" or "<alias>" (a connection of this project); got "${t}"`)}async function hi(e,t,n){let{projectId:o,alias:s}=fi(t,n,"--from"),r=await e.load(o),i=me(r,s);if(!i)throw new f(`no connection "${s}" in project "${o}"; see: fui db list -p ${o}`);let a=r.connections[i].userToken;if(!a)throw new f(`connection ${o}/${i} has no userToken to borrow; set one there first: fui db user-token -p ${o} --db ${i} ...`);return{userToken:a,source:`${o}/${i}`}}async function nd(e,t){let n=await pe(ui(e.projectDir(t),"project.json")),o;try{o=n===null?null:JSON.parse(n)}catch{return null}if(!o||typeof o!=="object")return null;let s=o,i=(s.data&&typeof s.data==="object"&&!Array.isArray(s.data)?s.data:s).apiDomain;if(typeof i!=="string"||!i.trim())return null;return dt(i)}async function ir(e,t,n){if(n){let o=me(await e.load(t),n);if(o)return e.resolve({project:t,db:o})}return e.resolve({project:t})}async function lo(e,t,n){let o=n.apiDomain?dt(n.apiDomain):void 0;if(o&&n.userToken)return{apiDomain:o,userToken:n.userToken,note:null};try{let{alias:i,config:a}=await ir(e,t,n.apiName),c=o??(a.apiDomain?dt(a.apiDomain):a.apiDomain),l=n.userToken??a.userToken;if(c&&l)return{apiDomain:c,userToken:l,note:`using apiDomain + userToken of connection "${i}" (project ${t})`}}catch{}let s=o??await nd(e.paths,t);if(!s)throw new f(`cannot tell the apiDomain of the acc system for project "${t}"; pass --api-domain <host> (and a userToken if the project has no connection), or run: fui project sync -p ${t}`);if(n.userToken)return{apiDomain:s,userToken:n.userToken,note:null};let r=sr((await rr(e)).filter((i)=>i.apiDomain===s));if(r.length===1){let i=r[0];return{apiDomain:s,userToken:i.token,note:`project "${t}" has no connection; borrowing the userToken of ${ci(i.sources)} (same apiDomain ${s})`}}if(r.length===0)throw new f(`no userToken for apiDomain "${s}" in the workspace; pass --user-token-env VAR, or run fui db user-token on a project connected to that domain`);throw new f([`${r.length} different userTokens share apiDomain "${s}"; not picking one (a wrong token lists another system's data that still looks valid)`,"sources (one line per distinct token):",...r.map((i)=>` ${ci(i.sources)}`),"pass --user-token-env VAR with the token of the source you mean"].join(`
|
|
20
|
+
`))}async function En(e){return(await Pn(e.apiDomain,e.userToken,"SM_Modules_ManagerSelect",{})).data??[]}function ar(e){return e.map((t)=>({apiName:String(t.APIName??""),sqlServer:String(t.SQLServer??""),database:String(t.DatabaseName??""),enable:t.Enable===!1?"no":"yes",moduleName:String(t.ModuleName??"")}))}function li(e){let t;try{t=In(e)}catch{return null}let n=/(?:^|;)\s*UID\s*=\s*([^;]*)/i.exec(t)?.[1]?.trim(),o=/(?:^|;)\s*PWD\s*=\s*([^;]*)/i.exec(t)?.[1]?.trim();if(!n||!o)return null;return{uid:n,password:o}}async function gi(e,t,n,o){let s=async(a,c)=>{let l=await e.load(a),u=me(l,c);if(!u)throw new f(`no connection "${c}" in project "${a}"; see: fui db list -p ${a}`);let d=li(l.connections[u].dbToken??"");if(!d)throw new f(`the dbToken of ${a}/${u} has no UID/PWD to borrow (not a standard connection string); use fui db add with your own token`);return{creds:d,source:`${a}/${u}`}};if(n){let{projectId:a,alias:c}=fi(t,n,"--borrow-from");return s(a,c)}let r=[];for(let a of await di(e.paths.root)){let c=await e.load(a);for(let[l,u]of Object.entries(c.connections)){if(u.apiDomain!==o||!u.dbToken)continue;let d=li(u.dbToken);if(d)r.push({source:`${a}/${l}`,creds:d})}}let i=new Map;for(let a of r){let c=JSON.stringify([a.creds.uid,a.creds.password]);if(!i.has(c))i.set(c,a)}if(i.size===1)return[...i.values()][0];if(i.size===0)throw new f(`no connection in the workspace uses apiDomain "${o}" to borrow a SQL login from; use fui db add <alias> --api-domain ${o} --token -`);throw new f([`${i.size} different SQL logins on apiDomain "${o}"; not picking one (each database group has its own login)`,"sources:",...[...i.values()].map((a)=>` --borrow-from ${a.source}`)].join(`
|
|
21
|
+
`))}async function wi(e,t){let n,o;try{let i=me(await e.load(t),"acc");if(!i)throw Error("no acc alias");let a=await e.resolve({project:t,db:i});n=Ee(a.config.apiDomain),o=a.dbToken}catch{throw new f(`cannot compute a new ModuleID: project "${t}" has no connection to the permission database (alias "acc"). ModuleID must be MAX(ModuleID)+1 over ALL of acc.tblModules. connect it: fui db add-by-name acc, or pass --module-id <n>`)}let s=await new ot(n,o).query("SELECT ISNULL(MAX(ModuleID), 0) + 1 AS NextID FROM tblModules"),r=Number(s[0]?.NextID);if(!Number.isInteger(r)||r<=0)throw Error(`could not read MAX(ModuleID)+1 from acc.tblModules (got ${JSON.stringify(s[0])})`);return r}async function cr(e,t,n,o){try{let s=ui(e.dbDir(t),"rights.json"),r={};try{r=JSON.parse(await pe(s)??"")}catch{}let i=Object.keys(r).find((c)=>c.toLowerCase()===n.toLowerCase())??n,a=r[i]??{};r[i]={...a,...o.system?{systemRights:o.system,systemFetchedAt:new Date().toISOString()}:{},...o.functions?{functionRights:o.functions,functionFetchedAt:new Date().toISOString()}:{}},await Bun.write(s,JSON.stringify(r,null,2))}catch{}}import{mkdir as Wd,rename as qd,unlink as Jd}from"fs/promises";import{dirname as Vd}from"path";var{SQL:od}=globalThis.Bun;class lr{engine;sql;constructor(e,t){this.engine=e;this.sql=new od(t)}async query(e){return[...await this.sql.unsafe(e)]}async exec(e){return{count:(await this.sql.unsafe(e)).count??null}}close(){return this.sql.close()}}function ur(e){if(e.engine==="mssql")return new ot(Ee(e.config.apiDomain),e.dbToken);return new lr(e.engine,In(e.dbToken))}async function ve(e,t){let n=ur(e);try{return await t(n)}finally{await n.close()}}import{mkdir as jd,readdir as Id,unlink as Cd}from"fs/promises";import{dirname as Rd,join as Pd}from"path";var bi=`
|
|
28
22
|
SELECT
|
|
29
23
|
o.type AS ObjectTypeCode, o.name AS ObjectName, c.name AS ColumnName, t.name AS DataType,
|
|
30
24
|
c.max_length, c.precision, c.scale, c.is_nullable, c.is_identity,
|
|
@@ -48,7 +42,7 @@ WHERE o.is_ms_shipped = 0 AND o.type IN ('U','V') AND s.name NOT IN ('sys','INFO
|
|
|
48
42
|
AND o.name NOT LIKE 'sys%' AND o.name NOT LIKE 'dt[_]%'
|
|
49
43
|
AND o.name NOT LIKE 'queue[_]%' AND o.name NOT LIKE 'filestream[_]%' AND o.name NOT LIKE 'cdc.%'
|
|
50
44
|
ORDER BY ObjectTypeCode, ObjectName
|
|
51
|
-
`.trim(),
|
|
45
|
+
`.trim(),ki=`
|
|
52
46
|
SELECT
|
|
53
47
|
o.type AS ObjectTypeCode, o.name AS ObjectName,
|
|
54
48
|
p.name AS ParameterName, pt.name AS ParameterType,
|
|
@@ -63,31 +57,340 @@ WHERE o.is_ms_shipped = 0 AND o.type IN ('P','FN','TF','IF') AND s.name NOT IN (
|
|
|
63
57
|
AND o.name NOT IN ('fn_diagramobjects','sp_alterdiagram','sp_creatediagram','sp_dropdiagram',
|
|
64
58
|
'sp_helpdiagramdefinition','sp_helpdiagrams','sp_renamediagram','sp_upgraddiagrams')
|
|
65
59
|
ORDER BY ObjectTypeCode, ObjectName
|
|
66
|
-
`.trim(),
|
|
60
|
+
`.trim(),rd=new Set(["nvarchar","nchar","ntext"]);function $i(e,t,n,o){let s=e.toLowerCase();if(t===-1)return`${e}(max)`;if(s==="decimal"||s==="numeric")return o!==void 0?`${e}(${n},${o})`:`${e}(${n})`;let r=rd.has(s)?t/2:t;return r>0?`${e}(${r})`:e}function sd(e){let t=[String(e.ColumnName),$i(String(e.DataType??""),Number(e.max_length??0),Number(e.precision??0),Number(e.scale??0)),e.is_nullable?"NULL":"NOT NULL"];if(e.IsPrimaryKey)t.push("PRIMARY KEY");if(e.is_identity)t.push("IDENTITY");if(e.ForeignKeyName)t.push(`REFERENCES ${String(e.RefTable??"")}(${String(e.RefColumn??"")})`);return t.join(" ")}function dr(e,t){if(t)return"Function";if(/^spAPIFILE_/i.test(e))return"API File";if(/^spAPI_/i.test(e))return"API";return"Procedure"}function vi(e,t){let n=new Map;for(let s of e){let r=String(s.ObjectName??"");if(!r)continue;let i=n.get(r);if(!i)i={keyword:String(s.ObjectTypeCode??"").trim()==="V"?"VIEW":"TABLE",columns:[]},n.set(r,i);if(s.ColumnName)i.columns.push(sd(s))}let o=new Map;for(let s of t){let r=String(s.ObjectName??""),i=String(s.ObjectTypeCode??"").trim();if(!r||!["P","FN","TF","IF"].includes(i))continue;let a=o.get(r);if(!a)a={keyword:i==="P"?"PROCEDURE":"FUNCTION",params:[]},o.set(r,a);if(s.ParameterName){let c=$i(String(s.ParameterType??""),Number(s.ParameterMaxLength??0),Number(s.ParameterPrecision??0));a.params.push(`@${String(s.ParameterName).replace(/^@/,"")} ${c}`)}}return{fetchedAt:new Date().toISOString(),tables:[...n].map(([s,r])=>`CREATE ${r.keyword} ${s} (${r.columns.join(",")});`),procedures:[...o].map(([s,r])=>`CREATE ${r.keyword} ${s} (${r.params.join(",")});`)}}function Ct(e){if(!e||typeof e!=="object")return!1;let t=e,n=(o)=>Array.isArray(o)&&o.every((s)=>typeof s==="string");return n(t.tables)&&n(t.procedures)}function Si(e){return/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(e)?.[1]??null}var pr=["Table","View","API","API File","Procedure","Function"];function mr(e){if(/^CREATE TABLE/i.test(e))return"Table";if(/^CREATE VIEW/i.test(e))return"View";return null}function fr(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+)/i);return t?dr(t[2],t[1].toUpperCase()==="FUNCTION"):null}var id=/^(\S+)\s+(\S+)\s+(NULL|NOT NULL)(\s+PRIMARY KEY)?(\s+IDENTITY)?(?:\s+REFERENCES\s+(\S+)\((\S+)\))?$/,yi=/^@(\S+)\s+(\S+)$/;function ji(e){let t=[],n=0,o="";for(let s of e){if(s==="(")n++;else if(s===")")n--;if(s===","&&n===0)t.push(o),o="";else o+=s}if(o.trim())t.push(o);return t}function hr(e){let t=e.match(/^CREATE (TABLE|VIEW) (\S+) \((.*)\);?$/s),n=t?ji(t[3]):null;if(t&&n.every((s)=>id.test(s.trim())))return{name:t[2],objectType:t[1]==="VIEW"?"View":"Table",count:n.length};let o=Si(e);if(!o)throw Error(`Cannot parse table DDL: ${e.slice(0,120)}`);return{name:o,objectType:mr(e)??"Table",count:0,opaque:!0}}function uo(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+) \((.*)\);?$/s),n=t?ji(t[3]):null;if(t&&n.every((r)=>yi.test(r.trim())))return{name:t[2],objectType:dr(t[2],t[1]==="FUNCTION"),count:n.length,parameters:n.map((r)=>yi.exec(r.trim())[1])};let o=Si(e);if(!o)throw Error(`Cannot parse proc DDL: ${e.slice(0,120)}`);let s=/^CREATE\s+FUNCTION/i.test(e);return{name:o,objectType:fr(e)??dr(o,s),count:0,parameters:[],opaque:!0}}import{mkdir as md,stat as fd}from"fs/promises";import{basename as hd,dirname as gd,join as wr,resolve as Ri,sep as wd}from"path";import{mkdir as ad,readdir as cd}from"fs/promises";import{join as Kt,sep as ld}from"path";var Gt="_publishState.json";function ke(e){return e.split(ld).join("/")}function Rt(e){return`sha1:${new Bun.CryptoHasher("sha1").update(e.replace(/\r\n/g,`
|
|
61
|
+
`)).digest("hex")}`}async function po(e){try{let t=JSON.parse(await H(Kt(e,Gt)));return t&&typeof t==="object"&&!Array.isArray(t)?t:{}}catch{return{}}}async function Ii(e,t){await ad(e,{recursive:!0}),await Bun.write(Kt(e,Gt),JSON.stringify(t,null,2))}async function Te(e,t){if(t.length===0)return;let n=await po(e),o=!1;for(let s of t){let r=ke(s);try{n[r]=Rt(await H(Kt(e,r))),o=!0}catch{if(r in n)delete n[r],o=!0}}if(o)await Ii(e,n)}async function Ci(e,t){if(t.length===0)return;let n=await po(e),o=!1;for(let s of t){let r=ke(s);if(r in n)delete n[r],o=!0}if(o)await Ii(e,n)}var ud=["module.json","header.html","body.html","script.js","style.css","project.json"];async function dd(e){try{return(await cd(Kt(e,"components"),{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.toLowerCase().endsWith(".vue")).map((n)=>ke(Kt("components",n.name)))}catch{return[]}}async function Tn(e){let t=await po(e),n=[];for(let o of[...ud,...await dd(e)]){let s;try{s=await H(Kt(e,o))}catch{continue}let r=t[o];if(r===void 0)n.push({relPath:o,reason:"new"});else if(r!==Rt(s))n.push({relPath:o,reason:"changed"})}return n}async function zt(e,t,n){let o=(await po(e))[ke(t)];return o!==void 0&&Rt(n)===o}var pd={"module.json":"fui module publish json","header.html":"fui module publish html","body.html":"fui module publish html","script.js":"fui module publish script","style.css":"fui module publish css"};function mo(e){if(e.startsWith("components/"))return"fui component update";return pd[e]??"fui module publish json"}function gr(e){if(e.length===0)return null;return`unpublished local files: ${e.map((n)=>`${n.relPath} (${mo(n.relPath)})`).join(", ")}`}var yd=172800000,bd=new Set(["_moduleInfo.json","_projectInfo.json","_modules.json","_components.json","_imports.json",Gt]);function Pi(e){return bd.has(hd(e))}function kd(e,t){return e.replace(/\r\n/g,`
|
|
62
|
+
`)===t.replace(/\r\n/g,`
|
|
63
|
+
`)}function $d(e,t,n){let o=Ri(e),s=Ri(t);if(s!==o&&!s.startsWith(o+wd))throw Error(`server file path escapes the target folder: ${JSON.stringify(n)} -> ${s} (must be inside ${o}); nothing written`)}async function Ae(e,t,n){let o=[],s=[],r=[],i=ao(),a=[];for(let c of Object.keys(n)){let l=ke(c);$d(t,wr(t,l),l)}for(let[c,l]of Object.entries(n)){let u=ke(c),d=wr(t,u),p=await pe(d);if(p!==null&&kd(p,l))s.push(u);else if(p!==null&&!Pi(u)){let m=await he(e,d,{stamp:i});if(m)r.push({relPath:u,historyPath:jt(e,m)})}if(await md(gd(d),{recursive:!0}),await Bun.write(d,l),o.push(d),!Pi(u))a.push(u)}if(a.length>0)await Te(t,a).catch(()=>{return});return{written:o,unchanged:s,archived:r}}function yr(e){let t=e.written.length-e.unchanged.length,n=[`wrote ${e.written.length}`,`changed ${t}`,`unchanged ${e.unchanged.length}`];if(e.archived.length>0)n.push(`archived ${e.archived.length}`);return n.join(", ")}var vd=8;function br(e){if(e.length===0)return[];let t=e.slice(0,vd).map((o)=>o.relPath),n=e.length-t.length;return[`archived ${e.length} local file(s) that differed from the server: ${t.join(", ")}${n>0?` (+${n} more)`:""}`,`restore by copying back from ${e[0].historyPath}`]}async function Sd(e,t){try{return(await fd(wr(e,t))).mtime}catch{return null}}async function Pt(e,t,n,o,s=Date.now()){let r=await Sd(e,o);if(!r)return null;let i=s-r.getTime();if(i<yd)return null;let a=Math.floor(i/3600000),c=a>=48?`${Math.floor(a/24)} days`:`${a} hours`;return`warning: ${t} last synced ${c} ago; the server may have changed. run: ${n}`}var Ed=/^[A-Za-z0-9_]+$/,Ei=50;class fo extends Error{name="SchemaFormatError"}async function Td(e){try{return JSON.parse(await H(e))}catch{return null}}async function kr(e,t,n){return Td(e.schemaFile(t,n))}async function $r(e,t,n){let o=await kr(e,t,n);if(!o)return null;if(!Ct(o))throw new fo(`_db/${n}/schema.json of project "${t}" is not in the current compact format`);try{return{fetchedAt:o.fetchedAt,tables:o.tables.map(hr),procedures:o.procedures.map(uo)}}catch(s){throw new fo(`_db/${n}/schema.json of project "${t}": ${s.message}`)}}async function Ti(e){try{return(await Id(e)).filter((t)=>t.toLowerCase().endsWith(".sql"))}catch{return null}}async function Ad(e,t){try{let n=await H(t);return await he(e,t,{content:n,suffix:"deleted"}),await Cd(t),!0}catch{return!1}}async function Nd(e,t,n,o,s,r){let i=[],a=[],c=await Ti(e.connDir(t,n));if(!c)return{removed:i,localOnly:a};let l=(p)=>new Set([...p].map((m)=>m.toLowerCase())),u=l(o),d=l(s);for(let p of c){let m=p.slice(0,-4).toLowerCase();if(d.has(m))continue;if(u.has(m)||r){if(await Ad(e,Pd(e.connDir(t,n),p)))i.push(p)}else a.push(p)}return{removed:i,localOnly:a}}async function _d(e,t,n,o){let s=await Ti(e.connDir(n,o));if(!s||s.length===0)return{sync:null,skipped:[],missing:[]};let r=[],i=[];for(let d of s){let p=d.slice(0,-4);if(Ed.test(p))r.push(p);else i.push(d)}let a=new Map;for(let d=0;d<r.length;d+=Ei){let p=r.slice(d,d+Ei).map((w)=>`N'${w}'`).join(","),m=await t.query(`SELECT o.name AS name, ISNULL(OBJECT_DEFINITION(o.object_id), '') AS sqlText
|
|
64
|
+
FROM sys.objects o WHERE o.name IN (${p})`);for(let w of m){let y=String(w.name??""),v=String(w.sqlText??"");if(y&&v.trim())a.set(y.toLowerCase(),v)}}let c={},l=[],u=new Set(i);for(let d of s){if(u.has(d))continue;let p=a.get(d.slice(0,-4).toLowerCase());if(p===void 0)l.push(d);else c[`${o}/${d}`]=p}if(Object.keys(c).length===0)return{sync:null,skipped:i,missing:l};return{sync:await Ae(e,e.dbDir(n),c),skipped:i,missing:l}}async function ho(e,t,n,o,s=!1){let r=null;try{r=await $r(e,n,o)}catch{}let i=new Set((r?.procedures??[]).map((w)=>w.name)),[a,c]=await Promise.all([t.query(bi),t.query(ki)]),l=vi(a,c),u=e.schemaFile(n,o);await jd(Rd(u),{recursive:!0}),await Bun.write(u,JSON.stringify(l,null,2));let d=new Set(l.procedures.map((w)=>uo(w).name)),p=await Nd(e,n,o,i,d,s),m=await _d(e,t,n,o);return{schemaFile:u,schema:l,refresh:m.sync,skipped:m.skipped,missingOnServer:m.missing,removed:p.removed,localOnly:p.localOnly}}async function go(e,t,n,o){try{return await $r(e,t,n)}catch(s){if(!(s instanceof fo))throw s;return await o(),$r(e,t,n)}}async function Ai(e,t,n,o){let s=await kr(e,t,n);if(!s)return null;if(Ct(s))return s;await o();let r=await kr(e,t,n);if(!Ct(r))throw Error("refetched the schema but it is still not in the compact format");return r}function Ni(e){let t={Table:0,View:0,API:0,"API File":0,Procedure:0,Function:0};for(let n of e.tables)t[hr(n).objectType]+=1;for(let n of e.procedures)t[uo(n).objectType]+=1;return t}var Dd=/token|passw|pwd|secret|api[-_]?key|credential|authorization/i,_i=[/\b(?:PWD|Password)\s*=\s*[^;]+/i,/^Bearer\s+\S+/i,/:\/\/[^/\s:@]+:[^@\s]+@/];function Di(e){return`<redacted ${e.length} chars>`}function xi(e){if(_i.some((t)=>t.test(e)))return!0;if(e.length<40||!/^[A-Za-z0-9+/=]+$/.test(e))return!1;try{return _i.some((t)=>t.test(atob(e)))}catch{return!1}}function Xt(e){if(Array.isArray(e))return e.map((t)=>Xt(t));if(e&&typeof e==="object")return Object.fromEntries(Object.entries(e).map(([t,n])=>[t,typeof n==="string"&&(Dd.test(t)||xi(n))?Di(n):Xt(n)]));if(typeof e==="string"&&xi(e))return Di(e);return e}function Ue(e,t={}){let n=t.strings!==!1,o=e.split(""),s=0;while(s<e.length)if(e[s]==="-"&&e[s+1]==="-")while(s<e.length&&e[s]!==`
|
|
65
|
+
`)o[s++]=" ";else if(e[s]==="/"&&e[s+1]==="*"){let r=e.indexOf("*/",s+2),i=r===-1?e.length:r+2;while(s<i)o[s++]=" "}else if(e[s]==="'"){if(n)o[s]=" ";s++;while(s<e.length){if(e[s]==="'"){if(n)o[s]=" ";if(s++,e[s]==="'"){if(n)o[s]=" ";s++;continue}break}if(n)o[s]=" ";s++}}else s++;return o.join("")}function Et(e){return e.replace(/--[^\n]*/g," ").replace(/\/\*[\s\S]*?\*\//g," ").replace(/N?'(?:[^']|'')*'/gi,"''")}function vr(e){return Ue(e,{strings:!1})}function Sr(e){let t=Ue(e),n=[],o=0;for(let s of t.matchAll(/;|^[ \t]*GO[ \t]*$/gim))n.push({start:o,end:s.index}),o=s.index+s[0].length;return n.push({start:o,end:e.length}),n.filter((s)=>s.end>s.start)}function An(e,t=","){let n=[],o=0,s="";for(let r of e){if(r==="(")o++;else if(r===")")o--;if(r===t&&o===0)n.push(s),s="";else s+=r}if(s.trim())n.push(s);return n}function Tt(e){let t=e.match(/^(spAPIFILE_UPLOAD_|spAPIFILE_|spAPI_)(AUTH_)?(.+)$/i);if(!t)return null;return{prefix:t[1],isAuth:Boolean(t[2]),fnName:t[3]}}function jr(e){let t=Ue(e).match(/\b(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC|FUNCTION)\s+(?:(?:\[[^\]]+\]|\w+)\s*\.\s*){0,2}(?:\[([^\]]+)\]|(\w+))/i);if(!t)return null;return(t[1]??t[2])||null}function Oi(e){return/\b(?:CREATE|ALTER)\s+FUNCTION\b/i.test(Ue(e))}function Mi(e,t){let n={sql:e,changed:!1,from:null,to:null};if(t===null)return n;let o=[...Ue(e).matchAll(/\b(CREATE|ALTER)(\s+)(?:PROCEDURE|PROC|FUNCTION)\b/gi)];if(o.length!==1)return n;let s=o[0],r=s[1].toUpperCase(),i=t?"ALTER":"CREATE";if(r===i)return n;return{sql:e.slice(0,s.index)+i+e.slice(s.index+s[1].length),changed:!0,from:r,to:i}}function Ir(e){let t=Ue(e),n=t.match(/(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC|FUNCTION)\s+(?:(?:\[[^\]]+\]|\w+)\s*\.\s*){0,2}(?:\[[^\]]+\]|\w+)\s*\(?/i);if(!n||n.index===void 0)return[];let o=n.index+n[0].length,s=n[0].endsWith("(")?1:0,r=t.length,i=/\bAS\b/gi;i.lastIndex=o;for(let d=i.exec(t);d;d=i.exec(t)){let p=t.slice(o,d.index);if((p.match(/\(/g)??[]).length+s===(p.match(/\)/g)??[]).length){r=d.index;break}}let a=(d)=>d.replace(/^\(/,"").replace(/\)\s*$/,""),c=An(a(t.slice(o,r))),l=An(a(e.slice(o,r))),u=[];return c.forEach((d,p)=>{if(/^\s*@\w+[\s\S]*=/.test(d))u.push((l[p]??d).trim())}),u}function xd(e){return`N'${e.replace(/'/g,"''")}'`}function Nn(e){return`GRANT EXECUTE ON [dbo].[${e}] TO [public] AS [dbo]`}function Fi(e){return`SELECT granted = COUNT(*) FROM sys.database_permissions p JOIN sys.database_principals u ON u.principal_id = p.grantee_principal_id WHERE p.class = 1 AND p.major_id = OBJECT_ID(${xd(e)}) AND p.permission_name = 'EXECUTE' AND p.state IN ('G','W') AND u.name = 'public'`}var Od=/\bALTER\s+TABLE\s+(?:(?:\[[^\]]*\]|\w+)\s*\.\s*)*(?:\[[^\]]*\]|\w+)\s+(?:WITH\s+(?:CHECK|NOCHECK)\s+)?(\w+)/i,Md=new Set(["ADD","ALTER","DROP"]);function Fd(e){let t=vr(e),n=null;for(let o of Sr(e)){let s=t.slice(o.start,o.end),r=e.slice(o.start,o.end).trim();if(/\bDROP\s+TABLE\b/i.test(s))return{severity:"block",reason:"DROP TABLE",statement:r};if(!/\bALTER\s+TABLE\b/i.test(s))continue;if(/\bDROP\s+COLUMN\b/i.test(s))return{severity:"block",reason:"ALTER TABLE ... DROP COLUMN",statement:r};if(/\bSWITCH\b/i.test(s))return{severity:"block",reason:"ALTER TABLE ... SWITCH",statement:r};let i=Od.exec(s)?.[1]?.toUpperCase();if(!i||!Md.has(i))return{severity:"block",reason:`ALTER TABLE ... ${i??"(unknown action)"}`,statement:r};if(!n&&/\bALTER\s+COLUMN\b/i.test(s))n={severity:"confirm",reason:"ALTER TABLE ... ALTER COLUMN (narrowing numeric/date precision loses data silently)",statement:r}}return n}function Li(e,t){let o=(t==="DELETE"?/\bDELETE\s+(?:TOP\s*\([^)]*\)\s*)?(?:FROM\s+)?(#{1,2}\w+|@\w+|\w+)/i:/\bUPDATE\s+(?:TOP\s*\([^)]*\)\s*)?(#{1,2}\w+|@\w+|\w+)/i).exec(e)?.[1];return Boolean(o&&(o.startsWith("#")||o.startsWith("@")))}function Ld(e){let t=vr(e);for(let{start:n,end:o}of Sr(e)){let s=t.slice(n,o);if(/\bWHERE\b/i.test(s))continue;let r=e.slice(n,o).trim();if(/\bDELETE\b/i.test(s)&&!Li(s,"DELETE"))return{severity:"block",reason:"DELETE without WHERE",statement:r};if(/\bUPDATE\b/i.test(s)&&/\bSET\b/i.test(s)&&!Li(s,"UPDATE"))return{severity:"block",reason:"UPDATE without WHERE",statement:r}}return null}function Ui(e,t){if(/\bINSERT\b/i.test(e))t.add("INSERT");if(/\bUPDATE\b/i.test(e)&&/\bSET\b/i.test(e))t.add("UPDATE");if(/\bDELETE\b/i.test(e))t.add("DELETE");if(/\bMERGE\b/i.test(e))t.add("MERGE");if(/\bTRUNCATE\b/i.test(e))t.add("TRUNCATE")}var Ud=["INSERT","UPDATE","DELETE","MERGE","TRUNCATE"];function Yt(e){let t=new Set;return Ui(Et(e),t),Ui(Ue(e),t),Ud.filter((n)=>t.has(n))}function Cr(e,t,n){let o=Fd(e);if(o&&(o.severity==="block"||!t))return o;if(n==="nonquery"){let r=Ld(e);if(r)return r}let s=Yt(e);if(s.length>0&&!t)return{severity:"confirm",reason:`writes real data (${s.join(", ")})`,statement:e.trim()};return null}function Bd(e){let t=e.trim(),n=t.match(/\bAS\s+\[?(\w+)\]?\s*$/i);if(n)return n[1];let o=t.match(/^(.+?)\s+\[?(\w+)\]?$/);if(o){let i=o[1].trim();if(!/[+\-*/,(]$/.test(i)&&!/^(AND|OR|ON|WHEN|THEN|ELSE)$/i.test(i))return o[2]}let s=t.match(/\.\s*\[?(\w+)\]?\s*$/);if(s)return s[1];let r=t.match(/^\[?(\w+)\]?$/);if(r)return r[1];return t.length>30?`${t.slice(0,30)}\u2026`:t}function Rr(e){let t=[],n=0,o=e.length;for(let s=0;s<o;s++){let r=e[s];if(r==="("){n++;continue}if(r===")"){n--;continue}if(n!==0)continue;if(r!=="S"&&r!=="s")continue;if(s>0&&/[A-Za-z0-9_]/.test(e[s-1]))continue;if(!/^SELECT\b/i.test(e.slice(s,s+7)))continue;let i=0,a=o;for(let y=s+6;y<o;y++){let v=e[y];if(v==="("){i++;continue}if(v===")"){i--;continue}if(i!==0)continue;if(v===";"){a=y;break}if(/[A-Za-z]/.test(v)&&(y===0||!/[A-Za-z0-9_]/.test(e[y-1]))){let T=e.slice(y).match(/^[A-Za-z]+/)?.[0]?.toUpperCase();if(T&&["INSERT","UPDATE","DELETE","MERGE","SELECT"].includes(T)){a=y;break}}}let c=e.slice(s,a),l=/\bINTO\s+[#@[\w]/i.test(c),u=0,d=-1;for(let y=6;y<c.length;y++){let v=c[y];if(v==="("){u++;continue}if(v===")"){u--;continue}if(u!==0)continue;if(/^FROM\b/i.test(c.slice(y))&&(y===0||!/[A-Za-z0-9_]/.test(c[y-1]))){d=y;break}}let p=(d>=0?c.slice(6,d):c.slice(6)).trim();p=p.replace(/^(TOP\s*\(?\s*\d+\s*\)?\s*(PERCENT)?\s*)/i,"").replace(/^DISTINCT\s+/i,"").trim();let m=/(^|,)\s*\*\s*($|,)/.test(p)||p==="*",w=null;if(!m&&p)w=An(p,",").map(Bd).filter(Boolean);t.push({columns:w,hasStar:m,isInto:l})}return t}function Bi(e){return/RAISERROR\s*\([^)]*\)[^;]*;?\s*(\r?\n)?\s*RETURN\b/i.test(e)}function Pr(e){let t=e.toLowerCase();if(/(^|_)id$/.test(t)||t==="id")return 1;if(/date|time/.test(t))return"2026-01-01";if(/(count|qty|quantity|amount|price|total|sum|so[_ ]?luong)/.test(t))return 0;if(/(code|ma[_ ]?)/.test(t))return"C001";return`Sample ${e}`}function Hi(e){let t=Rr(Et(e)).filter((o)=>!o.isInto)[0];if(!t||t.hasStar||!t.columns||t.columns.length===0)return{data:[]};let n={};for(let o of t.columns)n[o]=Pr(o);return{data:[n,{...n}]}}function Hd(e){let t=Et(e),n=[];if(/\b(?:EXEC|EXECUTE)\s*\(/i.test(t)||/\bsp_executesql\b/i.test(t))n.push("dynamic SQL (EXEC(...)/sp_executesql)");if(/\b(?:EXEC|EXECUTE)\s+(?:@\w+\s*=\s*)?[A-Za-z_#[]/i.test(t))n.push("calls another SP (EXEC <proc>)");if(/\bINTO\s+(?!#|@)[A-Za-z_[]/i.test(t)&&/\bSELECT\b/i.test(t)&&!/\bINSERT\b/i.test(t))n.push("SELECT ... INTO a real table");return n}function Wi(e){if(e===null)return"unknown";if(Yt(e).length>0)return"unsafe-write";if(Hd(e).length>0)return"opaque";return"safe-read"}var Kd=/^[A-Za-z0-9_]+$/,qi=200,Ji=2000;function pt(e){if(!Kd.test(e))throw new f(`invalid object name "${e}": letters, digits and "_" only (it becomes _db/<alias>/<name>.sql)`);return e}function Se(e,t){return{project:e.project,contextProject:e.contextProject,...t}}function rt(e,t){if(e.engine!=="mssql")throw new f(`${t} supports MSSQL connections only; ${e.alias} is ${e.engine}`)}function Vi(e){let t=e.statement.length>400?`${e.statement.slice(0,400)}...`:e.statement,n=e.severity==="block"?"blocked: no flag unlocks this; the user must run it by hand":"needs --confirm-write; pass it only after the user approved this write";return new $e(`${e.reason}
|
|
66
|
+
${n}
|
|
67
|
+
---
|
|
68
|
+
${t}`,e.severity==="block"?"hard":"soft")}function Ki(e,t){let n=Ni(t.schema),o=[`tables ${n.Table} views ${n.View} api ${n.API} api-file ${n["API File"]} procedures ${n.Procedure} functions ${n.Function}`],s=t.refresh;if(s)o.push(`sql bodies in _db/${e}/: wrote ${s.written.length}, unchanged ${s.unchanged.length}, archived ${s.archived.length}`);if(t.skipped.length>0)o.push(`not refreshed (file name is not a SQL identifier): ${t.skipped.join(" ")}`);if(t.removed.length>0)o.push(`archived to _history (dropped on server): ${t.removed.join(" ")}`);if(t.localOnly.length>0)o.push(`local only, not on server: ${t.localOnly.join(" ")}; undeployed draft -> keep or deploy; stale -> fui schema pull --db ${e} --prune-local-only`);return o}async function _n(e,t,n){Vt(n.alias);let{engine:o}=Cn(n.dbToken);if(o==="mssql"&&!n.apiDomain)throw new f("MSSQL connections need --api-domain <host>");let s=n.apiDomain?dt(n.apiDomain):void 0,r={projectId:t,alias:n.alias,config:{apiDomain:s,dbToken:n.dbToken},engine:o,dbToken:n.dbToken,note:null};if(n.test)await ve(r,(w)=>w.query("SELECT 1 AS ok"));let i=await e.store.load(t),a=me(i,n.alias),c=a?i.connections[a]:void 0,l=n.userToken??c?.userToken,u={...s?{apiDomain:s}:{},...n.sqlServer?{sqlServer:n.sqlServer}:{},...n.database?{database:n.database}:{},dbToken:n.dbToken,...l?{userToken:l}:{}},d=await e.store.save(t,n.alias,u,n.makeDefault),m=[`saved ${t}/${d} ${o}${o==="mssql"&&!l?" (no userToken; tAPI calls need it: fui db add ... --user-token-env VAR)":""}`];if(o==="mssql"){let w={...r,alias:d,config:u},y=await ve(w,(v)=>ho(e.store.paths,v,t,d));m.push(...Ki(d,y))}return{notes:m}}async function Gi(e,t,n){let o=await e.store.resolve(Se(e,{db:t}));rt(o,"schema pull");let s=await ve(o,(r)=>ho(e.store.paths,r,o.projectId,o.alias,n));return{notes:[...o.note?[o.note]:[],`ok ${o.projectId}/${o.alias} schema refreshed`,...Ki(o.alias,s)]}}function wo(e,t){return()=>ve(t,(n)=>ho(e.store.paths,n,t.projectId,t.alias))}async function zi(e,t){let n=await e.store.resolve(Se(e,{db:t.db}));rt(n,"schema");let{projectId:o,alias:s}=n,r=await Ai(e.store.paths,o,s,wo(e,n));if(!r)throw new f(`no local schema for ${o}/${s}; run: fui schema pull --db ${o}/${s}`);let i=t.search?.toLowerCase(),a=(y)=>!i||y.toLowerCase().includes(i),c=t.objectType,l=!c||c==="Table"||c==="View",u=!c||c!=="Table"&&c!=="View",d=l?r.tables.filter((y)=>(!c||mr(y)===c)&&a(y)):[],p=u?r.procedures.filter((y)=>(!c||fr(y)===c)&&a(y)):[],m=await Pt(e.store.paths.connDir(o,s),`schema of ${o}/${s}`,`fui schema pull --db ${o}/${s}`,"schema.json"),w=[...d,...p];return{notes:[...n.note?[n.note]:[],...m?[m]:[],`${o}/${s} fetchedAt=${r.fetchedAt} tables/views=${d.length} procs/functions=${p.length}`],data:w.length>0?`${w.join(`
|
|
69
|
+
`)}
|
|
70
|
+
`:""}}async function Xi(e,t){let n=await e.store.resolveProject(Se(e,{db:t.db})),o=await e.store.load(n);if(Object.keys(o.connections).length===0)throw new f(`project "${n}" has no DB connections; run: fui db add <alias> -p ${n}`);let s=t.db?[(await e.store.resolve(Se(e,{db:t.db}))).alias]:Object.keys(o.connections),r=t.search?.toLowerCase(),i=(u)=>!r||u.toLowerCase().includes(r),a=t.objectType,c=[],l=[];for(let u of s){let d;try{let m=await e.store.resolve({project:n,db:u});d=await go(e.store.paths,n,u,wo(e,m))}catch(m){l.push(`${u}: schema unreadable: ${m.message}`);continue}if(!d){l.push(`${u}: no local schema; run: fui schema pull --db ${n}/${u}`);continue}l.push(`${u} fetchedAt=${d.fetchedAt}`);let p=`${n}/${u}`;if(!a||a==="Table"||a==="View"){for(let m of d.tables)if((!a||m.objectType===a)&&i(m.name))c.push({db:p,name:m.name,kind:m.objectType,count:m.count})}if(!a||a!=="Table"&&a!=="View"){for(let m of d.procedures)if((!a||m.objectType===a)&&i(m.name))c.push({db:p,name:m.name,kind:m.objectType,count:m.count})}}return{rows:c,notes:l}}var Er=(e)=>e.replace(/'/g,"''");async function Yi(e,t,n){pt(t);let o=await e.store.resolve(Se(e,{db:n,objectName:t}));rt(o,"sp get");let s=await ve(o,(l)=>l.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Er(t)}')),'') AS sqlText`)),r=String(s[0]?.sqlText??"");if(!r.trim())throw Error(`object "${t}" not found or has no definition in the database`);let i=e.store.paths,a=await Ae(i,i.dbDir(o.projectId),{[`${o.alias}/${t}.sql`]:r}),c=o.note?[o.note]:[];for(let l of a.archived)c.push(`old local copy archived: ${l.historyPath}`);return{path:i.spFile(o.projectId,o.alias,t),body:r,sync:a,notes:c}}async function Qi(e,t,n,o){pt(t);let s=await e.store.resolve(Se(e,{db:o,objectName:t})),r=e.store.paths.spFile(s.projectId,s.alias,t);return await Wd(Vd(r),{recursive:!0}),await Bun.write(r,n),{path:r,alias:s.alias,notes:s.note?[s.note]:[]}}function Gd(e){return/(CREATE\s+PROCEDURE|CREATE\s+PROC|CREATE\s+FUNCTION)/im.test(e)}async function Tr(e,t){try{let n=await e.query(Fi(t)),o=Number(n[0]?.granted??0);return Number.isFinite(o)?o>0:null}catch{return null}}async function zd(e){return await e.wasSkillLoaded()?[]:["warning: skill not loaded this session; read the fui-skill references (tapi-reference, db-workflow), then run: fui skill loaded"]}async function Xd(e,t){if(!qt()||await e.wasSkillLoaded())return;throw new $e(`[FUI_MCP_STRICT] ${t} blocked: skill not loaded this session; read tapi-reference.md and db-workflow.md, run fui skill loaded, then retry`,"hard","error")}async function Zi(e,t,n){await Xd(e.session,"sp deploy"),pt(t);let o=await e.store.resolve(Se(e,{db:n,objectName:t}));rt(o,"sp deploy");let{projectId:s,alias:r}=o,i=e.store.paths,a=i.spFile(s,r,t),c;try{c=await H(a)}catch{throw new f(`file not found: ${a}; run fui sp save or fui sp get first`)}if(!c.trim())throw new f(`file is empty: ${a}`);let l=jr(c)??t,u=Tt(l);if(u){let m=Ir(c);if(m.length>0)throw new $e(`tAPI does not support parameter default values; remove "= ..." and save before deploying:
|
|
71
|
+
`+m.map((w)=>` - ${w}`).join(`
|
|
72
|
+
`),"hard","error")}let d=`${s}/${r}`,p=[];return await ve(o,async(m)=>{let w=null,y="";try{let b=await m.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Er(l)}')), '') AS def`);y=String(b[0]?.def??"").trim(),w=y.length>0}catch{}let v="";if(y)try{v=`rollback copy: ${await he(i,a,{content:y})}`}catch(b){throw Error(`could not save the rollback copy (${b.message}); not deploying`)}let T=Mi(c,w);if(await m.exec(T.sql),o.note)p.push(o.note);if(p.push(`ok deployed ${t} to ${d}`,`file: ${a}`),T.changed)p.push(`ran as ${T.to} because "${l}" ${T.to==="ALTER"?"exists":"does not exist"}; file unchanged`);if(v)p.push(v);let I=w===null?Gd(c):!w,g=Oi(c);if(I&&!g){let b=Nn(l);try{await m.exec(b),p.push(`GRANT EXECUTE ON [dbo].[${l}] TO [public]: applied`)}catch(R){p.push(`deploy OK but grant FAILED (${R.message}); do not redeploy, run: fui exec "${b}" --db ${d}`)}}if(!g){let b=await Tr(m,l);if(b===!1)p.push(`[public] lacks EXECUTE on "${l}"; tAPI calls will fail. run: fui exec "${Nn(l)}" --db ${d}`);else if(b===!0&&!I)p.push(`[public] has EXECUTE on "${l}"`)}if(u)try{let b=await or(Ee(o.config.apiDomain),r,u.isAuth,u.fnName,o.config.userToken);p.push(b.ok?`tAPI cache cleared (GET ${b.url} -> HTTP ${b.status})`:`tAPI /help HTTP ${b.status}; ignored, deploy done. only if the endpoint still takes old params: fui sp help ${l}`)}catch(b){p.push(`tAPI /help failed (${b.message}); ignored, deploy done`)}}),{notes:[...await zd(e.session),...p]}}async function ea(e,t,n,o){let s=[t,`spAPI_${t}`,`spAPI_AUTH_${t}`,`spAPIFILE_${t}`,`spAPIFILE_UPLOAD_${t}`],r=await e.store.resolve(Se(e,{db:n,objectName:s}));rt(r,"sp help");let i=null;try{i=await go(e.store.paths,r.projectId,r.alias,wo(e,r))}catch{}let a=t,c=i?.procedures.find((m)=>s.some((w)=>w.toLowerCase()===m.name.toLowerCase()));if(c)a=c.name;let l=Tt(a);if(!l)throw new f(`"${a}" is not an API SP (spAPI_/spAPIFILE_ prefix); only those have a /help route. `+(i?"check the name: fui sp list --type API":"no local schema; run: fui schema pull"));let u=r.note?[r.note]:[],d;try{d=await or(Ee(r.config.apiDomain),r.alias,l.isAuth,l.fnName,r.config.userToken)}catch(m){return u.push(`/help for "${a}" failed (${m.message}); ignored, does not affect the deployed SP`),{notes:u}}if(u.push(`GET ${d.url} -> HTTP ${d.status}`),!d.ok)return u.push("/help unavailable for this function; ignored. it only shows params and clears the tAPI cache. do not try other URL shapes"),{notes:u};u.push("tAPI cache cleared for this function");let p;try{let m=JSON.stringify(Xt(JSON.parse(d.text)));p=o>0&&m.length>o?`${m.slice(0,o)}\u2026(truncated)`:m}catch{p=o>0?d.text.slice(0,o):d.text}return{notes:u,data:`${p}
|
|
73
|
+
`}}async function ta(e,t,n){let o=Cr(t,n.confirmWrite,"query");if(o)throw Vi(o);let s=await e.store.resolve(Se(e,{db:n.db,write:!n.db&&Yt(t).length>0}));return{rows:await ve(s,(i)=>i.query(t)),connection:s}}function Yd(e){if(!/\b(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC)\b/i.test(Ue(e)))return null;return jr(e)}async function na(e,t,n){let o=Cr(t,n.confirmWrite,"nonquery");if(o)throw Vi(o);let s=await e.store.resolve(Se(e,{db:n.db,write:!n.db})),r=ur(s),i=[];try{let a=await r.exec(t),c=s.engine==="mssql"?Yd(t):null;if(c){let l=`${s.projectId}/${s.alias}`,u=await Tr(r,c);if(u===!1)i.push(`[public] lacks EXECUTE on "${c}"; tAPI calls will fail. run: fui exec "${Nn(c)}" --db ${l} (fui sp save + sp deploy grants automatically)`);else if(u===!0)i.push(`[public] has EXECUTE on "${c}"`);if(Tt(c))i.push(`"${c}" is an API SP: the tAPI param cache is not cleared here; run: fui sp help ${c}`)}return{result:a,connection:s,notes:i}}finally{await r.close()}}async function oa(e,t){let{name:n}=t,o=[n,`spAPI_${n}`,`spAPI_AUTH_${n}`,`spAPIFILE_${n}`,`spAPIFILE_UPLOAD_${n}`],s=await e.store.resolve(Se(e,{db:t.db,objectName:o}));rt(s,"sp verify");let{projectId:r,alias:i,config:a}=s,c=e.store.paths,l=null;try{l=await go(c,r,i,wo(e,s))}catch{}let u=n,d=l?.procedures.find((B)=>o.some((ae)=>ae.toLowerCase()===B.name.toLowerCase()));if(d)u=d.name;let p=Tt(u);if(!p)throw new f(`"${u}" is not an API SP (spAPI_/spAPIFILE_ prefix); plain SPs have no HTTP endpoint. `+(l?"check the name: fui sp list --type API":"no local schema; run: fui schema pull"));let{prefix:m,isAuth:w,fnName:y}=p,v=/^spAPIFILE_UPLOAD_/i.test(m),T=/^spAPIFILE_/i.test(m)&&!v,I=await Qd(e,s,u);if(!I)throw Error(`cannot read the real body of "${u}": no _db/${i}/${u}.sql and the live OBJECT_DEFINITION lookup failed (no permission or no such object). sp verify never guesses from the name; fetch it first: fui sp get ${u} --db ${r}/${i}`);await e.session.markSpVerified([u,y]);let g=I.source==="local"?await Pt(c.connDir(r,i),`_db/${i}/${u}.sql`,`fui schema pull --db ${r}/${i}`,"schema.json"):null,b=I.source==="local"?`_db/${i}/${u}.sql`:"OBJECT_DEFINITION (live definition read, no call)",R=v?"API File Upload":T?"API File Get":"API",C=T||v?"GET/POST":"POST",S=[...s.note?[s.note]:[],...g?[g]:[],`verify ${u} (from ${b})`,`kind: ${R}${w?" (AUTH_: public, no Bearer)":""}`,`url (reference only, not called; copy verbatim): ${C} https://${a.apiDomain}/${i}/${w?"auth/":""}${y}`,"","## params"],N=0;if(d){let B=[];for(let ge of d.parameters??[]){let We=ge.replace(/^@/,"");if(/^sys_/i.test(We))continue;if(/^url\d+_/i.test(We)){N++;continue}B.push(We)}if(S.push(`route params (@url*_): ${N}`,`body params: ${B.length?B.join(", "):"(none)"}`),t.params){let ge=new Set(Object.keys(t.params).map((Ce)=>Ce.toLowerCase())),We=B.filter((Ce)=>!ge.has(Ce.toLowerCase())),Ye=Object.keys(t.params).filter((Ce)=>!/^@?sys_/i.test(Ce)&&!B.some(($t)=>$t.toLowerCase()===Ce.replace(/^@/,"").toLowerCase()));if(We.length)S.push(` warning: missing from --params: ${We.join(", ")}`);if(Ye.length)S.push(` warning: not declared by the SP: ${Ye.join(", ")}`)}let ae=(t.urlParams??[]).length;if(t.urlParams&&ae!==N)S.push(` warning: the SP declares ${N} route params, --url-param gave ${ae}`)}else S.push(`warning: "${u}" is not in the local schema; params not checked. just deployed? run: fui schema pull --db ${r}/${i}`);if(t.checkGrant){let B=await ve(s,(ae)=>Tr(ae,u));S.push(B===!0?`[public] has EXECUTE on "${u}"`:B===!1?`[public] LACKS EXECUTE on "${u}"; the endpoint fails with a permission error. fix: fui exec "${Nn(u)}" --db ${r}/${i}`:"warning: could not read sys.database_permissions; EXECUTE grant unknown")}let D=Ir(I.sql);if(D.length)S.push(`warning: tAPI does not support default values; sp deploy will block: ${D.join("; ")}`);S.push("","## response shape (static, best effort)");let A=Rr(Et(I.sql)),G=A.filter((B)=>!B.isInto),_=A.length-G.length;if(_>0)S.push(`(${_} SELECT ... INTO #temp/@table not returned to the client; not counted)`);if(G.length===0)S.push("warning: no result-returning SELECT; the SP may only write (check any OUT binding in module.json)");else if(G.length===1){let B=G[0];if(S.push("1 SELECT: data is an array (one resultset)"),B.hasStar)S.push("warning: SELECT *: columns unknown from the body; check fui schema --search <table>");else if(B.columns)S.push(`columns: ${B.columns.join(", ")}`)}else S.push(`${G.length} result SELECTs: data may be array-of-arrays (data[0], data[1], ...)`),G.forEach((B,ae)=>{S.push(` data[${ae}]: ${B.hasStar?"SELECT * (columns unknown)":B.columns?B.columns.join(", "):"(unknown)"}`)});S.push(Bi(I.sql)?"error contract { Message }: RAISERROR(...) + RETURN present":"warning: no clear RAISERROR(...) + RETURN; error branches must RAISERROR level 16 then RETURN"),S.push("","## read/write");let le=Yt(I.sql);if(le.length===0)S.push("read API: no INSERT/UPDATE+SET/DELETE/MERGE in the body");else S.push(`WRITE API: ${le.join(", ")}`,"never call it to test; check the body: WHERE on the key/route param, multi-step writes in a TRANSACTION, RAISERROR when the row is missing",`test the UI flow with fake data: fui module simulate --mocks '{"${y}":{"data":[...]}}'`);S.push("","## apiMocks snippet (fake data)");let Ie=G[0];if(Ie&&!Ie.hasStar&&Ie.columns?.length){let B={};for(let ae of Ie.columns)B[ae]=Pr(ae);S.push(JSON.stringify({apiMocks:{[y]:{data:[B,B]}}},null,2))}else S.push("(not generated: SELECT * or unknown columns; write apiMocks from fui schema --search <table>)");return{notes:S}}async function Qd(e,t,n){try{let o=await H(e.store.paths.spFile(t.projectId,t.alias,n));if(o.trim())return{sql:o,source:"local"}}catch{}try{let o=await ve(t,(r)=>r.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Er(n)}')),'') AS sqlText`)),s=String(o[0]?.sqlText??"");if(s.trim())return{sql:s,source:"live"}}catch{}return null}async function ra(e,t,n,o){pt(t);let s=await e.store.resolve(Se(e,{db:o,objectName:t}));rt(s,"sp delete");let{projectId:r,alias:i}=s,a=n==="Function"?`DROP FUNCTION [dbo].[${t}]`:`DROP PROCEDURE [dbo].[${t}]`;await ve(s,(u)=>u.exec(a));let c=[...s.note?[s.note]:[],`ok dropped ${n} [dbo].[${t}] on ${r}/${i}`],l=e.store.paths.spFile(r,i,t);try{let u=await H(l),d=await he(e.store.paths,l,{content:u,suffix:"deleted"});await Jd(l),c.push(`local file archived: ${d?jt(e.store.paths,d):""} (removed from _db/${i}/)`)}catch{}return{notes:c}}async function sa(e,t,n,o){pt(t),pt(n);let s=await e.store.resolve(Se(e,{db:o,objectName:t}));rt(s,"sp rename");let{projectId:r,alias:i}=s;await ve(s,(c)=>c.exec(`EXEC sp_rename '[dbo].[${t}]', '${n.replace(/'/g,"''")}'`));let a=[...s.note?[s.note]:[],`ok renamed [dbo].[${t}] -> ${n} on ${r}/${i}`,`refresh the local schema: fui schema pull --db ${r}/${i}`];try{await qd(e.store.paths.spFile(r,i,t),e.store.paths.spFile(r,i,n)),a.push(`local file renamed: _db/${i}/${t}.sql -> _db/${i}/${n}.sql`)}catch{}return{notes:a}}async function ia(e,t){let n=await e.store.resolve(Se(e,{db:t.db,write:!t.db}));rt(n,"db token-rebuild");let{projectId:o,alias:s,config:r}=n;if(!r.sqlServer||!r.database)throw new f(`connection ${o}/${s} has no sqlServer/database saved; re-add it: fui db add ${s} -p ${o} --sql-server <host> --database <name> ...`);let i=Jt(r.sqlServer,r.database,t.uid,t.password);return await new ot(Ee(r.apiDomain),i).query("SELECT 1 AS ok"),await e.store.save(o,s,{...r,dbToken:i},!1),{notes:["ok dbToken rebuilt and tested",`connection: ${o}/${s}`,`sql server: ${r.sqlServer}`,`database: ${r.database}`,`uid: ${t.uid}`,`config: ${e.store.paths.connectionsFile(o)}`]}}async function Dn(e){return e.store.resolveProject({project:e.project,contextProject:e.contextProject})}async function aa(e,t){let n=await Dn(e),o=e.store.paths.connectionsFile(n);if(t.tokenSource){let{userToken:r,source:i}=await hi(e.store,n,t.tokenSource),a=await e.store.resolve({project:n,db:t.db});return await e.store.save(n,a.alias,{...a.config,userToken:r},!1),{notes:[...a.note?[a.note]:[],`ok borrowed the userToken of ${i} into ${n}/${a.alias}`,`config: ${o}`]}}if(!t.userToken){let r=await rr(e.store);if(r.length===0)return{notes:["no userToken in any project of this workspace; pass one: fui db user-token --token -"]};let i=sr(r);return{notes:[`${i.length} distinct userToken(s) across ${r.length} connection(s); pick one (same apiDomain preferred):`,...i.map((a)=>`fingerprint ${mi(a.token)} sources: ${pi(a.sources)}`),"then: fui db user-token --from <project/alias> (the value is copied internally, never printed)"]}}let s=await e.store.resolve({project:n,db:t.db});return await e.store.save(n,s.alias,{...s.config,userToken:t.userToken},!1),{notes:[...s.note?[s.note]:[],`ok userToken saved for ${n}/${s.alias}`,`config: ${o}`]}}async function ca(e,t){let n=await Dn(e),o=await lo(e.store,n,t),s=await En(o),r=t.search?.trim().toLowerCase(),i=r?s.filter((c)=>[c.APIName,c.DatabaseName,c.ModuleName].some((l)=>String(l??"").toLowerCase().includes(r))):s,a=o.note?[o.note]:[];if(i.length===0)a.push(r?`no alias matches "${t.search}" among the ${s.length} databases you may manage; drop --search to see all`:`empty: this token manages no database on ${o.apiDomain}`);else a.push(`${i.length}/${s.length} databases you may manage on ${o.apiDomain}; connect one: fui db add-by-name ${String(i[0].APIName??"<alias>")}`);return{rows:ar(i),notes:a,total:s.length}}async function la(e,t){Vt(t.apiName);let n=await Dn(e),o=await lo(e.store,n,{...t}),s=(await En(o)).find((l)=>String(l.APIName??"").toLowerCase()===t.apiName.toLowerCase());if(s)throw new f(`alias "${t.apiName}" already exists on ${o.apiDomain} (ModuleID ${s.ModuleID}, database ${s.DatabaseName}); db alias new only registers NEW aliases. see: fui db alias list --search ${t.apiName}`);let r=t.moduleId??await wi(e.store,n);await Pn(o.apiDomain,o.userToken,"SM_Modules_Update",{ModuleID:r,ModuleName:t.moduleName,APIName:t.apiName,Enable:1,UpdateConnection:0}),await Pn(o.apiDomain,o.userToken,"SM_Modules_Update",{ModuleID:r,SQLServer:t.sqlServer,DatabaseName:t.database,Username:t.username,Password:t.password,UpdateConnection:1});let a=(await En(o).catch(()=>null))?.find((l)=>String(l.APIName??"").toLowerCase()===t.apiName.toLowerCase()),c=a?ar([a])[0]:null;return{notes:[...o.note?[o.note]:[],`ok registered alias "${t.apiName}" (ModuleID ${r}) on ${o.apiDomain}`,c?`post-check: ${c.apiName} ${c.sqlServer} ${c.database} enable=${c.enable} ${c.moduleName}`:`warning: post-check: alias not in the managed list yet; check: fui db alias list --search ${t.apiName}`,`login stored: ${t.username} (password not printed)`,`connect it: fui db add-by-name ${t.apiName}`]}}async function ua(e,t){let n=await Dn(e);Vt(t.apiName);let o=await lo(e.store,n,t),s=(await En(o)).filter((u)=>String(u.APIName??"").toLowerCase()===t.apiName.toLowerCase());if(s.length===0)throw new f(`alias "${t.apiName}" not found on ${o.apiDomain}: it does not exist, or your account may not manage it (the list is scoped to your rights). see: fui db alias list`);if(s.length>1)throw new f(`alias "${t.apiName}" matches ${s.length} acc records; not picking one. use fui db add with an explicit token`);let r=s[0];if(!r.SQLServer||!r.DatabaseName)throw new f(`the acc record of "${t.apiName}" has no SQLServer/DatabaseName; use fui db add with your own token`);let{creds:i,source:a}=await gi(e.store,n,t.borrowFrom,o.apiDomain),c=Jt(r.SQLServer,r.DatabaseName,i.uid,i.password);try{await new ot(Ee(o.apiDomain),c).query("SELECT 1 AS ok")}catch(u){throw Error(`test connection to ${r.SQLServer}/${r.DatabaseName} failed; nothing saved.
|
|
74
|
+
${u.message}
|
|
75
|
+
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 _n(e,n,{alias:t.apiName,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 da(e,t){let n=await Dn(e),o=await ir(e.store,n,t),{apiDomain:s,userToken:r}=o.config;return{projectId:n,call:(i,a)=>Pn(s,r,i,a),target:{ModuleID:0,APIName:t??o.alias}}}async function pa(e,t,n){let{projectId:o,call:s,target:r}=await da(e,n),a=(await s(t==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",r)).data??[];return await cr(e.store.paths,o,r.APIName,t==="system"?{system:a}:{functions:a}),{apiName:r.APIName,rows:a}}async function ma(e,t,n){let{projectId:o,call:s,target:r}=await da(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 f(`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 cr(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 Nr={"api-domain":{type:"string"},"user-token":{type:"string"},"user-token-env":{type:"string"}};async function _r(e){return{apiDomain:h(e.values,"api-domain"),userToken:await Pe(h(e.values,"user-token"),h(e.values,"user-token-env"),"--user-token")}}function xn(e){k(`${e.join(`
|
|
76
|
+
`)}
|
|
77
|
+
`)}var Zd={name:"db user-token",mcpName:"db_user_token_set",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 Pe(h(e.values,"token"),h(e.values,"token-env"),"--token"),n=h(e.values,"from");if(n&&t)throw new f("pass --from or --token/--token-env, not both");let o=await aa(await z(e),{db:h(e.values,"db"),userToken:t,tokenSource:n});xn(o.notes)}},ep={name:"db token-rebuild",mcpName:"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=h(e.values,"uid");if(!t)throw new f("missing --uid");let n=await Pe(h(e.values,"pwd"),h(e.values,"pwd-env"),"--pwd");if(!n)throw new f("missing --pwd - or --pwd-env VAR");xn((await ia(await z(e),{db:h(e.values,"db"),uid:t,password:n})).notes)}},tp={name:"db alias list",mcpName:"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"},...Nr},async run(e){let t=await ca(await z(e),{...await _r(e),search:h(e.values,"search")});for(let n of t.notes)E(`# ${n}`);if(t.rows.length>0)k(X(t.rows,e.render))}},np={name:"db alias new",mcpName:"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"},...Nr},async run(e){let{values:t}=e,n=e.positionals[0],o=h(t,"module-name"),s=h(t,"sql-server"),r=h(t,"database"),i=h(t,"username");if(!n||!o||!s||!r||!i)throw new f("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Pe(h(t,"password"),h(t,"password-env"),"--password");if(!a)throw new f("missing --password - or --password-env VAR");let c=h(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new f("--module-id must be an integer");let u=await la(await z(e),{...await _r(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});xn(u.notes)}},op={name:"db add-by-name",mcpName:"db_connect_by_name",usage:"fui db add-by-name <apiName> [-p P] [--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:{"borrow-from":{type:"string"},default:{type:"boolean"},...Nr},async run(e){let t=e.positionals[0];if(!t)throw new f("missing <apiName>; see: fui db alias list");let n=await ua(await z(e),{...await _r(e),apiName:t,borrowFrom:h(e.values,"borrow-from"),makeDefault:e.values.default===!0});xn(n.notes)}};function ga(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function wa(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 ya={"api-name":{type:"string"}};function Ar(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new f(`${t} must be ${o}`);return s}function fa(e,t,n){if(e!==void 0&&e.length>t)throw new f(`${n} is at most ${t} characters`);return e}function ha(e){return{name:`right ${e} list`,mcpName:`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:ya,async run(t){let{apiName:n,rows:o}=await pa(await z(t),e,h(t.values,"api-name"));if(E(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){E(`# none defined; create one: fui right ${e} new ...`);return}k(X(e==="system"?ga(o):wa(o),t.render))}}}function Qt(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}`,mcpName:`right_${e}_${t}`,usage:n,summary:o[e][t],options:{...ya,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:Ar(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:fa(h(r,"note"),50,"--note")};else{let l=h(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:Ar(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:Ar(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:fa(h(r,"name"),100,"--name")}}let c=await ma(await z(s),a,h(r,"api-name"));xn(c.notes),k(X(e==="system"?ga(c.rows):wa(c.rows),s.render))}}}var ba=[Zd,ep,tp,np,op,ha("system"),Qt("system","new"),Qt("system","update"),Qt("system","delete"),ha("function"),Qt("function","new"),Qt("function","update"),Qt("function","delete")];import{mkdir as up}from"fs/promises";import{realpathSync as sp}from"fs";import{join as ip,sep as Zt}from"path";var je="0.4.0";var On="@fui-org/fui-cli",ap=86400000,cp=5000;function ka(e=Bun.env){return ip(Ze(e),"..","update.json")}async function $a(e=Bun.env){try{let t=await Bun.file(ka(e)).json();if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function At(e,t=Bun.env){await Bun.write(ka(t),`${JSON.stringify(e,null,2)}
|
|
78
|
+
`)}function va(e=Bun.main){let t;try{t=sp(e)}catch{return null}let n=`${Zt}install${Zt}global${Zt}node_modules${Zt}@fui-org${Zt}fui-cli${Zt}`,o=t.indexOf(n);return o<0?null:t.slice(0,o+n.length-1)}async function Dr(e=Bun.env){let t=e.FUI_AUTO_UPDATE?.trim().toLowerCase();if(t)return!["0","off","false","no"].includes(t);try{return(await ut(e))["auto-update"]!=="off"}catch{return!0}}async function xr(e=Bun.env){let t=(e.FUI_UPDATE_REGISTRY||"https://registry.npmjs.org").replace(/\/+$/,""),n=await fetch(`${t}/${On.replace("/","%2F")}/latest`,{signal:AbortSignal.timeout(cp),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 Or(e,t=je){return Bun.semver.order(e,t)===1}async function lp(e){let t=await Bun.$`${process.execPath} add -g ${`${On}@${e}`}`.quiet().nothrow();return{ok:t.exitCode===0,output:`${t.stdout}${t.stderr}`.trim()}}async function Mr(e=Bun.env){let t=await $a(e),n=new Date().toISOString(),o;try{o=await xr(e)}catch(r){throw await At({...t,checkedAt:n,error:r.message},e),r}if(!Or(o))return await At({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"current"};if(!va())return await At({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"manual",detail:`bun add -g ${On}@${o}`};let s=await lp(o);if(!s.ok)return await At({...t,checkedAt:n,latest:o,error:s.output.slice(-500)},e),{latest:o,action:"failed",detail:s.output};return await At({checkedAt:n,latest:o,updatedFrom:je,updatedTo:o,noticeShown:!1},e),{latest:o,action:"installed"}}async function Sa(e=Bun.env){if(!va()||!await Dr(e))return null;let t=await $a(e),n=null;if(t.updatedTo===je&&t.noticeShown===!1)n=`# fui updated ${t.updatedFrom} -> ${je}; refresh installed skills: fui skill install`,t.noticeShown=!0,await At(t,e);let o=t.checkedAt?Date.parse(t.checkedAt):0;if(Date.now()-o<ap)return n;return await At({...t,checkedAt:new Date().toISOString()},e),Bun.spawn([process.execPath,Bun.main,"update","--background"],{stdio:["ignore","ignore","ignore"],env:{...e,FUI_AUTO_UPDATE:"off"}}).unref(),n}function ja(e){let t=e[0];if(!t)throw new f(`missing <key>; keys: ${no.join(", ")}`);if(!no.includes(t))throw new f(`unknown key "${t}"; keys: ${no.join(", ")}`);return t}async function dp(){let e=await Dr(),t=Bun.env.FUI_AUTO_UPDATE?.trim(),n=(await ut())["auto-update"],o=t?"env":n?"config":"default",s=t?"FUI_AUTO_UPDATE":n?Ze():"built-in (global bun installs only)";return{key:"auto-update",value:e?"on":"off",source:o,from:s}}var pp={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 oo();E(`# config file: ${Ze()}`),k(X([{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 dp()],t))}},mp={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=ja(e),n=e[1];if(!n)throw new f(`missing <value> for ${t}`);let o;if(t==="workdir")o=Wt(n),await up(o,{recursive:!0});else if(t==="auto-update"){if(n!=="on"&&n!=="off")throw new f("auto-update takes on or off");o=n}else if(t==="api-token")o=await Pe(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let s=await ut();s[t]=o;let r=await Ko(s),i={workdir:"FUI_MCP_WORKDIR","api-base":"FUI_API_BASE","api-token":"FUI_API_TOKEN","auto-update":"FUI_AUTO_UPDATE"}[t];if(Bun.env[i])E(`# note: ${i} is set in this shell and overrides the config file`);k(`set ${t}${t==="api-token"?"":`=${o}`} in ${r}
|
|
79
|
+
`)}},fp={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=ja(e),n=await ut();delete n[t];let o=await Ko(n);k(`unset ${t} in ${o}
|
|
80
|
+
`)}},Ia=[pp,mp,fp];var hp={name:"db list",mcpName:"db_config_read",usage:"fui db list [-p project]",summary:"List connections across the workspace (no secrets)",async run({store:e,values:t,render:n}){let o=h(t,"project"),s=o?[tr(o)]:[...new Set([...await e.listProjects(),...await e.listLegacyProjects()])].sort(),r=[];for(let i of s){let a=await e.load(i),c=await e.loadLegacy(i),l=c?.apiName??"legacy";if(c&&!me(a,l))r.push({db:`${i}/${l}`,engine:"legacy",default:"",apiDomain:c.apiDomain??"",database:c.database??"",userToken:c.userToken?"yes":"no"}),E(`# ${i}: _db/config.json is the pre-1.4 format and unused; run: fui db import-legacy -p ${i}`);for(let[u,d]of Object.entries(a.connections)){let p="invalid",m=d.database??"";try{if(d.dbToken){let w=io(d.dbToken);p=w.engine,m||=w.database}}catch{}r.push({db:`${i}/${u}`,engine:p,default:a.default===u?"*":"",apiDomain:d.apiDomain??"",database:m,userToken:d.userToken?"yes":"no"})}}if(r.length===0){E("no connections; add one: fui db add <alias> -p <project> --token -");return}k(X(r,n))}},gp={name:"db add",mcpName:"db_connect",usage:`fui db add <alias> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
|
|
81
|
+
[--api-domain host] [--sql-server host] [--database name] [--user-token-env VAR] [--default] [--no-test]`,summary:"Add or replace a connection: test, save, and (MSSQL) fetch the schema",options:{token:{type:"string"},"token-env":{type:"string"},conn:{type:"string"},"conn-env":{type:"string"},"api-domain":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},"user-token-env":{type:"string"},default:{type:"boolean"},"no-test":{type:"boolean"}},async run(e){let{values:t,positionals:n,targets:o}=e,s=n[0];if(!s)throw new f("missing <alias>");let r=(await o.resolveProject(h(t,"project"))).projectId,i=await Pe(h(t,"token"),h(t,"token-env"),"--token"),a=await Pe(h(t,"conn"),h(t,"conn-env"),"--conn");if(i===void 0===(a===void 0))throw new f("pass exactly one of --token/--token-env or --conn/--conn-env");let c=await _n(await z(e),r,{alias:s,dbToken:i??Zo(a),apiDomain:h(t,"api-domain"),sqlServer:h(t,"sql-server"),database:h(t,"database"),userToken:await Pe(void 0,h(t,"user-token-env"),"--user-token"),makeDefault:t.default===!0,test:t["no-test"]!==!0});k(`${c.notes.join(`
|
|
82
|
+
`)}
|
|
83
|
+
`)}},wp={name:"db default",usage:"fui db default <project>/<alias>",summary:"Set the project default connection",async run(e){let t=e.positionals[0]??h(e.values,"db");if(!t)throw new f("missing <project>/<alias>");let n=await z(e),o=await e.store.resolveProject({db:t,project:n.project,contextProject:n.contextProject}),s=await e.store.setDefault(o,t.split("/").pop());k(`default ${o}/${s}
|
|
84
|
+
`)}},yp={name:"db test",usage:"fui db test [--db ref]",summary:"Run SELECT 1 and report latency",async run(e){let t=await z(e),n=await e.store.resolve({db:h(e.values,"db"),project:t.project,contextProject:t.contextProject}),o=Bun.nanoseconds();await ve(n,(r)=>r.query("SELECT 1 AS ok"));let s=Math.round((Bun.nanoseconds()-o)/1e6);k(`ok ${n.projectId}/${n.alias} ${n.engine} ${s}ms
|
|
85
|
+
`)}},bp={name:"db import-legacy",usage:"fui db import-legacy -p <project> [--alias name] [--default] [--no-test]",summary:"Import a pre-1.4 _db/config.json as a named connection (same flow as db add; secrets never printed)",options:{alias:{type:"string"},default:{type:"boolean"},"no-test":{type:"boolean"}},async run(e){let{values:t,targets:n,store:o}=e,s=(await n.resolveProject(h(t,"project"))).projectId,r=await o.loadLegacy(s);if(!r)throw new f(`project "${s}" has no legacy _db/config.json with a dbToken`);let i=h(t,"alias")??r.apiName;if(!i)throw new f("legacy config has no apiName; pass --alias <name> (the tAPI alias in module.json API paths)");let a=await _n(await z(e),s,{alias:i,dbToken:r.dbToken,apiDomain:r.apiDomain,sqlServer:r.sqlServer,database:r.database,userToken:r.userToken,makeDefault:t.default===!0,test:t["no-test"]!==!0});k(`${a.notes.join(`
|
|
86
|
+
`)}
|
|
87
|
+
legacy _db/config.json left unchanged
|
|
88
|
+
`)}},Ca=[hp,gp,bp,wp,yp];import{readdir as Rp}from"fs/promises";import{join as Lr}from"path";import{readdir as kp}from"fs/promises";import{basename as $p,dirname as vp,extname as Sp,isAbsolute as jp,join as bo,relative as yo,resolve as Ip,sep as Cp}from"path";function ko(e,t,n=process.cwd()){let o=Ip(n,t),s=yo(e.root,o);if(!s||s.startsWith("..")||jp(s))return null;let r=s.split(Cp),i=r[0];if(i.startsWith("_")||r.length<2)return null;if(r[1]==="modules"&&r.length>=4){let c=r[2],l=bo(e.root,i,"modules",c);return{abs:o,baseDir:l,rel:ke(yo(l,o)),projectId:i,moduleId:c}}let a=bo(e.root,i);return{abs:o,baseDir:a,rel:ke(yo(a,o)),projectId:i}}var Ra=(e)=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");async function Fr(e,t){let n=yo(e.root,t),o=Sp(n),s=$p(n,o),r=bo(e.historyRoot(),vp(n)),i=new RegExp(`^${Ra(s)}_(\\d{8}_\\d{6})((?:_[A-Za-z0-9-]+)*)${Ra(o)}$`),a;try{a=await kp(r)}catch{return[]}return a.map((c)=>{let l=i.exec(c);return l?{stamp:l[1],suffix:l[2].replace(/^_/,""),path:bo(r,c)}:null}).filter((c)=>c!==null).sort((c,l)=>l.stamp.localeCompare(c.stamp)||l.path.localeCompare(c.path))}function Ur(e,t){let n=ko(e.paths,t);if(!n)throw new f(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var Pp={name:"file where",usage:"fui file where [-p project -m module]",summary:"Show the workspace, resolved project/module, their folders and where each came from",options:{module:{type:"string",short:"m"}},async run(e){let{paths:t,workdir:n,session:o,targets:s,values:r,render:i}=e,a=[{key:"workspace",value:t.root,source:n.source},{key:"session",value:o.file,source:"FUI_SESSION"}];try{let c=await s.resolveProjectModule(h(r,"project"),h(r,"module"));a.push({key:"project",value:c.projectId,source:c.source},{key:"module",value:c.moduleId,source:c.source},{key:"module-dir",value:c.moduleDir,source:c.source})}catch{try{let c=await s.resolveProject(h(r,"project"));a.push({key:"project",value:c.projectId,source:c.source})}catch{a.push({key:"project",value:"",source:"none"})}}k(X(a,i))}};async function Ep(e){try{return(await Rp(Lr(e,"modules"),{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>Lr(e,"modules",n.name))}catch{return[]}}var Tp={name:"file status",usage:"fui file status [-p project -m module] [--all]",summary:"Local files not matching the last publish/sync (ledger); --all = every checked-out module of the project",options:{module:{type:"string",short:"m"},all:{type:"boolean"}},async run(e){let{values:t,targets:n,paths:o,render:s}=e,r=[];if(t.all===!0){let{projectId:a}=await n.resolveProject(h(t,"project"));r.push(o.projectDir(a),...await Ep(o.projectDir(a)))}else{let a=await n.resolveProjectModule(h(t,"project"),h(t,"module"));r.push(a.moduleDir)}let i=[];for(let a of r)for(let c of await Tn(a))i.push({dir:a,path:c.relPath,reason:c.reason,push:mo(c.relPath)});E(`# ${i.length} unpublished file(s)`),k(X(i,s))}},Ap={name:"file history",usage:"fui file history <path>",summary:"List archived versions of a workspace file (newest first)",async run(e){let t=e.positionals[0];if(!t)throw new f("missing <path>");let n=Ur(e,t),o=await Fr(e.paths,n.abs);E(`# ${o.length} version(s) of ${n.rel}`),k(X(o.map((s)=>({...s,path:jt(e.paths,s.path)})),e.render))}},Np={name:"file restore",usage:"fui file restore <path> [--at YYYYMMDD_HHMMSS]",summary:"Copy an archived version back (current file archived first); local only, ledger untouched",options:{at:{type:"string"}},async run(e){let t=e.positionals[0];if(!t)throw new f("missing <path>");let n=Ur(e,t),o=await Fr(e.paths,n.abs),s=h(e.values,"at"),r=s?o.find((c)=>c.stamp===s):o[0];if(!r)throw new f(s?`no version ${s} of ${n.rel}; run: fui file history ${t}`:`no archived versions of ${n.rel}`);let i=await pe(n.abs),a=i===null?null:await he(e.paths,n.abs,{content:i});if(await Bun.write(n.abs,await H(r.path)),a)E(`# previous content archived: ${jt(e.paths,a)}`);k(`restored ${n.rel} from ${r.stamp}; push it with: ${mo(n.rel)}
|
|
89
|
+
`)}},_p={name:"file ledger",usage:"fui file ledger <path...> [--mark | --forget]",summary:"Show, mark or forget publish-ledger entries (_publishState.json)",options:{mark:{type:"boolean"},forget:{type:"boolean"}},async run(e){let{positionals:t,values:n,render:o}=e;if(t.length===0)throw new f("missing <path>");if(n.mark===!0&&n.forget===!0)throw new f("pass --mark or --forget, not both");let s=t.map((a)=>Ur(e,a)),r=Map.groupBy(s,(a)=>a.baseDir);for(let[a,c]of r){let l=c.map((u)=>u.rel);if(n.mark===!0)await Te(a,l);if(n.forget===!0)await Ci(a,l)}let i=[];for(let[a,c]of r){let l={};try{l=JSON.parse(await H(Lr(a,Gt)))}catch{}for(let u of c){let d=await pe(u.abs),p=l[u.rel]??"",m=d===null?"missing":!p?"new":p===Rt(d)?"published":"changed";i.push({path:u.rel,state:m,ledger:p,dir:a})}}k(X(i,o))}},Pa=[Pp,Tp,Ap,Np,_p];import{rename as om}from"fs/promises";import{basename as rm,dirname as es,join as Be}from"path";function en(e){return e==="text/css"||e==="text/javascript"}function Dp(e){return(e||"").trim().replace(/[<>:"/\\|?*\x00-\x1F]/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"import-file"}function Mn(e,t,n){let o=n.startsWith(".")?n:`.${n}`;return`${Dp(e)}-${t}${o}`}function mt(e){return e.map((t)=>({name:t.FileName,type:t.FileType,contentType:t.ContentType,fileID:t.fileID,scope:t.Scope,sort:t.FileSort??null,...en(t.ContentType)?{filePath:`imports/${Mn(t.FileName,t.fileID,t.FileType)}`}:{}}))}function tn(e,t,n=[],o="",s="",r=[]){let i={},a={ModuleName:e.ModuleName,data:[],watch:{},controls:[],set:{}},c=t.moduleUI;if(c&&c.trim())try{a=JSON.parse(c)}catch(l){throw Error(`module.json of module "${e.ModuleID}" from the server is not valid JSON: ${l.message}; nothing written, local copy kept. ${c.length} chars, starts with ${JSON.stringify(c.slice(0,200))}`)}i["module.json"]=JSON.stringify(a,null,2),i["script.js"]=o??"",i["style.css"]=s??"",i["header.html"]=t.mHeader??"",i["body.html"]=t.mBody??"",i["imports/_imports.json"]=JSON.stringify(mt(r.filter((l)=>l.Scope!=="project")),null,2),i["components/_components.json"]=JSON.stringify(n.map((l)=>({comID:l.comID,comName:l.comName})),null,2);for(let l of n)i[`components/${l.comName}.vue`]=l.comCode;return i}function Br(e,t,n,o,s){let r={};r["_projectInfo.json"]=JSON.stringify(e,null,2),r["project.json"]=JSON.stringify(t,null,2),r["modules/_modules.json"]=JSON.stringify(n,null,2),r["imports/_imports.json"]=JSON.stringify(mt(s),null,2),r["components/_components.json"]=JSON.stringify(o.map((i)=>({comID:i.comID,comName:i.comName})),null,2);for(let i of o)r[`components/${i.comName}.vue`]=i.comCode;return r}function Ea(e,t){let n=[`${e}/`],o=new Set;for(let s of Object.keys(t).sort()){let r=s.split("/");for(let i=0;i<r.length-1;i++){let a=r.slice(0,i+1).join("/");if(!o.has(a))o.add(a),n.push(`${" ".repeat(i+1)}${r[i]}/`)}n.push(`${" ".repeat(r.length)}${r[r.length-1]}`)}return n.join(`
|
|
90
|
+
`)}import{mkdir as nn,readdir as Nt,rm as xp,stat as Op,unlink as Hr}from"fs/promises";import{basename as Ta,dirname as $o,join as se}from"path";function _t(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}async function F(e,t=""){try{return await H(e)}catch{return t}}async function Wr(e,t){try{return await H(e)}catch(n){if(Sn(n)==="ENOENT")throw new f(`local ${t} does not exist (${e}); run fui module get first, or check -p/-m. Nothing published (an empty publish would wipe the server copy)`);throw Error(`cannot read ${t} at ${e}: ${n.message}`)}}async function U(e,t){try{return JSON.parse(await H(e))}catch{return t}}async function ht(e){try{return(await Op(e)).isDirectory()}catch{return!1}}async function Me(e){try{return(await Nt(e,{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.endsWith(".vue")).map((n)=>n.name)}catch{return[]}}async function Fn(e,t){let n=[];for(let[o,s]of Object.entries(t)){let r=se(e,o);await nn($o(r),{recursive:!0}),await Bun.write(r,s),n.push(r)}return n}async function Aa(e,t,n){let o=[],s;try{s=await Nt(t,{withFileTypes:!0})}catch{return o}for(let r of s){if(!r.isFile()||!r.name.endsWith(".vue")||n.has(r.name))continue;let i=se(t,r.name);await he(e,i,{suffix:"deleted"}),await Hr(i),o.push(i)}return o}function Na(e){return new Set(Object.keys(e).filter((t)=>t.startsWith("components/")&&t.endsWith(".vue")).map((t)=>Ta(t)))}async function qr(e,t,n){return await nn(t,{recursive:!0}),await Aa(e,se(t,"components"),Na(n)),Ae(e,t,n)}async function _a(e,t,n,o,s){await nn(t,{recursive:!0});let r={"_moduleInfo.json":JSON.stringify({...n,HTMLOnly:o.HTMLOnly,Framework:o.Framework,mTitle:o.mTitle},null,2),...s},i=[];for(let l of["header.html","body.html"])if(!r[l])try{let u=await H(se(t,l));if(u.trim())i.push(`${l}: server returned empty; local content (${u.length} chars) was overwritten. check the server SP if unexpected`)}catch{}let a=await Aa(e,se(t,"components"),Na(r)),c=await Ae(e,t,r);return{moduleDir:t,removedFiles:a,warnings:i,sync:c}}async function Da(e,t,n){let o=await U(se(t,"components","_components.json"),[]),s=[];if(!Array.isArray(o))return s;for(let r of o)if(r?.comName&&!n.has(r.comName)){let i=se(t,"components",`${r.comName}.vue`);try{await he(e,i,{suffix:"deleted"}),await Hr(i),s.push(`${r.comName}.vue`)}catch{}}return s}async function Ln(e,t,n){let o=se(t,"imports"),s=[],r;try{r=await Nt(o,{withFileTypes:!0})}catch{return s}let i=new Set([...n].map((a)=>Ta(a)));for(let a of r){if(!a.isFile()||a.name==="_imports.json"||i.has(a.name))continue;let c=se(o,a.name);try{await he(e,c,{suffix:"deleted"}),await Hr(c),s.push(`imports/${a.name}`)}catch{}}return s}async function xa(e,t,n){let o=se(t,"modules"),s=[],r;try{r=await Nt(o,{withFileTypes:!0})}catch{return s}for(let i of r){if(!i.isDirectory()||n.has(i.name))continue;let a=se(o,i.name);await co(e,a,{suffix:"deleted"}),await xp(a,{recursive:!0,force:!0}),s.push(i.name)}return s}function Jr(e){if(!e||typeof e!=="object")return!1;let t=e;return typeof t.ModuleID==="string"&&t.mDomain!==void 0&&typeof t.ProjectID==="string"}function Vr(e,t){return se(e.projectDir(t),"modules","_modules.json")}async function Oa(e,t){let n=await U(Vr(e,t),[]);return Array.isArray(n)?n.filter(Jr):[]}async function Un(e,t,n){let o=e.projectDir(t);await Fn(o,{"modules/_modules.json":JSON.stringify(n,null,2)});for(let s of n){let r=se(o,"modules",s.ModuleID,"_moduleInfo.json"),i=await U(r,null);if(!i)continue;await Bun.write(r,JSON.stringify({...i,...s},null,2))}return n.length}async function vo(e,t,n,o){let s=Vr(e,t),r=await U(s,[]),i=Array.isArray(r)?r.filter(Jr):[],a=o??n.ModuleID,{mTitle:c,...l}=n,u=i.findIndex((d)=>d.ModuleID===a||n.id&&d.id===n.id);if(u>=0)i[u]={...i[u],...l};else i.push(l);await nn($o(s),{recursive:!0}),await Bun.write(s,JSON.stringify(i,null,2))}async function Ma(e,t,n){let o=Vr(e,t),s=await U(o,[]),r=Array.isArray(s)?s.filter(Jr):[],i=r.findIndex((a)=>a.ModuleID===n);if(i<0)return!1;return r.splice(i,1),await nn($o(o),{recursive:!0}),await Bun.write(o,JSON.stringify(r,null,2)),!0}async function Fa(e){try{return(await Nt(e.root,{withFileTypes:!0})).filter((n)=>n.isDirectory()&&!n.name.startsWith("_")).map((n)=>n.name)}catch{return[]}}async function La(e){let t=[],n;try{n=await Nt(e.root,{withFileTypes:!0})}catch{return t}for(let o of n){if(!o.isDirectory()||o.name.startsWith("_"))continue;let s=se(e.root,o.name);t.push({baseDir:s,projectId:o.name});try{for(let r of await Nt(se(s,"modules"),{withFileTypes:!0}))if(r.isDirectory())t.push({baseDir:se(s,"modules",r.name),projectId:o.name,moduleId:r.name})}catch{}}return t}async function Kr(e,t){let n=[];for(let o of await La(e)){let s=await U(se(o.baseDir,"imports/_imports.json"),[]),r=Array.isArray(s)?s.find((i)=>i?.fileID===t):void 0;if(r)n.push({...o,entry:r})}return n}async function Gr(e,t,n){let o=[];for(let{baseDir:s}of await La(e)){let r=await U(se(s,"imports/_imports.json"),[]),i=Array.isArray(r)?r.find((c)=>c?.fileID===t&&c?.filePath):void 0;if(!i?.filePath)continue;let a=se(s,i.filePath);try{await nn($o(a),{recursive:!0}),await Bun.write(a,n),o.push(a)}catch{}}return o}var Mp=20,Fp=50;function zr(e,t){let n=t==="module"?Fp:Mp;if(e===void 0)return{effectiveSort:n,note:`fileSort not given; using ${n} (${t} imports must load after the framework)`};if(e<n)return{effectiveSort:n,note:`fileSort ${e} raised to ${n}: lower values load before the framework (~1-10) and can break the page`};return{effectiveSort:e,note:""}}function gt(e){return JSON.stringify(e??{},null,2)}function ft(e){return e.replace(/\r\n/g,`
|
|
91
|
+
`).trim()}var Lp=["module.json","header.html","body.html","script.js","style.css"];async function So(e){let t=[];for(let o of Lp)t.push(`${o}
|
|
92
|
+
${await F(se(e,o))}`);let n=se(e,"components");for(let o of(await Me(n)).slice().sort())t.push(`components/${o}
|
|
93
|
+
${await F(se(n,o))}`);return Rt(t.join(`
|
|
94
|
+
\x00
|
|
95
|
+
`))}function Ua(e,t,n){let o=e.trim(),s=t.trim(),r=n,i=o.match(/^<head[^>]*>([\s\S]*)<\/head>\s*$/i);if(i)o=i[1].trim();let a=o.match(/<title[^>]*>([\s\S]*?)<\/title>/i);if(a)r=a[1].replace(/\s+/g," ").trim(),o=o.replace(/<title[^>]*>[\s\S]*?<\/title>\s*/i,"").trim();let c=s.match(/^<body[^>]*>([\s\S]*)<\/body>\s*$/i);if(c)s=c[1].trim();return{pageHeader:o,pageBody:s,mTitle:r}}function Ba(e,t){let n=[],o=new Map(e.map((r)=>[r.comName,r.comCode])),s=new Set(Object.keys(t));for(let[r,i]of Object.entries(t)){let a=o.get(r);if(a===void 0)n.push(`added component ${r}`);else if(ft(a)!==ft(i))n.push(`updated component ${r}`)}for(let r of e)if(!s.has(r.comName))n.push(`deleted component ${r.comName}`);return n}function Xr(e){if(!_t(e))throw new f("project.json must be a JSON object at the top level");if(_t(e.data))return{config:e.data,wasEnveloped:!0};return{config:e,wasEnveloped:!1}}function jo(e){let t=0,n=0,o=new Set;try{let s=e.moduleUI?JSON.parse(e.moduleUI):{};t=(s.controls||[]).length,n=(s.data||[]).length;for(let r of s.controls||[])for(let i of r.rows||[])for(let a of i.cols||[])if(a.el)o.add(a.el)}catch{}return[`framework ${e.Framework}, htmlOnly ${e.HTMLOnly?"yes":"no"}, controls ${t}, data keys ${n}, referenced components ${o.size}`]}import{join as ie}from"path";function wt(e){if(!e)throw Error("component source is empty");let t=/<template(\s[^>]*)?>/i.exec(e);if(!t)throw Error("no <template> in the source");let n=t.index+t[0].length,o=e.toLowerCase(),s=Bp(e,o,n);if(s===-1)throw Error("no </template> closing the template block");let r=o.lastIndexOf("</script");if(r===-1)throw Error("no <script> in the source");let i=/<script(\s[^>]*)?>/gi;i.lastIndex=s;let a=i.exec(e);if(!a||a.index>=r)throw Error("no <script> in the source");let c=e.substring(n,s).trim(),l=Hp(e.substring(a.index+a[0].length,r).trim()),u=Up(e,o,r);return{template:c,script:l,style:u}}function Up(e,t,n){let o=/<style(\s[^>]*)?>/i.exec(e.slice(n));if(!o)return"";let s=n+o.index+o[0].length,r=t.lastIndexOf("</style");if(r===-1||r<s)return"";return e.substring(s,r).trim()}function Bp(e,t,n){let o=/<!--|<\/?([a-zA-Z][a-zA-Z0-9-]*)/g;o.lastIndex=n;let s=1,r=e.length;for(let i=o.exec(e);i!==null;i=o.exec(e)){if(i[0]==="<!--"){let d=e.indexOf("-->",o.lastIndex);if(d===-1)return-1;o.lastIndex=d+3;continue}let a=e.charAt(i.index+1)==="/",c=i[1].toLowerCase(),l=o.lastIndex,u=!1;while(l<r){let d=e.charAt(l);if(d==='"'||d==="'"){let p=e.indexOf(d,l+1);if(p===-1)return-1;l=p+1;continue}if(d===">"){u=e.charAt(l-1)==="/",l++;break}l++}if(o.lastIndex=l,c==="template"){if(a){if(s--,s===0)return i.index}else if(!u)s++;continue}if(!a&&(c==="script"||c==="style"||c==="textarea"||c==="title")){let d=t.indexOf(`</${c}`,l);if(d===-1)return-1;let p=e.indexOf(">",d);if(p===-1)return-1;o.lastIndex=p+1}}return-1}function Hp(e){let t=e.match(/export\s+default\s*\{/);if(!t)throw Error('no "export default {" in the script');let n=e.lastIndexOf("}");if(n<=t.index)throw Error('no "}" closing "export default {"');return e.substring(t.index+t[0].length,n).trim()}function on(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function Ha(e){let t=(e??"").trim();if(!t)return{};let n=t.startsWith("//")?`https:${t}`:t;if(/^https?:\/\//i.test(n))try{let s=new URL(n);return{host:s.host,path:s.pathname||"/"}}catch{}let o=t.split(/[?#]/)[0];if(!o)return{};return{path:o.startsWith("/")?o:`/${o}`}}function Wp(e){let t=e.trim();if(/^(https?:)?\/\//i.test(t)){let{host:o,path:s}=Ha(t);return o?{host:o,path:s}:null}let n=t.split("/")[0];if(n&&n.includes(".")){let{host:o,path:s}=Ha(`//${t}`);return o?{host:o,path:s}:null}return null}function Wa(e){let n=e.trim().replace(/^https?:\/\/[^/]+/i,"").split(/[?#]/,1)[0].replace(/^\/+|\/+$/g,"").split("/").filter(Boolean);if(n.length<2)throw new f(`target "${e}" must look like /projectId/moduleId`);return{projectHint:n[0],moduleHint:n.slice(1).join("/")}}function qp(e){let t=[e.ModuleID,e.ModuleName,e.mDomain,e.url].filter((n)=>Boolean(n));return[...new Set(t.flatMap((n)=>[n.toLowerCase(),on(n),n.replace(/^\/+|\/+$/g,"").toLowerCase()]))]}function Yr(e,t){let n=t.trim().toLowerCase().replace(/^\/+|\/+$/g,""),o=on(t);return e.filter((s)=>{let r=qp(s);return r.includes(n)||r.includes(o)})}function rn(e,t,n){if(n?.refreshed)return new f(`module "${t}" does not exist in project "${e}" (the module list was just refreshed from the server). check the name: fui module list -p ${e}, or create it: fui module new`);let o=n?.refreshError?` refreshing the list also failed: ${n.refreshError}.`:"";return new f(`module "${t}" is not in the local cache (${e}/modules/_modules.json).${o} run: fui module list -p ${e}`)}class Bn{paths;api;constructor(e,t){this.paths=e;this.api=t}baseDir(e,t){return t?ie(this.paths.projectDir(e),"modules",t):this.paths.projectDir(e)}async lookupModuleInfo(e,t){let n=t(await Oa(this.paths,e));if(n)return{found:n,refreshed:!1};let o;try{o=await this.api.listModules(e),await Un(this.paths,e,o)}catch(s){return{found:null,refreshed:!1,refreshError:s.message}}return{found:t(o),refreshed:!0}}resolveModuleInfoFromCache(e,t){return this.lookupModuleInfo(e,(n)=>n.find((o)=>o.ModuleID===t)??null)}async refreshProjectInfoCache(e){let t=this.paths.projectDir(e);if(!await ht(t))return null;let n=(await this.api.listProjects()).find((o)=>o.ProjectID===e);if(!n)return null;return await Fn(t,{"_projectInfo.json":JSON.stringify({...n,fetchedAt:new Date().toISOString()},null,2)}),e}async projectInfoRefreshNote(e){if(typeof e!=="string"||!e.trim())return null;try{return await this.refreshProjectInfoCache(e)?`refreshed ${e}/_projectInfo.json`:null}catch{return`could not refresh ${e}/_projectInfo.json; run fui project sync -p ${e} if needed`}}async resolveProjectIdFromHint(e){let t=e.trim().toLowerCase(),n=on(e),o=(await Fa(this.paths)).filter((a)=>a.toLowerCase()===t||on(a)===n);if(o.length>1)throw new f(`project hint "${e}" matched several local project folders: ${o.join(", ")}`);if(o.length===1)return o[0];let s;try{s=await this.api.listProjects()}catch{return e}let r=s.filter((a)=>on(a.ProjectName)===n||a.ProjectName.trim().toLowerCase()===t);if(r.length===1)return r[0].ProjectID;if(r.length>1)throw new f(`project hint "${e}" matched several projects by name: ${r.map((a)=>`${a.ProjectName} (${a.ProjectID})`).join(", ")}`);let i=s.filter((a)=>a.ProjectName.toLowerCase().includes(t)||on(a.ProjectName).includes(n)).slice(0,10);if(i.length>0)throw new f(`no project matches "${e}" exactly; did you mean: ${i.map((a)=>`${a.ProjectName} (${a.ProjectID})`).join(", ")}`);return e}async resolveModuleTargetByUrl(e,t){let n=(t??"").split("/").filter(Boolean),s=(await this.api.getRuntimeModuleVersion(e,n[0]??"",n[1]??""))[0];if(!s||s.Status!==1||!s.ProjectID||!s.ModuleID)throw new f(`cannot resolve a module from domain "${e}"${t?` path "${t}"`:""} via FUI_Runtime_ModuleVersionSelect${s?` (Status=${s.Status})`:" (no result)"}; check the URL or look it up: fui project list / fui module list`);let r=await this.resolveModuleInfoFromCache(s.ProjectID,s.ModuleID);if(!r.found)throw rn(s.ProjectID,s.ModuleID,r);return{projectId:s.ProjectID,moduleId:s.ModuleID,moduleInfo:r.found,matchedBy:"runtime_lookup"}}async resolveProjectAndModuleHint(e){let t=Wp(e);if(t)try{let o=await this.resolveModuleTargetByUrl(t.host,t.path);return{projectId:o.projectId,moduleHint:o.moduleId,matchedBy:o.matchedBy,moduleInfo:o.moduleInfo}}catch(o){try{let s=Wa(e);return{projectId:await this.resolveProjectIdFromHint(s.projectHint),moduleHint:s.moduleHint,matchedBy:"target_path"}}catch(s){throw new f(`cannot resolve target "${e}": as URL/domain: ${o.message}; as /projectId/moduleId: ${s.message}`)}}let n=Wa(e);return{projectId:await this.resolveProjectIdFromHint(n.projectHint),moduleHint:n.moduleHint,matchedBy:"target_path"}}async resolveModuleTarget(e,t,n){if(t&&n){let r=await this.resolveModuleInfoFromCache(t,n);if(!r.found)throw rn(t,n,r);return{projectId:t,moduleId:n,moduleInfo:r.found,matchedBy:"explicit_ids"}}if(!e)throw new f("give a target (/project/module or a URL) or both -p and -m");let o=await this.resolveProjectAndModuleHint(e);if(o.moduleInfo)return{projectId:o.projectId,moduleId:o.moduleInfo.ModuleID,moduleInfo:o.moduleInfo,matchedBy:o.matchedBy};let s=await this.lookupModuleInfo(o.projectId,(r)=>{let i=Yr(r,o.moduleHint);if(i.length>1)throw new f(`target "${e}" matched several modules in project ${o.projectId}: ${i.map((a)=>a.ModuleID).join(", ")}`);return i[0]??null});if(!s.found)throw rn(o.projectId,o.moduleHint,s);return{projectId:o.projectId,moduleId:s.found.ModuleID,moduleInfo:s.found,matchedBy:"target_path"}}async fetchModuleBundle(e){let{ProjectID:t,ModuleID:n}=e,[o,s,r,i,a]=await Promise.all([this.api.getModuleContent(t,n),this.api.getModuleComponents(t,n),this.api.getModuleScript(t,n),this.api.getModuleCSS(t,n),this.api.getModuleDependencies(t,n)]);return{moduleInfo:e,content:o,components:s,script:r,css:i,dependencies:a}}async fetchProjectBundle(e){let[t,n,o,s,r]=await Promise.all([this.api.listProjects(),this.api.listModules(e),this.api.getProjectJson(e),this.api.getModuleComponents(e),this.api.listImportFiles(e)]);return{projectInfo:{...t.find((c)=>c.ProjectID===e)??{ProjectID:e,ProjectName:e},fetchedAt:new Date().toISOString()},modules:n,projectJson:o,projectComponents:s,projectImports:r}}async downloadImportContents(e){let t={},n=[];for(let o of e){if(!en(o.ContentType))continue;try{let r=(await this.api.getFileContent(o.fileID))?.data?.FileContent??"";t[`imports/${Mn(o.FileName,o.fileID,o.FileType)}`]=r}catch(s){n.push(`${o.FileName} (fileID ${o.fileID}): ${s?.message??String(s)}`)}}return{files:t,failures:n}}async uploadImportContents(e){let t=await U(ie(e,"imports/_imports.json"),[]);for(let n of t){if(!n.fileID||!n.filePath||!en(n.contentType??""))continue;let o=await F(ie(e,n.filePath));if(o)await this.api.uploadFileContent(n.fileID,o)}}async syncComponentsLocal(e,t){let n=await this.api.getModuleComponents(e,t),o=n.map((i)=>({comID:i.comID,comName:i.comName})),s=this.baseDir(e,t),r=await Da(this.paths,s,new Set(o.map((i)=>i.comName)));return await Fn(s,{"components/_components.json":JSON.stringify(o,null,2)}),{count:n.length,removed:r}}async syncImportsLocal(e,t){let n=await this.api.listImportFiles(e,t),o=t?n.filter((c)=>c.Scope!=="project"):n,s=this.baseDir(e,t),r=mt(o),i=new Set(r.map((c)=>c.filePath).filter((c)=>!!c)),a=await Ln(this.paths,s,i);return await Fn(s,{"imports/_imports.json":JSON.stringify(r,null,2)}),{list:n,count:o.length,removed:a}}async publishStagedModuleDir(e){let t=await U(ie(e,"_moduleInfo.json"),null);if(!t)throw new f(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await F(ie(e,"module.json")),o;try{o=JSON.parse(n)}catch(g){throw new f(`module.json has invalid JSON syntax: ${g.message}; fix it before publishing`)}let s=await F(ie(e,"header.html")),r=await F(ie(e,"body.html")),i=await F(ie(e,"script.js")),a=await F(ie(e,"style.css")),{pageHeader:c,pageBody:l,mTitle:u}=Ua(s,r,t.mTitle??""),{ProjectID:d,ModuleID:p}=t;if(await this.api.updateModule(d,p,{moduleUI:o,title:u,pageHeader:c,pageBody:l}),i.trim())await this.api.updateModuleScript(d,p,i);if(a.trim())await this.api.updateModuleCSS(d,p,a);let m=ie(e,"components"),w=await U(ie(m,"_components.json"),[]),y=new Map(w.map((g)=>[g.comName,g.comID])),v=await Me(m),T=new Set(v.map((g)=>g.replace(/\.vue$/i,"")));for(let g of v){let b=g.replace(/\.vue$/i,""),{template:R,script:C,style:S}=wt(await F(ie(m,g)));await this.api.updateComponent({ProjectID:d,ModuleID:p,ComID:y.get(b),ComName:b,ComTemplate:R,ComScript:C,ComCSS:S})}for(let g of w)if(!T.has(g.comName))await this.api.updateComponent({ProjectID:d,ModuleID:p,ComID:g.comID,ComName:"",ComTemplate:"",ComScript:""});await this.syncComponentsLocal(d,p),await this.uploadImportContents(e);let I=["module.json","header.html","body.html"];if(i.trim())I.push("script.js");if(a.trim())I.push("style.css");for(let g of v)I.push(ke(ie("components",g)));return await Te(e,I).catch(()=>{return}),t}async previewStagedModuleDir(e){let t=await U(ie(e,"_moduleInfo.json"),null);if(!t)throw new f(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await this.fetchModuleBundle(t),o=tn(n.moduleInfo,n.content,n.components,n.script,n.css,n.dependencies),s;try{s=JSON.parse(await F(ie(e,"module.json")))}catch(p){throw new f(`module.json has invalid JSON syntax: ${p.message}; fix it before previewing`)}let r=await F(ie(e,"header.html")),i=await F(ie(e,"body.html")),a=await F(ie(e,"script.js")),c=await F(ie(e,"style.css")),l=await U(ie(e,"imports/_imports.json"),[]),u={};for(let p of await Me(ie(e,"components")))u[p.replace(/\.vue$/i,"")]=await F(ie(e,"components",p));let d=[];if(gt(s)!==gt(JSON.parse(o["module.json"])))d.push("module.json will be updated");for(let p of["header.html","body.html","script.js","style.css"]){let m={"header.html":r,"body.html":i,"script.js":a,"style.css":c}[p];if(ft(m)!==ft(o[p]||""))d.push(`${p} will be updated`)}if(gt(l)!==gt(JSON.parse(o["imports/_imports.json"]||"[]")))d.push("imports/_imports.json differs locally (metadata only, not sent)");return d.push(...Ba(n.components,u)),{moduleInfo:t,changes:d,remoteSummary:jo(n.content)}}}import{cp as Jp,mkdir as Qr,readdir as Vp,rm as Kp}from"fs/promises";import{dirname as Gp,isAbsolute as zp,join as Ne,relative as Xp,sep as Yp}from"path";var Dt="fui-skill",Qp=["references/controls-patterns.md","references/module-structure.md","references/INDEX.md"],qa=`# FUI Skill Summary
|
|
96
|
+
|
|
97
|
+
Use these rules whenever editing a FUI module:
|
|
98
|
+
|
|
99
|
+
- Always work in the staged local module structure: _info.json, module.json, script.js, header.html, body.html, dependencies.json, components/_components.json, components/*.vue.
|
|
100
|
+
- In module.json, keep the metadata-driven structure: data, watch, controls, set.
|
|
101
|
+
- controls must start with the mandatory grid wrapper: container > rows > cols.
|
|
102
|
+
- Inside attr, use JSON-safe directives like v-on:click, never @click.
|
|
103
|
+
- In action IN blocks, force literal strings when needed by using backtick or quoted-string patterns.
|
|
104
|
+
- Reference reactive state with vueData. prefixes.
|
|
105
|
+
- Custom Vue components should use the uc- prefix.
|
|
106
|
+
- Run module_preview first, then ask the user if they want to push. Only call module_publish_html (with the returned approvalToken) \u2014 or module_publish_json / module_publish_script / component_update for their respective files \u2014 after explicit approval.
|
|
107
|
+
- Build fullstack modules UI-first: design the UI with hardcoded/fake data and verify it visually (module_simulate renderUI) before designing the API contract and wiring real bindings.
|
|
108
|
+
- Never call a live API/SP endpoint to verify it \u2014 read the SP body and reason about it (db_sp_verify) instead; that rule does not apply to db_sql_execute/db_sql_execute_nonquery, which remain the user's direct data-edit tools.
|
|
109
|
+
`,Zp="https://github.com/fui-org/fui-skill";function em(){return`${qa}
|
|
110
|
+
|
|
111
|
+
## Source
|
|
112
|
+
|
|
113
|
+
- Bundled with fui-mcp from ${Zp}
|
|
114
|
+
|
|
115
|
+
## Notes
|
|
116
|
+
|
|
117
|
+
- This fallback file exists so every editor can read a project-local skill snapshot after checkout.
|
|
118
|
+
- fui-mcp copies the package-level src/skills/ folder into each staged project workspace.
|
|
119
|
+
- If the bundled src/skills/fui-skill folder is missing from this MCP package, this summary still gives the agent the critical FUI rules.`}async function tm(){let e=import.meta.dir;for(let t of[Ne(e,"..","..","..","skills"),Ne(e,"..","skills")])if(await Bun.file(Ne(t,Dt,"SKILL.md")).exists())return t;return Ne(e,"..","..","..","skills")}async function sn(){return Ne(await tm(),Dt)}function nm(e){return Ne(e,"_skills",Dt,"SKILL.md")}async function Io(e){let t=Ne(e,"_skills"),n=Ne(t,Dt);await Qr(e,{recursive:!0});try{let o=await sn();if(!await Bun.file(Ne(o,"SKILL.md")).exists())throw Error(`bundled skill missing at ${o}`);return await Kp(t,{recursive:!0,force:!0}),await Qr(t,{recursive:!0}),await Jp(o,n,{recursive:!0,filter:(s)=>!s.split(Yp).includes(".git")}),{workspaceRoot:e,skillDir:n,source:"bundled-copy"}}catch(o){let s=`Could not copy bundled skills/ into ${n}: ${o.message}`;return await Qr(n,{recursive:!0}),await Bun.write(Ne(n,"SKILL.md"),`${em()}
|
|
120
|
+
|
|
121
|
+
## Sync Warning
|
|
122
|
+
|
|
123
|
+
${s}
|
|
124
|
+
`),{workspaceRoot:e,skillDir:n,source:"bundled"}}}async function Ja(e){if(e){let t=nm(e),n=await pe(t);if(n&&n.trim()){let o=[n];for(let s of Qp){let r=await pe(Ne(Gp(t),s));if(r?.trim())o.push(`
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
<!-- ${s} -->
|
|
128
|
+
|
|
129
|
+
${r}`)}return{text:o.join(""),source:t}}}return{text:qa.trim(),source:"bundled-summary"}}async function Va(e,t){return(await Promise.all(t.map(async(o)=>{let s=Ne(e,o),r=Xp(e,s);if(r.startsWith("..")||zp(r))return`
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
<!-- ${o} -->
|
|
133
|
+
|
|
134
|
+
invalid path (outside the skill folder)`;try{return`
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
<!-- ${o} -->
|
|
138
|
+
|
|
139
|
+
${await H(s)}`}catch{return`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
<!-- ${o} -->
|
|
143
|
+
|
|
144
|
+
not found at ${s}`}}))).join("")}async function Zr(){let e=Ne(await sn(),"design-md");return(await Vp(e,{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>n.name).sort()}async function ce(e){return new Bn(e.paths,await e.api())}function Ka(e){if(e.length===0)return[];return[`${e.length} import content(s) NOT downloaded; their local copies are stale even though imports/_imports.json is fresh. do not edit and publish them:`,...e.map((t)=>` - ${t}`),"re-run the sync, or fetch one: fui import get <fileId>"]}async function xt(e,t){let n=await e.wasSkillLoaded();if(!n&&qt())throw new $e(`[FUI_MCP_STRICT] ${t} blocked: skill not loaded this session; read the fui-skill references, run fui skill loaded, then retry`,"hard","error");return n?[]:["warning: skill not loaded this session; read fui-skill (fui skill get / references), then run: fui skill loaded"]}async function Ga(e,t){let n=await(await ce(e)).resolveModuleTarget(t);return{notes:[],data:`project ${n.projectId}
|
|
145
|
+
module ${n.moduleId}
|
|
146
|
+
matchedBy ${n.matchedBy}
|
|
147
|
+
moduleDir ${e.paths.projectDir(n.projectId)}/modules/${n.moduleId}
|
|
148
|
+
`}}async function za(e,t){let{projectId:n,moduleId:o}=t,s=t.moduleDir,r="module_dir";if(!s){if(!t.target&&!(n&&o))throw new f("give a target (/project/module or URL), -p and -m, or --module-dir");let a=await(await ce(e)).resolveModuleTarget(t.target,n,o);n=a.projectId,o=a.moduleId,s=Be(e.paths.projectDir(n),"modules",o),r=a.matchedBy}else{let a=await U(Be(s,"_moduleInfo.json"),null);if(!a)throw new f(`missing _moduleInfo.json in ${s}`);n=a.ProjectID,o=a.ModuleID}let i=t.moduleDir?es(es(es(s))):e.paths.root;return await e.session.setTarget({projectId:n??"",moduleId:o??"",moduleDir:s,workspaceRoot:i,target:t.target}),{notes:[`active ${n}/${o} (${r}) -> ${s}`,`session ${e.session.file}`]}}async function Xa(e){return(await e.api()).listProjects()}async function Ya(e,t){let n=await ce(e),o=e.paths.projectDir(t),s=await n.fetchProjectBundle(t),r=Br(s.projectInfo,s.projectJson,s.modules,s.projectComponents,s.projectImports),{"modules/_modules.json":i,...a}=r,c=new Set(mt(s.projectImports).map((y)=>y.filePath).filter((y)=>!!y)),l=await Ln(e.paths,o,c),u=await n.downloadImportContents(s.projectImports),d=await qr(e.paths,o,{...a,...u.files}),p=await xa(e.paths,o,new Set(s.modules.map((y)=>y.ModuleID)));await Un(e.paths,t,s.modules);let m=!1,w=await e.session.getTarget();if(w?.projectId===t&&w.moduleId&&p.includes(w.moduleId))await e.session.clearTarget(),m=!0;return{notes:[`ok synced project ${t} -> ${o}`,`modules ${s.modules.length}, project components ${s.projectComponents.length}, project imports ${s.projectImports.length} (contents ${Object.keys(u.files).length})`,`files: ${yr(d)}`,...l.length>0?[`removed orphaned import files: ${l.join(", ")}`]:[],...p.length>0?[`removed orphaned module dirs (archived): ${p.join(", ")}`]:[],...m?["session target cleared: it pointed to a removed module; run fui use / fui module get again"]:[],...br(d.archived),...Ka(u.failures)]}}async function Qa(e,t){let n=await(await e.api()).listModules(t),o;try{await Un(e.paths,t,n),o="cache updated: modules/_modules.json (+ _moduleInfo.json of checked-out modules)"}catch(s){o=`warning: could not write the local cache: ${s.message}`}return{rows:n,notes:[o]}}async function Za(e,t){let n=await ce(e),o=(await n.resolveModuleInfoFromCache(t.projectId,t.moduleId)).found;if(o)throw new f(`module "${t.projectId}/${t.moduleId}" already exists ("${o.ModuleName}"); edit it: fui module update -p ${t.projectId} -m ${t.moduleId}, or pick another id`);let s={...t.extra,Framework:t.framework??"V2",mDomain:t.mDomain??"",ModuleID:t.moduleId,ModuleName:t.moduleName,HTMLOnly:t.htmlOnly??!1,dialogModel:"new"},i=(await n.api.editModule(t.projectId,s)).data??[],a=i.find((l)=>l.ModuleID===t.moduleId)??i[0],c=[`ok created module ${a?.id??`${t.projectId}/${t.moduleId}`} url ${a?.url??`/${t.projectId}/${t.moduleId}`} framework ${s.Framework}`];if(a)try{await vo(e.paths,t.projectId,a),c.push("modules/_modules.json updated")}catch{c.push("warning: module created but modules/_modules.json update failed")}return c.push(`next: fui module get -p ${t.projectId} -m ${t.moduleId}`),{notes:c}}async function ec(e,t){let n=await ce(e),{projectId:o,moduleId:s,newModuleId:r}=t,i=Be(e.paths.projectDir(o),"modules",s),a=await U(Be(i,"_moduleInfo.json"),null),c=a?null:await n.resolveModuleInfoFromCache(o,s),l=a??c?.found;if(!l)throw rn(o,s,c??void 0);let u=[];if(t.framework&&t.framework!==l.Framework)u.push(`warning: framework ${l.Framework} -> ${t.framework} switches the CDN set (V2/V3); re-check header/body/script and components`);if(r&&r!==s)u.push(`warning: ModuleID "${s}" -> "${r}" changes the module URL; links and menus to the old URL break`);let{mTitle:d,...p}=l,m={...p,...t.extra,Framework:t.framework??l.Framework,mDomain:t.mDomain??l.mDomain??"",ModuleID:r??s,ModuleName:t.moduleName??l.ModuleName,HTMLOnly:t.htmlOnly??l.HTMLOnly,dialogModel:"edit"},y=((await n.api.editModule(o,m)).data??[]).find((I)=>I.ModuleID===m.ModuleID),v={...l,Framework:m.Framework,mDomain:m.mDomain,ModuleID:m.ModuleID,ModuleName:m.ModuleName,HTMLOnly:m.HTMLOnly};if(r&&r!==s)v.id=`${o}/${r}`,v.url=`/${o}/${r}`;if(y)v={...v,...y};await vo(e.paths,o,v,s);let T=["modules/_modules.json updated"];if(a){let I=i;if(r&&r!==s){let b=Be(e.paths.projectDir(o),"modules",r);if(await ht(b))u.push(`warning: cannot rename the local folder: ${b} exists; _moduleInfo.json updated in place`);else await om(i,b),I=b,T.push(`local folder renamed: modules/${s} -> modules/${r}`)}await Bun.write(Be(I,"_moduleInfo.json"),JSON.stringify({...a,...v},null,2)),T.push("_moduleInfo.json updated");let g=await e.session.getTarget();if(g&&g.projectId===o&&g.moduleId===s)await e.session.setTarget({projectId:o,moduleId:v.ModuleID,moduleDir:I,workspaceRoot:g.workspaceRoot,target:g.target}),T.push("session target updated")}return{notes:[`ok updated ${v.id} url ${v.url} name ${v.ModuleName} framework ${v.Framework} htmlOnly ${v.HTMLOnly}`,...T,...u]}}async function tc(e,t,n){await(await e.api()).deleteModule(t,n);let o=[`ok deleted module ${n} from project ${t}`];try{o.push(await Ma(e.paths,t,n)?"entry removed from modules/_modules.json":"no entry in modules/_modules.json (already absent)")}catch(i){o.push(`warning: could not remove the cache entry: ${i.message}`)}let s=Be(e.paths.projectDir(t),"modules",n);if(await ht(s)){await co(e.paths,s,{suffix:"deleted"});let{rm:i}=await import("fs/promises");await i(s,{recursive:!0,force:!0}),o.push(`local folder removed (archived to _history first): modules/${n}`)}let r=await e.session.getTarget();if(r&&r.projectId===t&&r.moduleId===n)await e.session.clearTarget(),o.push("session target cleared (it pointed to the deleted module)");return{notes:o}}async function nc(e,t){let n=await ce(e),o,s,r,i;if(t.projectId&&t.moduleId)o=t.projectId,s=t.moduleId,r="explicit_ids";else if(t.target){let _=await n.resolveProjectAndModuleHint(t.target);o=_.projectId,s=_.moduleInfo?.ModuleID??_.moduleHint,r=_.matchedBy,i=_.moduleInfo}else{let _=await e.targets.resolveProjectModule();o=_.projectId,s=_.moduleId,r=_.source==="cwd"?"cwd":"active_session"}let a=e.paths.projectDir(o),c=await U(Be(a,"_projectInfo.json"),null)!==null,l=[];if(!c){let _=await n.fetchProjectBundle(o);await qr(e.paths,a,Br(_.projectInfo,_.projectJson,_.modules,_.projectComponents,_.projectImports));let le=await n.downloadImportContents(_.projectImports);if(l.push(...le.failures),Object.keys(le.files).length>0)await Ae(e.paths,a,le.files)}let u=i??null;if(!u){let _=await n.lookupModuleInfo(o,(le)=>{let Ie=le.find((ae)=>ae.ModuleID===s);if(Ie||r!=="target_path")return Ie??null;let B=Yr(le,s);if(B.length>1)throw new f(`target "${t.target}" matched several modules in project ${o}: ${B.map((ae)=>ae.ModuleID).join(", ")}`);return B[0]??null});if(u=_.found,!u)throw rn(o,s,_)}let{content:d,components:p,script:m,css:w,dependencies:y}=await n.fetchModuleBundle(u),v=tn(u,d,p,m,w,y),T=Be(e.paths.projectDir(u.ProjectID),"modules",u.ModuleID),I=new Set(mt(y.filter((_)=>_.Scope!=="project")).map((_)=>_.filePath).filter((_)=>!!_)),g=await Ln(e.paths,T,I),[b,R]=await Promise.all([n.downloadImportContents(y.filter((_)=>_.Scope==="module")),n.downloadImportContents(c?y.filter((_)=>_.Scope==="project"):[])]);l.push(...b.failures,...R.failures);let C=await _a(e.paths,T,u,d,{...v,...b.files}),S=Object.keys(R.files).length>0?await Ae(e.paths,a,R.files):null;await vo(e.paths,o,{...u,Framework:d.Framework,HTMLOnly:d.HTMLOnly});let N=await Io(e.paths.root);await e.session.setTarget({projectId:u.ProjectID,moduleId:u.ModuleID,moduleDir:C.moduleDir,workspaceRoot:e.paths.root,target:t.target});let D=[...C.removedFiles.map((_)=>rm(_)),...g],A=[...C.sync.archived,...S?.archived??[]],G=c?await Pt(a,"project data (project.json, components, imports)",`fui project sync -p ${u.ProjectID}`,"_projectInfo.json"):null;return{notes:[`ok fetched ${u.ProjectID}/${u.ModuleID} (${r}) -> ${C.moduleDir}`,`files: ${yr(C.sync)}`,...D.length>0?[`removed orphaned local files: ${D.join(", ")}`]:[],...br(A),c?"project data already local (not fetched); refresh: fui project sync":"project data bootstrapped (project.json, components, imports, modules/_modules.json)",...G?[G]:[],`skills: ${N.skillDir}${N.source==="bundled"?" (fallback summary; copy failed)":""}`,...C.warnings.map((_)=>`warning: ${_}`),...Ka(l),...jo(d),"now active for fui module preview / publish / validate"],data:`${Ea(u.ModuleID,{"_moduleInfo.json":"{}",...v})}
|
|
149
|
+
`}}async function oc(e,t,n,o){let s=await e.api(),r=o==="ui"?await s.getModuleUI(t,n):await s.getModuleHtml(t,n),i=r.data,a=Be(e.paths.projectDir(t),"modules",n),c=[];if(o==="ui"&&i?.moduleUI!==void 0&&i.moduleUI!==null&&await ht(a)){let l;if(typeof i.moduleUI==="string")try{l=JSON.stringify(JSON.parse(i.moduleUI),null,2)}catch{l=i.moduleUI}else l=JSON.stringify(i.moduleUI,null,2);await Ae(e.paths,a,{"module.json":l}),c.push(`module.json written to ${a}`)}if(o==="html"&&typeof i?.mBody==="string"&&await ht(a))await Ae(e.paths,a,{"body.html":i.mBody}),c.push(`body.html written to ${a}`);return{notes:c,data:`${JSON.stringify(r)}
|
|
150
|
+
`}}async function an(e,t,n){let o=t,s=n;if(!o){let a=e.targets.cwdTarget()??await e.session.getTarget();if(!a?.projectId)throw new f("no project: pass -p <project> (and -m <module> for module scope), or cd into a module");o=a.projectId,s=n||a.moduleId||void 0}let r=s?Be(e.paths.projectDir(o),"modules",s):e.paths.projectDir(o);return{projectId:o,moduleId:s,baseDir:r}}async function rc(e,t,n){let o=await(await e.api()).getModuleComponents(t.projectId,t.moduleId),s=o.find((a)=>a.comName===n),r=t.moduleId?`module ${t.moduleId}`:`project ${t.projectId} (global)`;if(!s)throw new f(`component "${n}" not found in ${r}; available: ${o.map((a)=>a.comName).join(", ")}`);let i=[`comID ${s.comID} scope ${t.moduleId?`module:${t.moduleId}`:"project (global)"}`];if(await ht(t.baseDir))await Ae(e.paths,t.baseDir,{[`components/${n}.vue`]:s.comCode}),i.push(`${n}.vue written to ${t.baseDir}/components/`);return{notes:i,data:s.comCode}}function cn(e){if(e===void 0)return;let t=Array.isArray(e)?e:[e],n={};for(let o of t){let s=String(o),r=s.indexOf("=");if(r<=0)throw new f(`--set expects key=value, got "${s}"`);let i=s.slice(r+1);try{n[s.slice(0,r)]=JSON.parse(i)}catch{n[s.slice(0,r)]=i}}return n}async function ts(e){let t=typeof e.data==="string"?e.data:void 0,n=typeof e["data-file"]==="string"?e["data-file"]:void 0;if(t===void 0===(n===void 0))throw new f("pass exactly one of --data '<json>' | --data - | --data-file <path>");let o=n!==void 0?await Bun.file(n).text():t==="-"?await Bun.stdin.text():t,s;try{s=JSON.parse(o)}catch(r){throw new f(`--data is not valid JSON: ${r.message}`)}if(!s||typeof s!=="object"||Array.isArray(s))throw new f("--data must be a JSON object");return s}async function yt(e,t,n){let o=typeof e[t]==="string"?e[t]:void 0,s=typeof e[`${t}-file`]==="string"?e[`${t}-file`]:void 0;if(o===void 0&&s===void 0)return;if(o!==void 0&&s!==void 0)throw new f(`pass --${t} or --${t}-file, not both`);let r=s!==void 0?await Bun.file(s).text():o==="-"?await Bun.stdin.text():o,i;try{i=JSON.parse(r)}catch(c){throw new f(`--${t} is not valid JSON: ${c.message}`)}if(!(n==="array"?Array.isArray(i):!!i&&typeof i==="object"&&!Array.isArray(i)))throw new f(`--${t} must be a JSON ${n}`);return i}function ns(...e){return Object.fromEntries(e.flatMap((t)=>[[t,{type:"string"}],[`${t}-file`,{type:"string"}]]))}var ln={module:{type:"string",short:"m"}};function Co(e){for(let t of e.notes)E(`# ${t}`);if(e.data)k(e.data)}function Hn(e){if(k(`${e.notes.join(`
|
|
151
|
+
`)}
|
|
152
|
+
`),e.data)k(e.data)}async function os(e){return(await e.targets.resolveProject(h(e.values,"project"))).projectId}async function ic(e){return e.targets.resolveProjectModule(h(e.values,"project"),h(e.values,"module"))}function sm(e,t){if(e===void 0)return;if(e==="true")return!0;if(e==="false")return!1;throw new f(`${t} takes true or false`)}function ac(e){if(e===void 0)return;let t=e.toUpperCase();if(t!=="V2"&&t!=="V3")throw new f("--framework must be V2 or V3");return t}var im={name:"use",mcpName:"session_set",usage:"fui use [<target> | -p P -m M | --module-dir D] | fui use --clear",summary:"Show, set or clear the session target (target: /project/module, a live URL, or a display name)",options:{...ln,"module-dir":{type:"string"},clear:{type:"boolean"}},async run(e){let{values:t,positionals:n,session:o,render:s}=e;if(t.clear===!0){await o.clearTarget(),k(`session target cleared
|
|
153
|
+
`);return}let r=n[0],i=h(t,"project"),a=h(t,"module"),c=h(t,"module-dir");if(!r&&!i&&!a&&!c){let l=await o.getTarget();if(!l){E("# no session target; set one: fui use <project>/<module>");return}k(X([{...l,session:o.file}],s));return}if(i&&!a||!i&&a)throw new f("pass both -p and -m");Hn(await za(O(e),{target:r,projectId:i,moduleId:a,moduleDir:c}))}},am={name:"resolve",mcpName:"session_resolve",usage:"fui resolve <target>",summary:"Resolve /project/module, a live module URL or a display name to ids (no session change)",async run(e){let t=e.positionals[0];if(!t)throw new f("missing <target>");Co(await Ga(O(e),t))}},cm={name:"project list",mcpName:"project_list",usage:"fui project list",summary:"List projects you can access",async run(e){let t=(await Xa(O(e))).map((n)=>({group:n.GroupName||"Khac",id:n.ProjectID,name:n.ProjectName,framework:n.Framework??"",domain:n.pDomain??""}));k(X(t,e.render))}},lm={name:"project sync",mcpName:"project_sync",usage:"fui project sync [-p project]",summary:"Re-fetch project data (project.json, components, imports, module list); archives what changed or vanished",async run(e){Hn(await Ya(O(e),await os(e)))}},um={name:"module list",mcpName:"module_list",usage:"fui module list [-p project]",summary:"List modules from the server and refresh the local module cache",async run(e){let{rows:t,notes:n}=await Qa(O(e),await os(e));for(let o of n)E(`# ${o}`);k(X(t.map((o)=>({id:o.ModuleID,name:o.ModuleName,framework:o.Framework??"",domain:o.mDomain??"",htmlOnly:o.HTMLOnly?"yes":"no",updated:o.UpdateTime??"",by:o.UpdateUser??""})),e.render))}},dm={name:"module new",mcpName:"module_new",usage:"fui module new <moduleId> -p <project> --name <name> [--framework V2|V3] [--html-only] [--domain d] [--set key=json ...]",summary:"Create an empty module on the server (then fui module get)",options:{name:{type:"string"},framework:{type:"string"},"html-only":{type:"boolean"},domain:{type:"string"},set:{type:"string",multiple:!0}},async run(e){let t=e.positionals[0],n=h(e.values,"name");if(!t||!n)throw new f("need <moduleId> and --name");Hn(await Za(O(e),{projectId:await os(e),moduleId:t,moduleName:n,framework:ac(h(e.values,"framework")),htmlOnly:e.values["html-only"]===!0?!0:void 0,mDomain:h(e.values,"domain"),extra:cn(e.values.set)}))}},pm={name:"module update",mcpName:"module_update",usage:"fui module update [-p P -m M] [--name n] [--framework V2|V3] [--html-only true|false] [--domain d] [--new-id id] [--set key=json ...]",summary:"Edit metadata of an existing module (name, id, framework, domain, HTMLOnly)",options:{...ln,name:{type:"string"},framework:{type:"string"},"html-only":{type:"string"},domain:{type:"string"},"new-id":{type:"string"},set:{type:"string",multiple:!0}},async run(e){let{projectId:t,moduleId:n}=await ic(e);Hn(await ec(O(e),{projectId:t,moduleId:n,moduleName:h(e.values,"name"),framework:ac(h(e.values,"framework")),htmlOnly:sm(h(e.values,"html-only"),"--html-only"),mDomain:h(e.values,"domain"),newModuleId:h(e.values,"new-id"),extra:cn(e.values.set)}))}},mm={name:"module delete",mcpName:"module_delete",usage:"fui module delete -p <project> -m <module>",summary:"Delete a module on the server and archive its local folder (ask the user first; ids must be explicit)",options:ln,async run(e){let t=h(e.values,"project"),n=h(e.values,"module");if(!t||!n)throw new f("module delete needs explicit -p <project> -m <module>");Hn(await tc(O(e),t,n))}},fm={name:"module get",mcpName:"module_get",usage:"fui module get [<target> | -p P -m M]",summary:"Check out a module into the workspace (archives differing local files) and make it the session target",options:ln,async run(e){let t=h(e.values,"project"),n=h(e.values,"module");if(t&&!n||!t&&n)throw new f("pass both -p and -m, or a target");Co(await nc(O(e),{target:e.positionals[0],projectId:t,moduleId:n}))}};function sc(e){return{name:`module get-${e}`,mcpName:`module_get_${e}`,usage:`fui module get-${e} [-p P -m M]`,summary:`Fetch only the module ${e==="ui"?"UI JSON (writes module.json":"HTML body (writes body.html"} if checked out)`,options:ln,async run(t){let{projectId:n,moduleId:o}=await ic(t);Co(await oc(O(t),n,o,e))}}}var hm={name:"component get",mcpName:"component_get",usage:"fui component get <name> [-p P [-m M]]",summary:"Print a component's .vue source (writes it locally when the scope folder exists); -p without -m = project scope",options:ln,async run(e){let t=e.positionals[0];if(!t)throw new f("missing <name>");let n=O(e),o=await an(n,h(e.values,"project"),h(e.values,"module"));Co(await rc(n,o,t))}},cc=[im,am,cm,lm,um,dm,pm,mm,fm,sc("ui"),sc("html"),hm];import{join as it}from"path";import st from"path";import{join as lc}from"path";async function Ot(e,t){let n={functionRights:null,systemRights:null},o;try{o=JSON.parse(await H(lc(e,"_db","rights.json")))}catch{return n}let s=Object.keys(o??{});if(s.length===0)return n;let r=(l)=>s.find((u)=>u.toLowerCase()===l.toLowerCase())??null,i;if(t)i=r(t);else if(s.length===1)i=s[0];else{let l=await U(lc(e,"_db","_connections.json"),null);i=l?.default?r(l.default):null}if(!i)return n;let a=o[i],c=(l,u)=>Array.isArray(l)?new Set(l.map((d)=>d&&typeof d==="object"?String(d[u]??""):"").filter(Boolean)):null;return{functionRights:c(a?.functionRights,"FunctionCode"),systemRights:c(a?.systemRights,"SysRight")}}function Mt(e){return dc("FunctionRight",e)}function uc(e){return dc("SystemRight",e)}function Wn(e,t){if(e.length===0)return null;if(t===null)return`Ch\u01B0a c\xF3 cache _db/rights.json \u2014 KH\xD4NG ki\u1EC3m \u0111\u01B0\u1EE3c ${e.length} m\xE3 FunctionRight (${e.join(", ")}) `+"c\xF3 th\u1EADt hay kh\xF4ng. Preview c\u1EA5p s\u1EB5n \u0111\xFAng nh\u1EEFng m\xE3 n\xE0y n\xEAn control v\u1EABn hi\u1EC7n \u1EDF \u0110\xC2Y; user th\u1EADt ch\u1EC9 th\u1EA5y n\u1EBFu m\xE3 \u0111\xE3 "+"\u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a. Ch\u1EA1y right_function_list (m\u1ED9t l\u1EA7n cho m\u1ED7i project) r\u1ED3i render l\u1EA1i.";let n=e.filter((o)=>!t.has(o));if(n.length===0)return null;return`\uD83D\uDEA8 FunctionRight CH\u01AFA \u0110\u01AF\u1EE2C \u0110\u1ECANH NGH\u0128A tr\xEAn h\u1EC7 th\u1ED1ng acc: ${n.join(", ")} \u2014 preview c\u1EA5p s\u1EB5n n\xEAn control v\u1EABn `+"hi\u1EC7n \u1EDF \u0110\xC2Y, nh\u01B0ng ch\u1EA1y th\u1EADt th\xEC rightTest() lu\xF4n tr\u1EA3 false \u2192 control/menu bi\u1EBFn m\u1EA5t m\xE0 kh\xF4ng c\xF3 l\u1ED7i JS n\xE0o. "+'T\u1EA1o m\xE3: right_function_new({ functionCode: \u2026, functionName: "\u2026" }); ho\u1EB7c right_function_list n\u1EBFu cache c\u0169.'}function dc(e,t){let n=new Set,o=new RegExp(`${e}['"]?\\s*:\\s*\\[([^\\]]*)\\]`,"g"),s=new RegExp(`${e}\\s*\\.\\s*includes\\(\\s*['"]([^'"]+)['"]`,"g");for(let r of t){if(!r)continue;let i;o.lastIndex=0;while(i=o.exec(r))for(let a of i[1].split(",")){let c=a.trim().replace(/^['"]|['"]$/g,"");if(c)n.add(c)}s.lastIndex=0;while(i=s.exec(r))n.add(i[1])}return Array.from(n).sort()}var gm=new Set(["f-table","f-dialog","f-window","f-button","f-date","f-time","f-time-counter","f-search","f-menu","f-radiobox","f-file-upload","f-image-update","f-excel-reader","f-qrcode","f-qrcode-reader","f-editor","f-editor-dialog","f-echart","f-chart","f-sheet","f-pdfmake","f-label","f-box","f-header","f-title","f-slider","fp-profile","f-dynamic-component","f-openwindow-control","f-table-item-component"]),pc=new Set(["t-html","t-label","t-num","t-time","t-boolean","t-check","t-text","t-select","t-combobox","t-menu","t-link","t-button"]),wm={"f-pdfmake":["pdfmake.min.js","print.min.js","vfs_fonts.js"],"f-file-upload":["plupload.full.min.js"],"f-image-update":["index.umd.js","style.css"],"f-qrcode":["qrcode.min.js"],"f-qrcode-reader":["qrcode-reader.min.js"],"f-echart":["echarts.min.js","fechart.js"],"f-sheet":["ag-grid-community.min.js","fsheet.js"],"f-editor":["ckeditor.js"],"f-editor-dialog":["ckeditor.js"]};var un=new Set(["API","CALL","IF","FUN","EXE","MESS","MESSBOX","ARRAY","CONFIRM","IN","OUT","THEN","ELSE","CALLBACK","ERROROUT","ERRORCALLBACK","CANCEL","METHOD","HEADER","COL"]),mc=new Set(["item","index","v_old","user","vueData","p_router","p_routers","v_Set","moment"]);function j(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function is(e){if(Array.isArray(e))return e.length>0&&e.every((t)=>j(t)||typeof t==="string")&&e.some((t)=>j(t)&&Object.keys(t).some((n)=>un.has(n)));if(!j(e))return!1;return Object.keys(e).some((t)=>un.has(t))}function Ge(e){let t=e.trim();if(t.startsWith("vueData."))t=t.slice(8);let n=t.match(/^[A-Za-z_$][\w$]*/);return n?n[0]:t}function qn(e){return!!e&&!e.includes(" ")&&!e.includes("{{")&&!e.includes("(")&&!e.startsWith("`")&&!e.startsWith("#")&&!e.startsWith("!")&&/^[A-Za-z_$]/.test(e.replace(/^vueData\./,""))}function ym(e){let t=e.trim();if(!t)return!0;if(/^-?\d+(\.\d+)?$/.test(t)||t==="true"||t==="false"||t==="null")return!0;if(/^'[^']*'$/.test(t)||/^"[^"]*"$/.test(t))return!0;return/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(t)}function bm(e){let t=e.split(""),n=null;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(s==="\\"){if(t[o]=" ",o+1<e.length)t[o+1]=" ",o++;continue}if(s===n){n=null;continue}t[o]=" "}else if(s==="'"||s==='"'||s==="`")n=s}return t.join("")}var km=[{re:/\bfunction\s*\(/,what:'khai b\xE1o "function(...)"'},{re:/\bfunction\s+[A-Za-z_$]/,what:'khai b\xE1o "function ten(...)"'},{re:/\breturn\b/,what:'l\u1EC7nh "return"'},{re:/\bvar\s+[A-Za-z_$]/,what:'khai b\xE1o "var"'},{re:/\blet\s+[A-Za-z_$]/,what:'khai b\xE1o "let"'},{re:/\bconst\s+[A-Za-z_$]/,what:'khai b\xE1o "const"'}];function $m(e,t,n,o){if(typeof t!=="string")return;if(!(e.startsWith(":")&&!e.startsWith("::")||e.startsWith("v-bind:")))return;let r=bm(t),i=[];for(let{re:a,what:c}of km)if(a.test(r))i.push(c);if(r.replace(/;\s*$/,"").includes(";"))i.push('d\u1EA5u ";" n\u1ED1i nhi\u1EC1u c\xE2u l\u1EC7nh');if(i.length===0)return;o.push(`attr "${e}" (t\u1EA1i ${n}) ch\u1EE9a ${i.join(" + ")} \u2014 gi\xE1 tr\u1ECB binding \u0111i th\u1EB3ng v\xE0o template compiler c\u1EE7a Vue, `+"ch\u1EC9 nh\u1EADn M\u1ED8T BI\u1EC2U TH\u1EE8C thu\u1EA7n (object/array literal, ternary, chu\u1ED7i .map()/.filter() arrow, truy c\u1EADp thu\u1ED9c t\xEDnh). "+"Statement \u1EDF \u0111\xE2y l\xE0m compile template h\u1ECFng \u2192 TRANG TR\u1EAENG m\xE0 kh\xF4ng c\xF3 JS error hay [Vue warn] n\xE0o. "+'C\xE1ch s\u1EEDa: \u0111\u01B0a logic v\xE0o m\u1ED9t h\xE0m trong script.js (function buildX(){ \u2026 vueData.x = \u2026; }), g\u1ECDi qua "EXE": "buildX()" '+`trong CALLBACK c\u1EE7a action n\u1EA1p d\u1EEF li\u1EC7u, r\u1ED3i "${e}" ch\u1EC9 tr\u1ECF t\u1EDBi bi\u1EBFn vueData \u0111\xF3 (vd "${e}": "x"). `+"Chi ti\u1EBFt: references/advanced-techniques.md.")}function Fe(e,t,n="$",o=!1){if(Array.isArray(e))e.forEach((s,r)=>Fe(s,t,`${n}[${r}]`,o));else if(j(e)){t(e,n,o);for(let[s,r]of Object.entries(e))Fe(r,t,`${n}.${s}`,o||s.startsWith("#"))}}var vm="(chu\u1EA9n m\u1EB7c \u0111\u1ECBnh fui/DESIGN.md \u2014 ch\u1EC9 gi\u1EEF nguy\xEAn n\u1EBFu user y\xEAu c\u1EA7u r\xF5 ki\u1EC3u kh\xE1c)",Sm=new Set([400,550,600,650,800]),jm=/(x\u00F3a|xo\u00E1|\bdelete\b|\bremove\b)/i,Im=/(delete|remove|xoa)/i,Cm=new Set(["v-text-field","v-textarea","v-select","v-combobox","v-autocomplete","f-date","f-time","f-search"]),fc=new Set(["9"]);function Rm(e,t,n){let o=JSON.stringify(e),s=Mt([o]),r=uc([o]).filter((l)=>!fc.has(l)),i=s.length+r.length;if(i===0)return 0;let a=t.definedFunctionRights??null,c=t.definedSystemRights??null;if(a===null&&c===null)return n.push(`Module g\xE1c quy\u1EC1n b\u1EB1ng ${i} m\xE3 literal (${[...s,...r].join(", ")}) nh\u01B0ng ch\u01B0a c\xF3 cache _db/rights.json `+"\u0111\u1EC3 \u0111\u1ED1i chi\u1EBFu \u2014 KH\xD4NG ki\u1EC3m \u0111\u01B0\u1EE3c m\xE3 n\xE0o c\xF3 th\u1EADt. M\xE3 kh\xF4ng t\u1ED3n t\u1EA1i l\xE0m rightTest() lu\xF4n tr\u1EA3 false: "+"control/menu bi\u1EBFn m\u1EA5t im l\u1EB7ng, kh\xF4ng l\u1ED7i JS. Ch\u1EA1y right_function_list + right_system_list (m\u1ED9t l\u1EA7n cho m\u1ED7i project) r\u1ED3i validate l\u1EA1i."),i;if(a&&s.length>0){let l=a,u=s.filter((d)=>!l.has(d));for(let d of u)n.push(`FunctionRight "${d}" ch\u01B0a \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a cho project (_db/rights.json c\xF3 ${l.size} m\xE3`+`${l.size?`: ${Array.from(l).slice(0,15).join(", ")}${l.size>15?", \u2026":""}`:""}). `+"rightTest() s\u1EBD lu\xF4n tr\u1EA3 false \u2192 control/menu g\xE1c b\u1EB1ng m\xE3 n\xE0y bi\u1EBFn m\u1EA5t m\xE0 kh\xF4ng b\xE1o l\u1ED7i. "+`T\u1EA1o m\xE3: right_function_new({ functionCode: ${/^\d+$/.test(d)?d:`"${d}"`}, functionName: "\u2026" }); `+"ho\u1EB7c ch\u1EA1y right_function_list n\u1EBFu m\xE3 \u0111\xE3 c\xF3 tr\xEAn server m\xE0 cache c\u0169.")}if(c&&r.length>0){let l=c,u=r.filter((d)=>!l.has(d));for(let d of u)n.push(`SystemRight ${d} ch\u01B0a \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a cho project (_db/rights.json c\xF3: `+`${l.size?Array.from(l).sort().join(", "):"(ch\u01B0a m\u1EE9c n\xE0o)"}${fc.size?" \u2014 m\u1EE9c 9 lu\xF4n h\u1EE3p l\u1EC7":""}). `+"Kh\xF4ng ai \u0111\u01B0\u1EE3c c\u1EA5p m\u1EE9c n\xE0y th\xEC control g\xE1c b\u1EB1ng n\xF3 bi\u1EBFn m\u1EA5t im l\u1EB7ng. "+`\u0110\u1ECBnh ngh\u0129a: right_system_new({ sysRight: ${d}, note: "\u2026" }); ho\u1EB7c ch\u1EA1y right_system_list n\u1EBFu cache c\u0169.`)}return i}function Pm(e,t){let n=(a)=>t.push(`[design] ${a} ${vm}`),o=(a)=>typeof a==="string"?a:"";Fe(e,(a,c)=>{let l=a.API;if(typeof l==="string"&&Im.test(l)&&a.CONFIRM===void 0)n(`Action g\u1ECDi API "${l}" (t\u1EA1i ${c}) c\xF3 v\u1EBB l\xE0 thao t\xE1c X\xD3A nh\u01B0ng thi\u1EBFu CONFIRM \u2014 m\u1ECDi action x\xF3a ph\u1EA3i h\u1ECFi x\xE1c nh\u1EADn.`)});let s=new Set;Fe(e,(a,c)=>{if(s.has(a))return;let l=j(a.attr)?a.attr:a;if(l!==a)s.add(l);let u=o(l.label)||o(l.text)||(typeof a.innerHTML==="string"?a.innerHTML:""),d=o(l["icon-text"]);if(!jm.test(u)&&d!=="mdi-delete")return;let p=o(l.color)||o(l[":color"]);if(/\b(error|red)\b/i.test(p)&&!p.includes("?"))n(`N\xFAt/m\u1EE5c x\xF3a (t\u1EA1i ${c}) \u0111ang m\xE0u "${p}" \u2014 n\xFAt x\xF3a d\xF9ng m\xE0u "warning" (cam), kh\xF4ng d\xF9ng error/\u0111\u1ECF.`)});let r=(a,c,l,u)=>{if(Array.isArray(a)){a.forEach((I,g)=>r(I,`${c}[${g}]`,l,u));return}if(!j(a))return;let d=o(a.el),p=o(a.prop),m=j(a.attr)?a.attr:{},w=l||d==="v-dialog"||d==="f-dialog"||d==="hidden-container"||p.includes("hidden-container"),y=u||d==="v-card-actions";if(d==="v-toolbar"&&["color",":color","dark",":dark"].some((I)=>(I in m)))n(`v-toolbar (t\u1EA1i ${c}) c\xF3 color/dark \u2014 toolbar ti\xEAu \u0111\u1EC1 ph\u1EA3i "dense flat" c\xF9ng m\xE0u n\u1EC1n v\u1EDBi th\xE2n dialog, kh\xF4ng t\xF4 m\xE0u/dark ri\xEAng.`);if(d==="v-dialog"||d==="f-dialog"){let I=m.width??m[":width"],g=typeof I==="number"?I:typeof I==="string"&&/^\d+$/.test(I.trim())?parseInt(I,10):NaN;if(!Number.isNaN(g)&&!Sm.has(g))n(`Dialog (t\u1EA1i ${c}) width=${g} \u2014 b\u1ED9 width chu\u1EA9n: 400 / 550 / 600 / 650 / 800.`)}if(w&&d==="v-card"&&["elevation",":elevation","outlined",":outlined"].some((I)=>(I in m)))n(`v-card trong dialog (t\u1EA1i ${c}) c\xF3 elevation/outlined \u2014 b\u1ECF \u0111i: v-dialog \u0111\xE3 t\u1EF1 c\xF3 b\xF3ng \u0111\u1ED5, th\xEAm n\u1EEFa l\xE0 b\xF3ng ch\u1ED3ng b\xF3ng. v-card ch\u1EC9 c\u1EA7n class "pa-0" (tri\u1EC7t \u0111\u1EC7m \u0111\u1EC3 toolbar ti\xEAu \u0111\u1EC1 s\xE1t m\xE9p).`);let v=o(m.class),T=v.includes("flex-md-grow-0")&&v.includes("ml-2");if(T)n(`Control (t\u1EA1i ${c}) mang class "flex-md-grow-0 ml-2" \u2014 d\u1EA5u hi\u1EC7u pattern label-div r\u1EDDi \u0111\xE3 lo\u1EA1i b\u1ECF; form d\xF9ng floating label ("label" trong attr c\u1EE7a field).`);if(w&&!T&&Cm.has(d)&&!("label"in m)&&!(":label"in m)&&!("placeholder"in m))n(`${d} trong dialog (t\u1EA1i ${c}) thi\u1EBFu "label" \u2014 form dialog d\xF9ng floating label.`);if(d==="v-snackbar"||d==="v-alert")n(`${d} (t\u1EA1i ${c}) \u2014 ph\u1EA3n h\u1ED3i/th\xF4ng b\xE1o d\xF9ng h\u1EC7 th\u1ED1ng c\xF3 s\u1EB5n c\u1EE7a FUI: MESS (toast) / MESSBOX (h\u1ED9p tho\u1EA1i) trong action, CONFIRM \u0111\u1EC3 x\xE1c nh\u1EADn \u2014 kh\xF4ng t\u1EF1 d\u1EF1ng ${d} trong controls.`);if(d==="f-table"||d==="f-sheet"){let I=m[":height"]??m.height;if(I===void 0)n(`${d} (t\u1EA1i ${c}) kh\xF4ng c\xF3 ":height" \u2014 b\u1EA3ng s\u1EBD t\u1EF1 gi\xE3n theo s\u1ED1 d\xF2ng v\xE0 l\xE0m c\u1EA3 TRANG cu\u1ED9n. B\u1EA3ng data ch\xEDnh n\xEAn \u0111\u1EB7t \`":height": -16\` (fill t\u1EDBi \u0111\xE1y m\xE0n h\xECnh, ch\u1EEBa 16px \u2014 component t\u1EF1 \u0111o v\u1ECB tr\xED th\u1EADt, kh\xF4ng c\u1EA7n t\xEDnh tay); trong dialog d\xF9ng s\u1ED1 d\u01B0\u01A1ng (px c\u1ED1 \u0111\u1ECBnh, vd 360). B\u1EA3ng ng\u1EAFn/lookup ch\u1EE7 \xFD t\u1EF1 gi\xE3n th\xEC b\u1ECF qua.`);else if(typeof I==="string"&&/\$\s*\(\s*window\s*\)\s*\.\s*height\s*\(\s*\)/.test(I))n(`${d} (t\u1EA1i ${c}) t\xEDnh ":height" th\u1EE7 c\xF4ng theo viewport (\`${I}\`) \u2014 kh\xF4ng c\u1EA7n n\u1EEFa: ${d} t\u1EF1 \u0111o v\u1ECB tr\xED th\u1EADt c\u1EE7a b\u1EA3ng sau layout v\xE0 t\u1EF1 c\u1EADp nh\u1EADt khi resize. Thay b\u1EB1ng \`":height": -16\` (s\u1ED1 \xE2m = kho\u1EA3ng ch\u1EEBa \u1EDF \u0111\xE1y).`)}if(y&&(d==="f-button"||d==="v-btn")){let I=o(m.label)||(typeof a.innerHTML==="string"?a.innerHTML:"");if(/^\s*(\u0111\u00F3ng|dong|close)\s*$/i.test(I))n(`N\xFAt "${I.trim()}" trong v-card-actions (t\u1EA1i ${c}) \u2014 dialog chu\u1EA9n ch\u1EC9 d\xF9ng icon close tr\xEAn toolbar, kh\xF4ng th\xEAm n\xFAt \u0110\xF3ng \u1EDF actions.`)}for(let[I,g]of Object.entries(a))r(g,`${c}.${I}`,w,y)};(Array.isArray(e.controls)?e.controls:[]).forEach((a,c)=>r(a,`$.controls[${c}]`,!1,!1))}var Em=12,rs=25,gc=/\b(shrink|flex-grow-0)\b/;function wc(e){return typeof e==="string"?e:j(e)?[e.class,e[":class"]].filter((t)=>typeof t==="string").join(" "):""}function Tm(e){return e.w===void 0||e.w===null||e.w===""||e.w===0}function Am(e,t){Fe(e.controls,(n,o)=>{if(!Array.isArray(n.cols))return;Nm(n.cols,o,t),n.cols.forEach((s,r)=>{if(!j(s))return;let i=s,a=i.w,c=typeof a==="number"?a:typeof a==="string"&&/^\d+$/.test(a.trim())?parseInt(a.trim(),10):NaN;if(Number.isNaN(c)||c<=Em)return;let l=`${o}.cols[${r}]${typeof i.el==="string"?` (el: ${i.el})`:""}`;if(c<rs){t.push(`${l} c\xF3 "w": ${c} \u2014 v\xF9ng ch\u1EBFt 13\u201324: V2 hi\u1EC3u l\xE0 ${c}px, c\xF2n V3 sinh class "v-col-${c}" m\xE0 Vuetify 3 kh\xF4ng \u0111\u1ECBnh ngh\u0129a (ch\u1EC9 c\xF3 .v-col-1 \u2026 .v-col-12) n\xEAn c\u1ED9t KH\xD4NG c\xF3 b\u1EC1 r\u1ED9ng n\xE0o, r\u01A1i v\u1EC1 chia \u0111\u1EC1u. D\xF9ng 1\u201312 (chia c\u1ED9t) ho\u1EB7c >= ${rs} (px, k\xE8m \`"col": { "class": "shrink" }\`).`);return}let u=i.col;if(gc.test(wc(u)))return;t.push(j(u)&&"shrink"in u?`${l} c\xF3 "w": ${c} (px) k\xE8m \`"col": { "shrink": ... }\` \u2014 d\u1EA1ng thu\u1ED9c t\xEDnh tr\u1EA7n l\xE0 prop c\u1EE7a v-flex (Vuetify 2); Vuetify 3 kh\xF4ng c\xF3 prop \u0111\xF3 n\xEAn n\xF3 tr\u01A1 v\xE0 c\u1ED9t v\u1EABn gi\xE3n h\u1EBFt ch\u1ED7 tr\u1ED1ng. Vi\u1EBFt \`"col": { "class": "shrink" }\` \u0111\u1EC3 \u0111\xFAng \u1EDF c\u1EA3 hai framework.`:`${l} c\xF3 "w": ${c} \u2014 s\u1ED1 >= ${rs} ngh\u0129a l\xE0 b\u1EC1 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh px, nh\u01B0ng c\u1ED9t grid m\u1EB7c \u0111\u1ECBnh flex-grow:1 n\xEAn flex-basis ${c}px KH\xD4NG c\xF3 t\xE1c d\u1EE5ng: c\u1ED9t v\u1EABn gi\xE3n h\u1EBFt ch\u1ED7 tr\u1ED1ng (\u0111o th\u1EADt: w=300 ra 687px \u1EDF V2, 738px \u1EDF V3). Th\xEAm \`"col": { "class": "shrink" }\`.`)})},"$.controls")}function Nm(e,t,n){let o=(s)=>j(s)&&s.el==="v-spacer";if(!e.some(o))return;e.forEach((s,r)=>{if(!j(s))return;let i=s;if(typeof i.el!=="string"||!i.el)return;let a=`${t}.cols[${r}] (el: ${i.el})`,c=i.col,l=gc.test(wc(c));if(i.el==="v-spacer"){if(l)n.push(`${a} \u2014 \`v-spacer\` m\xE0 c\xF3 \`shrink\` th\xEC co l\u1EA1i c\xF2n 0px (\u0111o th\u1EADt: 24px \u1EDF V3, 0px \u1EDF V2), kh\xF4ng \u0111\u1EA9y \u0111\u01B0\u1EE3c nh\xF3m sau sang ph\u1EA3i n\u1EEFa v\xE0 c\u1EA3 h\xE0ng d\u1ED3n v\u1EC1 tr\xE1i. B\u1ECF \`shrink\` kh\u1ECFi ri\xEAng c\u1ED9t n\xE0y.`);return}if(!Tm(i)||l)return;n.push(j(c)&&"shrink"in c?`${a} n\u1EB1m c\xF9ng h\xE0ng v\u1EDBi \`v-spacer\`, \`w\` r\u1ED7ng v\xE0 ch\u1EC9 c\xF3 \`"col": { "shrink": ... }\` \u2014 d\u1EA1ng thu\u1ED9c t\xEDnh tr\u1EA7n l\xE0 prop c\u1EE7a v-flex (Vuetify 2); Vuetify 3 kh\xF4ng c\xF3 prop \u0111\xF3 n\xEAn c\u1ED9t v\u1EABn gi\xE3n v\xE0 chi\u1EBFm m\u1ED9t ph\u1EA7n \u0111\u1EC1u. Vi\u1EBFt \`"col": "shrink"\` (ho\u1EB7c \`{ "class": "shrink" }\`).`:`${a} n\u1EB1m c\xF9ng h\xE0ng v\u1EDBi \`v-spacer\` nh\u01B0ng thi\u1EBFu \`shrink\` \u2014 \`w\` r\u1ED7ng KH\xD4NG c\xF3 ngh\u0129a "t\u1EF1 co theo n\u1ED9i dung": runtime b\u1ECDc ph\u1EA7n b\u1EC1 r\u1ED9ng trong \`if (c.w)\` n\xEAn c\u1ED9t n\xE0y kh\xF4ng c\xF3 class grid l\u1EABn flex-basis, c\xF2n tr\u01A1 \`flex-grow: 1\` v\xE0 \u0111\u01B0\u1EE3c CHIA PH\u1EA6N TR\u1ED0NG y nh\u01B0 \`v-spacer\`. H\u1EC7 qu\u1EA3: control b\u1ECB k\xE9o d\xE3n, h\u1EDF kho\u1EA3ng gi\u1EEFa c\xE1c n\xFAt, nh\xF3m cu\u1ED1i h\xE0ng kh\xF4ng s\xE1t m\xE9p ph\u1EA3i. Chu\u1EA9n cho h\xE0ng nhi\u1EC1u control: M\u1ECCI c\u1ED9t \`"col": "shrink"\`, tr\u1EEB \u0111\xFAng \`v-spacer\`.`)})}var hc=["API","CALL","EXE","FUN","IF","MESS","MESSBOX"],_m=new Set(["IN","OUT","HEADER","METHOD","CALLBACK","ERROROUT","ERRORCALLBACK","THEN","ELSE","CONFIRM","CANCEL","ARRAY","COL","MAP"]);function Dm(e,t,n){for(let[o,s]of Object.entries(e)){if(o==="set")continue;Fe(s,(r,i)=>{let a=Object.keys(r),c=hc.filter((m)=>a.includes(m));if(c.length===0)return;let l=c[0],u=new Set([l]);if(l==="API"&&c.includes("MESS"))u.add("MESS");let d=c.filter((m)=>!u.has(m));if(d.length>0){t.push(`${i} g\u1ED9p ${c.length} l\u1EC7nh trong M\u1ED8T object \`{}\` (${c.join(", ")}) \u2014 ch\u1EC9 \`${l}\` ch\u1EA1y, c\xF2n ${d.map((m)=>`\`${m}\``).join(", ")} b\u1ECB b\u1ECF IM L\u1EB6NG (kh\xF4ng JS error, kh\xF4ng [Vue warn]). \`vueAction\` d\xF2 key l\u1EC7nh theo th\u1EE9 t\u1EF1 c\u1ED1 \u0111\u1ECBnh API \u2192 CALL \u2192 EXE \u2192 FUN \u2192 IF \u2192 MESS \u2192 MESSBOX v\xE0 \`return\` ngay \u1EDF nh\xE1nh \u0111\u1EA7u ti\xEAn kh\u1EDBp, n\xEAn th\u1EE9 t\u1EF1 vi\u1EBFt kh\xF4ng c\xF3 t\xE1c d\u1EE5ng. Nhi\u1EC1u l\u1EC7nh c\xF3 th\u1EE9 t\u1EF1 tr\u01B0\u1EDBc sau th\xEC ph\u1EA3i l\xE0 M\u1EA2NG, m\u1ED7i l\u1EC7nh m\u1ED9t object: \`[{ "${c[0]}": \u2026 }, { "${c[1]}": \u2026 }]\`.`);return}let p=a.filter((m)=>!u.has(m)&&!_m.has(m)&&!hc.includes(m));if(p.length>0)n.push(`${i} c\xF3 l\u1EC7nh \`${l}\` n\u1EB1m chung object v\u1EDBi key g\xE1n d\u1EEF li\u1EC7u ${p.map((m)=>`\`${m}\``).join(", ")} \u2014 fragment g\xE1n ch\u1EA1y \u1EDF nh\xE1nh CU\u1ED0I c\u1EE7a \`vueAction\`, nh\xE1nh \u0111\xF3 ch\u1EC9 t\u1EDBi l\u01B0\u1EE3t khi KH\xD4NG key l\u1EC7nh n\xE0o kh\u1EDBp, n\xEAn c\xE1c key n\xE0y kh\xF4ng bao gi\u1EDD \u0111\u01B0\u1EE3c g\xE1n. T\xE1ch th\xE0nh m\u1EA3ng: \`[{ ${p.map((m)=>`"${m}": \u2026`).join(", ")} }, { "${l}": \u2026 }]\`.`)},`$.${o}`)}}function xm(e,t){let n=[],o=[];if(t.schemaWarning)o.push(t.schemaWarning);if(!j(e))return{errors:["module.json g\u1ED1c ph\u1EA3i l\xE0 m\u1ED9t object v\u1EDBi c\xE1c kh\u1ED1i data/watch/controls/set."],warnings:o,stats:ss()};let s=e,r=new Set(["data","watch","controls","set"]);for(let g of Object.keys(s))if(!r.has(g))o.push(`Kh\u1ED1i c\u1EA5p 1 kh\xF4ng chu\u1EA9n: "${g}" \u2014 module.json ch\u1EC9 c\xF3 data/watch/controls/set (xem module-json-anatomy.md).`);if(s.data!==void 0&&!Array.isArray(s.data))n.push(`"data" ph\u1EA3i l\xE0 M\u1EA2NG (array), hi\u1EC7n l\xE0 ${typeof s.data}.`);if(s.watch!==void 0&&!j(s.watch))n.push(`"watch" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ${Array.isArray(s.watch)?"array":typeof s.watch}.`);if(s.controls!==void 0&&!Array.isArray(s.controls))n.push(`"controls" ph\u1EA3i l\xE0 M\u1EA2NG container, hi\u1EC7n l\xE0 ${typeof s.controls}.`);if(s.set!==void 0&&!j(s.set))n.push(`"set" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ${Array.isArray(s.set)?"array":typeof s.set}.`);let i=new Set(mc),a=new Set,c=Array.isArray(s.data)?s.data:[];for(let g of c){if(!j(g)){o.push(`data[] ch\u1EE9a ph\u1EA7n t\u1EED kh\xF4ng ph\u1EA3i object: ${JSON.stringify(g)?.slice(0,60)}`);continue}for(let[b,R]of Object.entries(g)){if(un.has(b))continue;if(b.startsWith("#"))continue;if(is(R))a.add(b);else i.add(Ge(b))}}let l=0;Fe(s,(g,b,R)=>{if(R)return;for(let C of["OUT","ERROROUT"]){let S=g[C];if(typeof S==="string"){if(qn(S))i.add(Ge(S));else if(S.trim())l++}else if(j(S))for(let N of Object.keys(S))i.add(Ge(N))}});let u=new Map,d=new Map;Fe(s,(g,b,R)=>{let C=g.CALL;if(typeof C==="string"){if(qn(C))(R?d:u).set(Ge(C),b);else if(C.trim()&&!R)l++}let S=g.API;if(typeof S==="string"&&S.trim()&&!S.startsWith("/")&&!qn(S))l++;for(let[N,D]of Object.entries(g)){if(typeof D!=="string")continue;let A=R||N.startsWith("#")?d:u;for(let G of D.matchAll(/\bCALL\(\s*([A-Za-z_$][\w$.]*)\s*\)/g))A.set(Ge(G[1]),b)}});for(let[g,b]of u)if(!a.has(g))n.push(`CALL "${g}" (t\u1EA1i ${b}) kh\xF4ng kh\u1EDBp named action n\xE0o trong data[]. Named actions hi\u1EC7n c\xF3: ${[...a].join(", ")||"(kh\xF4ng c\xF3)"}.`);let p=0,m=0,w=Array.isArray(s.controls)?s.controls:[];for(let g=0;g<w.length;g++){let b=w[g];if(p++,!j(b)||!Array.isArray(b.rows)){n.push(`controls[${g}] ph\u1EA3i l\xE0 container c\xF3 "rows" (grid b\u1EAFt bu\u1ED9c: container > rows > cols).`);continue}for(let R=0;R<b.rows.length;R++){let C=b.rows[R];if(!j(C)||!Array.isArray(C.cols)){n.push(`controls[${g}].rows[${R}] ph\u1EA3i c\xF3 "cols" (m\u1EA3ng Control Object).`);continue}for(let S=0;S<C.cols.length;S++){let N=C.cols[S];if(m++,!j(N))continue;if(N.w===void 0&&N.el!==void 0)o.push(`controls[${g}].rows[${R}].cols[${S}] (el: ${String(N.el)}) thi\u1EBFu "w" \u2014 b\u1EAFt bu\u1ED9c cho item trong cols.`)}}}let y=new Set;if(Fe(s,(g,b)=>{if("children"in g&&(("el"in g)||("rows"in g)||("cols"in g)))n.push(`"children" kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 (t\u1EA1i ${b}) \u2014 d\xF9ng "innerHTML" (m\u1EA3ng Control Object) \u0111\u1EC3 l\u1ED3ng.`);let R=g.el;if(typeof R==="string"&&R){if(y.add(R),(R.startsWith("f-")||R.startsWith("fp-"))&&!gm.has(R)&&!t.localComponents.has(R))o.push(`Component "${R}" (t\u1EA1i ${b}) kh\xF4ng c\xF3 trong registry FUI (component-quickref.md) v\xE0 kh\xF4ng th\u1EA5y trong components local \u2014 ki\u1EC3m tra ch\xEDnh t\u1EA3 ho\u1EB7c ch\u1EA1y component_list.`);else if(R.startsWith("t-")&&!pc.has(R))o.push(`Cell component "${R}" (t\u1EA1i ${b}) kh\xF4ng c\xF3 trong danh s\xE1ch t-* h\u1EE3p l\u1EC7: ${[...pc].join(", ")}.`);else if(R.startsWith("uc-")&&!t.localComponents.has(R))o.push(`Custom component "${R}" (t\u1EA1i ${b}) ch\u01B0a th\u1EA5y file .vue/local metadata \u2014 t\u1EA1o component tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y component_list.`)}let C=g.attr;if(j(C))for(let[S,N]of Object.entries(C)){if(S.startsWith("@"))n.push(`attr "${S}" (t\u1EA1i ${b}) \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 shorthand "@"; d\xF9ng "v-on:${S.slice(1)}".`);else if(S.startsWith("#"))n.push(`attr "${S}" (t\u1EA1i ${b}) \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 slot shorthand "#"; d\xF9ng "v-slot:${S.slice(1)}".`);else if((S==="style"||S===":style")&&typeof N==="string"){if(N.split(";").map((A)=>A.trim()).filter(Boolean).filter((A)=>!/^(max-|min-)?width\s*:/i.test(A)).filter((A)=>!(g.el==="v-card"&&/^padding\s*:\s*0(px)?$/i.test(A))).length>0)o.push(`attr style "${N}" (t\u1EA1i ${b}) \u2014 module th\u1EF1c t\u1EBF g\u1EA7n nh\u01B0 kh\xF4ng d\xF9ng style ngo\xE0i width (controls-styling-vocabulary.md). C\xE2n nh\u1EAFc b\u1ECF.`)}else if((S.startsWith(":")||S.startsWith("v-bind:"))&&typeof N==="string"&&!ym(N))l++;else if(S==="v-model"&&typeof N==="string"){if(qn(N)){let D=Ge(N);if(!i.has(D)&&!a.has(D))o.push(`v-model "${N}" (t\u1EA1i ${b}) \u2014 bi\u1EBFn g\u1ED1c "${D}" ch\u01B0a khai b\xE1o trong data[]. Khai b\xE1o state tr\u01B0\u1EDBc \u0111\u1EC3 reactive ho\u1EA1t \u0111\u1ED9ng \u0111\xFAng.`)}else if(N.trim())l++}$m(S,N,b,n)}}),j(s.watch)){let g=(b,R)=>{for(let C of Object.keys(b)){if(C==="deep-watch"){if(j(b[C]))g(b[C],`${R}.deep-watch`);continue}let S=Ge(C);if(!i.has(S))o.push(`watch "${C}" (${R}) \u2014 bi\u1EBFn "${S}" ch\u01B0a khai b\xE1o trong data[], watcher s\u1EBD kh\xF4ng bao gi\u1EDD ch\u1EA1y.`)}};g(s.watch,"watch")}for(let g of y){let b=wm[g];if(!b)continue;let R=b.filter((C)=>!t.importsText.includes(C.toLowerCase()));if(R.length>0)n.push(`"${g}" c\u1EA7n import: ${R.join(", ")} \u2014 ch\u01B0a th\u1EA5y trong _imports.json (module l\u1EABn project). D\xF9ng file_import_list/file_import_new (xem component-quickref.md).`)}let v=0,T=new Set;if(t.schemaProcs){let g=new Set;for(let b of t.schemaProcs.values())for(let R of b)g.add(R);if(Fe(s,(b,R)=>{let C=b.API;if(typeof C!=="string"||!C||C.includes("{{"))return;v++;let S=null,N=null;if(C.startsWith("/")){let A=C.replace(/^\/+/,"").split("/").filter(Boolean);if(A.length<2)return;if(N=t.schemaProcs.get(A[0].toLowerCase())??null,!N){T.add(A[0]);return}S=A[1]==="auth"?A[2]??null:A[1]}else if(qn(C))S=C,N=g;if(!S||S.includes("`")||!N)return;if(![`spAPI_${S}`,`spAPI_AUTH_${S}`,`spAPIFILE_${S}`,`spAPIFILE_UPLOAD_${S}`,S].some((A)=>N.has(A)))o.push(`API "${C}" (t\u1EA1i ${R}) \u2014 kh\xF4ng t\xECm th\u1EA5y spAPI_${S} trong schema local. Deploy SP tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y db_schema_get (b\u1ECF qua n\u1EBFu API thu\u1ED9c backend kh\xE1c).`)}),T.size>0){let b=[...T].sort();o.push(`${b.length} alias \u0111\u01B0\u1EE3c g\u1ECDi nh\u01B0ng ch\u01B0a c\xF3 k\u1EBFt n\u1ED1i local \u2014 KH\xD4NG \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c t\xEAn SP: ${b.join(", ")}. `+`K\u1EBFt n\u1ED1i t\u1EEBng c\xE1i b\u1EB1ng db_connect({ apiName: "${b[0]}", apiDomain: "\u2026", dbToken: "\u2026" }) \u0111\u1EC3 b\u01B0\u1EDBc ki\u1EC3m n\xE0y ch\u1EA1y (b\u1ECF qua n\u1EBFu alias thu\u1ED9c backend c\u1EE7a nh\xF3m kh\xE1c).`)}}let I=Rm(s,t,o);return Pm(s,o),Am(s,o),Dm(s,n,o),{errors:n,warnings:o,stats:{stateKeys:i.size-mc.size,namedActions:a.size,containers:p,controls:m,callRefs:u.size,apiRefs:v,crossWindowCalls:d.size,dynamicRefs:l,rightRefs:I}}}function ss(){return{stateKeys:0,namedActions:0,containers:0,controls:0,callRefs:0,apiRefs:0,crossWindowCalls:0,dynamicRefs:0,rightRefs:0}}function Om(e){return e.match(/^CREATE (?:PROCEDURE|FUNCTION)\s+(\S+)\s*\(/i)?.[1]??null}async function Mm(e){let t=st.dirname(st.dirname(e)),n=new Set;for(let c of[st.join(e,"components"),st.join(t,"components")]){for(let u of await Me(c))n.add(u.replace(/\.vue$/i,""));let l=await U(st.join(c,"_components.json"),[]);for(let u of l)if(u?.comName)n.add(u.comName)}let o=[];for(let c of[e,t])o.push(await F(st.join(c,"imports","_imports.json")));let s=await U(st.join(t,"_db","_connections.json"),null),r=new Map,i=[];for(let c of Object.keys(s?.connections??{})){let l=await U(st.join(t,"_db",c,"schema.json"),null),u=Array.isArray(l?.procedures)?l.procedures:null;if(u===null)continue;if(!u.every((d)=>typeof d==="string")){i.push(c);continue}r.set(c.toLowerCase(),new Set(u.map(Om).filter((d)=>d!==null)))}let a=await Ot(t);return{localComponents:n,importsText:o.join(`
|
|
154
|
+
`).toLowerCase(),schemaProcs:r.size>0?r:null,schemaWarning:i.length>0?`_db/${i.join("/schema.json, _db/")}/schema.json sai \u0111\u1ECBnh d\u1EA1ng (b\u1EA3n cache c\u0169) \u2014 b\u1ECF qua b\u01B0\u1EDBc \u0111\u1ED1i chi\u1EBFu t\xEAn API v\u1EDBi stored procedure \u1EDF ${i.length>1?"c\xE1c k\u1EBFt n\u1ED1i n\xE0y":"k\u1EBFt n\u1ED1i n\xE0y"}. Ch\u1EA1y db_schema_get \u0111\u1EC3 l\u1EA5y b\u1EA3n m\u1EDBi.`:null,definedFunctionRights:a.functionRights,definedSystemRights:a.systemRights}}async function Ro(e){let t=await F(st.join(e,"module.json"));if(!t.trim())return{errors:[`module.json r\u1ED7ng ho\u1EB7c kh\xF4ng t\u1ED3n t\u1EA1i trong ${e}.`],warnings:[],stats:ss()};let n;try{n=JSON.parse(t)}catch(s){return{errors:[`module.json c\xF3 l\u1ED7i JSON syntax: ${s.message}`],warnings:[],stats:ss()}}let o=await Mm(e);return xm(n,o)}function Po(e){let t=[];if(e.errors.length===0&&e.warnings.length===0)t.push("\u2705 module.json h\u1EE3p l\u1EC7 \u2014 kh\xF4ng ph\xE1t hi\u1EC7n l\u1ED7i ng\u1EEF ngh\u0129a.");else{if(e.errors.length>0)t.push(`\u274C ${e.errors.length} l\u1ED7i (s\u1EBD h\u1ECFng khi ch\u1EA1y):`),t.push(...e.errors.map((o)=>` - ${o}`));if(e.warnings.length>0){if(t.length)t.push("");t.push(`\u26A0\uFE0F ${e.warnings.length} c\u1EA3nh b\xE1o (n\xEAn xem l\u1EA1i):`),t.push(...e.warnings.map((o)=>` - ${o}`))}}let n=e.stats;if(t.push(""),n.dynamicRefs>0)t.push(`\u2139\uFE0F ${n.dynamicRefs} bi\u1EC3u th\u1EE9c \u0111\u1ED9ng (\`:attr\` ph\u1EE9c t\u1EA1p, CALL/API/v-model/OUT d\u1EA1ng \`{{\u2026}}\`/g\u1ECDi h\xE0m/template string) KH\xD4NG ki\u1EC3m t\u0129nh \u0111\u01B0\u1EE3c \u2014 bi\u1EBFn b\xEAn trong ch\xFAng kh\xF4ng \u0111\u01B0\u1EE3c \u0111\u1ED1i chi\u1EBFu v\u1EDBi data[]. "H\u1EE3p l\u1EC7" \u1EDF \u0111\xE2y KH\xD4NG c\xF3 ngh\u0129a l\xE0 ch\u1EA1y \u0111\xFAng: d\xF9ng module_simulate \u0111\u1EC3 ph\u1EE7 ph\u1EA7n n\xE0y.`);return t.push(`Th\u1ED1ng k\xEA: ${n.stateKeys} state \xB7 ${n.namedActions} named action \xB7 ${n.containers} container / ${n.controls} control \xB7 ${n.callRefs} CALL ref${n.apiRefs?` \xB7 ${n.apiRefs} API ref \u0111\u1ED1i chi\u1EBFu schema`:""}${n.crossWindowCalls?` \xB7 ${n.crossWindowCalls} CALL xuy\xEAn c\u1EEDa s\u1ED5 (thu\u1ED9c module kh\xE1c, kh\xF4ng ki\u1EC3m \u0111\u01B0\u1EE3c t\u1EEB \u0111\xE2y)`:""}${n.rightRefs?` \xB7 ${n.rightRefs} m\xE3 quy\u1EC1n \u0111\u1ED1i chi\u1EBFu _db/rights.json`:""}`),t.join(`
|
|
155
|
+
`)}function ee(e,t){let n=e.replace(/\s+/g," ").trim();return n.length>t?n.slice(0,t)+"\u2026":n}function Jn(e){if(e===null)return"null";if(Array.isArray(e))return e.length===0?"[]":`[${e.length} items]`;if(j(e)){let t=Object.keys(e);return t.length===0?"{}":`{${ee(t.join(","),40)}}`}if(typeof e==="string")return`"${ee(e,30)}"`;return String(e)}function _e(e,t=0){if(t>3)return"\u2026";if(typeof e==="string")return ee(e,70);if(Array.isArray(e))return e.map((o)=>_e(o,t+1)).join(" ; ");if(!j(e))return ee(JSON.stringify(e)??"",60);let n=[];if(e.CONFIRM!==void 0)n.push(`CONFIRM "${ee(String(e.CONFIRM),40)}"`);if(e.IF!==void 0)n.push(`IF(${ee(String(e.IF),50)})`);if(e.API!==void 0)n.push(`API ${ee(String(e.API),60)}`);if(e.METHOD!==void 0)n.push(`[${String(e.METHOD)}]`);if(e.IN!==void 0)n.push(`IN{${j(e.IN)?ee(Object.keys(e.IN).join(","),50):ee(String(e.IN),40)}}`);if(e.OUT!==void 0)n.push(`OUT\u2192${j(e.OUT)?Object.keys(e.OUT).join(","):String(e.OUT)}`);if(e.ARRAY!==void 0)n.push(`ARRAY ${ee(String(e.ARRAY),30)}${e.COL!==void 0?` COL ${ee(String(e.COL),30)}`:""}`);if(e.CALL!==void 0)n.push(`CALL ${ee(String(e.CALL),40)}`);if(e.FUN!==void 0)n.push(`FUN ${ee(String(e.FUN),40)}`);if(e.EXE!==void 0)n.push(`EXE ${ee(String(e.EXE),70)}`);if(e.MESS!==void 0)n.push(`MESS "${ee(String(e.MESS),40)}"`);if(e.MESSBOX!==void 0)n.push(`MESSBOX "${ee(String(e.MESSBOX),40)}"`);if(e.THEN!==void 0)n.push(`THEN[${_e(e.THEN,t+1)}]`);if(e.ELSE!==void 0)n.push(`ELSE[${_e(e.ELSE,t+1)}]`);if(e.CALLBACK!==void 0)n.push(`CALLBACK[${_e(e.CALLBACK,t+1)}]`);if(e.ERROROUT!==void 0)n.push(`ERROROUT\u2192${String(e.ERROROUT)}`);if(e.ERRORCALLBACK!==void 0)n.push(`ERRORCALLBACK[${_e(e.ERRORCALLBACK,t+1)}]`);if(e.CANCEL!==void 0)n.push(`CANCEL[${_e(e.CANCEL,t+1)}]`);return n.join(" ")||ee(JSON.stringify(e)??"",60)}var Fm=["v-model","v-if","v-show","v-for","label",":label","placeholder",":items",":data",":headers",":config",":option",":options","value",":value","readonly","disabled",":disabled","multiple","type"];function Lm(e){let t=[],n=e.w!==void 0?`w=${String(e.w)}`:"w=?",o=typeof e.el==="string"&&e.el?e.el:e.rows?"(container)":"(text)";t.push(`${n} ${o}`);let s=j(e.attr)?e.attr:{};for(let r of Fm){let i=s[r];if(i===void 0)continue;t.push(`${r}=${typeof i==="string"?ee(i,45):Jn(i)}`)}for(let[r,i]of Object.entries(s)){if(!r.startsWith("v-on:"))continue;let a=r.slice(5);t.push(`${a}\u2192${typeof i==="string"?ee(i,60):_e(i)}`)}for(let r of["innerText","html","text"])if(typeof e[r]==="string"&&e[r])t.push(`"${ee(String(e[r]),35)}"`);return t.join(" ")}function yc(e,t,n,o){let s=o?"\u2514\u2500":"\u251C\u2500",r=t+(o?" ":"\u2502 ");if(!j(e)){n.push(`${t}${s} ${ee(JSON.stringify(e)??String(e),60)}`);return}n.push(`${t}${s} ${Lm(e)}`);let i=e.innerHTML;if(Array.isArray(i))i.forEach((a,c)=>yc(a,r,n,c===i.length-1));if(Array.isArray(e.rows))bc(e.rows,r,n)}function bc(e,t,n){e.forEach((o,s)=>{if(!j(o)||!Array.isArray(o.cols)){n.push(`${t}row ${s+1}: \u274C thi\u1EBFu "cols"`);return}let r=o.cols,a=r.map((u)=>j(u)?u.w:void 0).filter((u)=>typeof u==="number"),c="";if(r.length>1&&a.length===r.length){let u=a.reduce((d,p)=>d+p,0);c=` (\u03A3w=${u}${u>12?" \u26A0 v\u01B0\u1EE3t 12":""})`}let l=[];for(let u of["v-if","v-show","prop"])if(typeof o[u]==="string")l.push(`${u}=${ee(String(o[u]),40)}`);n.push(`${t}row ${s+1}${l.length?` [${l.join(" ")}]`:""}${c}`),r.forEach((u,d)=>yc(u,t+" ",n,d===r.length-1))})}function kc(e){if(!j(e))return"\u274C module.json g\u1ED1c kh\xF4ng ph\u1EA3i object.";let t=e,n=[],o=new Map,s=new Map,r=[],i=Array.isArray(t.data)?t.data:[];for(let d of i){if(!j(d))continue;let p=Object.keys(d);if(p.length>0&&p.every((m)=>un.has(m))){r.push(d);continue}for(let[m,w]of Object.entries(d)){if(un.has(m)){r.push({[m]:w});continue}if(m.startsWith("#"))continue;if(is(w))s.set(m,w);else o.set(Ge(m),w)}}let a=JSON.stringify({c:t.controls,w:t.watch,s:t.set,a:[...s.values()],u:r})??"",c=(d)=>new RegExp(`\\b${d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`).test(a);n.push("\u2550\u2550\u2550 LAYOUT (controls) \u2550\u2550\u2550");let l=Array.isArray(t.controls)?t.controls:[];if(l.length===0)n.push("(kh\xF4ng c\xF3 controls)");let u=[];if(l.forEach((d,p)=>{if(!j(d)){n.push(`[container ${p+1}] \u274C kh\xF4ng ph\u1EA3i object`);return}let m=typeof d.el==="string"?d.el:"",w=m==="hidden-container",y=j(d.attr)?d.attr:{},v=[];if(m)v.push(`el=${m}`);if(typeof d.prop==="string")v.push(`prop="${ee(d.prop,40)}"`);for(let T of["v-if","v-show","v-model"]){let I=d[T]??y[T];if(typeof I==="string")v.push(`${T}=${ee(I,40)}`)}if(n.push(`[container ${p+1}${w?" \u2014 DIALOG":""}] ${v.join(" ")||"(m\u1EB7c \u0111\u1ECBnh)"}`),w){let T=d["v-model"]??y["v-model"];if(typeof T==="string")u.push({vModel:Ge(T),where:`container ${p+1}`});else u.push({vModel:"",where:`container ${p+1}`})}if(Array.isArray(d.rows))bc(d.rows," ",n);else n.push(' \u274C thi\u1EBFu "rows"')}),n.push("","\u2550\u2550\u2550 STATE (data) \u2550\u2550\u2550"),o.size===0)n.push("(kh\xF4ng c\xF3 state)");for(let[d,p]of o){let m=c(d);n.push(`${d} = ${Jn(p)}${m?"":" \u26A0 kh\xF4ng th\u1EA5y d\xF9ng \u1EDF controls/watch/actions"}`)}if(n.push("","\u2550\u2550\u2550 ACTIONS \u2550\u2550\u2550"),s.size===0&&r.length===0)n.push("(kh\xF4ng c\xF3 action)");for(let[d,p]of s){let m=JSON.stringify({c:t.controls,w:t.watch,s:t.set,u:r,a:[...s.entries()].filter(([y])=>y!==d).map(([,y])=>y)})??"",w=new RegExp(`\\b${d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`).test(m);n.push(`${d}: ${_e(p)}${w?"":" \u26A0 kh\xF4ng th\u1EA5y n\u01A1i g\u1ECDi (CALL/watch/v-on)"}`)}if(r.forEach((d,p)=>{n.push(`(auto-startup ${p+1}): ${_e(d)}`)}),j(t.watch)&&Object.keys(t.watch).length>0){n.push("","\u2550\u2550\u2550 WATCH \u2550\u2550\u2550");let d=(p,m)=>{for(let[w,y]of Object.entries(p)){if(w==="deep-watch"&&j(y)){d(y,"deep ");continue}n.push(`${m}${w} \u2192 ${_e(y)}`)}};d(t.watch,"")}if(u.length>0){n.push("","\u2550\u2550\u2550 DIALOGS \u2550\u2550\u2550");let d=JSON.stringify(t)??"";for(let p of u){if(!p.vModel){n.push(`${p.where}: \u26A0 hidden-container kh\xF4ng c\xF3 v-model \u2014 kh\xF4ng \u0111i\u1EC1u khi\u1EC3n m\u1EDF/\u0111\xF3ng \u0111\u01B0\u1EE3c`);continue}let m=p.vModel.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),w=new RegExp(`\\b${m}\\b\\s*=\\s*true`).test(d);n.push(`${p.where}: v-model=${p.vModel}${w?` \u2014 c\xF3 l\u1EC7nh m\u1EDF (${p.vModel} = true)`:` \u26A0 kh\xF4ng th\u1EA5y l\u1EC7nh m\u1EDF "${p.vModel} = true" \u1EDF \u0111\xE2u`}`)}}if(j(t.set)&&Object.keys(t.set).length>0){n.push("","\u2550\u2550\u2550 SET (override) \u2550\u2550\u2550");for(let[d,p]of Object.entries(t.set))n.push(`${d} = ${typeof p==="string"?ee(p,60):Jn(p)}`)}return n.push("",`Th\u1ED1ng k\xEA: ${o.size} state \xB7 ${s.size} named action \xB7 ${r.length} auto-startup \xB7 ${l.length} container \xB7 ${u.length} dialog`),n.join(`
|
|
156
|
+
`)}async function pn(e){let t=gr(await Tn(e));return t?[t]:[]}async function $c(e,t){let n=await Ro(e);if(n.errors.length===0&&n.warnings.length===0)return[];let o=Po(n);if(n.errors.length>0&&qt())throw new $e(`[FUI_MCP_STRICT] ${t} blocked: module.json has semantic errors; fix them and retry
|
|
157
|
+
|
|
158
|
+
${o}`,"hard","error");return["--- module validate ---",o]}async function Ft(e){let t=await U(it(e,"_moduleInfo.json"),null),n=t?.ProjectID&&t?.ModuleID?`fui module get -p ${t.ProjectID} -m ${t.ModuleID}`:"fui module get";return Pt(e,"local module",n,"_moduleInfo.json")}async function vc(e){let t=await Ft(e),n=await Ro(e);return{notes:[...t?[t]:[],...await pn(e)],data:`${Po(n)}
|
|
159
|
+
`,errorCount:n.errors.length}}async function Sc(e,t){let o=await(await ce(e)).previewStagedModuleDir(t),s=await e.session.createApproval({projectId:o.moduleInfo.ProjectID,moduleId:o.moduleInfo.ModuleID,moduleDir:t,contentHash:await So(t)}),r=await Ft(t),i=await Ro(t);return{token:s.token,notes:[...r?[r]:[],`preview of ${o.moduleInfo.ProjectID}/${o.moduleInfo.ModuleID} (${t})`,`remote: ${o.remoteSummary.join("; ")}`,"changes that would be pushed:",...o.changes.length===0?[" none"]:o.changes.map((a)=>` ${a}`),"--- module validate ---",Po(i),`approval token: ${s.token}`,"ask the user; only after explicit approval run: fui module publish html --approval <token>"]}}async function jc(e,t,n){let o=await xt(e.session,"module publish html"),s=(c)=>new $e(c,"hard","error");if(!n)throw s("missing --approval: run fui module preview, show it to the user, and publish only after explicit approval");let r=await e.session.getApproval();if(!r)throw s("no pending publish approval; run fui module preview first");if(r.token!==n)throw s("invalid approval token; run fui module preview again and use the latest token after approval");if(r.moduleDir!==t)throw s(`approval token is for ${r.moduleDir}, not ${t}`);if(await So(t)!==r.contentHash)throw s("files changed since the preview (module.json, header.html, body.html, script.js, style.css or components/*.vue); run fui module preview again and get a fresh approval");let i=await $c(t,"module publish html"),a=await(await ce(e)).publishStagedModuleDir(t);return await e.session.clearApproval(),{notes:[...o,`ok published ${a.ProjectID}/${a.ModuleID} (${t})`,...i,...await pn(t)]}}async function To(e,t){let n=await xt(e.session,"module publish json"),o=await F(it(t.moduleDir,"module.json"));if(!o.trim())throw new f("local module.json is empty or missing");try{JSON.parse(o)}catch(r){throw new f(`module.json has invalid JSON syntax: ${r.message}`)}if(await zt(t.moduleDir,"module.json",o))return{notes:[...n,`skip module.json unchanged since the last publish/sync of ${t.projectId}/${t.moduleId}`]};let s=await $c(t.moduleDir,"module publish json");return await(await e.api()).updateModuleUI(t.projectId,t.moduleId,o),await Te(t.moduleDir,["module.json"]).catch(()=>{return}),{notes:[...n,`ok published module.json to ${t.projectId}/${t.moduleId}`,...s,...await pn(t.moduleDir)]}}async function Ao(e,t,n){let o=`module publish ${n==="script.js"?"script":"css"}`,s=await xt(e.session,o),r=await Wr(it(t.moduleDir,n),n);if(await zt(t.moduleDir,n,r))return{notes:[...s,`skip ${n} unchanged since the last publish/sync of ${t.projectId}/${t.moduleId}`]};let i=await e.api();if(n==="script.js")await i.updateModuleScript(t.projectId,t.moduleId,r);else await i.updateModuleCSS(t.projectId,t.moduleId,r);return await Te(t.moduleDir,[n]).catch(()=>{return}),{notes:[...s,`ok published ${n} to ${t.projectId}/${t.moduleId}`,...await pn(t.moduleDir)]}}function dn(e){return e.moduleId?`module (${e.moduleId})`:"project (global, loaded in all modules)"}async function Ic(e,t){let n=await F(it(e.baseDir,"components",`${t}.vue`));if(!n.trim())throw new f(`${t}.vue not found in ${e.baseDir}/components/${e.moduleId?"":"; for module scope pass -m <module>"}`);return n}async function Eo(e){return U(it(e,"components","_components.json"),[])}async function Cc(e,t){let{count:n,removed:o}=await(await ce(e)).syncComponentsLocal(t.projectId,t.moduleId);return{notes:[`${n} component(s) in ${t.moduleId?`module ${t.moduleId}`:`project ${t.projectId}`}; _components.json refreshed`,...o.length>0?[`removed orphaned local files: ${o.join(", ")}`]:[]]}}async function No(e,t,n){let o=await xt(e.session,"component new"),s=await Ic(t,n),r=await ce(e);await r.syncComponentsLocal(t.projectId,t.moduleId);let i=(await Eo(t.baseDir)).find((m)=>m.comName===n);if(i)throw new f(`component "${n}" already exists in ${dn(t)} (ComID ${i.comID}); overwrite it: fui component update ${n}, or pick another name`);let{template:a,script:c,style:l}=wt(s);await r.api.updateComponent({ProjectID:t.projectId,ModuleID:t.moduleId,ComName:n,ComTemplate:a,ComScript:c,ComCSS:l});let{count:u,removed:d}=await r.syncComponentsLocal(t.projectId,t.moduleId),p=(await Eo(t.baseDir)).find((m)=>m.comName===n);return await Te(t.baseDir,[ke(it("components",`${n}.vue`))]).catch(()=>{return}),{notes:[...o,`ok created component ${n} in ${dn(t)} (ComID ${p?.comID??"not visible yet; run fui component list"})`,`${u} component(s) synced${d.length>0?`; removed orphaned: ${d.join(", ")}`:""}`,...await pn(t.baseDir)]}}async function _o(e,t,n){let o=await xt(e.session,"component update"),s=await Ic(t,n),r=(await Eo(t.baseDir)).find((m)=>m.comName===n)?.comID;if(r===void 0)throw new f(`component "${n}" is not in _components.json of ${dn(t)}, so there is no ComID to overwrite. new component: fui component new ${n}; stale cache: fui component list, then retry`);let i=ke(it("components",`${n}.vue`));if(await zt(t.baseDir,i,s))return{notes:[...o,`skip ${n}.vue unchanged since the last publish/sync`]};let{template:a,script:c,style:l}=wt(s),u=await ce(e);await u.api.updateComponent({ProjectID:t.projectId,ModuleID:t.moduleId,ComID:r,ComName:n,ComTemplate:a,ComScript:c,ComCSS:l});let{count:d,removed:p}=await u.syncComponentsLocal(t.projectId,t.moduleId);return await Te(t.baseDir,[i]).catch(()=>{return}),{notes:[...o,`ok updated component ${n} in ${dn(t)} (ComID ${r})`,`${d} component(s) synced${p.length>0?`; removed orphaned: ${p.join(", ")}`:""}`,...await pn(t.baseDir)]}}async function Rc(e,t,n){let o=(await Eo(t.baseDir)).find((a)=>a.comName===n);if(!o)throw new f(`component "${n}" not in the local _components.json of ${dn(t)}; run fui component list first`);let s=await ce(e);await s.api.updateComponent({ProjectID:t.projectId,...t.moduleId?{ModuleID:t.moduleId}:{},ComID:o.comID,ComName:"",ComTemplate:"",ComScript:""});let{count:r,removed:i}=await s.syncComponentsLocal(t.projectId,t.moduleId);return{notes:[`ok deleted component ${n} from ${dn(t)}; ${r} remain`,...i.length>0?[`local files removed (archived): ${i.join(", ")}`]:[]]}}async function Pc(e,t){let{id:n,...o}=t,s=await ce(e),r=await s.api.editProject(o),i=await s.projectInfoRefreshNote(Tc(o,r));return{notes:["ok project created",...n!==void 0?[`warning: projectData had id="${String(n)}"; removed to create a new project (use fui project update to edit)`]:[],...i?[i]:[]],data:`${JSON.stringify(r)}
|
|
160
|
+
`}}async function Ec(e,t){let n=t.id;if(n===void 0||n===null||n==="")throw new f("projectData.id is required to update (set it to the ProjectID); to create a project use fui project new");let o=await ce(e),s=await o.api.editProject(t),r=await o.projectInfoRefreshNote(Tc(t,s));return{notes:["ok project updated",...r?[r]:[]],data:`${JSON.stringify(s)}
|
|
161
|
+
`}}function Tc(e,t){let n=_t(e)?e.id??e.ProjectID:void 0;if(typeof n==="string"&&n.trim())return n;let o=_t(t)?t.data:void 0,s=Array.isArray(o)?o[0]:o,r=_t(s)?s.ProjectID??s.id:void 0;return typeof r==="string"&&r.trim()?r:null}async function Do(e,t){let n=await xt(e.session,"project publish-config"),o=e.paths.projectDir(t),s=await F(it(o,"project.json"));if(!s.trim())throw new f(`cannot read ${t}/project.json (missing or empty); run fui project sync -p ${t}, edit it, then publish`);let r;try{r=JSON.parse(s)}catch(l){throw new f(`${t}/project.json has invalid JSON: ${l.message}; nothing published`)}let{config:i,wasEnveloped:a}=Xr(r);if(await zt(o,"project.json",s))return{notes:[...n,`skip ${t}/project.json unchanged since the last publish/sync`]};let c=await(await e.api()).updateProjectData(t,i);return await Te(o,["project.json"]).catch(()=>{return}),{notes:[...n,`ok published ${t}/project.json (${Object.keys(i).length} keys)`,...a?["the file is an API envelope { data: {...} }; the data layer was sent, the file is unchanged"]:[]],data:`${JSON.stringify(c)}
|
|
162
|
+
`}}async function Um(e,t){let n=await F(it(e,"module.json"));if(!n.trim())throw new f(`module.json is empty or missing in ${e}`);try{return JSON.parse(n)}catch(o){throw new f(`module.json has a JSON syntax error: ${o.message}; fix it, ${t} needs valid JSON`)}}async function Ac(e){let t=await Um(e,"outline"),n=await Ft(e);return{notes:n?[n]:[],data:`Outline: ${e}
|
|
163
|
+
|
|
164
|
+
${kc(t)}
|
|
165
|
+
`}}import{basename as Df}from"path";import{join as Bm}from"path";var Lt=8,xo=5000;function Hm(e){let t=e.split(/[?#]/)[0],n=t.split("/").filter(Boolean);return(n.length?n[n.length-1]:t).toLowerCase()}function _c(e,t,n){let o=new Map;for(let r of t){o.set(r.toLowerCase(),r);let i=Tt(r);if(i)o.set(i.fnName.toLowerCase(),r)}let s=[];for(let r of e){let i=o.get(Hm(r));if(i&&!n.has(i.toLowerCase()))s.push({mockKey:r,spName:i})}return s}function Vn(e){let t=e.match(/^\/([A-Za-z0-9_-]+)\/(auth\/)?([A-Za-z0-9_]+)/);if(!t)return null;return{alias:t[1],isAuth:Boolean(t[2]),fnName:t[3]}}async function as(e){try{return JSON.parse(await H(e))}catch{return null}}function Nc(e){if(!Ct(e))return[];let t=[];for(let n of e.procedures){let o=/^CREATE\s+(?:PROCEDURE|FUNCTION)\s+(\S+)/i.exec(n);if(o)t.push(o[1])}return t}function mn(e,t){let n=e.paths,o=(r,i,a)=>Bm(n.projectDir(r),"_testdata",i,`${a}.json`),s=async(r,i,a)=>{let c=await pe(n.spFile(r,i,a));return c!==null&&c.trim()?c:null};return{async loadAllSchemaProcNames(r){let i=new Set;for(let a of Object.keys((await e.load(r)).connections))for(let c of Nc(await as(n.schemaFile(r,a))))i.add(c);return[...i]},getVerifiedSpNames:()=>t.getVerifiedSpNames(),async getConnectionAlias(r,i){return me(await e.load(r),i)},async resolveApiSpName(r,i,a){let c=await as(n.schemaFile(r,i));if(!Ct(c))return null;let l=Nc(c),u=[a,`spAPI_${a}`,`spAPI_AUTH_${a}`,`spAPIFILE_${a}`,`spAPIFILE_UPLOAD_${a}`];for(let d of u){let p=l.find((m)=>m.toLowerCase()===d.toLowerCase());if(p)return p}return null},async classifySpSafety(r,i,a){return Wi(await s(r,i,a))},async callLiveApiSp(r,i,a,c,l="GET",u,d=xo){try{let{config:p}=await e.resolve({project:r,db:i}),m=`${Ee(p.apiDomain)}/${i}/${a?"auth/":""}${c}`,w={"content-type":"application/json"};if(!a&&p.userToken)w.authorization=`Bearer ${p.userToken}`;let y=await fetch(m,{method:l,headers:w,body:u!==void 0?JSON.stringify(u):void 0,signal:AbortSignal.timeout(d)}),v=await y.text(),T;try{T=JSON.parse(v)}catch{return{ok:!1,status:y.status,error:"Ph\u1EA3n h\u1ED3i kh\xF4ng ph\u1EA3i JSON"}}if(!y.ok)return{ok:!1,status:y.status,data:T,error:`HTTP ${y.status}`};return{ok:!0,status:y.status,data:T}}catch(p){return{ok:!1,error:p instanceof Error?p.message:String(p)}}},async readTestData(r,i,a){return await as(o(r,i,a))},async writeTestData(r,i,a,c,l){try{let u={source:c,savedAt:new Date().toISOString(),data:l};await Bun.write(o(r,i,a),JSON.stringify(u,null,2))}catch{}},async generateMockForLocalSp(r,i,a){let c=await s(r,i,a);return c===null?null:Hi(c)}}}import{promises as ze}from"fs";import ue from"path";import{homedir as Wm}from"os";import{join as ls}from"path";var cs=null;function us(e){cs={...e,live:mn(e.store,e.session)}}function He(){if(!cs){let e=Bun.env.FUI_MCP_WORKDIR||ls(Wm(),Go),t=new jn;us({store:new nt(new Rn(e)),session:t,api:so,activeTarget:()=>t.getTarget()})}return cs}var fn=()=>He().store.paths.root,hn=(e)=>ls(He().store.paths.root,tt(e,"projectId")),ds=(e,t)=>ls(hn(e),"modules",tt(t,"moduleId")),Dc=()=>He().activeTarget();async function xc(e){return await(await He().api()).listFrameworkImportFiles(e)}async function Oc(e,t){let n=await He().store.resolve({project:e,db:t.db});return{alias:n.alias,config:{...n.config,apiName:n.alias},note:n.note}}var Mc=(...e)=>He().live.getConnectionAlias(...e),Fc=(...e)=>He().live.resolveApiSpName(...e),Lc=(...e)=>He().live.classifySpSafety(...e),Uc=(...e)=>He().live.readTestData(...e),ps=(...e)=>He().live.writeTestData(...e),Bc=(...e)=>He().live.generateMockForLocalSp(...e);import{promises as at}from"fs";import{createHash as rl}from"crypto";import K from"path";import{promises as ms}from"fs";import Jc from"path";var qm=86400000,Hc={V2:{styles:["https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900","/include/icons/mdi/materialdesignicons.min.css","/include/vuetify.min.css","/include/toast/vue-toast-notification.css","/include/jquery-confirm.min.css","/projectdefaultstyle.css"],scripts:["/include/jquery-3.6.0.min.js","/include/jquery-confirm.min.js","/include/lodash.4.17.min.js","/include/moment/moment-vi.min.js","/include/numeral.min.js","/include/vue@2.js","/include/vuetify.min.js","/include/toast/vue-toast-notification.js","/defaultfunction.js","/componentTable.js","/component.js","/fastproject.js"]},V3:{styles:["https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900","/include/icons/mdi/materialdesignicons.min.css","/Framework/V3/vuetify@3.7.min.css","/Framework/V3/vuetify-labs@3.7.min.css","/Framework/V3/vue-toast-notification@3.1.3.theme-bootstrap.css","/include/jquery-confirm.min.css","/Framework/V3/projectdefaultstyle.css"],scripts:["/include/jquery-3.6.0.min.js","/include/jquery-confirm.min.js","/include/lodash.4.17.min.js","/include/numeral.min.js","/Framework/V3/dayjs-vi.min.js","/Framework/V3/vue.global.min.3.5.js","/Framework/V3/vuetify@3.7.min.js","/Framework/V3/vuetify-labs@3.7.min.js","/Framework/V3/vue-toast-notification@3.1.3.js","/Framework/V3/defaultfunction-3.0.js","/Framework/V3/fastproject-3.0.js","/Framework/V3/component-3.0.js","/Framework/V3/componentTable-3.0.js"]}};function Jm(e){return Jc.join(fn(),"_cache","framework",`${e}.json`)}function Vm(e,t){let n=Hc[e]??Hc.V3,o=(s,r)=>s.map((i,a)=>({fileID:"",ProjectID:"",ModuleID:"",FileType:r,ContentType:"link",EditContent:0,FileName:/^https?:\/\//i.test(i)?i:`${t}${i}`,FileSort:a,CreateTime:"",CreateUser:"",UpdateTime:null,UpdateUser:null,Scope:"framework"}));return[...o(n.styles,".css"),...o(n.scripts,".js")]}async function Vc(e,t){let n=Jm(e),o;try{o=JSON.parse(await ms.readFile(n,"utf-8"))}catch{}let s=o?.fetchedAt?Date.now()-Date.parse(o.fetchedAt):Number.POSITIVE_INFINITY;if(Array.isArray(o?.data)&&o.data.length>0&&s<qm)return{deps:o.data,source:`cache (${o.data.length} file, t\u1EA3i l\xFAc ${o.fetchedAt})`};try{let r=await xc(e);if(r.length===0)throw Error("API tr\u1EA3 danh m\u1EE5c r\u1ED7ng");try{await ms.mkdir(Jc.dirname(n),{recursive:!0}),await ms.writeFile(n,JSON.stringify({fetchedAt:new Date().toISOString(),data:r},null,2),"utf-8")}catch{}return{deps:r,source:`API /fp/FUI_Dev_File/select {frameWork:"${e}"} (${r.length} file)`}}catch(r){if(Array.isArray(o?.data)&&o.data.length>0)return{deps:o.data,source:`cache C\u0168 (${o.data.length} file, t\u1EA3i l\xFAc ${o.fetchedAt})`,warning:`Kh\xF4ng g\u1ECDi \u0111\u01B0\u1EE3c API danh m\u1EE5c framework ${e} (${r.message}) \u2014 d\xF9ng cache c\u0169 tr\xEAn \u0111\u0129a.`};let i=Vm(e,t);return{deps:i,source:`danh s\xE1ch d\u1EF1 ph\xF2ng hardcode (${i.length} file)`,warning:`Kh\xF4ng g\u1ECDi \u0111\u01B0\u1EE3c API danh m\u1EE5c framework ${e} (${r.message}) v\xE0 ch\u01B0a c\xF3 cache \u2014 `+"d\xF9ng danh s\xE1ch hardcode trong renderAssets.ts. Danh s\xE1ch n\xE0y c\xF3 th\u1EC3 \u0111\xE3 l\u1EC7ch v\u1EDBi server."}}}function Km(e){if(e===".css")return"css";if(e===".js")return"js";return}function Kc(e){return[...e].sort((t,n)=>t.sort-n.sort||(t.name<n.name?-1:t.name>n.name?1:0))}function Gc(e,t){let n=Km(e.FileType);if(!n)return;let o=(e.FileName??"").trim();if(!o)return;return{label:`[framework] ${o}`,scope:"framework",kind:n,sort:typeof e.FileSort==="number"?e.FileSort:0,name:o,fileID:e.fileID,contentType:e.ContentType,domain:t.replace(/^https?:\/\//,""),fixHint:"file_import_list"}}var Wc="render";async function qc(e){try{let t=await fetch(e,{headers:{Accept:"*/*"}});if(!t.ok)return{ok:!1,reason:`HTTP ${t.status} ${t.statusText}`};return{ok:!0,text:await t.text()}}catch(t){return{ok:!1,reason:t.message}}}async function zc(e,t){let n=t.replace(/^https?:\/\//,"").replace(/\/+$/,""),o=`https://${n}/_aset/fp/auth/projectdata/${e}@${n.replace(/\//g,"")}/${Wc}.js`,s=`https://${n}/_aset/fp/auth/StyleCSS/${e}/${Wc}.css`,r=[],i=[],[a,c]=await Promise.all([qc(o),qc(s)]),l;if(a.ok)if(/\$projectData\s*=/.test(a.text))l=a.text,r.push(`projectdata.js \u2190 ${o} (${a.text.length} bytes)`);else i.push(`projectdata.js t\u1EA3i v\u1EC1 t\u1EEB ${o} kh\xF4ng ch\u1EE9a "$projectData =" (${a.text.length} bytes) \u2014 `+"kh\xF4ng d\xF9ng, quay v\u1EC1 d\u1EF1ng $projectData + component project t\u1EEB workspace local.");else i.push(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${o}: ${a.reason} \u2014 quay v\u1EC1 d\u1EF1ng t\u1EEB workspace local.`);let u;if(c.ok)if(/<html|<!DOCTYPE/i.test(c.text.slice(0,200)))i.push(`StyleCSS c\u1EA5p project t\u1EA3i v\u1EC1 t\u1EEB ${s} l\xE0 HTML ch\u1EE9 kh\xF4ng ph\u1EA3i CSS \u2014 b\u1ECF qua.`);else if(c.text.trim())u=c.text,r.push(`StyleCSS project \u2190 ${s} (${c.text.length} bytes)`);else r.push(`StyleCSS project r\u1ED7ng (${s}) \u2014 project ch\u01B0a c\xF3 component n\xE0o khai style.`);else i.push(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${s}: ${c.reason} \u2014 CSS component c\u1EA5p project s\u1EBD d\u1EF1ng t\u1EEB local.`);return{js:l,css:u,jsUrl:o,cssUrl:s,notes:r,problems:i}}function fs(e,t){let n=[],o=new RegExp(`<${t}(\\s[^>]*)?>`,"gi"),s=new RegExp(`</${t}>`,"gi"),r=0;while(r<e.length){o.lastIndex=r;let i=o.exec(e);if(!i)break;let a=1,c=o.lastIndex;while(a>0){s.lastIndex=c;let l=s.exec(e);if(!l){r=e.length;break}o.lastIndex=c;let u=o.exec(e);if(u&&u.index<l.index){a+=1,c=o.lastIndex;continue}if(a-=1,c=s.lastIndex,a===0)n.push(e.slice(i.index+i[0].length,l.index)),r=s.lastIndex}}return n}function Gm(e){return e.replace(/[\r\n\t]+/g," ").replace(/\\/g,"\\\\").replace(/`/g,"\\`").replace(/\$\{/g,"\\${")}function zm(e){let t=/export\s+default\s*\{([\s\S]*)\}\s*;?\s*$/.exec(e.trim());return t?t[1].trim():void 0}function Xc(e,t){let n=fs(t,"template")[0]??"",o=fs(t,"script")[0]??"",s=fs(t,"style").map((c)=>c.trim()).filter(Boolean).join(`
|
|
166
|
+
`);if(!n&&!o&&t.includes("Vue.component("))return{compiled:{name:e,script:t,css:s}};let r=zm(o),i=Gm(n);if(r!==void 0)return{compiled:{name:e,script:`Vue.component('${e}',{template:\`${i}\`,${r}})`,css:s}};let a=/module\.exports\s*=/.test(o)?o.replace(/module\.exports\s*=/,"return "):o.trim()?o:"return {};";return{compiled:{name:e,script:`;(() => {
|
|
167
|
+
const component = (() => {
|
|
168
|
+
${a}
|
|
169
|
+
})() || {};
|
|
170
|
+
component.template = \`${i}\`;
|
|
171
|
+
Vue.component(${JSON.stringify(e)}, component);
|
|
172
|
+
})();`,css:s},warning:`component "${e}.vue" kh\xF4ng c\xF3 khu\xF4n \`export default { \u2026 }\` \u2014 server bi\xEAn d\u1ECBch b\u1EB1ng c\xE1ch `+"n\u1ED1i th\u1EB3ng ph\u1EA7n <script> v\xE0o `{template:`\u2026`, \u2026}` n\xEAn b\u1EA3n tr\xEAn server c\xF3 th\u1EC3 l\u1ED7i c\xFA ph\xE1p. "+"renderUI \u0111\xE3 b\u1ECDc an to\xE0n \u0111\u1EC3 ch\u1EA1y ti\u1EBFp, nh\u01B0ng n\xEAn s\u1EEDa .vue v\u1EC1 \u0111\xFAng khu\xF4n."}}function gn(e){return e.replace(/\s+/g," ").trim()}function Xm(e){let t="";for(let n=0;n<e.length;n+=1){let o=e[n];if(o==="\\"&&n+1<e.length){let s=e[n+1];if(s==="\\"||s==="`"||s==="$"){t+=s,n+=1;continue}}t+=o}return t}function Ym(e){let t=new Map,n=/Vue\.component\(\s*(['"])([^'"]+)\1\s*,\s*\{\s*template\s*:\s*`/g,o=[],s;while((s=n.exec(e))!==null)o.push({name:s[2],templateStart:n.lastIndex,matchStart:s.index});for(let r=0;r<o.length;r+=1){let{name:i,templateStart:a}=o[r],c=a,l=-1;while(c<e.length){let w=e[c];if(w==="\\"){c+=2;continue}if(w==="`"){l=c;break}c+=1}if(l===-1)continue;let u=Xm(e.slice(a,l)),d=r+1<o.length?o[r+1].matchStart:e.length,p,m=e.slice(l+1,d).trim();if(m.startsWith(",")){let w=m.replace(/[;\s]+$/,"");if(w.endsWith("})"))p=w.slice(1,w.length-2).trim()}t.set(i,{template:u,body:p})}return t}function Yc(e,t,n){let o={missing:[],changed:[],changedCss:[],unverifiable:[]},s=Ym(t),r=gn(n);for(let i of e){if(i.parseError){o.unverifiable.push(`${i.name} (${i.parseError})`);continue}if(!new RegExp(`Vue\\.component\\(\\s*(['"])${i.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\1`).test(t)){o.missing.push(i.name);continue}let c=s.get(i.name);if(!c){o.unverifiable.push(`${i.name} (bundle server kh\xF4ng theo khu\xF4n ComponentBuild)`);continue}if(gn(i.template??"")!==gn(c.template)){o.changed.push(i.name);continue}if(c.body===void 0)o.unverifiable.push(`${i.name} (ch\u1EC9 \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c template, kh\xF4ng b\xF3c \u0111\u01B0\u1EE3c th\xE2n script)`);else if(gn(i.script??"")!==gn(c.body)){o.changed.push(i.name);continue}let l=gn(i.css??"");if(l&&!r.includes(l))o.changedCss.push(i.name)}return o}function hs(e){if(e.length===0)return"";return`${e.map((t)=>t.script).join(`;
|
|
173
|
+
`)};
|
|
174
|
+
`}function gs(e){let t=e.map((n)=>n.css).filter((n)=>n.trim());if(t.length===0)return"";return`${t.join(`
|
|
175
|
+
`)}
|
|
176
|
+
`}var Mo="https://fui.vn",Qm=45000,Zm=700,ws=4000,Qc=1200000,ef=new Set(["script","stylesheet","image","font","media","manifest","texttrack"]),tf=new Set([".js",".css",".map",".woff",".woff2",".ttf",".otf",".eot",".png",".jpg",".jpeg",".gif",".svg",".ico",".webp"]),Zc={project:90000,module:95000},nf={desktop:{viewport:{width:1366,height:768}},tablet:{viewport:{width:768,height:1024},hasTouch:!0,userAgent:"Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"},mobile:{viewport:{width:390,height:844},isMobile:!0,hasTouch:!0,userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"}},of=3;function sl(e,t){return K.join(e,`last-vitals-${t}.json`)}async function rf(e,t){try{let n=await at.readFile(sl(e,t),"utf8"),o=JSON.parse(n);return typeof o?.jsErrors==="number"?o:null}catch{return null}}function il(e,t){let n=[],o=(s,r,i)=>{if(r!==i)n.push(`${s} ${r}\u2192${i}${i<r?" \u2193":" \u2191"}`)};if(o("JS error",e.jsErrors,t.jsErrors),o("init error",e.initErrors,t.initErrors),o("Vue warn",e.vueWarns,t.vueWarns),o("file m\xF4i tr\u01B0\u1EDDng thi\u1EBFu",e.envMissing,t.envMissing),o("text nghi v\u1EA5n",e.suspectTexts,t.suspectTexts),o("\u1EA3nh v\u1EE1",e.brokenImages,t.brokenImages),o("canvas ch\u1EBFt",e.deadCanvas,t.deadCanvas),o("text d\xEDnh/ch\u1ED3ng l\u1EA5n",e.textCollisions,t.textCollisions),o("assert h\u1ECFng",e.assertFail,t.assertFail),e.appEmpty!==t.appEmpty)n.push(t.appEmpty?"app render R\u1ED6NG (tr\u01B0\u1EDBc c\xF3 n\u1ED9i dung) \u2191":"app \u0111\xE3 c\xF3 n\u1ED9i dung \u2193");if(e.overflowX!==t.overflowX)n.push(t.overflowX?"tr\xE0n ngang (tr\u01B0\u1EDBc kh\xF4ng) \u2191":"h\u1EBFt tr\xE0n ngang \u2193");return n}function sf(e,t){let n=il(e,t),o=e.ts.replace("T"," ").slice(0,19);if(n.length>0)return`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): ${n.join(" \xB7 ")}`;return ks(t)?`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): KH\xD4NG ch\u1EC9 s\u1ED1 n\xE0o \u0111\u1ED5i \u2014 thay \u0111\u1ED5i v\u1EEBa r\u1ED3i kh\xF4ng ch\u1EA1m t\u1EDBi l\u1ED7i \u0111ang c\xF3. \u0110\u1ED5i h\u01B0\u1EDBng ch\u1EA9n \u0111o\xE1n, \u0111\u1EEBng render l\u1EA1i c\xF9ng m\u1ED9t c\xE1ch.`:`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): kh\xF4ng \u0111\u1ED5i \u2014 c\u1EA3 hai l\u01B0\u1EE3t \u0111\u1EC1u s\u1EA1ch.`}function el(e,t){let n=new Set;for(let o of e){let s=String(o).split(`
|
|
177
|
+
`)[0].trim();if(s)n.add(s.length>200?s.slice(0,200)+"\u2026":s);if(n.size>=t)break}return[...n]}function af(e,t){return["",`\uD83D\uDED1 \u0110ANG GI\u1EACM CH\xC2N \u2014 ${e} l\u01B0\u1EE3t render h\u1ECFng li\xEAn ti\u1EBFp m\xE0 KH\xD4NG ch\u1EC9 s\u1ED1 n\xE0o \u0111\u1ED5i.`," Render l\u1EA1i l\u1EA7n n\u1EEFa theo c\xF9ng h\u01B0\u1EDBng s\u1EBD cho \u0111\xFAng k\u1EBFt qu\u1EA3 n\xE0y. Ch\u1ECDn M\u1ED8T trong ba:",t.length>0?" 1. Ch\u1EA1y t\u1EA7ng r\u1EBB h\u01A1n \u0111\u01B0\u1EE3c n\xEAu \u1EDF d\xF2ng \u21AA tr\xEAn \u2014 n\xF3 ch\u1EC9 th\u1EB3ng v\xE0o ch\u1ED7 h\u1ECFng.":" 1. L\xF9i v\u1EC1 t\u1EA7ng t\u0129nh: module_validate (l\u1ED7i quy t\u1EAFc) \u2192 module_outline (c\u1EA5u tr\xFAc) \u2192 module_simulate kh\xF4ng renderUI (action/watch)."," 2. \u0110\u1ECDc l\u1EA1i screenshot + kh\u1ED1i l\u1ED7i \u1EDF cu\u1ED1i report b\u1EB1ng m\u1EAFt, \u0111\u1EEBng ch\u1EC9 \u0111\u1ECDc con s\u1ED1."," 3. H\u1ECEI NG\u01AF\u1EDCI D\xD9NG. Ba l\u01B0\u1EE3t kh\xF4ng nh\xFAc nh\xEDch th\u01B0\u1EDDng ngh\u0129a l\xE0 gi\u1EA3 \u0111\u1ECBnh ban \u0111\u1EA7u sai, kh\xF4ng ph\u1EA3i s\u1EEDa ch\u01B0a \u0111\u1EE7."]}function ks(e){return e.jsErrors>0||e.vueWarns>0||e.envMissing>0||e.appEmpty||e.overflowX||e.suspectTexts>0||e.brokenImages>0||e.deadCanvas>0||e.textCollisions>0||e.assertFail>0}function cf(e,t,n,o){let s=["\u2550\u2550\u2550 CH\u1EA8N \u0110O\xC1N \u2550\u2550\u2550"];s.push(e.envMissing>0?`[M\xD4I TR\u01AF\u1EDCNG] ${e.envMissing} file import/asset KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c \u2192 s\u1EEDa ph\u1EA7n n\xE0y TR\u01AF\u1EDAC (project_sync / module_get / file_import_*). L\u1ED7i \u1EDF hai nh\xF3m d\u01B0\u1EDBi c\xF3 th\u1EC3 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3.`:"[M\xD4I TR\u01AF\u1EDCNG] \u2713 m\u1ECDi import/asset n\u1EA1p \u0111\u1EE7.");let r=[];if(e.jsErrors>0)r.push(`${e.jsErrors} JS error${e.initErrors?` (${e.initErrors} l\xFAc init)`:""}`);if(e.vueWarns>0)r.push(`${e.vueWarns} [Vue warn]`);if(e.assertFail>0)r.push(`${e.assertFail} assert h\u1ECFng`);s.push(r.length>0?`[MODULE] ${r.join(" \xB7 ")} \u2192 l\u1ED7i n\u1EB1m trong module.json / script.js / component.`:"[MODULE] \u2713 kh\xF4ng JS error, kh\xF4ng Vue warn.");for(let a of el(n,3))s.push(` \u2718 ${a}`);for(let a of el(o,2))s.push(` \u26A0 ${a}`);let i=[];if(e.appEmpty)i.push("app render R\u1ED6NG");if(e.overflowX)i.push("tr\xE0n ngang viewport");if(e.suspectTexts>0)i.push(`${e.suspectTexts} text nghi v\u1EA5n (undefined/NaN)`);if(e.brokenImages>0)i.push(`${e.brokenImages} \u1EA3nh v\u1EE1`);if(e.deadCanvas>0)i.push(`${e.deadCanvas} canvas chart 0-size`);if(e.textCollisions>0)i.push(`${e.textCollisions} ch\u1ED7 text d\xEDnh/ch\u1ED3ng l\u1EA5n`);s.push(i.length>0?`[HI\u1EC2N TH\u1ECA] ${i.join(" \xB7 ")} \u2192 xem screenshot; \u0111\xE2y l\xE0 nh\xF3m CH\u1EC8 renderUI th\u1EA5y \u0111\u01B0\u1EE3c.`:"[HI\u1EC2N TH\u1ECA] \u2713 DOM audit s\u1EA1ch.");for(let a of t)s.push(` \u21AA ${a}`);return s}function lf(e,t,n){let o=[];if(n.some((s)=>/\bis not defined\b/.test(s)))o.push("`\u2026 is not defined` = m\u1ED9t \u0111\u1ECBnh danh tr\u1EA7n kh\xF4ng c\xF3 tr\xEAn window. Ca th\u01B0\u1EDDng g\u1EB7p nh\u1EA5t: `EXE`/`FUN` g\u1ECDi h\xE0m ch\u01B0a vi\u1EBFt trong script.js \u2014 ho\u1EB7c vi\u1EBFt r\u1ED3i m\xE0 qu\xEAn `module_publish_script` (module_publish_json KH\xD4NG \u0111\u1EA9y script.js). So\xE1t t\xEAn h\xE0m trong script.js tr\u01B0\u1EDBc khi \u0111i t\xECm ch\u1ED7 kh\xE1c.");if(e.appEmpty&&e.jsErrors===0&&e.vueWarns===0&&e.envMissing===0)o.push("Trang tr\u1EAFng m\xE0 KH\xD4NG c\xF3 JS error/Vue warn l\xE0 d\u1EA5u hi\u1EC7u kinh \u0111i\u1EC3n c\u1EE7a statement trong m\u1ED9t `:attr` (Vue nu\u1ED1t l\u1ED7i compile template) \u2014 ch\u1EA1y module_validate, n\xF3 b\u1EAFt t\u0129nh \u0111\u01B0\u1EE3c ca n\xE0y.");if(t.some((s)=>/is not defined|Property or method/i.test(s)))o.push('[Vue warn] ki\u1EC3u "property is not defined" = bi\u1EBFn d\xF9ng trong template ch\u01B0a khai trong data[] \u2014 module_validate b\u1EAFt \u0111\u01B0\u1EE3c (v-model/watch/state), kh\xF4ng c\u1EA7n browser.');if(e.suspectTexts>0&&e.jsErrors===0)o.push('Text "undefined"/"NaN" th\u01B0\u1EDDng l\xE0 OUT/mapData tr\u1ECF sai kho\xE1 ch\u1EE9 kh\xF4ng ph\u1EA3i l\u1ED7i hi\u1EC3n th\u1ECB \u2014 module_simulate (engine vm) ch\u1EC9 ra ch\u1ED7 g\xE1n, ch\u1EA1y nhanh h\u01A1n nhi\u1EC1u.');return o}async function uf(){for(let e of["playwright","playwright-core"])for(let t of[void 0,process.cwd()])try{return await(t?import(Bun.resolveSync(e,t)):import(e))}catch{}throw Error(`rendering needs Playwright (optional, not installed). Install once:
|
|
178
|
+
bun add -g playwright
|
|
179
|
+
bunx playwright install chromium
|
|
180
|
+
(module simulate without --render works without Playwright.)`)}var df=60000,ys=new Map;function $s(){return K.join(fn(),"_cache","cdn")}function vs(e){return rl("sha1").update(e).digest("hex")}function al(e){return rl("sha256").update(e).digest("hex")}async function pf(e){let t=vs(e),n=$s();try{let o=JSON.parse(await at.readFile(K.join(n,`${t}.meta.json`),"utf-8"));return{body:await at.readFile(K.join(n,`${t}.bin`)),contentType:o.contentType??cl(e),meta:o}}catch{return}}async function mf(e,t,n,o){try{let s=vs(e),r=$s();await at.mkdir(r,{recursive:!0}),await at.writeFile(K.join(r,`${s}.bin`),t);let i={url:e,contentType:n,cachedAt:new Date().toISOString(),etag:o.etag,lastModified:o.lastModified,sha256:al(t)};await at.writeFile(K.join(r,`${s}.meta.json`),JSON.stringify(i),"utf-8")}catch{}}async function ff(e,t){try{let n=vs(e);await at.writeFile(K.join($s(),`${n}.meta.json`),JSON.stringify({...t,cachedAt:new Date().toISOString()}),"utf-8")}catch{}}function bs(e){if(!e)return"kh\xF4ng r\xF5 tu\u1ED5i";let t=Date.now()-Date.parse(e);if(!Number.isFinite(t))return"kh\xF4ng r\xF5 tu\u1ED5i";let n=Math.floor(t/86400000);if(n>=1)return`c\u0169 ${n} ng\xE0y`;let o=Math.floor(t/3600000);return o>=1?`c\u0169 ${o} gi\u1EDD`:"c\u0169 d\u01B0\u1EDBi 1 gi\u1EDD"}async function hf(e,t){let n=t.refresh?void 0:await pf(e);if(t.offline){if(!n)throw Error("offline: ch\u01B0a c\xF3 b\u1EA3n cache n\xE0o cho URL n\xE0y");return{body:n.body,contentType:n.contentType,origin:"stale",note:`offline \u2014 d\xF9ng cache ${bs(n.meta.cachedAt)}`}}if(n&&!t.refresh){let r=ys.get(e);if(r!==void 0&&Date.now()-r<df)return{body:n.body,contentType:n.contentType,origin:"memo"}}let o={},s=Boolean(n?.meta.etag||n?.meta.lastModified);if(n&&s&&!t.refresh){if(n.meta.etag)o["If-None-Match"]=n.meta.etag;if(n.meta.lastModified)o["If-Modified-Since"]=n.meta.lastModified}try{let r=await fetch(e,{headers:o,signal:AbortSignal.timeout(20000)});if(r.status===304&&n)return ys.set(e,Date.now()),await ff(e,n.meta),{body:n.body,contentType:n.contentType,origin:"revalidated"};if(!r.ok)throw Error(`HTTP ${r.status}`);let i=Buffer.from(await r.arrayBuffer()),a=r.headers.get("content-type")??cl(e),c=r.headers.get("etag")??void 0,l=r.headers.get("last-modified")??void 0;await mf(e,i,a,{etag:c,lastModified:l}),ys.set(e,Date.now());let u=Boolean(n?.meta.sha256&&n.meta.sha256!==al(i));return{body:i,contentType:a,origin:n?"updated":"fetched",changed:u,note:u?`n\u1ED9i dung \u0110\u1ED4I so v\u1EDBi b\u1EA3n cache ${bs(n?.meta.cachedAt)}`:void 0}}catch(r){if(n)return{body:n.body,contentType:n.contentType,origin:"stale",note:`kh\xF4ng revalidate \u0111\u01B0\u1EE3c (${r.message}) \u2014 d\xF9ng cache ${bs(n.meta.cachedAt)}`};throw r}}function cl(e){switch(K.extname(new URL(e).pathname).toLowerCase()){case".js":return"application/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".woff":return"font/woff";case".woff2":return"font/woff2";case".ttf":return"font/ttf";default:return"application/octet-stream"}}function wn(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function bt(e,t){return e.replace(new RegExp(`</(?=${t})`,"gi"),"<\\/")}function Fo(e,t){return JSON.stringify(e,null,t).replace(/</g,"\\u003c")}function gf(e,t){if(!e)return;let n=e.trim();if(!n)return;if(/^https?:\/\//i.test(n))return n;if(n.startsWith("//"))return`https:${n}`;if(n.startsWith("/"))return`https://${t}${n}`;return}function tl(e){if(typeof e!=="string")return[];let t=e.trim();if(!t)return[];if(t.startsWith("["))try{let n=JSON.parse(t);if(Array.isArray(n))return n.filter((o)=>typeof o==="string").map((o)=>o.trim()).filter(Boolean)}catch{}return[t]}function wf(e){let t=(e??"").trim();if(!t)return{};let n=t.startsWith("//")?`https:${t}`:t;if(/^https?:\/\//i.test(n))try{let s=new URL(n);return{host:s.host,path:s.pathname||"/"}}catch{}let o=t.split(/[?#]/)[0];if(!o)return{};return{path:o.startsWith("/")?o:`/${o}`}}function yf(e,t,n){let o=[],s=wf(e.url),r=tl(e.mDomain),i=tl(t?.pDomain),a=s.host;if(!a)a=r[0]??i[0]??n,o.push("_moduleInfo.json kh\xF4ng c\xF3 url k\xE8m host \u2014 origin l\u1EA5y t\u1EEB "+`${r.length?"mDomain":i.length?"pDomain":"domain asset m\u1EB7c \u0111\u1ECBnh"}: ${a}. `+"Ch\u1EA1y `module_get` \u0111\u1EC3 c\xF3 url th\u1EADt n\u1EBFu render sai domain.");let c=[...new Set([...s.host?[s.host]:[],...r,...i])];if(c.length>1)o.push(`Project/module ph\u1EE5c v\u1EE5 tr\xEAn nhi\u1EC1u domain (${c.join(", ")}) \u2014 l\u1EA7n render n\xE0y ch\u1EA1y \u1EDF "${a}". `+"v_Set.domainSetting c\u1EE7a domain kh\xE1c KH\xD4NG \u0111\u01B0\u1EE3c \xE1p d\u1EE5ng; mu\u1ED1n th\u1EED domain kh\xE1c th\xEC render module c\xF3 url thu\u1ED9c domain \u0111\xF3.");let l=s.path&&s.path!=="/"?s.path:`/${e.ModuleID}`;return{host:a,basePath:l,notes:o}}function nl(e,t){if(!e)return null;let o=new RegExp(`var\\s+\\$${t}\\s*=\\s*\\{`).exec(e);if(!o)return null;let s=e.indexOf("{",o.index),r=0,i=null;for(let a=s;a<e.length;a+=1){let c=e[a];if(i){if(c==="\\"){a+=1;continue}if(c===i)i=null;continue}if(c==='"'||c==="'"){i=c;continue}if(c==="{")r+=1;else if(c==="}"){if(r-=1,r===0)try{return JSON.parse(e.slice(s,a+1))}catch{return null}}}return null}function bf(e,t){let n={},o={},s=(i,a)=>{if(!j(i))return;for(let[c,l]of Object.entries(i))n[c]=l,o[c]=a};s(e.groupSetting,"$projectGroupSetting"),s(e.projectData,"$projectData"),s(e.moduleSet,"module.json set");let r=n.domainSetting;if(j(r))s(r[t],`domainSetting[${t}]`),delete n.domainSetting,delete o.domainSetting;return{settings:n,origins:o}}async function ol(e){let t=await Me(e),n=[],o=[];for(let s of t){let r=await F(K.join(e,s));if(!r.trim())continue;let{compiled:i,warning:a}=Xc(s.replace(/\.vue$/,""),r);if(n.push(i),a)o.push(a)}return{components:n,warnings:o}}async function kf(e){let t=await Me(e),n=[];for(let o of t){let s=o.replace(/\.vue$/i,""),r=await F(K.join(e,o));if(!r.trim())continue;try{let{template:i,script:a,style:c}=wt(r);n.push({name:s,template:i,script:a,css:c})}catch(i){n.push({name:s,parseError:i.message})}}return n}async function $f(e,t,n){let o=[],s=[],r=[],i={project:[],module:[]},a=[{scope:"project",items:await U(K.join(t,"imports","_imports.json"),[]),baseDir:t,fixHint:"project_sync"},{scope:"module",items:await U(K.join(e,"imports","_imports.json"),[]),baseDir:e,fixHint:"module_get"}];for(let{scope:c,items:l,baseDir:u,fixHint:d}of a){if(!Array.isArray(l))continue;for(let p of l){if(!p||!p.name)continue;let m=`[${c}] ${p.name}`,w=p.type===".css"?"css":p.type===".js"?"js":void 0;if(!w){s.push(`import ${m}: type "${p.type??"(tr\u1ED1ng)"}" kh\xF4ng ph\u1EA3i .js/.css \u2014 kh\xF4ng n\u1EA1p v\xE0o trang render`);continue}if(typeof p.sort!=="number")i[c].push(p.name);o.push({label:m,scope:c,kind:w,sort:typeof p.sort==="number"?p.sort:Zc[c],name:p.name,fileID:p.fileID,contentType:p.contentType,filePath:p.filePath,baseDir:u,domain:n,fixHint:d})}}for(let c of["project","module"]){let l=i[c];if(l.length===0)continue;r.push(`${l.length} import c\u1EA5p ${c} kh\xF4ng c\xF3 FileSort trong _imports.json (${l.join(", ")}) \u2192 t\u1EA1m x\u1EBFp cu\u1ED1i `+`(${Zc[c]}). Th\u1EE9 t\u1EF1 n\u1EA1p c\xF3 th\u1EC3 kh\xE1c production; ch\u1EA1y \`${c==="project"?"project_sync":"module_get"}\` \u0111\u1EC3 l\u1EA5y FileSort th\u1EADt.`)}return{entries:o,problems:s,notes:r}}async function vf(e){let t=[],n=[],o=[],s=[],r=[],i=new Set;for(let a of Kc(e)){let c=a.fileID?`id:${a.fileID}`:`name:${a.scope}:${a.name}`;if(i.has(c))continue;if(a.filePath&&a.baseDir){let p=await F(K.join(a.baseDir,a.filePath));if(p.trim()){i.add(c);let m=wn(a.label);if(a.kind==="css")t.push(`<style data-fui-import="${m}">
|
|
181
|
+
${bt(p,"style")}
|
|
182
|
+
</style>`);else n.push(`<script data-fui-import="${m}">
|
|
183
|
+
${bt(p,"script")}
|
|
184
|
+
</script>`);s.push(`${a.sort}. ${a.label} \u2014 inline ${p.length} bytes`);continue}if(!a.fileID){o.push(`import ${a.label}: file n\u1ED9i b\u1ED9 "${a.filePath}" thi\u1EBFu/r\u1ED7ng v\xE0 kh\xF4ng c\xF3 fileID \u0111\u1EC3 l\u1EA5y b\u1EA3n server \u2192 KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c. `+`Ch\u1EA1y \`${a.fixHint}\`. L\u1ED7i JS/hi\u1EC3n th\u1ECB b\xEAn d\u01B0\u1EDBi c\xF3 th\u1EC3 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3 c\u1EE7a vi\u1EC7c n\xE0y.`);continue}r.push(`${a.label}: ch\u01B0a c\xF3 b\u1EA3n local \u2192 d\xF9ng b\u1EA3n tr\xEAn server. Ch\u1EA1y \`${a.fixHint}\` n\u1EBFu c\u1EA7n s\u1EEDa file n\xE0y.`)}let u=!a.filePath&&(!a.contentType||a.contentType==="link"||a.contentType==="comp-link")?a.name:a.fileID?`//${a.domain}/_aset/fp/auth/moduleImportFile/${a.fileID}${a.kind==="css"?".css":".js"}`:void 0,d=gf(u,a.domain);if(!d){o.push(`import ${a.label}: kh\xF4ng c\xF3 file n\u1ED9i b\u1ED9 v\xE0 t\xEAn kh\xF4ng ph\u1EA3i URL/\u0111\u01B0\u1EDDng d\u1EABn tuy\u1EC7t \u0111\u1ED1i \u2192 kh\xF4ng x\xE1c \u0111\u1ECBnh \u0111\u01B0\u1EE3c ngu\u1ED3n \u0111\u1EC3 n\u1EA1p. `+`N\u1EBFu \u0111\xE2y l\xE0 file t\u1EF1 t\u1EA1o, ch\u1EA1y \`${a.fixHint}\`.`);continue}if(i.has(`url:${d}`))continue;if(i.add(c),i.add(`url:${d}`),a.kind==="css")t.push(`<link href="${wn(d)}" rel="stylesheet" type="text/css" />`);else n.push(`<script src="${wn(d)}" type="text/javascript"></script>`);s.push(`${a.sort}. ${a.label} \u2192 ${d}`)}return{styles:t,scripts:n,problems:o,loaded:s,serverFallbackNotes:r}}function Sf(e,t,n){return`(() => {
|
|
185
|
+
const previewVueData = ${Fo(n,2)};
|
|
186
|
+
const basePath = ${JSON.stringify(t)};
|
|
187
|
+
const moduleId = ${JSON.stringify(e.ModuleID)};
|
|
188
|
+
|
|
189
|
+
document.cookie = 'awt=fui-render-preview; path=/; SameSite=Lax';
|
|
190
|
+
|
|
191
|
+
const syncRouteState = (target) => {
|
|
192
|
+
const currentPath = window.location.pathname || '/';
|
|
193
|
+
let relativePath = currentPath;
|
|
194
|
+
if (basePath !== '/' && currentPath.startsWith(basePath)) {
|
|
195
|
+
relativePath = currentPath.slice(basePath.length) || '/';
|
|
196
|
+
}
|
|
197
|
+
if (!relativePath.startsWith('/')) relativePath = '/' + relativePath;
|
|
198
|
+
const routers = relativePath.split('/');
|
|
199
|
+
Object.assign(target, previewVueData, {
|
|
200
|
+
p_params: window.location.search || '',
|
|
201
|
+
p_routers: routers,
|
|
202
|
+
p_path: relativePath,
|
|
203
|
+
p_render: true,
|
|
204
|
+
p_module_id: moduleId
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const existingDesc = Object.getOwnPropertyDescriptor(window, 'vueData');
|
|
209
|
+
if (!existingDesc || existingDesc.configurable) {
|
|
210
|
+
let vueDataValue = window.vueData || {};
|
|
211
|
+
Object.defineProperty(window, 'vueData', {
|
|
212
|
+
configurable: true,
|
|
213
|
+
get() { return vueDataValue; },
|
|
214
|
+
set(nextValue) {
|
|
215
|
+
vueDataValue = nextValue || {};
|
|
216
|
+
syncRouteState(vueDataValue);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (window.vueData && Object.keys(window.vueData).length) {
|
|
222
|
+
syncRouteState(window.vueData);
|
|
223
|
+
}
|
|
224
|
+
})();`}async function jf(e,t){let n=[],o=await U(K.join(e,"_moduleInfo.json"),{ProjectID:"render-project",ModuleID:K.basename(e)}),s=hn(o.ProjectID),r=await U(K.join(s,"_projectInfo.json"),null),i=await U(K.join(s,"project.json"),null),a=(o.Framework??"V3").toUpperCase()==="V2"?"V2":"V3",c=Mo.replace(/^https:\/\//,""),l=yf(o,r,c),u=l.host,d=`https://${u}`;n.push(...l.notes);let p=await F(K.join(e,"module.json")),m={ModuleName:o.ModuleName??o.ModuleID,data:[],watch:{},controls:[],set:{}};if(p.trim())try{m=JSON.parse(p)}catch(ne){throw Error(`module.json c\xF3 l\u1ED7i JSON syntax: ${ne.message} \u2014 s\u1EEDa tr\u01B0\u1EDBc khi renderUI.`)}else n.push("module.json r\u1ED7ng \u2014 render shell v\u1EDBi module tr\u1ED1ng");let w=await F(K.join(e,"header.html")),y=await F(K.join(e,"body.html")),v=await F(K.join(e,"script.js")),T=await F(K.join(e,"style.css")),I=o.HTMLOnly===!0,g=I?{deps:[],source:"(b\u1ECF qua \u2014 HTMLOnly)",warning:""}:await Vc(a,Mo);if(g.warning)n.push(g.warning);let b=g.deps.map((ne)=>Gc(ne,Mo)).filter((ne)=>Boolean(ne)),R=await $f(e,s,u),C=await vf([...b,...R.entries]);C.problems.unshift(...R.problems),n.push(...R.notes);let S=I?`Framework ${a}: KH\xD4NG nh\xFAng (HTMLOnly=true \u2014 server c\u0169ng kh\xF4ng nh\xFAng)`:`Framework ${a}: danh m\u1EE5c t\u1EEB ${g.source}`;n.push(...C.serverFallbackNotes);let N=await ol(K.join(s,"components"));n.push(...N.warnings);let D=I?{js:"",css:"",jsUrl:"",notes:[],problems:[]}:await zc(o.ProjectID,u),A=Boolean(D.js)&&!t.forceLocalProjectBundle;if(I)n.push("HTMLOnly=true \u2192 render KH\xD4NG nh\xFAng framework "+a+", KH\xD4NG nh\xFAng projectdata/StyleCSS c\u1EA5p project "+"v\xE0 KH\xD4NG d\u1EF1ng #fastProjectAPP \u2014 gi\u1ED1ng h\u1EC7t trang production. Th\u01B0 vi\u1EC7n n\xE0o module c\u1EA7n (Vue, jQuery, Vuetify, "+"lodash\u2026) ph\u1EA3i do CH\xCDNH module khai l\xE0m import; thi\u1EBFu l\xE0 tr\u1EAFng trang th\u1EADt, kh\xF4ng ph\u1EA3i l\u1ED7i c\u1EE7a render.");else if(t.forceLocalProjectBundle)n.push("T\u1EA7ng project d\u1EF1ng t\u1EEB workspace LOCAL (ch\u1EBF \u0111\u1ED9 preview c\u1EA5p component) \u2014 kh\xF4ng d\xF9ng bundle server. "+"Ch\u1EA1y `project_sync` n\u1EBFu nghi file project trong workspace \u0111\xE3 c\u0169.");else if(!A)n.push(...D.problems);else{let ne=await kf(K.join(s,"components")),De=Yc(ne,D.js,D.css??""),Ht=[...De.changed.map((de)=>`${de} (\u0111\xE3 s\u1EEDa local, ch\u01B0a push)`),...De.missing.map((de)=>`${de} (ch\u01B0a c\xF3 tr\xEAn server)`),...De.changedCss.filter((de)=>!De.changed.includes(de)).map((de)=>`${de} (<style> \u0111\xE3 s\u1EEDa local, ch\u01B0a push)`)];if(Ht.length>0)throw Error(`\u26D4 Component c\u1EA5p project l\u1EC7ch v\u1EDBi b\u1EA3n tr\xEAn server: ${Ht.join(", ")}.
|
|
225
|
+
|
|
226
|
+
`+"renderUI d\u1EF1ng trang b\u1EB1ng bundle project T\u1EA2I T\u1EEA SERVER (_aset/.../projectdata) n\xEAn b\u1EA3n local ch\u01B0a push "+`kh\xF4ng xu\u1EA5t hi\u1EC7n \u2014 n\u1EBFu v\u1EABn render, t\u1EA5m \u1EA3nh b\u1EA1n nh\u1EADn \u0111\u01B0\u1EE3c l\xE0 B\u1EA2N C\u0168, kh\xF4ng ph\u1EA3i b\u1EA3n v\u1EEBa s\u1EEDa.
|
|
227
|
+
|
|
228
|
+
`+"\u2022 N\u1EBFu b\u1EA3n local l\xE0 b\u1EA3n b\u1EA1n v\u1EEBa s\u1EEDa \u2192 `component_preview` \u0111\u1EC3 ki\u1EC3m ch\u1EE9ng ri\xEAng component (ch\u1EA1y \u0111\u01B0\u1EE3c b\u1EB1ng "+"file local, kh\xF4ng c\u1EA7n push), r\u1ED3i `component_update` t\u1EEBng component, r\u1ED3i render l\u1EA1i.\n"+"\u2022 N\u1EBFu b\u1EA3n tr\xEAn server m\u1EDBi h\u01A1n (ai \u0111\xF3 s\u1EEDa tr\xEAn web IDE) \u2192 `project_sync` \u0111\u1EC3 l\u1EA5y v\u1EC1, \u0110\u1EEANG push \u0111\xE8.");if(De.unverifiable.length>0)n.push(`Kh\xF4ng \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c component c\u1EA5p project: ${De.unverifiable.join(", ")}. `+"Kh\xF4ng ch\u1EB7n render, nh\u01B0ng n\u1EBFu giao di\u1EC7n kh\xE1c mong \u0111\u1EE3i th\xEC \u0111\xE2y l\xE0 ch\u1ED7 \u0111\xE1ng nghi \u0111\u1EA7u ti\xEAn.");let Re=N.components.filter((de)=>de.css.trim()).map((de)=>de.name);if(Re.length>0&&!(D.css??"").trim())n.push(`StyleCSS c\u1EA5p project t\u1EA3i v\u1EC1 R\u1ED6NG nh\u01B0ng component local c\xF3 khai <style>: ${Re.join(", ")}. `+"Ho\u1EB7c c\xE1c component \u0111\xF3 ch\u01B0a push (`component_update`), ho\u1EB7c route StyleCSS \u0111ang h\u1ECFng \u2014 "+"d\xF9 nguy\xEAn nh\xE2n n\xE0o th\xEC style c\u1EA5p project trong l\u1EA7n render n\xE0y c\u0169ng \u0111ang thi\u1EBFu.")}let G=await ol(K.join(e,"components"));n.push(...G.warnings);let _=j(i?.data)?i.data:i??{},le={..._,ProjectName:_.ProjectName??r?.ProjectName??o.ProjectID,title:_.title??o.mTitle??o.ModuleName??o.ModuleID},Ie=nl(D.js,"projectGroupSetting"),B=nl(D.js,"projectData");if(A&&!B)n.push("Kh\xF4ng b\xF3c \u0111\u01B0\u1EE3c `var $projectData` kh\u1ECFi bundle projectdata t\u1EA3i t\u1EEB server \u2014 apiDomain/userInfo "+"trong l\u1EA7n render n\xE0y suy ra t\u1EEB project.json local, c\xF3 th\u1EC3 l\u1EC7ch v\u1EDBi b\u1EA3n trang th\u1EADt \u0111ang ch\u1EA1y.");let ae=String(_.apiDomain??""),ge=typeof B?.apiDomain==="string"?B.apiDomain:"";if(B&&ae&&ge&&ae!==ge)n.push(`project.json local c\xF3 apiDomain "${ae}" nh\u01B0ng b\u1EA3n tr\xEAn server l\xE0 "${ge}" \u2014 `+"render d\xF9ng b\u1EA3n server (gi\u1ED1ng trang th\u1EADt). Ch\u1EA1y `project_sync` \u0111\u1EC3 \u0111\u1ED3ng b\u1ED9 file local.");let We=j(m.set)?m.set:null,Ye=bf({groupSetting:Ie,projectData:B??_,moduleSet:We},u),Ce=String(Ye.settings.apiDomain??""),$t=Ye.origins.apiDomain??"(kh\xF4ng t\u1EA7ng n\xE0o khai)",qe=(()=>{let ne=typeof Ye.settings.userInfo==="string"?Ye.settings.userInfo.trim():"";if(!ne)return"";return/^https?:\/\//i.test(ne)?ne:`${Ce}${ne}`})();if(!Ce)n.push(`Kh\xF4ng t\u1EA7ng n\xE0o ($projectGroupSetting \u2192 $projectData \u2192 module.json set \u2192 domainSetting["${u}"]) khai apiDomain cho "${o.ProjectID}" \u2014 API s\u1EBD g\u1ECDi b\u1EB1ng \u0111\u01B0\u1EDDng d\u1EABn t\u01B0\u01A1ng \u0111\u1ED1i v\xE0 apiMocks kh\u1EDBp theo URL `+"g\u1EA7n nh\u01B0 ch\u1EAFc ch\u1EAFn tr\u01B0\u1EE3t. Ch\u1EA1y `project_sync` n\u1EBFu d\u1EEF li\u1EC7u project \u0111\xE3 c\u0169.");let Qe={},vt=l.basePath,Y=t.user??{},Yn=!("SystemRight"in Y),yn=!("FunctionRight"in Y),Bt=Mt([p,JSON.stringify(_),w,y,v]),Qn={user:{UserID:"render-preview",FirstName:"Render",LastName:"Preview",Email:"render@preview.local",SystemRight:9,FunctionRight:Bt,...Y},...t.vueData??{}},Zn=yn?Wn(Bt,(await Ot(s)).functionRights):null,eo=(()=>{let ne=[];if(Yn)ne.push("SystemRight=9 (m\u1EB7c \u0111\u1ECBnh, quy\u1EC1n cao nh\u1EA5t)");if(yn)ne.push(Bt.length?`FunctionRight=[${Bt.join(", ")}] (m\u1EB7c \u0111\u1ECBnh \u2014 qu\xE9t \u0111\u01B0\u1EE3c t\u1EEB module.json/project)`:"FunctionRight=[] (m\u1EB7c \u0111\u1ECBnh \u2014 kh\xF4ng qu\xE9t \u0111\u01B0\u1EE3c m\xE3 n\xE0o)");return ne.length?`user preview: ${ne.join(" \xB7 ")}. rightTest()/v-if theo FunctionRight kh\xE1c m\xE3 tr\xEAn s\u1EBD \u1EA9n \u2014 truy\u1EC1n tham s\u1ED1 \`user\` \u0111\u1EC3 test vai tr\xF2 h\u1EA1n ch\u1EBF h\u01A1n.`:null})(),Je=wn(o.mTitle||o.ModuleName||o.ModuleID),te=I?"":A?D.css??"":gs(N.components),Le=[T,gs(G.components)].filter((ne)=>ne.trim()).join(`
|
|
229
|
+
`),bn=I?"":A?`<script data-fui-projectdata="${wn(D.jsUrl)}">
|
|
230
|
+
${bt(D.js??"","script")}
|
|
231
|
+
</script>`:`<script data-fui-projectdata="local">
|
|
232
|
+
var $projectData = ${Fo(le,2)};
|
|
233
|
+
${bt(hs(N.components),"script")}
|
|
234
|
+
</script>`,to=`<script data-fui-moduledata="local">
|
|
235
|
+
var $moduleUI = ${Fo(m,2)};
|
|
236
|
+
${bt(v,"script")}
|
|
237
|
+
${bt(hs(G.components),"script")}
|
|
238
|
+
</script>`;return{html:`<!DOCTYPE html>
|
|
239
|
+
<html>
|
|
240
|
+
<head>
|
|
241
|
+
<meta charset="UTF-8" />
|
|
242
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
|
|
243
|
+
<title>${Je}</title>
|
|
244
|
+
${C.styles.join(`
|
|
245
|
+
`)}
|
|
246
|
+
${w}
|
|
247
|
+
${te.trim()?`<style data-fui-project-css>
|
|
248
|
+
${bt(te,"style")}
|
|
249
|
+
</style>`:""}
|
|
250
|
+
${Le.trim()?`<style data-fui-module-css>
|
|
251
|
+
${bt(Le,"style")}
|
|
252
|
+
</style>`:""}
|
|
253
|
+
<script>
|
|
254
|
+
var urlReferrer = '';
|
|
255
|
+
var ProjectID = ${JSON.stringify(o.ProjectID)};
|
|
256
|
+
var ModuleID = ${JSON.stringify(o.ModuleID)};
|
|
257
|
+
var Framework = ${JSON.stringify(a)};
|
|
258
|
+
</script>
|
|
259
|
+
<script>
|
|
260
|
+
${Sf(o,vt,Qn)}
|
|
261
|
+
</script>
|
|
262
|
+
</head>
|
|
263
|
+
<body>
|
|
264
|
+
${I?"":`<div id="fastProjectAPP">
|
|
265
|
+
<div v-if="v_Loading" class="loader-wrapper">
|
|
266
|
+
<div class="loader"></div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>`}
|
|
269
|
+
${y}
|
|
270
|
+
${C.scripts.join(`
|
|
271
|
+
`)}
|
|
272
|
+
${I?"":`<script>
|
|
273
|
+
var $projectGroupSetting = ${Fo(Qe,2)};
|
|
274
|
+
</script>`}
|
|
275
|
+
${bn}
|
|
276
|
+
${to}
|
|
277
|
+
</body>
|
|
278
|
+
</html>`,moduleInfo:o,basePath:vt,origin:d,apiDomain:Ce,apiDomainOrigin:$t,userInfoUrl:qe,previewUser:Qn.user,importsLoaded:C.loaded,importProblems:C.problems,frameworkNote:S,htmlOnly:I,projectBundleNotes:I?["projectdata/StyleCSS: KH\xD4NG nh\xFAng (HTMLOnly=true \u2014 server c\u0169ng kh\xF4ng nh\xFAng)"]:A?D.notes:[...t.forceLocalProjectBundle?[]:D.notes,t.forceLocalProjectBundle?"projectdata/StyleCSS: d\u1EF1ng t\u1EEB workspace local (ch\u1EBF \u0111\u1ED9 preview c\u1EA5p component \u2014 b\u1EA3n local ch\u01B0a push v\u1EABn hi\u1EC7n)":"projectdata/StyleCSS: d\u1EF1ng t\u1EEB workspace local (kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c b\u1EA3n server)"],rightDefaultNote:eo,undefinedRightsNote:Zn,warnings:n}}function If(e,t,n){let o=[t,n];for(let s of o){let r=Object.keys(e).find((i)=>i===s);if(r??=Object.keys(e).find((i)=>s.endsWith(i)||i.endsWith(s)),r??=Object.keys(e).find((i)=>s.includes(i)||i.includes(s)),r!==void 0)return{key:r,value:e[r]}}return}var Oo={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS","Access-Control-Allow-Headers":"*"};function Cf(e){if(!e)return null;try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return null}}async function Rf(e,t,n,o,s,r,i,a){if(!o||n!==o)return null;let c=Vn(s);if(!c)return null;let l=await Mc(t,c.alias);if(!l)return null;let u=await Fc(t,l,c.fnName);if(!u)return null;let d=`${l}:${u}`,p=r.get(d);if(p===void 0)p=await Lc(t,l,u),r.set(d,p);if(p==="safe-read"&&i.count<Lt){i.count++;try{let y;if(!c.isAuth){let{config:T}=await Oc(t,{db:l});if(T.userToken)y={...await e.request().allHeaders(),authorization:`Bearer ${T.userToken}`}}let v=await e.fetch({timeout:xo,headers:y});if(v.ok()){let T=await v.json().catch(()=>null);if(T!==null)return await ps(t,l,u,"live",T),a.live++,{payload:T,note:`g\u1ECDi tAPI th\u1EADt (SP ${u}, ${l})`}}}catch{}}let m=await Uc(t,l,u);if(m)return a.cache++,{payload:m.data,note:`_testdata cache (SP ${u}, ${l}, ngu\u1ED3n: ${m.source})`};let w=await Bc(t,l,u);if(w)return await ps(t,l,u,"generated",w),a.generated++,{payload:w,note:`t\u1EF1 sinh mock (SP ${u}, ${l}), \u0111\xE3 l\u01B0u cache`};return null}async function Lo(e,t){let{html:n,moduleInfo:o,basePath:s,origin:r,apiDomain:i,apiDomainOrigin:a,userInfoUrl:c,previewUser:l,importsLoaded:u,importProblems:d,frameworkNote:p,htmlOnly:m,projectBundleNotes:w,rightDefaultNote:y,undefinedRightsNote:v,warnings:T}=await jf(e,t),I=await uf(),g=[],b=new Set(T),R=new Set,C=[],S=[],N=[],D=[],A={revalidated:0,updated:0,fetched:0,memo:0,stale:0},G=new Set,_=new Set,le=!1,Ie=new Map,B={count:0},ae=Cf(i),ge={live:0,cache:0,generated:0},We=t.urlParams&&Object.keys(t.urlParams).length?"?"+new URLSearchParams(t.urlParams).toString():"",Ye=`${r}${s}${We}`,Ce=(()=>{try{return c?new URL(c).pathname:""}catch{return""}})(),$t;try{$t=await I.chromium.launch({headless:!0})}catch(qe){let Qe=qe.message;if(/executable doesn't exist|Executable doesn't exist|browserType.launch/i.test(Qe))throw Error(`Kh\xF4ng launch \u0111\u01B0\u1EE3c Chromium: ${Qe}
|
|
279
|
+
\u2192 Ch\u1EA1y m\u1ED9t l\u1EA7n: npx playwright install chromium`);throw qe}try{let qe=t.device??"desktop",Qe=nf[qe],vt=t.viewport??Qe.viewport,Y=await $t.newPage({viewport:vt,isMobile:Qe.isMobile,hasTouch:Qe.hasTouch,userAgent:Qe.userAgent,deviceScaleFactor:1}),Yn=Math.min(Math.max(t.settleMs??Zm,100),5000);Y.on("pageerror",(P)=>{C.push(String(P?.message??P))}),Y.on("console",(P)=>{let W=P.type(),q=P.text();if(W==="error"){if(!/Failed to load resource/i.test(q))C.push(q)}else if(W==="warning"&&/\[Vue warn\]|\[Vuetify\]/i.test(q))S.push(q)}),await Y.route("**/*",async(P)=>{let W=P.request(),q=W.url(),fe=W.method(),x=W.resourceType();if(W.isNavigationRequest()){if(q.startsWith(r))await P.fulfill({status:200,contentType:"text/html; charset=utf-8",body:n});else b.add(`runtime \u0111i\u1EC1u h\u01B0\u1EDBng ra "${q}" \u2014 \u0111\xE3 ch\u1EB7n (th\u01B0\u1EDDng do login gate / redirect trong action)`),await P.fulfill({status:200,contentType:"text/html; charset=utf-8",body:`<html><body>[fui-render] navigation blocked: ${wn(q)}</body></html>`});return}let M;try{M=new URL(q)}catch{await P.fulfill({status:400,contentType:"text/plain",body:""});return}if(fe==="OPTIONS"){await P.fulfill({status:204,headers:Oo,body:""});return}if(M.pathname==="/favicon.ico"){await P.fulfill({status:204,body:""});return}let we=K.extname(M.pathname).toLowerCase();if(ef.has(x)||fe==="GET"&&tf.has(we)){try{let V=await hf(q,{refresh:t.refreshAssets,offline:t.offline});if(A[V.origin]++,V.origin==="stale")G.add(`${x} "${q}" \u2014 ${V.note}`);else if(V.changed)_.add(`${q} \u2014 ${V.note}`);await P.fulfill({status:200,contentType:V.contentType,body:V.body,headers:Oo})}catch(V){let be=`${x} "${q}" t\u1EA3i th\u1EA5t b\u1EA1i (${V.message})`;if(x==="script"||x==="stylesheet")R.add(be);else b.add(`${be} \u2014 tr\u1EA3 404 (offline + ch\u01B0a cache?)`);await P.fulfill({status:404,contentType:"text/plain",body:""})}return}N.push(`${fe} ${q}`);let ye=If(t.apiMocks,q,M.pathname),Z;if(ye)Z=ye.value,g.push(`API ${fe} ${M.pathname} \u2192 mock "${ye.key}"`);else if(Ce&&(q===c||M.pathname===Ce))Z=l,g.push(`API ${fe} ${M.pathname} \u2192 user preview (userInfo c\u1EE7a project, auto-mock)`);else{let V=await Rf(P,o.ProjectID,M.hostname,ae,M.pathname,Ie,B,ge);if(V)Z=V.payload,g.push(`API ${fe} ${M.pathname} \u2192 ${V.note}`);else Z={data:[]},b.add(`kh\xF4ng c\xF3 mock cho API "${q}" \u2014 tr\u1EA3 {data: []}`),g.push(`API ${fe} ${M.pathname} \u2192 {data:[]} (kh\xF4ng c\xF3 mock)`)}if(j(Z)&&Z.__error!==void 0){let V=j(Z.__error)?Z.__error:{};await P.fulfill({status:Number(V.status??500),contentType:"application/json; charset=utf-8",headers:Oo,body:JSON.stringify(V.responseJSON??{Message:"Simulated error"})});return}await P.fulfill({status:200,contentType:"application/json; charset=utf-8",headers:Oo,body:JSON.stringify(Z)})});let yn=Y;if(typeof yn.routeWebSocket==="function")await yn.routeWebSocket("**/*",(P)=>{b.add("module m\u1EDF WebSocket t\u1EDBi backend th\u1EADt \u2014 \u0111\xE3 ch\u1EB7n (renderUI ch\u1EC9 cho ph\xE9p g\u1ECDi th\u1EADt qua tAPI SP \u0111\xE3 x\xE1c nh\u1EADn an to\xE0n, kh\xF4ng qua WebSocket)"),P.close()});g.push("\u2500\u2500 INIT \u2500\u2500");let Bt=Date.now();if(await Y.goto(Ye,{waitUntil:"networkidle",timeout:Qm}),!m)try{await Y.waitForFunction("!!window.vm || !!document.querySelector('#mainContainer, #fastProjectAPP .v-application')",void 0,{timeout:15000})}catch{b.add("Vue ch\u01B0a mount sau 15s \u2014 trang ch\u01B0a init xong (xem JS error / asset h\u1ECFng / API treo b\xEAn d\u01B0\u1EDBi)")}await Y.waitForTimeout(Yn);let Qn=[A.revalidated?`${A.revalidated} revalidate 304`:"",A.updated?`${A.updated} C\u1EACP NH\u1EACT`:"",A.fetched?`${A.fetched} t\u1EA3i m\u1EDBi`:"",A.memo?`${A.memo} memo`:"",A.stale?`${A.stale} STALE`:""].filter(Boolean).join(" + ")||"0",Zn=[ge.live?`${ge.live} g\u1ECDi th\u1EADt`:"",ge.cache?`${ge.cache} cache`:"",ge.generated?`${ge.generated} t\u1EF1 sinh`:""].filter(Boolean).join(" + ");g.push(`trang load xong sau ${((Date.now()-Bt)/1000).toFixed(1)}s \xB7 asset: ${Qn} \xB7 ${N.length} API call${Zn?` (API kh\xF4ng-mock: ${Zn})`:""}`);let eo=C.length,Je=K.join(e,"_preview");await at.mkdir(Je,{recursive:!0});for(let P=0;P<t.scenario.length;P++){let W=t.scenario[P],q=P+1,fe=C.length;try{if(j(W.set))g.push("",`\u2500\u2500 STEP ${q}: set ${Object.keys(W.set).join(", ")} \u2500\u2500`),await Y.evaluate((x)=>{let M=window,we=(Z,V,be)=>{if(M.Vue&&typeof M.Vue.set==="function")M.Vue.set(Z,V,be);else Z[V]=be},ye=(Z,V,be)=>{let xe=String(V).match(/[^.[\]']+/g)||[V],J=Z;for(let Q=0;Q<xe.length-1;Q++){if(J[xe[Q]]==null)we(J,xe[Q],/^\d+$/.test(xe[Q+1])?[]:{});J=J[xe[Q]]}we(J,xe[xe.length-1],be)};for(let[Z,V]of x)ye(M.vueData,Z,V)},Object.entries(W.set));else if(typeof W.click==="string"){g.push("",`\u2500\u2500 STEP ${q}: click "${W.click}" \u2500\u2500`);let x=W.click,M=typeof W.nth==="number"?W.nth-1:0,we=Y.locator(".v-btn, button, [role='button'], .v-list-item, .v-tab, a",{hasText:x}),ye=await we.count();if(ye===0)we=Y.locator(`text=${x}`),ye=await we.count();if(ye===0)b.add(`STEP ${q}: kh\xF4ng t\xECm th\u1EA5y ph\u1EA7n t\u1EED c\xF3 text "${x}" tr\xEAn DOM`),le=!0;else{if(ye>1)g.push(`(kh\u1EDBp ${ye} ph\u1EA7n t\u1EED \u2014 d\xF9ng #${M+1})`);await we.nth(Math.min(M,ye-1)).click({timeout:ws}),await Y.waitForTimeout(350);let Z=Y.locator(".jconfirm .jconfirm-buttons button"),V=await Z.count();if(V>0){let be=W.cancelConfirm===!0,xe=!1;for(let J=0;J<V;J++){let Q=(await Z.nth(J).innerText()).trim().toLowerCase(),oe=/h\u1EE7y|hu\u1EF7|cancel|kh\u00F4ng|no|\u0111\u00F3ng/.test(Q);if(be===oe){g.push(`CONFIRM \u2192 b\u1EA5m "${Q}"`),await Z.nth(J).click({timeout:ws}),xe=!0;break}}if(!xe)g.push(`CONFIRM \u2192 b\u1EA5m n\xFAt ${be?"cu\u1ED1i":"\u0111\u1EA7u"} (kh\xF4ng nh\u1EADn di\u1EC7n \u0111\u01B0\u1EE3c label)`),await Z.nth(be?V-1:0).click({timeout:ws})}}}else if(typeof W.call==="string")g.push("",`\u2500\u2500 STEP ${q}: call ${W.call} \u2500\u2500`),await Y.evaluate((x)=>{let M=window;if(typeof M.CALL!=="function")throw Error("global CALL kh\xF4ng t\u1ED3n t\u1EA1i trong runtime n\xE0y");let we=M.vueData&&M.vueData[x]!==void 0?M.vueData[x]:x;M.CALL(we)},W.call);else if(typeof W.exe==="string")g.push("",`\u2500\u2500 STEP ${q}: exe \u2500\u2500`),await Y.evaluate((x)=>{Function(x)()},W.exe);else if(typeof W.assert==="string"){let x=typeof W.label==="string"?W.label:W.assert,M=!1;try{M=await Y.evaluate((we)=>{try{return!!Function("with(window.vueData||{}){return ("+we+")}")()}catch{return!1}},W.assert)}catch{M=!1}D.push({label:x,ok:M}),g.push("",`\u2500\u2500 STEP ${q}: assert ${M?"\u2714":"\u2718"} ${x} \u2500\u2500`);continue}else{b.add(`event ${q} kh\xF4ng h\u1EE3p l\u1EC7 \u2014 c\u1EA7n m\u1ED9t trong: set / click / call / exe / assert`);continue}await Y.waitForTimeout(Yn);for(let x=fe;x<C.length;x++)g.push(`\u2718 pageerror: ${C[x]}`);if(t.screenshotSteps){let x=K.join(Je,`render-step-${q}.png`);await Y.screenshot({path:x}),g.push(`screenshot: ${x}`)}}catch(x){le=!0,g.push(`\u2718 STEP ${q} l\u1ED7i: ${x.message.split(`
|
|
280
|
+
`)[0]}`)}}let te=await Y.evaluate(()=>{let P=document.querySelector("#fastProjectAPP"),W=P?P.children.length:Array.from(document.body.children).filter((J)=>!/^(SCRIPT|STYLE|LINK|TEMPLATE|NOSCRIPT)$/.test(J.tagName)).length,q=window.innerWidth,fe=document.documentElement.scrollWidth>q+4,x=[],M=(J)=>J&&typeof J.className==="string"?J.className.trim().split(/\s+/).slice(0,2).join("."):"";if(fe)for(let J of document.body.querySelectorAll("*")){let Q=J.getBoundingClientRect();if(Q.width>0&&Q.right>q+4&&x.length<5){let oe=M(J);x.push(J.tagName.toLowerCase()+(oe?"."+oe:"")+" (right="+Math.round(Q.right)+"px)")}}let we=document.body.innerText||"",ye=[];for(let J of["undefined","NaN","[object Object]"])if(we.includes(J))ye.push(J);let Z=[];for(let J of document.querySelectorAll("img"))if(J.complete&&J.naturalWidth===0&&J.getBoundingClientRect().width>0&&Z.length<5)Z.push((J.getAttribute("src")||"(no src)").slice(0,120));let V=[];for(let J of document.querySelectorAll("canvas")){let Q=J.getBoundingClientRect();if((Q.width===0||Q.height===0)&&V.length<5){let oe=J.parentElement,re=M(oe);V.push("canvas trong "+(oe?oe.tagName.toLowerCase():"?")+(re?"."+re:"")+" ("+Math.round(Q.width)+"\xD7"+Math.round(Q.height)+")")}}let be=[];{let J=(oe)=>{let re="";for(let Ve of oe.childNodes)if(Ve.nodeType===Node.TEXT_NODE)re+=Ve.textContent||"";return re.trim()},Q=[];for(let oe of document.body.querySelectorAll("*")){if(Q.length>=600)break;let re=J(oe);if(!re)continue;let Ve=oe.getBoundingClientRect();if(Ve.width<=0||Ve.height<=0)continue;Q.push({el:oe,rect:Ve,text:re.slice(0,24)})}Q.sort((oe,re)=>oe.rect.top-re.rect.top||oe.rect.left-re.rect.left);e:for(let oe=0;oe<Q.length;oe++){let re=Q[oe];for(let Ve=oe+1;Ve<Q.length;Ve++){let Ke=Q[Ve];if(Ke.rect.top>re.rect.bottom+4)break;if(re.el.contains(Ke.el)||Ke.el.contains(re.el))continue;let fu=re.rect.top+re.rect.height/2,hu=Ke.rect.top+Ke.rect.height/2;if(Math.abs(fu-hu)>Math.min(re.rect.height,Ke.rect.height)*0.6)continue;let Vo=re.rect.left<=Ke.rect.left?Ke.rect.left-re.rect.right:re.rect.left-Ke.rect.right;if(Vo<2){let gu=Vo<0?"ch\u1ED3ng l\u1EA5n":`d\xEDnh s\xE1t (gap ${Math.round(Vo)}px)`;if(be.push(`${gu}: "${re.text}" \u2194 "${Ke.text}"`),be.length>=6)break e}}}}let xe=!!document.querySelector(".v-dialog--active, .v-overlay--active");return{appChildCount:W,overflowX:fe,overflowers:x,suspectTexts:ye,brokenImages:Z,deadCanvas:V,textCollisions:be,dialogOpen:xe}}),Le=[];if(te.appChildCount===0)Le.push((m?"\u2718 <body> kh\xF4ng render \u0111\u01B0\u1EE3c ph\u1EA7n t\u1EED n\xE0o (HTMLOnly \u2014 body.html L\xC0 c\u1EA3 trang)":"\u2718 #fastProjectAPP r\u1ED7ng \u2014 controls kh\xF4ng render")+(d.length>0||R.size>0?" (C\xD3 file to\xE0n c\u1EE5c n\u1EA1p thi\u1EBFu \u2014 xem m\u1EE5c M\xD4I TR\u01AF\u1EDCNG THI\u1EBEU FILE tr\u01B0\u1EDBc khi nghi module.json)":" (ki\u1EC3m tra pageerror / module.json)"));if(te.overflowX)Le.push(`\u26A0 trang tr\xE0n ngang viewport ${vt.width}px \u2014 ph\u1EA7n t\u1EED tr\xE0n: ${te.overflowers.join(", ")||"(kh\xF4ng x\xE1c \u0111\u1ECBnh)"}`);if(te.suspectTexts.length>0)Le.push(`\u26A0 text nghi v\u1EA5n hi\u1EC3n th\u1ECB tr\xEAn trang: ${te.suspectTexts.map((P)=>`"${P}"`).join(", ")} (binding thi\u1EBFu data?)`);if(te.brokenImages.length>0)Le.push(`\u26A0 \u1EA3nh v\u1EE1 (load fail nh\u01B0ng \u0111ang chi\u1EBFm ch\u1ED7): ${te.brokenImages.join(", ")}`);if(te.deadCanvas.length>0)Le.push(`\u26A0 canvas chart 0-size \u2014 chart init khi container ch\u01B0a c\xF3 k\xEDch th\u01B0\u1EDBc: ${te.deadCanvas.join(", ")}`);if(te.textCollisions.length>0)Le.push(`\u26A0 text d\xEDnh/ch\u1ED3ng l\u1EA5n (th\u1EA9m m\u1EF9 \u2014 PH\u1EA2I s\u1EEDa, kh\xF4ng \u0111\u01B0\u1EE3c b\u1ECF qua): ${te.textCollisions.join("; ")}`);let bn=K.join(Je,"render.png"),to=await Y.screenshot({path:bn}),kn,ne="image/png";if(to.length<=Qc)kn=to.toString("base64");else try{let P=await Y.screenshot({type:"jpeg",quality:60});if(P.length<=Qc)kn=P.toString("base64"),ne="image/jpeg"}catch{}try{await Y.screenshot({path:K.join(Je,"render-full.png"),fullPage:!0})}catch{}let De;if(te.dialogOpen)try{De=K.join(Je,"render-dialog.jpg"),await Y.locator(".v-dialog--active, .v-overlay--active").first().screenshot({path:De,type:"jpeg",quality:60,timeout:3000})}catch{De=void 0}let Ht=[];try{Ht=await Y.evaluate(()=>{let P=new Set(["p_domain","p_routers","p_params","p_urlReferrer","p_router","p_path","p_render","p_module_id","v_Loading","v_LoadingOverlay","v_containerOn","user","v_Set"]),W=[],q=window.vueData||{};for(let fe of Object.keys(q)){if(P.has(fe)||W.length>=40)continue;let x=q[fe];if(x&&typeof x==="object"&&!Array.isArray(x)&&(x.API!==void 0||x.CALL!==void 0||x.EXE!==void 0||x.FUN!==void 0||x.IF!==void 0))continue;let M;try{M=JSON.stringify(x)}catch{M=String(x)}if(M===void 0)M="undefined";if(M.length>120)M=M.slice(0,120)+"\u2026";W.push(fe+" = "+M)}return W})}catch{}let Re={ts:new Date().toISOString(),jsErrors:C.length,initErrors:eo,vueWarns:S.length,envMissing:d.length+R.size,appEmpty:te.appChildCount===0,overflowX:te.overflowX,suspectTexts:te.suspectTexts.length,brokenImages:te.brokenImages.length,deadCanvas:te.deadCanvas.length,textCollisions:te.textCollisions.length,assertFail:D.filter((P)=>!P.ok).length},de=await rf(Je,qe),pu=de!==null&&ks(Re)&&il(de,Re).length===0;Re.failStreak=!ks(Re)?0:pu?(de?.failStreak??1)+1:1,await at.writeFile(sl(Je,qe),JSON.stringify(Re,null,1),"utf8").catch(()=>{return});let Ds=[...new Set(C)],xs=[...S],Os=lf(Re,xs,Ds),L=[];if(L.push(t.reportLabel?`RENDER (browser th\u1EADt): ${t.reportLabel}`:`RENDER (browser th\u1EADt): ${e}`),L.push(...cf(Re,Os,Ds,xs)),de)L.push(sf(de,Re));if((Re.failStreak??0)>=of)L.push(...af(Re.failStreak,Os));L.push(""),L.push(`Framework ${o.Framework??"V3"}${m?" \xB7 HTMLOnly=true (KH\xD4NG nh\xFAng framework/projectdata \u2014 nh\u01B0 production)":""} \xB7 viewport ${vt.width}\xD7${vt.height}${qe!=="desktop"?` (${qe})`:""} \xB7 CDN ${Mo}`),L.push(`Origin trang: ${r} \xB7 apiDomain: ${i||"(tr\u1ED1ng)"} [${a}]${c?` \xB7 userInfo auto-mock: ${c}`:""}`),L.push(p);for(let P of w)L.push(` \u2713 ${P}`);L.push(`Import (framework \u222A project \u222A module, sort theo FileSort/FileName): ${u.length} n\u1EA1p \u0111\u01B0\u1EE3c${d.length?`, ${d.length} KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c`:""}`);for(let P of u)L.push(` \u2713 ${P}`);if(y)L.push(`\u2139 ${y}`);if(v)L.push(v);L.push(""),L.push(...g),L.push(""),L.push("\u2550\u2550\u2550 K\u1EBET QU\u1EA2 \u2550\u2550\u2550");for(let P of D)L.push(`${P.ok?"\u2714":"\u2718"} assert: ${P.label}`);if(d.length>0||R.size>0){L.push("","\u2500\u2500 \u26D4 M\xD4I TR\u01AF\u1EDCNG THI\u1EBEU FILE (\u0111\u1ECDc TR\u01AF\u1EDAC khi s\u1EEDa module) \u2500\u2500","C\xE1c file d\u01B0\u1EDBi \u0111\xE2y kh\xF4ng n\u1EA1p \u0111\u01B0\u1EE3c v\xE0o trang test. H\xE0m/bi\u1EBFn/style to\xE0n c\u1EE5c do ch\xFAng","\u0111\u1ECBnh ngh\u0129a s\u1EBD kh\xF4ng t\u1ED3n t\u1EA1i \u2192 l\u1ED7i JS v\xE0 l\u1ED7i hi\u1EC3n th\u1ECB b\xEAn d\u01B0\u1EDBi C\xD3 TH\u1EC2 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3,","kh\xF4ng ph\u1EA3i l\u1ED7i c\u1EE7a module. Kh\u1EAFc ph\u1EE5c ph\u1EA7n n\xE0y tr\u01B0\u1EDBc r\u1ED3i render l\u1EA1i.");for(let P of d)L.push(`\u26D4 ${P}`);for(let P of R)L.push(`\u26D4 ${P}`)}if(G.size>0){L.push("","\u2500\u2500 \u26D4 ASSET C\u0168, KH\xD4NG X\xC1C MINH \u0110\u01AF\u1EE2C (\u0111\u1ECDc TR\u01AF\u1EDAC khi k\u1EBFt lu\u1EADn v\u1EC1 platform) \u2500\u2500","Kh\xF4ng h\u1ECFi \u0111\u01B0\u1EE3c server xem c\xE1c file d\u01B0\u1EDBi \u0111\xE2y c\xF2n \u0111\xFAng kh\xF4ng, n\xEAn trang test \u0111ang ch\u1EA1y",'b\u1EB1ng b\u1EA3n tr\xEAn \u0111\u0129a. M\u1ECDi k\u1EBFt lu\u1EADn ki\u1EC3u "th\u01B0 vi\u1EC7n X c\xF3 bug" r\xFAt ra t\u1EEB l\u01B0\u1EE3t render n\xE0y',"\u0111\u1EC1u KH\xD4NG d\xF9ng \u0111\u01B0\u1EE3c \u2014 b\u1EA3n th\u1EADt tr\xEAn CDN c\xF3 th\u1EC3 \u0111\xE3 kh\xE1c. N\u1ED1i m\u1EA1ng r\u1ED3i render l\u1EA1i,","ho\u1EB7c \xE9p t\u1EA3i m\u1EDBi b\u1EB1ng refreshAssets: true.");for(let P of G)L.push(`\u26D4 ${P}`)}if(_.size>0){L.push("","\u2500\u2500 \u2139\uFE0F ASSET PLATFORM \u0110\xC3 \u0110\u1ED4I so v\u1EDBi l\u01B0\u1EE3t render tr\u01B0\u1EDBc \u2500\u2500","H\xE0nh vi \u0111\u1ED5i m\xE0 module.json kh\xF4ng \u0111\u1EE5ng t\u1EDBi c\xF3 th\u1EC3 \u0111\u1EBFn t\u1EEB \u0111\xE2y, kh\xF4ng ph\u1EA3i t\u1EEB module.");for(let P of _)L.push(`\u2139\uFE0F ${P}`)}if(C.length>0){L.push("","\u2500\u2500 JS/Vue errors \u2500\u2500");for(let P of[...new Set(C)].slice(0,20))L.push(`\u2718 ${P.split(`
|
|
281
|
+
`)[0].slice(0,300)}`)}if(S.length>0){L.push("","\u2500\u2500 [Vue warn] \u2500\u2500");for(let P of[...new Set(S)].slice(0,15))L.push(`\u26A0 ${P.split(`
|
|
282
|
+
`)[0].slice(0,300)}`)}if(Le.length>0)L.push("","\u2500\u2500 DOM AUDIT \u2500\u2500",...Le);if(b.size>0){L.push("","\u2500\u2500 C\u1EA3nh b\xE1o \u2500\u2500");for(let P of b)L.push(`\u26A0 ${P}`)}if(Ht.length>0)L.push("","\u2500\u2500 STATE CU\u1ED0I (vueData) \u2500\u2500",...Ht);if(L.push(""),L.push(`Screenshot: ${bn}${kn?" (\u0111\xEDnh k\xE8m b\xEAn d\u01B0\u1EDBi)":" (qu\xE1 l\u1EDBn \u0111\u1EC3 inline \u2014 d\xF9ng Read tool \u0111\u1EC3 xem)"}`),L.push(`Full-page: ${K.join(Je,"render-full.png")}`),De)L.push(`Dialog \u0111ang m\u1EDF \u2014 ch\u1EE5p ri\xEAng: ${De}`);L.push(""),L.push(`Th\u1ED1ng k\xEA: ${N.length} API call (t\u1EA5t c\u1EA3 mock) \xB7 ${C.length} JS error (${eo} l\xFAc init) \xB7 ${S.length} Vue warn \xB7 ${D.filter((P)=>P.ok).length}/${D.length} assert \u0111\u1EA1t${te.dialogOpen?" \xB7 dialog \u0111ang m\u1EDF":""}`);let mu=D.some((P)=>!P.ok)||le||C.length>0||te.appChildCount===0||d.length>0||R.size>0||G.size>0&&!t.offline;return{report:L.join(`
|
|
283
|
+
`),hasFailures:mu,screenshotPath:bn,inlineScreenshotBase64:kn,inlineScreenshotMime:ne}}finally{await $t.close().catch(()=>{return})}}var Pf="_componentPreview",ll=8;function Ef(e){return e.normalize("NFD").replace(/[\u0300-\u036F]/g,"").replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase()||"case"}function Tf(e){let t={},n={},o=[];for(let[s,r]of Object.entries(e)){if(s.startsWith("@")||s.startsWith("#")){o.push(`prop "${s}": FUI kh\xF4ng nh\u1EADn c\xFA ph\xE1p @/# trong JSON \u2014 \u0111\u1ED5i th\xE0nh "v-on:"/"v-slot:". \u0110\xE3 b\u1ECF qua.`);continue}if(s.startsWith(":")||s.startsWith("v-")){t[s]=r;continue}if(typeof r==="string"){t[s]=r;continue}if(typeof r==="boolean"||typeof r==="number"){t[`:${s}`]=JSON.stringify(r);continue}let i=`cp_${s.replace(/[^a-zA-Z0-9_]/g,"_")}`;n[i]=r,t[`:${s}`]=i}return{attr:t,hoisted:n,notes:o}}function Af(e,t,n){let{attr:o,hoisted:s,notes:r}=Tf(n.props??{}),i={...n.vueData??{},...s},a={w:"12",el:e,attr:o};if(n.slot!==void 0)a.innerHTML=n.slot;return{moduleJson:{ModuleName:`preview: ${e} \u2014 ${t}`,data:Object.keys(i).length>0?[i]:[],watch:{},controls:[{prop:"fluid pa-0",rows:[{prop:"",cols:[a]}]}],set:{}},notes:r}}async function ul(e,t){try{await ze.access(e)}catch{return!1}return await ze.cp(e,t,{recursive:!0}),!0}async function Nf(e,t){let n=hn(e),o=[];if(t){let i=await U(ue.join(ds(e,t),"_moduleInfo.json"),null);o.push(i?.Framework)}let s=await U(ue.join(n,"_projectInfo.json"),null);if(o.push(s?.Framework),!o.some(Boolean))try{let i=await ze.readdir(ue.join(n,"modules"),{withFileTypes:!0});for(let a of i){if(!a.isDirectory())continue;let c=await U(ue.join(n,"modules",a.name,"_moduleInfo.json"),null);if(c?.Framework){o.push(c.Framework);break}}}catch{}let r=o.find(Boolean);return String(r??"V3").toUpperCase()==="V2"?"V2":"V3"}async function dl(e){let t=await Dc(),n=e.projectId||t?.projectId;if(!n)throw Error("Kh\xF4ng x\xE1c \u0111\u1ECBnh \u0111\u01B0\u1EE3c projectId. Truy\u1EC1n projectId ho\u1EB7c ch\u1EA1y session_set tr\u01B0\u1EDBc.");let o=e.moduleId||(!e.projectId?t?.moduleId:void 0),s=hn(n),r=o?ds(n,o):s,i=ue.join(r,"components"),a=o?`module (${o})`:"project (global)",c=ue.join(i,`${e.componentName}.vue`);if(!(await F(c)).trim()){let I=await Me(i);throw Error(`Kh\xF4ng t\xECm th\u1EA5y ${e.componentName}.vue trong ${i}.`+(I.length?` C\xF3 s\u1EB5n: ${I.join(", ")}.`:" Th\u01B0 m\u1EE5c components r\u1ED7ng.")+(o?"":" N\u1EBFu \u0111\xE2y l\xE0 component c\u1EA5p module, truy\u1EC1n th\xEAm moduleId."))}let u=e.cases&&e.cases.length>0?e.cases:[{name:"default"}],d=e.devices&&e.devices.length>0?e.devices:["desktop","mobile"];if(u.length*d.length>ll)throw Error(`${u.length} case \xD7 ${d.length} device = ${u.length*d.length} l\u1EA7n render, v\u01B0\u1EE3t gi\u1EDBi h\u1EA1n ${ll}. `+"M\u1ED7i l\u1EA7n render l\xE0 m\u1ED9t phi\xEAn browser th\u1EADt \u2014 chia nh\u1ECF th\xE0nh nhi\u1EC1u l\u1EA7n g\u1ECDi.");let p=await Nf(n,o),m=ue.join(fn(),Pf,tt(n,"projectId"),tt(e.componentName,"componentName")),w=ue.join(m,"_preview");await ze.mkdir(w,{recursive:!0});let y=[],v=new Set;for(let[I,g]of u.entries()){let b=g.name?.trim()||`case-${I+1}`,R=Ef(b),C=ue.join(m,R);await ze.rm(C,{recursive:!0,force:!0}),await ze.mkdir(C,{recursive:!0}),await ze.writeFile(ue.join(C,"_moduleInfo.json"),JSON.stringify({ProjectID:n,ModuleID:`_cpreview_${e.componentName}`,ModuleName:`preview ${e.componentName}`,Framework:p},null,2),"utf-8");let{moduleJson:S,notes:N}=Af(e.componentName,b,g);for(let D of N)v.add(`[${b}] ${D}`);if(await ze.writeFile(ue.join(C,"module.json"),JSON.stringify(S,null,2),"utf-8"),await ul(i,ue.join(C,"components")),o)await ul(ue.join(r,"imports"),ue.join(C,"imports"));for(let D of d){let A=`${e.componentName} \xB7 ${b} \xB7 ${D}`,G=await Lo(C,{scenario:g.scenario??[],apiMocks:g.apiMocks??{},vueData:void 0,user:e.user,device:D,viewport:e.viewport,settleMs:e.settleMs,screenshotSteps:e.screenshotSteps,forceLocalProjectBundle:!0,reportLabel:A}),_=G.screenshotPath;if(G.screenshotPath){let le=ue.join(w,`${R}-${D}.png`);try{await ze.rename(G.screenshotPath,le),_=le;let Ie=ue.join(ue.dirname(G.screenshotPath),"render-full.png");await ze.rename(Ie,ue.join(w,`${R}-${D}-full.png`)).catch(()=>{})}catch{}}y.push({caseName:b,device:D,report:G.report,hasFailures:G.hasFailures,screenshotPath:_,inlineScreenshotBase64:G.inlineScreenshotBase64,inlineScreenshotMime:G.inlineScreenshotMime})}}let T=[`PREVIEW COMPONENT: ${e.componentName}`,`Scope: ${a} \xB7 Project: ${n} \xB7 Framework ${p}`,`Ngu\u1ED3n: ${c}`,`Cases: ${u.map((I)=>I.name?.trim()||"(kh\xF4ng t\xEAn)").join(", ")} \xD7 Devices: ${d.join(", ")} = ${y.length} l\u1EA7n render`,`\u1EA2nh: ${w}`,"","Kh\xF4ng gian ri\xEAng: ch\u1EC9 component n\xE0y + CSS c\u1EE7a ch\xEDnh n\xF3 + theme/import c\u1EA5p project.","style.css c\u1EE7a module KH\xD4NG n\u1EA1p \u2014 component ph\u1EA3i t\u1EF1 \u0111\u1EE9ng \u0111\u01B0\u1EE3c. T\u1EA7ng project d\u1EF1ng t\u1EEB workspace","local n\xEAn b\u1EA3n ch\u01B0a push v\u1EABn hi\u1EC7n; ch\u1EA1y `project_sync` n\u1EBFu nghi file local \u0111\xE3 c\u0169."];if(v.size>0){T.push("","\u26A0 Ghi ch\xFA d\u1EF1ng harness:");for(let I of v)T.push(` \u2022 ${I}`)}return{header:T.join(`
|
|
284
|
+
`),runs:y,hasFailures:y.some((I)=>I.hasFailures)}}import Ss from"vm";var pl=500,ml=15,fl=500;function Xe(e){if(typeof e==="string")return`"${e.length>40?e.slice(0,40)+"\u2026":e}"`;return Jn(e)}function js(e){return e.replace(/^`+|`+$/g,"")}function hl(e){let t=e.split(/[\\/]+/).filter(Boolean),n=t.lastIndexOf("modules");return n>0?t[n-1]:null}async function Uo(e,t,n){let{loadAllSchemaProcNames:o,getVerifiedSpNames:s}=e;if(!t||Object.keys(t).length===0)return null;let r=hl(n);if(!r)return null;let i=[];try{i=await o(r)}catch{return null}if(i.length===0)return null;let a=await s(),c=_c(Object.keys(t),i,a);if(c.length===0)return null;return["\uD83D\uDEA8 C\u1EA4U TR\xDAC MOCK CH\u01AFA \u0110\u01AF\u1EE2C KI\u1EC2M CH\u1EE8NG \u2014 nghi ng\u1EDD t\u1EF1 suy di\u1EC5n thay v\xEC \u0111\u1ECDc t\u1EEB SP th\u1EADt:",...c.map((l)=>` - apiMocks["${l.mockKey}"] kh\u1EDBp t\xEAn SP C\xD3 TH\u1EACT "${l.spName}" trong schema local, nh\u01B0ng SP \u0111\xF3 ch\u01B0a t\u1EEBng \u0111\u01B0\u1EE3c db_sp_verify \u0111\u1ECDc th\xE2n trong phi\xEAn n\xE0y.`),` \u2192 Ch\u1EA1y db_sp_verify({ name: "${c[0].spName}" }) \u0111\u1EC3 \u0111\u1ECDc th\xE2n SP th\u1EADt r\u1ED3i COPY NGUY\xCAN V\u0102N snippet apiMocks n\xF3 sinh s\u1EB5n \u2014 kh\xF4ng t\u1EF1 \u0111o\xE1n t\xEAn c\u1ED9t/shape cho SP \u0111\xE3 t\u1ED3n t\u1EA1i, k\u1EBFt qu\u1EA3 m\xF4 ph\u1ECFng s\u1EBD l\u1EC7ch v\u1EDBi th\u1EF1c t\u1EBF.`].join(`
|
|
285
|
+
`)}function _f(e){let t=[],n=new Set;return Fe(e,(o,s,r)=>{if(r)return;let i=o.API;if(typeof i!=="string"||!i||i.includes("`")||i.includes("{{"))return;let a=o.IN;if(a!==void 0&&!(j(a)&&Object.keys(a).length===0))return;if(n.has(i))return;n.add(i),t.push({url:i,method:typeof o.METHOD==="string"?o.METHOD:"POST"})}),t}async function gl(e,t,n,o){let{getConnectionAlias:s,resolveApiSpName:r,writeTestData:i,classifySpSafety:a,callLiveApiSp:c,readTestData:l,generateMockForLocalSp:u}=e,d={...o},p=[],m=hl(n);if(!m)return{mocks:d,trace:p};let w=_f(t);if(w.length===0)return{mocks:d,trace:p};let y=0;for(let{url:v,method:T}of w){let I=Vn(v);if(!I)continue;let g=await s(m,I.alias);if(!g)continue;let b=await r(m,g,I.fnName);if(!b)continue;let R=Object.keys(o).find((A)=>A===v);if(R??=Object.keys(o).find((A)=>v.endsWith(A)||A.endsWith(v)),R??=Object.keys(o).find((A)=>v.includes(A)||A.includes(v)),R!==void 0){await i(m,g,b,"user",o[R]);continue}let C=await a(m,g,b),S=!1;if(C==="safe-read"&&y<Lt){y++;let A=await c(m,g,I.isAuth,I.fnName,T);if(A.ok)d[v]=A.data,p.push(`live ${v} \u2192 SP ${b} (${g}) \u2014 g\u1ECDi tAPI th\u1EADt th\xE0nh c\xF4ng`),await i(m,g,b,"live",A.data),S=!0;else p.push(`\u26A0 live ${v} \u2192 SP ${b} (${g}) g\u1ECDi th\u1EADt th\u1EA5t b\u1EA1i (${A.error??`HTTP ${A.status}`}) \u2014 r\u01A1i xu\u1ED1ng cache/t\u1EF1 sinh`)}else if(C==="unsafe-write")p.push(`skip ${v} \u2192 SP ${b} (${g}) c\xF3 l\u1EC7nh ghi \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(C==="opaque")p.push(`skip ${v} \u2192 SP ${b} (${g}) c\xF3 \u0111o\u1EA1n \u0111\u1ECDc t\u0129nh kh\xF4ng nh\xECn xuy\xEAn qua \u0111\u01B0\u1EE3c (SQL \u0111\u1ED9ng / g\u1ECDi SP kh\xE1c) \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(C==="unknown")p.push(`skip ${v} \u2192 SP ${b} (${g}) ch\u01B0a c\xF3 th\xE2n SP local \u0111\u1EC3 ph\xE2n lo\u1EA1i an to\xE0n \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(y>=Lt)p.push(`skip ${v} \u2192 SP ${b} (${g}) \u0111\xE3 \u0111\u1EA1t tr\u1EA7n ${Lt} l\u01B0\u1EE3t g\u1ECDi th\u1EADt/l\u01B0\u1EE3t simulate \u2014 d\xF9ng cache/t\u1EF1 sinh`);if(S)continue;let N=await l(m,g,b);if(N){d[v]=N.data,p.push(`cache ${v} \u2192 SP ${b} (${g}) \u2014 d\xF9ng _testdata cache (ngu\u1ED3n: ${N.source}, l\u01B0u l\xFAc ${N.savedAt})`);continue}let D=await u(m,g,b);if(D)d[v]=D,p.push(`gen ${v} \u2192 SP ${b} (${g}) \u2014 t\u1EF1 sinh mock t\u1EEB c\u1EA5u tr\xFAc SELECT, \u0111\xE3 l\u01B0u cache`),await i(m,g,b,"generated",D)}return{mocks:d,trace:p}}class Is{vueData;moduleJson;opts;sandbox;ctx;trace=[];warnings=new Set;depth=0;apiCallCount=0;toastCount=0;watchTriggerCount=0;assertResults=[];dialogs=[];watchSnapshots=new Map;stubUsed=new Set;scannedFunctionRightCodes;systemRightDefaulted;functionRightDefaulted;constructor(e,t){this.moduleJson=e,this.opts=t,this.systemRightDefaulted=!("SystemRight"in(t.user??{})),this.functionRightDefaulted=!("FunctionRight"in(t.user??{})),this.scannedFunctionRightCodes=Mt([JSON.stringify(e)]),this.vueData={p_domain:"sim.local",p_routers:[],p_params:"",p_urlReferrer:"",p_router:"/sim/module",v_Loading:!1,v_LoadingOverlay:[],v_containerOn:!0,user:{UserID:null,UserName:null,LastName:null,FirstName:null,Sex:null,Phone:null,Email:null,DepartmentID:null,GroupID:null,SystemRight:9,FunctionRight:this.scannedFunctionRightCodes,LastLogin:null,...t.user??{}},v_Set:{title:null,apiDomain:"",login:!1,menu:[],userInfo:""}},this.sandbox=this.buildSandbox(),this.ctx=Ss.createContext(this.sandbox)}log(e){if(this.trace.length===pl)this.trace.push("\u2026 (trace b\u1ECB c\u1EAFt \u2014 qu\xE1 d\xE0i)");if(this.trace.length>pl)return;this.trace.push(" ".repeat(Math.min(this.depth,6))+e)}warn(e){this.warnings.add(e),this.log(`\u26A0 ${e}`)}rightTest(e){if(!j(e))return!0;let t=this.vueData.user??{};for(let[n,o]of Object.entries(e)){let s=Array.isArray(o)?o:[o],r=t[n],i=Array.isArray(r)?r:[r];if(!s.some((a)=>i.includes(a)))return!1}return!0}buildSandbox(){let e=this,t=(r,i)=>(...a)=>(e.stubUsed.add(r),e.log(`FUN ${r}(${a.map((c)=>Xe(c)).join(", ")}) \u2014 stub, kh\xF4ng m\xF4 ph\u1ECFng UI th\u1EADt`),i),n=(r)=>{e.stubUsed.add("moment");let i=r!==void 0&&r!==null?new Date(r):new Date,a=(u)=>String(u).padStart(2,"0"),c={},l=(u)=>{let d=`${i.getFullYear()}-${a(i.getMonth()+1)}-${a(i.getDate())}`;if(!u)return`${d}T${a(i.getHours())}:${a(i.getMinutes())}:${a(i.getSeconds())}`;return u.replace(/YYYY/g,String(i.getFullYear())).replace(/MM/g,a(i.getMonth()+1)).replace(/DD/g,a(i.getDate())).replace(/HH/g,a(i.getHours())).replace(/mm/g,a(i.getMinutes())).replace(/ss/g,a(i.getSeconds()))};return Object.assign(c,{format:l,add:()=>c,subtract:()=>c,startOf:()=>c,endOf:()=>c,valueOf:()=>i.getTime(),toDate:()=>i,toString:()=>l(),year:()=>i.getFullYear(),month:()=>i.getMonth(),date:()=>i.getDate(),daysInMonth:()=>new Date(i.getFullYear(),i.getMonth()+1,0).getDate(),locale:()=>c,isValid:()=>!isNaN(i.getTime())}),c},o={forEach:(r,i)=>{if(Array.isArray(r))r.forEach((a,c)=>i(a,c));else if(j(r))Object.entries(r).forEach(([a,c])=>i(c,a));return r},map:(r,i)=>{let a=Array.isArray(r)?r:j(r)?Object.values(r):[];if(typeof i==="string")return a.map((c)=>c?.[i]);return a.map(i)},filter:(r,i)=>Array.isArray(r)?r.filter(i):[],find:(r,i)=>Array.isArray(r)?r.find(i):void 0,findIndex:(r,i)=>Array.isArray(r)?r.findIndex(i):-1,includes:(r,i)=>Array.isArray(r)||typeof r==="string"?r.includes(i):!1,cloneDeep:(r)=>r===void 0?void 0:JSON.parse(JSON.stringify(r)),isArray:Array.isArray,isString:(r)=>typeof r==="string",isNumber:(r)=>typeof r==="number",isPlainObject:j,isEmpty:(r)=>r==null||typeof r==="string"&&r===""||Array.isArray(r)&&r.length===0||j(r)&&Object.keys(r).length===0,isUndefined:(r)=>r===void 0,keys:(r)=>j(r)?Object.keys(r):[],values:(r)=>j(r)?Object.values(r):[],uniq:(r)=>[...new Set(r)],uniqBy:(r,i)=>{let a=new Set;return(r??[]).filter((c)=>{let l=c?.[i];if(a.has(l))return!1;return a.add(l),!0})},sumBy:(r,i)=>(r??[]).reduce((a,c)=>a+Number(typeof i==="string"?c?.[i]??0:i(c)),0),maxBy:(r,i)=>(r??[]).reduce((a,c)=>a===void 0||Number(c?.[i])>Number(a?.[i])?c:a,void 0),minBy:(r,i)=>(r??[]).reduce((a,c)=>a===void 0||Number(c?.[i])<Number(a?.[i])?c:a,void 0),orderBy:(r,i,a)=>{let c=Array.isArray(i)?i:[i],l=Array.isArray(a)?a:[a??"asc"];return[...r??[]].sort((u,d)=>{for(let p=0;p<c.length;p++){let m=u?.[c[p]],w=d?.[c[p]];if(m===w)continue;let y=m>w?1:-1;return(l[p]??"asc")==="desc"?-y:y}return 0})},sortBy:(r,i)=>o.orderBy(r,i),groupBy:(r,i)=>{let a={};return(r??[]).forEach((c)=>{let l=String(c?.[i]);(a[l]??=[]).push(c)}),a},without:(r,...i)=>(r??[]).filter((a)=>!i.includes(a)),intersection:(r,i)=>(r??[]).filter((a)=>(i??[]).includes(a)),castArray:(r)=>Array.isArray(r)?r:[r],trim:(r,i)=>{if(typeof r!=="string")return r;if(!i)return r.trim();let a=i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return r.replace(new RegExp(`^[${a}]+|[${a}]+$`,"g"),"")},replace:(r,i,a)=>String(r).replace(i,a),split:(r,i,a)=>String(r).split(i,a),startsWith:(r,i)=>String(r).startsWith(i),endsWith:(r,i)=>String(r).endsWith(i),concat:(...r)=>[].concat(...r),take:(r,i)=>(r??[]).slice(0,i),last:(r)=>(r??[])[(r??[]).length-1],head:(r)=>(r??[])[0],first:(r)=>(r??[])[0],round:(r,i=0)=>Math.round(r*10**i)/10**i,assignIn:Object.assign,assign:Object.assign,at:(r,i)=>[e.getPath(r,i)],property:(r)=>(i)=>e.getPath(i,r)},s=new Proxy(o,{get(r,i){if(i in r)return r[i];return(...a)=>{e.warn(`_.${i} ch\u01B0a h\u1ED7 tr\u1EE3 trong simulator \u2014 tr\u1EA3 undefined`);return}}});return{vueData:this.vueData,console:{log:(...r)=>e.log(`console.log: ${r.map((i)=>typeof i==="string"?i:Xe(i)).join(" ")}`),error:(...r)=>e.log(`console.error: ${r.map((i)=>typeof i==="string"?i:Xe(i)).join(" ")}`),warn:(...r)=>e.log(`console.warn: ${r.map((i)=>typeof i==="string"?i:Xe(i)).join(" ")}`)},JSON,Math,Date,Array,Object,String,Number,Boolean,RegExp,parseInt,parseFloat,isNaN,isFinite,encodeURIComponent,decodeURIComponent,encodeURI,decodeURI,moment:n,_:s,CALL:(r,i)=>e.runAction(r,i),groupBy:(r,i,a)=>{let c=Array.isArray(a)?a:[a],l={};return(r??[]).forEach((u)=>{let d=String(u?.[i]);(l[d]??=[]).push(u)}),Object.entries(l).map(([u,d])=>{let p={[i]:u,count:d.length};for(let m of c)if(m)p[m]=d.reduce((w,y)=>w+Number(y?.[m]??0),0);return p})},buildHeader:(r,i)=>{let a=[];if(i)(Array.isArray(r)?r:[]).forEach((c)=>a.push({text:c[i.text],value:c[i.value],divider:!0}));else{let c=Array.isArray(r)?r[0]:j(r)&&Array.isArray(r.data)?r.data[0]:void 0;if(j(c))for(let[l,u]of Object.entries(c))a.push(typeof u==="number"?{el:"t-num",text:l,value:l,divider:!0}:{text:l,value:l,divider:!0})}return a},findInArray:(r)=>(r?.src??[]).find((i)=>i?.[r.key]===r.value),openWindow:(r)=>{e.stubUsed.add("openWindow"),e.log(`FUN openWindow id=${String(r?.id??"?")} url=${String(r?.url??"?")} \u2014 stub (sub-window kh\xF4ng m\xF4 ph\u1ECFng)`)},showMessage:(r)=>{if(e.log(`MESSBOX/showMessage "${String(r?.title??"")}"${r?.message?` \u2014 ${String(r.message)}`:""}`),r?.onclose)e.log("(gi\u1EA3 l\u1EADp user \u0111\xF3ng dialog \u2192 ch\u1EA1y onclose)"),e.runAction(r.onclose)},confirm:(r)=>{if(e.log(`CONFIRM "${String(r?.title??"")}" \u2192 OK (m\u1EB7c \u0111\u1ECBnh simulator)`),typeof r?.action==="function")r.action()},ajaxCALL:t("ajaxCALL"),jsonToExcel:t("jsonToExcel"),printPDF:t("printPDF"),copyToClipboard:t("copyToClipboard"),redirect:t("redirect"),reload:t("reload"),logout:t("logout"),webSocket_Send:t("webSocket_Send"),webSocketJoinGroup:t("webSocketJoinGroup"),appCommand:t("appCommand"),pushRouter:t("pushRouter"),capacityText:(r)=>`${r}`,generateID:()=>`sim${Math.floor(Math.random()*1e6)}`,rightTest:(r)=>e.rightTest(r),fillData:t("fillData"),chartDataBuild:t("chartDataBuild"),fixURL:(r)=>r,alert:t("alert")}}evalExpr(e,t){return this.sandbox.__scope__=t,this.sandbox.__ret__=void 0,Ss.runInContext(`with(__scope__) { __ret__ = ${e} }`,this.ctx,{timeout:fl}),this.sandbox.__ret__}evalJs(e,t){this.sandbox.__scope__=t,Ss.runInContext(`with(__scope__) { ${e} }`,this.ctx,{timeout:fl})}bindDataToString(e,t){if(typeof e!=="string")return e;return e.replace(/{{([\s\S]+?)}}/g,(n,o)=>{let s=this.evalExpr(o,t);return s===void 0||s===null?"":String(s)})}getPath(e,t){let n=String(t).match(/[^.[\]]+/g)??[],o=e;for(let s of n){if(o==null)return;o=o[s]}return o}getVueData(e,t){if(typeof e==="string"){if(e==="")return"";let n=e.replace("vueData.","").trim();if(n[0]==="`")return js(this.bindDataToString(n,t));let o=n;if(/^{{.*}}$/gm.test(o))o=o.replace(/^{{|}}$/gm,"");try{return this.evalExpr(o,t)}catch{if(/({{.*}}|[ ])/gm.test(n))try{return js(this.bindDataToString(n,t))}catch{return}return}}else if(j(e)&&e.ARRAY&&e.COL){let n=this.getPath(t,String(e.ARRAY));return Array.isArray(n)?n.map((o)=>this.getPath(o,String(e.COL))):[]}return e}setValue(e,t,n,o,s){if(Array.isArray(t)){if(!j(e)&&!Array.isArray(e))return;let r=e,i=s??0,a=t[i];if(typeof a==="string"&&a[a.length-1]==="]")if(a=a.replaceAll("]",""),/^[0-9]*$/gm.test(a))a=parseInt(a,10);else if(a[0]==="'")a=a.replaceAll("'","");else a=this.getVueData(a,o??this.vueData);if(t.length-1===i){r[a]=n;return}if(typeof r[a]>"u"){let c=t[i+1];r[a]=c[c.length-1]==="]"?[]:{}}this.setValue(r[a],t,n,o,i+1)}else if(typeof t==="string")if(t[0]==="`"||t[0]==="'")this.setValue(e,[t.replaceAll("'","").replaceAll("`","")],n,o,0);else this.setValue(e,t.match(/[^.[]+/g)??[t],n,o,0)}mapData(e,t,n,o){if(typeof e==="string"){this.setValue(t,e,n);return}if(!j(e))return;for(let[s,r]of Object.entries(e)){if(o)n={...this.vueData,...o};let i;try{i=this.bindDataToString(s,this.vueData)}catch{i=s}if(typeof r==="string"&&r[0]==="#")this.warn(`\u0111\u1ECDc cross-window "${r}" \u2014 kh\xF4ng m\xF4 ph\u1ECFng \u0111\u01B0\u1EE3c, g\xE1n undefined`),this.setValue(t,i,void 0);else if(i[0]==="#")this.log(`\u2192 ghi cross-window ${i} = ${Xe(this.getVueData(r,n))} (kh\xF4ng m\xF4 ph\u1ECFng)`);else this.setValue(t,i,this.getVueData(r,n),n)}}defProp(e){if(typeof e==="string"){if(typeof this.vueData[e]>"u")this.vueData[e]=[]}else if(j(e)){for(let t of Object.keys(e))if(typeof this.vueData[t]>"u")this.vueData[t]=void 0}}runAction(e,t){if(!e)return;let n={...this.vueData,...t??{}};if(typeof e==="string"){let s=this.getVueData(e,n);if(!s){this.warn(`Action "${e}" undefined \u2014 CALL tr\u1ECF t\u1EDBi action kh\xF4ng t\u1ED3n t\u1EA1i?`);return}e=s}if(!e)return;let o=Array.isArray(e)?e:[e];for(let s of o)if(j(s)&&s.CONFIRM){let r;try{r=this.bindDataToString(String(s.CONFIRM),n)}catch{r=String(s.CONFIRM)}let i=t?.__cancelConfirm===!0;if(this.log(`CONFIRM "${r}" \u2192 ${i?"H\u1EE6Y":"OK"}`),i){if(s.CANCEL)this.vueActionSafe(s.CANCEL,t)}else{let a=JSON.parse(JSON.stringify(s));delete a.CONFIRM,delete a.CANCEL,this.vueActionSafe(a,t)}}else this.vueActionSafe(s,t)}vueActionSafe(e,t){this.depth++;try{this.vueAction(e,t)}catch(n){this.warn(`L\u1ED7i khi ch\u1EA1y action: ${n.message} \u2014 action: ${_e(e)}`)}finally{this.depth--}}vueAction(e,t){if(!e)return;if(typeof e==="string"){this.log(`EXE ${e}`),this.evalJs(e,{...t??{},...this.vueData});return}if(!j(e))return;let n=JSON.parse(JSON.stringify(e)),o={...this.vueData,...t??{}};if(n.IN!==void 0)if(typeof n.IN==="string")this.mapData({IN:n.IN},n,o);else{let r={};this.mapData(n.IN,r,o),n.IN=r}if(n.API!==void 0){try{n.API=this.bindDataToString(String(n.API),o)}catch{}if(n.HEADER!==void 0)if(typeof n.HEADER==="string")this.mapData({HEADER:n.HEADER},n,o);else this.mapData(n.HEADER,n.HEADER,o);this.defProp(n.OUT),this.callAPI(n,t);return}if(n.CALL!==void 0){this.log(`CALL ${String(n.CALL)}`),this.runAction(n.CALL,t);return}if(n.EXE!==void 0){this.log(`EXE ${String(n.EXE).replace(/\s+/g," ").slice(0,100)}`),this.evalJs(String(n.EXE),{...t??{},...this.vueData});return}if(n.FUN!==void 0){let r;try{r=this.bindDataToString(String(n.FUN),o)}catch{r=String(n.FUN)}let i=this.sandbox[r];if(typeof i==="function"){let a=i(n.IN);if(n.OUT!==void 0)this.mapData(n.OUT,this.vueData,a);if(n.CALLBACK!==void 0)this.runAction(n.CALLBACK,t)}else this.warn(`FUN "${r}" kh\xF4ng t\u1ED3n t\u1EA1i (simulator ch\u1EC9 c\xF3 c\xE1c h\xE0m FUI chu\u1EA9n)`);return}if(n.IF!==void 0){let r=!1;try{r=String(this.evalExpr(String(n.IF),{...t??{},...this.vueData}))==="true"}catch(a){this.warn(`IF "${String(n.IF)}" l\u1ED7i eval: ${a.message} \u2192 coi nh\u01B0 false`)}this.log(`IF(${String(n.IF).slice(0,60)}) \u2192 ${r?"THEN":"ELSE"}`);let i=r?n.THEN:n.ELSE;if(typeof i==="string")this.log(`EXE ${i.slice(0,80)}`),this.evalJs(i,{...t??{},...this.vueData});else if(i!==void 0)this.runAction(i,t);return}if(n.MESS!==void 0){let r;try{r=this.bindDataToString(String(n.MESS),o)}catch{r=String(n.MESS)}this.toastCount++,this.log(`MESS \uD83D\uDD14 "${r}"`);return}if(n.MESSBOX!==void 0){let r;try{r=this.bindDataToString(String(n.MESSBOX),o)}catch{r=String(n.MESSBOX)}this.log(`MESSBOX "${r}"`);return}let s=[];for(let r of Object.keys(n))s.push(r);if(this.mapData(n,this.vueData,this.vueData,t),s.length>0)this.log(`SET ${s.map((r)=>`${r} = ${Xe(r[0]==="#"?"(cross-window)":this.getPath(this.vueData,r))}`).join(" \xB7 ")}`)}callAPI(e,t){let n=String(e.API),o=e.METHOD?String(e.METHOD):"POST";this.apiCallCount++,this.log(`API ${o} ${n} IN=${e.IN!==void 0?JSON.stringify(e.IN)?.slice(0,200):"{}"}`);let s=this.opts.apiMocks,r=Object.keys(s).find((a)=>a===n);r??=Object.keys(s).find((a)=>n.endsWith(a)||a.endsWith(n)),r??=Object.keys(s).find((a)=>n.includes(a)||a.includes(n));let i;if(r!==void 0)i=JSON.parse(JSON.stringify(s[r])),this.log(`(mock kh\u1EDBp "${r}")`);else i={data:[]},this.warn(`kh\xF4ng c\xF3 mock cho API "${n}" \u2014 tr\u1EA3 m\u1EB7c \u0111\u1ECBnh {data: []}`);if(j(i)&&i.__error!==void 0){let a=j(i.__error)?i.__error:{},c={status:a.status??500,responseJSON:a.responseJSON??{Message:"Simulated error"}};if(this.log(`\u2190 HTTP ${String(c.status)} (mock l\u1ED7i)`),e.ERROROUT!==void 0)this.mapData(e.ERROROUT,this.vueData,c);if(e.ERRORCALLBACK!==void 0)this.runAction(e.ERRORCALLBACK,t);if(e.ERROROUT!==void 0)return;let l=c.responseJSON;this.warn(`API "${n}" l\u1ED7i kh\xF4ng \u0111\u01B0\u1EE3c b\u1EAFt (kh\xF4ng c\xF3 ERROROUT) \u2192 hi\u1EC7n ERROR DIALOG m\u1EB7c \u0111\u1ECBnh: ${typeof l==="string"?l:j(l)?String(l.Message??""):""}`);return}if(e.OUT!==void 0){let a=i;if(typeof e.OUT==="string"&&j(i)&&Object.keys(i).length===1&&i.data)a=i.data;this.mapData(e.OUT,this.vueData,a);let c=typeof e.OUT==="string"?`${e.OUT} = ${Xe(this.vueData[e.OUT])}`:Object.keys(e.OUT).map((l)=>`${l} = ${Xe(this.getPath(this.vueData,l))}`).join(" \xB7 ");this.log(`\u2190 OUT ${c}`)}else this.log("\u2190 (kh\xF4ng c\xF3 OUT)");if(e.MESS!==void 0){let a;try{a=this.bindDataToString(String(e.MESS),this.vueData)}catch{a=String(e.MESS)}this.toastCount++,this.log(`MESS \uD83D\uDD14 "${a}"`)}if(e.CALLBACK!==void 0)this.runAction(e.CALLBACK,t)}initWatches(){let e=j(this.moduleJson.watch)?this.moduleJson.watch:{};for(let o of Object.keys(e)){if(o==="deep-watch")continue;this.setValue(this.vueData,o,this.getVueData(o,this.vueData))}let t=(o,s)=>{let r=this.getPath(this.vueData,o);this.watchSnapshots.set(`${s?"D:":"S:"}${o}`,{deep:s,snap:s?JSON.stringify(r):r})},n=e["deep-watch"];if(j(n))for(let o of Object.keys(n))t(o,!0);for(let o of Object.keys(e)){if(o==="deep-watch")continue;t(o,!1)}}checkWatches(){let e=j(this.moduleJson.watch)?this.moduleJson.watch:{},t=j(e["deep-watch"])?e["deep-watch"]:{};for(let n=0;n<ml;n++){let o=[];for(let[s,r]of this.watchSnapshots){let i=s.slice(2),a=this.getPath(this.vueData,i);if(r.deep){let c=JSON.stringify(a);if(c!==r.snap)o.push({key:i,handler:t[i],oldVal:r.snap}),r.snap=c}else if(!Object.is(a,r.snap))o.push({key:i,handler:e[i],oldVal:r.snap}),r.snap=a}if(o.length===0)return;for(let s of o){this.watchTriggerCount++,this.log(`WATCH ${s.key} thay \u0111\u1ED5i \u2192 ${_e(s.handler)}`);let r={};this.setValue(r,s.key,s.oldVal),this.depth++;try{this.runAction(s.handler,{v_old:r})}finally{this.depth--}}}this.warn(`watch cascade v\u01B0\u1EE3t ${ml} v\xF2ng \u2014 nguy c\u01A1 V\xD2NG L\u1EB6P V\xD4 H\u1EA0N gi\u1EEFa c\xE1c watch/action`)}initDialogs(){(Array.isArray(this.moduleJson.controls)?this.moduleJson.controls:[]).forEach((t,n)=>{if(!j(t)||t.el!=="hidden-container")return;let o=j(t.attr)?t.attr:{},s=t["v-model"]??o["v-model"];if(typeof s==="string"){let r=s.replace("vueData.","").split(/[.[]/)[0];this.dialogs.push({vModel:r,where:`container ${n+1}`,open:Boolean(this.getPath(this.vueData,r))})}})}checkDialogs(){for(let e of this.dialogs){let t=Boolean(this.getPath(this.vueData,e.vModel));if(t!==e.open)e.open=t,this.log(`DIALOG ${e.vModel} (${e.where}): ${t?"M\u1EDE \uD83D\uDD32":"\u0110\xD3NG \u25AA"}`)}}declareControlVars(){let e=/^[a-zA-Z0-9$_\-.[\]]*$/,t=(n)=>{if(Array.isArray(n)){n.forEach(t);return}if(!j(n))return;let o=j(n.attr)?n.attr:{};for(let[s,r]of Object.entries(o)){if(typeof r!=="string")continue;if(s==="v-model"){if(!r.includes("vueData.")&&this.getVueData(r,this.vueData)===void 0)this.mapData({[r]:void 0},this.vueData,{})}else if(s[0]===":"&&e.test(r)){if(!r.includes("vueData.")&&this.getVueData(r,this.vueData)===void 0)this.mapData({[r]:s===":items"?[]:void 0},this.vueData,{})}}if(Array.isArray(n.innerHTML))n.innerHTML.forEach(t);if(Array.isArray(n.rows))n.rows.forEach(t);if(Array.isArray(n.cols))n.cols.forEach(t)};t(this.moduleJson.controls)}collectClickables(){let e=[],t=(n,o)=>{if(Array.isArray(n)){n.forEach((a)=>t(a,o));return}if(!j(n))return;let s=j(n.attr)?n.attr:{},r=s["v-on:click"]??s[":action"]??s.action;if(r!==void 0){let a=[];for(let c of["label",":label","innerText","text"]){let l=s[c]??n[c];if(typeof l==="string"&&l)a.push(js(l))}if(typeof n.innerHTML==="string"&&n.innerHTML)a.push(n.innerHTML.replace(/<[^>]*>/g,"").trim());e.push({labels:a,handler:r,el:typeof n.el==="string"?n.el:"?",tableItemsExpr:o})}let i=o;if(n.el==="f-table"){let a=s[":items"];if(typeof a==="string")i=a}if(Array.isArray(n.innerHTML))n.innerHTML.forEach((a)=>t(a,i));if(Array.isArray(n.rows))n.rows.forEach((a)=>t(a,i));if(Array.isArray(n.cols))n.cols.forEach((a)=>t(a,i))};return t(this.moduleJson.controls),e}simulate(e){if(this.log("\u2500\u2500 INIT \u2500\u2500"),j(this.moduleJson.set))Object.assign(this.vueData.v_Set,this.moduleJson.set);if(this.opts.urlParams)for(let[n,o]of Object.entries(this.opts.urlParams))this.vueData[n]=o;try{this.runAction(this.moduleJson.data)}catch(n){this.warn(`L\u1ED7i khi init data[]: ${n.message}`)}this.declareControlVars(),this.initWatches(),this.initDialogs(),this.checkDialogs();let t=this.collectClickables();return e.forEach((n,o)=>{let s=o+1;if(j(n.set)){this.log(""),this.log(`\u2500\u2500 STEP ${s}: set ${Object.entries(n.set).map(([r,i])=>`${r}=${Xe(i)}`).join(", ")} \u2500\u2500`);for(let[r,i]of Object.entries(n.set))this.setValue(this.vueData,r,i)}else if(typeof n.click==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: click "${n.click}" \u2500\u2500`);let r=n.click.toLowerCase(),i=t.filter((d)=>d.labels.some((p)=>p.toLowerCase()===r)),a=i.length>0?i:t.filter((d)=>d.labels.some((p)=>p.toLowerCase().includes(r)));if(a.length===0){this.warn(`kh\xF4ng t\xECm th\u1EA5y control c\xF3 label "${n.click}" k\xE8m v-on:click/:action`);return}let c=typeof n.nth==="number"?n.nth-1:0;if(a.length>1)this.log(`(kh\u1EDBp ${a.length} control \u2014 d\xF9ng #${c+1})`);let l=a[Math.min(c,a.length-1)],u;if(l.tableItemsExpr!==void 0||n.item!==void 0||n.index!==void 0){let d=n.item,p=typeof n.index==="number"?n.index:0;if(d===void 0&&l.tableItemsExpr){let m=this.getVueData(l.tableItemsExpr,this.vueData);if(Array.isArray(m)&&m.length>0)d=m[Math.min(p,m.length-1)],this.log(`(control trong f-table \u2014 d\xF9ng row ${p} c\u1EE7a ${l.tableItemsExpr})`);else d={},this.warn(`control n\u1EB1m trong f-table nh\u01B0ng "${l.tableItemsExpr??""}" r\u1ED7ng \u2014 item = {} (truy\u1EC1n "item" trong event \u0111\u1EC3 ch\u1EC9 \u0111\u1ECBnh)`)}u={$row:d,item:d,index:p}}if(n.cancelConfirm===!0)u={...u??{},__cancelConfirm:!0};this.runAction(l.handler,u)}else if(typeof n.call==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: call ${n.call} \u2500\u2500`);let r;if(n.item!==void 0||n.index!==void 0)r={$row:n.item,item:n.item,index:typeof n.index==="number"?n.index:0};if(n.cancelConfirm===!0)r={...r??{},__cancelConfirm:!0};this.runAction(n.call,r)}else if(typeof n.exe==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: exe \u2500\u2500`),this.log(`EXE ${n.exe.slice(0,100)}`);try{this.evalJs(n.exe,{...this.vueData})}catch(r){this.warn(`exe l\u1ED7i: ${r.message}`)}}else if(typeof n.assert==="string"){let r=typeof n.label==="string"?n.label:n.assert,i=!1;try{i=Boolean(this.evalExpr(n.assert,{...this.vueData}))}catch(a){this.warn(`assert "${n.assert}" l\u1ED7i eval: ${a.message}`)}this.assertResults.push({label:r,ok:i}),this.log(""),this.log(`\u2500\u2500 STEP ${s}: assert ${i?"\u2714":"\u2718"} ${r} \u2500\u2500`);return}else{this.warn(`event ${s} kh\xF4ng h\u1EE3p l\u1EC7 \u2014 c\u1EA7n m\u1ED9t trong: set / click / call / exe / assert`);return}this.checkWatches(),this.checkDialogs()}),{report:this.buildReport(),hasFailures:this.assertResults.some((n)=>!n.ok)}}buildRightDefaultNote(){let e=[];if(this.systemRightDefaulted)e.push("SystemRight=9 (m\u1EB7c \u0111\u1ECBnh, quy\u1EC1n cao nh\u1EA5t)");if(this.functionRightDefaulted)e.push(this.scannedFunctionRightCodes.length?`FunctionRight=[${this.scannedFunctionRightCodes.join(", ")}] (m\u1EB7c \u0111\u1ECBnh \u2014 qu\xE9t \u0111\u01B0\u1EE3c t\u1EEB module.json)`:"FunctionRight=[] (m\u1EB7c \u0111\u1ECBnh \u2014 kh\xF4ng qu\xE9t \u0111\u01B0\u1EE3c m\xE3 n\xE0o trong module.json)");if(e.length===0)return null;return`user preview: ${e.join(" \xB7 ")}. rightTest()/v-if theo FunctionRight kh\xE1c m\xE3 tr\xEAn s\u1EBD tr\u1EA3 false \u2014 truy\u1EC1n tham s\u1ED1 \`user\` \u0111\u1EC3 test vai tr\xF2 h\u1EA1n ch\u1EBF h\u01A1n.`}buildReport(){let e=[...this.trace,""];e.push("\u2550\u2550\u2550 K\u1EBET QU\u1EA2 \u2550\u2550\u2550");let t=this.buildRightDefaultNote();if(t)e.push(`\u2139 ${t}`);if(this.functionRightDefaulted){let s=Wn(this.scannedFunctionRightCodes,this.opts.definedFunctionRights??null);if(s)e.push(s)}for(let s of this.assertResults)e.push(`${s.ok?"\u2714":"\u2718"} assert: ${s.label}`);if(this.warnings.size>0){e.push("","\u2500\u2500 C\u1EA3nh b\xE1o \u2500\u2500");for(let s of this.warnings)e.push(`\u26A0 ${s}`)}if(this.stubUsed.size>0)e.push("",`(stub \u0111\xE3 d\xF9ng \u2014 k\u1EBFt qu\u1EA3 c\xE1c h\xE0m n\xE0y KH\xD4NG ph\u1EA3n \xE1nh runtime th\u1EADt: ${[...this.stubUsed].join(", ")})`);e.push("","\u2500\u2500 STATE CU\u1ED0I \u2500\u2500");let n=new Set(["p_domain","p_routers","p_params","p_urlReferrer","p_router","v_Loading","v_LoadingOverlay","v_containerOn","user","v_Set"]);for(let[s,r]of Object.entries(this.vueData)){if(n.has(s))continue;if(j(r)&&(r.API!==void 0||r.CALL!==void 0||r.EXE!==void 0||r.FUN!==void 0||r.IF!==void 0))continue;e.push(`${s} = ${Xe(r)}`)}let o=this.dialogs.filter((s)=>s.open).map((s)=>s.vModel);return e.push("",`Th\u1ED1ng k\xEA: ${this.apiCallCount} API call \xB7 ${this.toastCount} toast \xB7 ${this.watchTriggerCount} watch trigger \xB7 ${this.assertResults.filter((s)=>s.ok).length}/${this.assertResults.length} assert \u0111\u1EA1t${o.length>0?` \xB7 dialog \u0111ang m\u1EDF: ${o.join(", ")}`:""}`),e.join(`
|
|
286
|
+
`)}}function wl(e){let t=new nt(e.paths);return us({store:t,session:e.session,api:e.api,activeTarget:async()=>{let n=e.targets.cwdTarget();if(n)return{projectId:n.projectId,moduleId:n.moduleId??"",moduleDir:n.moduleDir??""};return e.session.getTarget()}}),t}async function yl(e,t,n){let o=wl(e),s=await Ft(t),r=await Uo(mn(o,e.session),n.apiMocks,t),i=`${s?`${s}
|
|
287
|
+
|
|
288
|
+
`:""}${r?`${r}
|
|
289
|
+
|
|
290
|
+
`:""}`,a=await Lo(t,n);return{text:`${i}Render UI: ${t}
|
|
291
|
+
|
|
292
|
+
${a.report}
|
|
293
|
+
`,hasFailures:a.hasFailures,screenshots:a.screenshotPath?[a.screenshotPath]:[]}}async function bl(e,t){wl(e);let n=await dl(t),o=n.runs.map((r)=>`${r.hasFailures?"\u2718":"\u2714"} ${r.caseName} \xB7 ${r.device}${r.screenshotPath?` \u2192 ${Df(r.screenshotPath)}`:""}`).join(`
|
|
294
|
+
`),s=[`${n.header}
|
|
295
|
+
|
|
296
|
+
\u2500\u2500 T\u1ED4NG H\u1EE2P \u2500\u2500
|
|
297
|
+
${o}`];for(let r of n.runs)if(s.push(`
|
|
298
|
+
\u2550\u2550 ${r.caseName} \xB7 ${r.device} \u2550\u2550
|
|
299
|
+
${r.report}`),r.screenshotPath)s.push(`screenshot: ${r.screenshotPath}`);return{text:`${s.join(`
|
|
300
|
+
`)}
|
|
301
|
+
`,hasFailures:n.hasFailures,screenshots:n.runs.flatMap((r)=>r.screenshotPath?[r.screenshotPath]:[])}}import{dirname as kl,join as xf}from"path";async function $l(e,t,n){let o=mn(new nt(e.paths),e.session),s=await Ft(t),r=await Uo(o,n.apiMocks,t),i=`${s?`${s}
|
|
302
|
+
|
|
303
|
+
`:""}${r?`${r}
|
|
304
|
+
|
|
305
|
+
`:""}`,a=await F(xf(t,"module.json"));if(!a.trim())throw new f(`module.json is empty or missing in ${t}`);let c;try{c=JSON.parse(a)}catch(v){throw new f(`module.json has a JSON syntax error: ${v.message}; fix it, simulate needs valid JSON`)}if(!j(c))throw new f("module.json root is not an object");let l=await Ot(kl(kl(t))),{mocks:u,trace:d}=await gl(o,c,t,n.apiMocks??{}),p=new Is(c,{apiMocks:u,user:n.user,urlParams:n.urlParams,definedFunctionRights:l.functionRights}),{report:m,hasFailures:w}=p.simulate(n.scenario??[]),y=d.length>0?`
|
|
306
|
+
|
|
307
|
+
\u2500\u2500 Live/Cache data (SP ch\u01B0a c\xF3 mock, \u0111\xE3 t\u1EF1 resolve) \u2500\u2500
|
|
308
|
+
${d.join(`
|
|
309
|
+
`)}`:"";return{text:`${i}Simulate: ${t}
|
|
310
|
+
|
|
311
|
+
${m}${y}
|
|
312
|
+
`,hasFailures:w}}var ct={module:{type:"string",short:"m"}},jl={data:{type:"string"},"data-file":{type:"string"}};function kt(e){if(k(`${e.notes.join(`
|
|
313
|
+
`)}
|
|
314
|
+
`),e.data)k(e.data)}async function Il(e){return e.targets.resolveProjectModule(h(e.values,"project"),h(e.values,"module"))}async function Kn(e){return e.targets.resolveModuleDir(h(e.values,"module-dir"),h(e.values,"project"),h(e.values,"module"))}var Of={name:"module validate",mcpName:"module_validate",usage:"fui module validate [-p P -m M | --module-dir D]",summary:"Static semantic check of module.json (no network); fix every error before publishing",options:{...ct,"module-dir":{type:"string"}},async run(e){let t=await Kn(e),n=await vc(t);for(let o of n.notes)E(`# ${o}`);if(k(n.data??""),n.errorCount>0)throw new lt(`module.json has ${n.errorCount} error(s)`,[...n.notes,`Validate: ${t}`,"",n.data??""].join(`
|
|
315
|
+
`))}},Mf={name:"module outline",mcpName:"module_outline",usage:"fui module outline [-p P -m M | --module-dir D]",summary:"Text layout tree of module.json: grid widths, bindings, actions, dialogs, unused state (no network)",options:{...ct,"module-dir":{type:"string"}},async run(e){let t=await Ac(await Kn(e));for(let n of t.notes)E(`# ${n}`);k(t.data??"")}},Cl={device:{type:"string"},viewport:{type:"string"},"settle-ms":{type:"string"},"screenshot-steps":{type:"boolean"}},vl=["desktop","tablet","mobile"];function Rl(e){let n=(typeof e.device==="string"?e.device:void 0)?.split(",").map((r)=>r.trim());for(let r of n??[])if(!vl.includes(r))throw new f(`--device takes ${vl.join(", ")}`);let o;if(typeof e.viewport==="string"){let r=/^(\d+)x(\d+)$/i.exec(e.viewport);if(!r)throw new f("--viewport takes WIDTHxHEIGHT, e.g. 1366x768");o={width:Number(r[1]),height:Number(r[2])}}let s;if(typeof e["settle-ms"]==="string"){if(s=Number(e["settle-ms"]),!Number.isFinite(s)||s<0)throw new f("--settle-ms takes a number of milliseconds")}return{devices:n,viewport:o,settleMs:s,screenshotSteps:e["screenshot-steps"]===!0?!0:void 0}}var Ff={name:"module simulate",mcpName:"module_simulate",usage:`fui module simulate [-p P -m M | --module-dir D] [--scenario '[{"click":"Save"}]'] [--mocks '{"Fn":{"data":[]}}']
|
|
316
|
+
[--user '{"SystemRight":3}'] [--url-params '{"id":"1"}'] (each also as --<name>-file path)
|
|
317
|
+
real browser: --render [--device desktop|tablet|mobile] [--viewport 1366x768] [--vue-data {...}] [--settle-ms N]
|
|
318
|
+
[--screenshot-steps] [--refresh-assets | --offline] (needs Playwright)`,summary:"Run module.json in the simulated FUI runtime (or --render: a real browser); fails when an assert or the page fails",options:{...ct,"module-dir":{type:"string"},...ns("scenario","mocks","user","url-params","vue-data"),...Cl,render:{type:"boolean"},"refresh-assets":{type:"boolean"},offline:{type:"boolean"}},async run(e){let{values:t}=e,n=await Kn(e),o={scenario:await yt(t,"scenario","array"),apiMocks:await yt(t,"mocks","object"),user:await yt(t,"user","object"),urlParams:await yt(t,"url-params","object")};if(t.render!==!0){let i=["vue-data","vue-data-file","device","viewport","settle-ms","screenshot-steps"];for(let c of[...i,"refresh-assets","offline"])if(t[c]!==void 0)throw new f(`--${c} needs --render`);let a=await $l(O(e),n,o);if(k(a.text),a.hasFailures)throw new lt("simulate: an assert failed",a.text);return}let s=Rl(t);if((s.devices?.length??0)>1)throw new f("module render takes one --device; component preview takes several");let r=await yl(O(e),n,{...o,scenario:o.scenario??[],apiMocks:o.apiMocks??{},vueData:await yt(t,"vue-data","object"),device:s.devices?.[0],viewport:s.viewport,settleMs:s.settleMs,screenshotSteps:s.screenshotSteps,refreshAssets:t["refresh-assets"]===!0,offline:t.offline===!0});k(r.text);for(let i of r.screenshots)if(!r.text.includes(i))k(`screenshot: ${i}
|
|
319
|
+
`);if(r.hasFailures)throw new lt("render: the page has failures",r.text)}},Lf={name:"component preview",mcpName:"component_preview",usage:`fui component preview <uc-name> [-p P] [-m M] [--cases '[{"name":"empty","props":{"items":[]}}]'] [--device desktop,mobile]
|
|
320
|
+
[--viewport 1366x420] [--user {...}] [--settle-ms N] [--screenshot-steps] (needs Playwright)`,summary:"Render one local .vue alone in a real browser per case x device: JS errors, Vue warns, DOM audit, screenshots",options:{...ct,...ns("cases","user"),...Cl},async run(e){let{values:t}=e,n=e.positionals[0];if(!n)throw new f("missing <uc-name> (file name without .vue)");let o=Rl(t),s=await bl(O(e),{componentName:n,projectId:h(t,"project"),moduleId:h(t,"module"),cases:await yt(t,"cases","array"),devices:o.devices,viewport:o.viewport,user:await yt(t,"user","object"),settleMs:o.settleMs,screenshotSteps:o.screenshotSteps});if(k(s.text),s.hasFailures)throw new lt("component preview: a run has failures",s.text)}},Uf={name:"module preview",mcpName:"module_preview",usage:"fui module preview [-p P -m M | --module-dir D]",summary:"Diff local vs server, run the validator and issue an approval token for publish html",options:{...ct,"module-dir":{type:"string"}},async run(e){kt(await Sc(O(e),await Kn(e)))}},Bf={name:"module publish html",mcpName:"module_publish_html",usage:"fui module publish html --approval <token> [-p P -m M | --module-dir D]",summary:"Publish the whole staged module (needs the token from fui module preview, after user approval)",options:{...ct,"module-dir":{type:"string"},approval:{type:"string"}},async run(e){kt(await jc(O(e),await Kn(e),h(e.values,"approval")))}},Hf={name:"module publish json",mcpName:"module_publish_json",usage:"fui module publish json [-p P -m M]",summary:"Push local module.json (skips when unchanged since the last publish/sync)",options:ct,async run(e){kt(await To(O(e),await Il(e)))}};function Sl(e){let t=e==="script"?"script.js":"style.css";return{name:`module publish ${e}`,mcpName:`module_publish_${e}`,usage:`fui module publish ${e} [-p P -m M]`,summary:`Push local ${t} (an empty file clears it on the server; a missing file is refused)`,options:ct,async run(n){kt(await Ao(O(n),await Il(n),t))}}}function Bo(e,t,n){return{name:`component ${e}`,mcpName:`component_${e}`,usage:`fui component ${e}${e==="list"?"":" <name>"} [-p P [-m M]]`,summary:n,options:ct,async run(o){let s=o.positionals[0];if(e!=="list"&&!s)throw new f("missing <name>");kt(await t(o,s))}}}async function Ho(e){return an(O(e),h(e.values,"project"),h(e.values,"module"))}var Wf=[Bo("list",async(e)=>Cc(O(e),await Ho(e)),"Refresh _components.json from the server (archives .vue files the server dropped)"),Bo("new",async(e,t)=>No(O(e),await Ho(e),t),"Create a component on the server from local components/<name>.vue"),Bo("update",async(e,t)=>_o(O(e),await Ho(e),t),"Overwrite an existing component from local components/<name>.vue (skips when unchanged)"),Bo("delete",async(e,t)=>Rc(O(e),await Ho(e),t),"Delete a component on the server (ask the user first); archives the local .vue")],qf={name:"project new",mcpName:"project_new",usage:"fui project new (--data '<json>' | --data - | --data-file f)",summary:"Create a project (needs SystemRight>=3 on fp; server reads ProjectID, ProjectName, GroupName, Framework, pDomain, Router)",options:jl,async run(e){kt(await Pc(O(e),await ts(e.values)))}},Jf={name:"project update",mcpName:"project_update",usage:"fui project update (--data '<json with id>' | --data - | --data-file f)",summary:"Edit project metadata; projectData.id is required",options:jl,async run(e){kt(await Ec(O(e),await ts(e.values)))}},Vf={name:"project publish-config",mcpName:"project_publish_config",usage:"fui project publish-config [-p project]",summary:"Push local <project>/project.json (menu, routes, settings); skips when unchanged",async run(e){let{projectId:t}=await e.targets.resolveProject(h(e.values,"project"));kt(await Do(O(e),t))}},Pl=[Of,Mf,Ff,Uf,Bf,Hf,Sl("script"),Sl("css"),...Wf,Lf,qf,Jf,Vf];import{rename as Kf}from"fs/promises";import{join as El}from"path";async function Al(e,t,n){let o=await ce(e);if(!t)return{rows:await o.api.listImportFiles(void 0,n),notes:["no project given: read-only query, local workspace not synced"]};let s=await o.syncImportsLocal(t,n);return{rows:s.list,notes:[`_imports.json synced for ${n?`module ${n}`:`project ${t}`} (${s.count} file(s), metadata only; content: fui import get <fileId>)`,...s.removed.length>0?[`removed orphaned local files: ${s.removed.join(", ")}`]:[]]}}async function Nl(e,t){return(await e.api()).listFrameworkImportFiles(t)}async function _l(e,t){let o=(await(await e.api()).getFileContent(t))?.data?.FileContent??"",s=await Gr(e.paths,t,o);return{notes:s.length>0?s.map((r)=>`written: ${r}`):["no local _imports.json declares this fileID yet; content printed only. materialize it: fui import list -p <project> [-m <module>]"],data:o}}async function Dl(e,t,n){let o=await(await e.api()).uploadFileContent(t,n),s=await Gr(e.paths,t,n);return{notes:[`ok uploaded content of import ${t}`,...s.map((r)=>`local copy synced: ${r}`)],data:`${JSON.stringify(o)}
|
|
321
|
+
`}}async function xl(e,t){let n=await ce(e),{effectiveSort:o,note:s}=zr(t.fileSort,t.moduleId?"module":"project"),r=await n.api.insertImportFile({...t.extra,ProjectID:t.projectId,ModuleID:t.moduleId,FileType:t.fileType,ContentType:t.contentType,FileName:t.fileName,FileSort:o}),i=["ok import added",...s?[s]:[]];if(t.projectId)try{let{count:a}=await n.syncImportsLocal(t.projectId,t.moduleId);i.push(`_imports.json synced for ${t.moduleId?`module ${t.moduleId}`:`project ${t.projectId}`} (${a} file(s))`)}catch{i.push("warning: import added but the _imports.json sync failed")}return{notes:i,data:`${JSON.stringify(r)}
|
|
322
|
+
`}}async function Ol(e,t){let n=await ce(e),o=await Kr(e.paths,t.fileId),s=t.fileSort,r="";if(t.fileSort!==void 0&&o.length>0){let u=zr(t.fileSort,o.some((d)=>d.moduleId)?"module":"project");s=u.effectiveSort,r=u.note}let i=await n.api.updateImportFile(t.fileId,{...t.extra,FileType:t.fileType,ContentType:t.contentType,FileName:t.fileName,FileSort:s}),a=en(t.contentType)?`imports/${Mn(t.fileName,t.fileId,t.fileType)}`:void 0,c=[],l=new Set;for(let u of o){let d=u.entry.filePath;if(d&&a&&a!==d)try{await Kf(El(u.baseDir,d),El(u.baseDir,a)),c.push(`local file moved: ${d} -> ${a}`)}catch{}let p=`${u.projectId}::${u.moduleId??""}`;if(l.has(p))continue;l.add(p);let m=u.moduleId?`module ${u.moduleId}`:`project ${u.projectId}`;try{let{removed:w}=await n.syncImportsLocal(u.projectId,u.moduleId);c.push(`${m}: _imports.json synced${w.length>0?`, removed orphaned ${w.join(", ")}`:""}`)}catch{c.push(`${m}: sync failed`)}}return{notes:["ok import updated",...r?[r]:[],...c],data:`${JSON.stringify(i)}
|
|
323
|
+
`}}async function Ml(e,t){let n=await ce(e),o=await Kr(e.paths,t),s=await n.api.deleteImportFile(t),r=[],i=new Set;for(let a of o){let c=`${a.projectId}::${a.moduleId??""}`;if(i.has(c))continue;i.add(c);let l=a.moduleId?`module ${a.moduleId}`:`project ${a.projectId}`;try{let{removed:u}=await n.syncImportsLocal(a.projectId,a.moduleId);r.push(`${l}: _imports.json synced${u.length>0?`, removed ${u.join(", ")}`:""}`)}catch{r.push(`${l}: sync failed`)}}return{notes:[`ok import ${t} deleted`,...r],data:`${JSON.stringify(s)}
|
|
324
|
+
`}}var Gf={1:"View only",2:"Develop",3:"Admin"};function Gn(e){let t=typeof e==="number"?e:Number(e);if(!Number.isFinite(t))return String(e??"");let n=Gf[t];return n?`${t} (${n})`:String(t)}function Tl(e,t){let n=t.trim().toLowerCase();return e.find((o)=>String(o.UserID??"").trim().toLowerCase()===n)}function Wo(e,t){return e.map((n)=>({userId:String(n.UserID??"?"),username:n.UserName||"\u2014",fullName:n.FullName||"\u2014",...t?{right:Gn(n.UserRight)}:{}}))}async function Fl(e,t,n){return(await e.api()).searchUsers(t,n??"%")}async function Ll(e,t){return(await e.api()).listProjectUserRights(t)}async function Ul(e,t,n){let o=await e.api(),s=`project ${t.projectId}`,r=t.userId.trim();if(!r)throw new f("empty userId; find the real UserID: fui user search <text>");let i=Tl(await o.listProjectUserRights(t.projectId),r);if(n==="new"&&i)throw new f(`user "${r}" already has right ${Gn(i.UserRight)} on ${s}; change it: fui user set ${r} --right N`);if(n!=="new"&&!i)throw new f(`user "${r}" has no right on ${s}; ${n==="update"?`grant one: fui user add ${r} --right N`:"nothing to revoke; see: fui user list"}`);let a=n==="delete"?0:t.userRight;await o.updateProjectUserRight(t.projectId,a,JSON.stringify([r]));let c=await o.listProjectUserRights(t.projectId),l=Tl(c,r),u=n==="delete"?`ok revoked the right of "${r}" on ${s}`:`ok set right ${Gn(a)} for "${r}" on ${s}`,d=n==="delete"?l?`warning: post-check: the user is still listed with right ${Gn(l.UserRight)}; the server did not remove it`:"post-check: user no longer listed":l?`post-check: ${r} has ${Gn(l.UserRight)}`:"warning: post-check: user not found in the list after the write; check: fui user list";return{notes:[u,d,`current rights on ${s} (${c.length}):`],rows:c}}var Hl={module:{type:"string",short:"m"}},Wl={type:{type:"string"},"content-type":{type:"string"},name:{type:"string"},sort:{type:"string"},set:{type:"string",multiple:!0}};function qo(e){if(k(`${e.notes.join(`
|
|
325
|
+
`)}
|
|
326
|
+
`),e.data)k(e.data)}function ql(e){if(e===void 0)return;let t=Number(e);if(!Number.isFinite(t))throw new f("--sort must be a number");return t}function Jo(e){let t=e.positionals[0];if(!t)throw new f("missing <fileId>; list them: fui import list -p <project> [-m <module>]");return t}function Bl(e){return e.map((t)=>({fileId:String(t.fileID??""),name:String(t.FileName??""),type:String(t.FileType??""),contentType:String(t.ContentType??""),scope:String(t.Scope??""),sort:String(t.FileSort??"")}))}var zf={name:"import list",mcpName:"file_import_list",usage:"fui import list [-p P [-m M]] | fui import list --framework V2|V3",summary:"List import files (with -p it also syncs local _imports.json); --framework lists framework imports",options:{...Hl,framework:{type:"string"}},async run(e){let t=O(e),n=h(e.values,"framework");if(n){k(X(Bl(await Nl(t,n.toUpperCase())),e.render));return}let{rows:o,notes:s}=await Al(t,h(e.values,"project"),h(e.values,"module"));for(let r of s)E(`# ${r}`);k(X(Bl(o),e.render))}},Xf={name:"import get",mcpName:"file_import_get",usage:"fui import get <fileId> [-o file]",summary:"Print an inline import's source and write it to every local imports/ location declaring it",options:{out:{type:"string",short:"o"}},async run(e){let t=await _l(O(e),Jo(e));for(let o of t.notes)E(`# ${o}`);let n=h(e.values,"out");if(n)await Bun.write(n,t.data??""),k(`${n}
|
|
327
|
+
`);else k(t.data??"")}},Yf={name:"import upload",mcpName:"file_import_upload_content",usage:"fui import upload <fileId> (-f file | -)",summary:"Overwrite an existing inline import's source (text/javascript or text/css) and sync local copies",options:{file:{type:"string",short:"f"}},async run(e){let t=Jo(e),n=h(e.values,"file"),o=e.positionals[1];if(n===void 0===(o!=="-"))throw new f("pass the content with -f <file> or - (stdin)");let s=n?await Bun.file(n).text():await ro();qo(await Dl(O(e),t,s))}},Qf={name:"import new",mcpName:"file_import_new",usage:"fui import new -p P [-m M] --type js|css|... --content-type link|comp-link|text/javascript|text/css --name <name|url> [--sort N]",summary:"Add an import (CDN link or inline code); sort is raised to 20 (project) / 50 (module) when lower",options:{...Hl,...Wl},async run(e){let{values:t}=e,n=h(t,"type"),o=h(t,"content-type"),s=h(t,"name");if(!n||!o||!s)throw new f("need --type, --content-type and --name");qo(await xl(O(e),{projectId:h(t,"project"),moduleId:h(t,"module"),fileType:n,contentType:o,fileName:s,fileSort:ql(h(t,"sort")),extra:cn(t.set)}))}},Zf={name:"import update",mcpName:"file_import_update",usage:"fui import update <fileId> --type T --content-type C --name N [--sort N]",summary:"Edit import metadata (not its source); renames the local content file when the name changes",options:Wl,async run(e){let{values:t}=e,n=h(t,"type"),o=h(t,"content-type"),s=h(t,"name");if(!n||!o||!s)throw new f("need --type, --content-type and --name (the server replaces all three)");qo(await Ol(O(e),{fileId:Jo(e),fileType:n,contentType:o,fileName:s,fileSort:ql(h(t,"sort")),extra:cn(t.set)}))}},eh={name:"import delete",mcpName:"file_import_delete",usage:"fui import delete <fileId>",summary:"Delete an import on the server (ask the user first) and clean local _imports.json copies",async run(e){qo(await Ml(O(e),Jo(e)))}};async function Jl(e){return(await e.targets.resolveProject(h(e.values,"project"))).projectId}function th(e){if(e!=="1"&&e!=="2"&&e!=="3")throw new f("--right must be 1 (View only), 2 (Develop) or 3 (Admin)");return Number(e)}var nh={name:"user search",mcpName:"project_user_search",usage:"fui user search <text> [--id-like pattern]",summary:"Find a developer account; the only way to get the exact UserID for user add/set/remove",options:{"id-like":{type:"string"}},async run(e){let t=e.positionals[0];if(!t)throw new f("missing <text>");let n=await Fl(O(e),t,h(e.values,"id-like"));if(n.length===0){E(`# no user matches "${t}"; the user may not exist, or --id-like filtered it out (default % = no filter)`);return}E("# grant: fui user add <UserID> --right 3 (1=View only, 2=Develop, 3=Admin)"),k(X(Wo(n,!1),e.render))}},oh={name:"user list",mcpName:"project_user_list",usage:"fui user list [-p project]",summary:"Who holds an IDE right on the project (project-wide; not the acc SystemRight)",async run(e){let t=await Jl(e),n=await Ll(O(e),t);if(n.length===0){E(`# nobody has a right on project ${t}; find a user: fui user search <text>`);return}k(X(Wo(n,!0),e.render))}};function Cs(e,t,n){return{name:`user ${e}`,mcpName:`project_user_${t}`,usage:`fui user ${e} <UserID>${t==="delete"?"":" --right 1|2|3"} [-p project]`,summary:n,options:t==="delete"?{}:{right:{type:"string"}},async run(o){let s=o.positionals[0];if(s===void 0)throw new f("missing <UserID>; find it: fui user search <text>");let r=await Ul(O(o),{projectId:await Jl(o),userId:s,userRight:t==="delete"?void 0:th(h(o.values,"right"))},t);k(`${r.notes.join(`
|
|
328
|
+
`)}
|
|
329
|
+
`),k(X(Wo(r.rows,!0),o.render))}}}var Vl=[zf,Xf,Yf,Qf,Zf,eh,nh,oh,Cs("add","new","Grant a right to a user who has none (exact UserID from fui user search)"),Cs("set","update","Change the right level of a user who already has one"),Cs("remove","delete","Revoke a user's right (ask the user first)")];import{rm as Yl}from"fs/promises";import{appendFile as rh,mkdir as sh,rename as ih,stat as ah}from"fs/promises";import{dirname as ch,join as lh}from"path";var uh=1048576,Kl=["\u274C","\u26D4","\u2718","\u26A0\uFE0F"];function zn(e){return lh(e,"_logs","tool-errors.jsonl")}function dh(e,t){let n=e.split(`
|
|
330
|
+
`).map((i)=>i.trim()).filter(Boolean),o=[],s=new Set,r=0;for(let i of Kl)for(let a of n){if(!a.startsWith(i)||s.has(a))continue;let c=a.length>200?`${a.slice(0,200)}\u2026`:a;if(r+c.length+1>t)return o;s.add(a),o.push(c),r+=c.length+1}return o}function Rs(e,t,n){if(e.length<=t+n)return e;let o=e.slice(t,e.length-n),s=dh(o,Math.round((t+n)/2)),r=s.length>0?[`\u2026[c\u1EAFt ${o.length} k\xFD t\u1EF1 \u2014 gi\u1EEF l\u1EA1i ${s.length} d\xF2ng l\xFD do h\u1ECFng]\u2026`,...s]:[`\u2026[c\u1EAFt ${o.length} k\xFD t\u1EF1]\u2026`];return`${e.slice(0,t)}
|
|
331
|
+
${r.join(`
|
|
332
|
+
`)}
|
|
333
|
+
${e.slice(-n)}`}async function Gl(e,t,n,o){try{let s=zn(e);await sh(ch(s),{recursive:!0});try{if((await ah(s)).size>uh)await ih(s,s.replace(/\.jsonl$/,".1.jsonl")).catch(()=>{return})}catch{}let r={ts:new Date().toISOString(),pid:process.pid,tool:t,message:Rs(n,400,600),...o&&o!=="error"?{kind:o}:{}};await rh(s,`${JSON.stringify(r)}
|
|
334
|
+
`,"utf-8")}catch{}}async function zl(e){let t="";try{t=await H(zn(e))}catch{}let n=[];for(let o of t.split(`
|
|
335
|
+
`)){if(!o.trim())continue;try{n.push(JSON.parse(o))}catch{}}return n}function ph(e){let t=e.split(`
|
|
336
|
+
`).map((o)=>o.trim()).filter(Boolean),n=Kl.map((o)=>t.find((s)=>s.startsWith(o))).find(Boolean);if(!n)return"(b\u1EA3n ghi kh\xF4ng ch\u1EE9a d\xF2ng l\xFD do h\u1ECFng \u2014 xem v\xED d\u1EE5 b\xEAn d\u01B0\u1EDBi)";return n.replace(/^[\u274C\u26D4\u2718\u26A0\uFE0F]+\uFE0F?\s*/,"").replace(/'[^'\s]*'|"[^"\s]*"|`[^`\s]*`/g,"'\u2026'").replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f-]{12,}\b/gi,"\u2026").replace(/[A-Za-z]:[\\/][^\s,)]+|\/[^\s,)]*\/[^\s,)]+/g,"\u2026").replace(/\b\d+\b/g,"N").replace(/\s+/g," ").trim().slice(0,140)}function Xl(e){return{errors:e.filter((t)=>t.kind!=="guard"),guards:e.filter((t)=>t.kind==="guard")}}function Ps(e){let t=new Map;for(let n of e){let o=ph(n.message),s=`${n.tool} ${o}`,r=t.get(s);if(r)r.count+=1,r.lastTs=n.ts,r.sample=n.message;else t.set(s,{tool:n.tool,signature:o,count:1,firstTs:n.ts,lastTs:n.ts,sample:n.message})}return[...t.values()].sort((n,o)=>o.count-n.count||o.lastTs.localeCompare(n.lastTs))}function Ql(e,t,n,o,s){if(e===void 0)return s;let r=Number(e);if(!Number.isInteger(r)||r<n||r>o)throw new f(`${t} must be an integer ${n}-${o}`);return r}var mh={name:"log read",mcpName:"error_log_read",usage:"fui log read [--tool mcp_tool_name] [--limit N] [--summary] [--sample-chars N]",summary:"Shared tool-error log (Fui MCP + fui) grouped by tool and error signature, most frequent first",options:{tool:{type:"string"},limit:{type:"string"},summary:{type:"boolean"},"sample-chars":{type:"string"}},async run({paths:e,values:t}){let n=h(t,"tool"),o=Ql(h(t,"limit"),"--limit",0,Number.MAX_SAFE_INTEGER,15),s=Ql(h(t,"sample-chars"),"--sample-chars",200,1e5,900),r=zn(e.root),i=await zl(e.root);if(n)i=i.filter((m)=>m.tool===n);let{errors:a,guards:c}=Xl(i);if(a.length===0&&c.length===0){k(`no errors logged${n?` for ${n}`:""}
|
|
337
|
+
log file: ${r}
|
|
338
|
+
`);return}let l=new Map;for(let m of a)l.set(m.tool,(l.get(m.tool)??0)+1);let u=Ps(a),d=o===0?u:u.slice(0,o),p=[`tool error log: ${a.length} failures, ${u.length} groups${n?` (tool ${n})`:""}`,`file: ${r}`,"","failures per tool (most first: where the skill is weakest):",...[...l.entries()].sort((m,w)=>w[1]-m[1]).map(([m,w])=>` ${String(w).padStart(4)}x ${m}`),"",`recurring groups (${d.length}/${u.length}, most first):`];if(d.forEach((m,w)=>{if(p.push("",`[${w+1}] ${m.count}x ${m.tool} first ${m.firstTs} last ${m.lastTs}`,` ${m.signature}`),t.summary!==!0){p.push(" -- latest occurrence --");for(let y of Rs(m.sample,Math.round(s*0.4),Math.round(s*0.6)).split(`
|
|
339
|
+
`))p.push(` ${y}`)}}),u.length>d.length)p.push("",`${u.length-d.length} more groups; raise --limit (0 = all)`);if(c.length>0){p.push("",`guards that blocked by design: ${c.length} (not skill gaps; not counted above):`);for(let m of Ps(c).slice(0,5))p.push(` ${String(m.count).padStart(4)}x ${m.tool} ${m.signature}`)}k(`${p.join(`
|
|
340
|
+
`)}
|
|
341
|
+
`)}},fh={name:"log clear",mcpName:"error_log_clear",usage:"fui log clear",summary:"Delete the shared tool-error log (after its patterns were folded back into the skill)",async run({paths:e}){let t=zn(e.root);await Yl(t,{force:!0}),await Yl(t.replace(/\.jsonl$/,".1.jsonl"),{force:!0}),k(`cleared ${t}
|
|
342
|
+
`)}},Zl=[mh,fh];import{join as hh}from"path";var gh={module:{type:"string",short:"m"}},Es=["load","ui","html","script","css","components","imports"],Ts=["info","config","modules","components","imports"];async function eu(e,t){let n=typeof t==="string"?t:`${JSON.stringify(t,null,e.json?0:2)}
|
|
343
|
+
`,o=h(e.values,"out");if(o)await Bun.write(o,n),k(`${o}
|
|
344
|
+
`);else k(n)}var wh={name:"remote module",usage:`fui remote module [-p P -m M] --part ${Es.join("|")} [-o file]`,summary:"Print one part of a module as the server has it (script/css/html raw; others JSON); no local writes",options:{...gh,part:{type:"string"},out:{type:"string",short:"o"}},async run(e){let t=h(e.values,"part");if(!t||!Es.includes(t))throw new f(`--part must be one of: ${Es.join(", ")}`);let{projectId:n,moduleId:o}=await e.targets.resolveProjectModule(h(e.values,"project"),h(e.values,"module")),s=await O(e).api(),r=await{load:()=>s.getModuleContent(n,o),ui:async()=>(await s.getModuleUI(n,o)).data,html:async()=>String((await s.getModuleHtml(n,o)).data?.mBody??""),script:()=>s.getModuleScript(n,o),css:()=>s.getModuleCSS(n,o),components:()=>s.getModuleComponents(n,o),imports:()=>s.getModuleDependencies(n,o)}[t]();await eu(e,r)}},yh={name:"remote project",usage:`fui remote project [-p P] --part ${Ts.join("|")} [-o file]`,summary:"Print one part of a project as the server has it; no local writes",options:{part:{type:"string"},out:{type:"string",short:"o"}},async run(e){let t=h(e.values,"part");if(!t||!Ts.includes(t))throw new f(`--part must be one of: ${Ts.join(", ")}`);let{projectId:n}=await e.targets.resolveProject(h(e.values,"project")),o=await O(e).api(),s=await{info:async()=>(await o.listProjects()).find((r)=>r.ProjectID===n)??null,config:()=>o.getProjectJson(n),modules:()=>o.listModules(n),components:()=>o.getModuleComponents(n),imports:()=>o.listImportFiles(n)}[t]();await eu(e,s)}};function tu(e,t){let n=ko(e.paths,t);if(!n)throw new f(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var bh={name:"remote diff",usage:"fui remote diff <path...>",summary:"Compare local module files with the server copy (module preview logic, per file); no writes",async run(e){if(e.positionals.length===0)throw new f("missing <path>");let t=e.positionals.map((r)=>tu(e,r)),n=new Bn(e.paths,await O(e).api()),o=new Map,s=[];for(let r of t){if(!r.moduleId){s.push(`${r.rel} unsupported only module files can be diffed (use fui remote project --part config)`);continue}let i=`${r.projectId}/${r.moduleId}`,a=o.get(i);if(!a){let d=(await n.resolveModuleInfoFromCache(r.projectId,r.moduleId)).found;if(!d)throw new f(`module ${i} unknown; run fui module list -p ${r.projectId}`);let p=await n.fetchModuleBundle(d);a=tn(d,p.content,p.components,p.script,p.css,p.dependencies),o.set(i,a)}let c=await F(r.abs,"\x00missing"),l=a[r.rel],u;if(c==="\x00missing")u=l===void 0?"absent":"only-server";else if(l===void 0)u="only-local";else if(r.rel.endsWith(".json")){let d=!1;try{d=gt(JSON.parse(c))===gt(JSON.parse(l))}catch{}u=d?"same":"differs"}else u=ft(c)===ft(l)?"same":"differs";s.push(`${r.rel} ${u} ${hh(r.projectId,"modules",r.moduleId)}`)}k(`${s.join(`
|
|
345
|
+
`)}
|
|
346
|
+
`)}},kh={name:"file push",usage:"fui file push <path...> [--new]",summary:"Push local files through their guarded publisher (module.json, script.js, style.css, components/*.vue, project.json)",options:{new:{type:"boolean"}},async run(e){if(e.positionals.length===0)throw new f("missing <path>");let t=O(e),n=0;for(let o of e.positionals){let s=tu(e,o),r;if(!s.moduleId&&s.rel==="project.json")r=await Do(t,s.projectId);else if(s.rel.startsWith("components/")&&s.rel.endsWith(".vue")&&!s.rel.slice(11).includes("/")){let i=await an(t,s.projectId,s.moduleId),a=s.rel.slice(11,-4);r=e.values.new===!0?await No(t,i,a):await _o(t,i,a)}else if(s.moduleId&&(s.rel==="module.json"||s.rel==="script.js"||s.rel==="style.css")){let i={projectId:s.projectId,moduleId:s.moduleId,moduleDir:s.baseDir};r=s.rel==="module.json"?await To(t,i):await Ao(t,i,s.rel)}else if(s.rel==="header.html"||s.rel==="body.html"){E(`# ${o}: header/body publish only as a whole module: fui module preview, then fui module publish html --approval <token>`),n=Math.max(n,2);continue}else{E(`# ${o}: no publisher for this file`),n=Math.max(n,2);continue}k(`${r.notes.join(`
|
|
347
|
+
`)}
|
|
348
|
+
`)}return n}},nu=[wh,yh,bh,kh];function $h(e){if(e===void 0)return qi;let t=Number(e);if(!Number.isInteger(t)||t<0)throw new f("--limit must be an integer >= 0 (0 = no limit)");return t}var vh={name:"query",mcpName:"db_sql_execute",usage:'fui query ("<sql>" | -f file | -) [--db ref] [--limit 200] [--confirm-write] [--json]',summary:"Run SQL that returns rows (TSV); writes need --confirm-write",options:{file:{type:"string",short:"f"},limit:{type:"string"},"confirm-write":{type:"boolean"}},async run(e){let{values:t,positionals:n,render:o}=e,s=await vn(n[0],h(t,"file")),r=$h(h(t,"limit")),{rows:i,connection:a}=await ta(await z(e),s,{db:h(t,"db"),confirmWrite:t["confirm-write"]===!0});if(a.note)E(`# ${a.note}`);let c=ii(i,r);k(X(c.rows,o)),E(c.truncated?`# rows: ${c.rows.length} of ${c.total} (truncated; use --limit 0 or TOP/WHERE)`:`# rows: ${c.total}`)}},Sh={name:"exec",mcpName:"db_sql_execute_nonquery",usage:'fui exec ("<sql>" | -f file | -) [--db ref] [--confirm-write]',summary:"Run DDL/DML that returns no rows; several connections need --db",options:{file:{type:"string",short:"f"},"confirm-write":{type:"boolean"}},async run(e){let{values:t,positionals:n,json:o}=e,s=await vn(n[0],h(t,"file")),{result:r,connection:i,notes:a}=await na(await z(e),s,{db:h(t,"db"),confirmWrite:t["confirm-write"]===!0}),c=`${i.projectId}/${i.alias}`;if(i.note)E(`# ${i.note}`);let l=Xt(r);k(o?nr({db:c,result:l}):`ok ${c} ${JSON.stringify(l)}
|
|
349
|
+
`);for(let u of a)E(`# ${u}`)}},ou=[vh,Sh];function As(e){if(e===void 0)return;let t=pr.find((n)=>n.toLowerCase()===e.toLowerCase());if(!t)throw new f(`--type must be one of: ${pr.join(", ")}`);return t}var jh={name:"schema",mcpName:"db_schema_read",usage:'fui schema [--db ref] [--type Table|View|API|"API File"|Procedure|Function] [--search text]',summary:"Print compact DDL (one line per object) from the local cache; --search matches the whole line",options:{type:{type:"string"},search:{type:"string"}},async run(e){let t=await zi(await z(e),{db:h(e.values,"db"),objectType:As(h(e.values,"type")),search:h(e.values,"search")});for(let n of t.notes)E(`# ${n}`);k(t.data??"")}},Ih={name:"schema pull",mcpName:"db_schema_get",usage:"fui schema pull [--db ref] [--prune-local-only]",summary:"Refresh schema.json and local .sql bodies; archive files of objects dropped on the server",options:{"prune-local-only":{type:"boolean"}},async run(e){let t=await Gi(await z(e),h(e.values,"db"),e.values["prune-local-only"]===!0);k(`${t.notes.join(`
|
|
350
|
+
`)}
|
|
351
|
+
`)}},ru=[jh,Ih];import{cp as Rh,mkdir as Ph,rm as Eh}from"fs/promises";import{homedir as Th}from"os";import{join as Xn}from"path";var Ns="---\nname: fui\ndescription: Use the `fui` CLI for FUI (low-code platform) work \u2014 projects, modules (get, validate, preview, publish), components, imports, project users, DB connections, SQL, schema, stored procedures (tAPI spAPI_*), workspace files, publish ledger and history. Prefer it over Fui MCP tools whenever `fui` covers the tool (see the map below); same guards, a fraction of the tokens.\n---\n\n# fui CLI\n\n`fui` is the shell replacement for Fui MCP tools. Run it with your shell tool. It uses the same workspace, connection files, publish ledger, `_history` and error log as Fui MCP, so you can mix the two during the transition.\n\nDomain rules (module.json, controls, tAPI conventions) live in the `fui-skill` skill. Read its `references/` as needed, then run `fui skill loaded`; strict mode (`FUI_MCP_STRICT=1`) blocks deploys until you do.\n\n## Output contract\n\n- stdout is data only: TSV with a header when piped, an aligned table in a terminal, `--json` for one-line JSON, `--no-header` to drop the header.\n- stderr carries `# ...` notes and `error:` / `guard:` lines.\n- Exit codes:\n - `0`: ok, including \"skipped: unchanged\".\n - `1`: runtime error (network or SQL).\n - `2`: usage error. Read the message; it lists the valid choices.\n - `3`: blocked by a guard, or waiting for `--confirm-write`.\n\n## Targets\n\n- **Project and module**, first match wins:\n 1. `-p <project> -m <module>`. Passing only one of them is an error.\n 2. The current directory, if it is inside `<workspace>/<project>/modules/<module>/`.\n 3. The session target.\n- **Session:** one per `FUI_SESSION` (default `default`). Parallel agents should each set their own `FUI_SESSION`.\n- **Connection:**\n - `--db <alias>` or `--db <project>/<alias>`.\n - With no `--db`: the only connection, else one inferred from the object name (`sp` commands), else the default.\n - Writes never use the default when a project has several connections.\n- `fui file where` prints what resolved and why.\n- `fui config` shows the workspace and API settings with their sources. They inherit from the Fui MCP entry in `~/.claude.json`.\n- A global install updates itself in the background, at most once a day. A line starting `# fui updated` means a new version is running; run `fui skill install` if the skills were installed as files. `fui --version` prints the version.\n\n## Fui MCP tool \u2192 fui command\n\nImplemented (same guards, same files, same requests as Fui MCP 1.4.0):\n\n| MCP tool | fui |\n| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| session_set / session_read / session_clear / session_resolve | `use <target>` / `use` / `use --clear` / `resolve <target>` |\n| project_list / project_sync | `project list` / `project sync` |\n| project_new / project_update / project_publish_config | `project new --data {\u2026}` / `project update --data {id:\u2026}` / `project publish-config` |\n| project_user_search / list / new / update / delete | `user search <text>` / `user list` / `user add <UserID> --right N` / `user set \u2026` / `user remove <UserID>` |\n| module_list / new / update / delete | `module list` / `module new <id> --name N` / `module update [--new-id] [--framework] \u2026` / `module delete -p P -m M` |\n| module_get / module_get_ui / module_get_html | `module get [<target>]` / `module get-ui` / `module get-html` |\n| module_validate / module_preview | `module validate` / `module preview` |\n| module_outline / module_simulate | `module outline` / `module simulate [--scenario [...]] [--mocks {...}] [--user {...}] [--url-params {...}]` |\n| module_simulate renderUI / component_preview | `module simulate --render [--device mobile] [--vue-data {...}]` / `component preview <uc-name> [--cases [...]] [--device desktop,mobile]` |\n| error_log_read / error_log_clear | `log read [--tool T] [--limit N\\|0] [--summary]` / `log clear` |\n| module_publish_html / json / script / css | `module publish html --approval <token>` / `module publish json` / `\u2026 script` / `\u2026 css` |\n| component_list / get / new / update / delete | `component list` / `component get <name>` / `component new <name>` / `component update <name>` / `component delete <name>` |\n| file_import_list / get / new / update / upload_content / delete | `import list` / `import get <id>` / `import new \u2026` / `import update <id> \u2026` / `import upload <id> -f file` / `import delete <id>` |\n| db_config_read / db_connect | `db list` / `db add <alias> --token - --api-domain host` |\n| db_connect_by_name / db_alias_list / db_alias_new | `db add-by-name <alias>` / `db alias list [--search s]` / `db alias new <alias> \u2026` |\n| db_user_token_set / db_token_rebuild | `db user-token [--from p/alias]` / `db token-rebuild --uid U --pwd-env VAR` |\n| right_system_* / right_function_* | `right system list\\|new <0-9>\\|update <0-9>\\|delete <0-9>` / `right function list\\|new --code N\\|update <id>\\|delete <id>` |\n| db_schema_get / db_schema_read | `schema pull [--prune-local-only]` / `schema [--type T] [--search text]` |\n| db_sp_list / db_sp_get / db_sp_save | `sp list` / `sp get <name> [--print]` / `sp save <name> -f file` |\n| db_sp_deploy / db_sp_help | `sp deploy <name>` / `sp help <name\\|Fn> [--full]` |\n| db_sp_verify / db_sp_delete / db_sp_rename | `sp verify <name\\|Fn> [--params {\u2026}] [--check-grant]` / `sp delete <name> --type Procedure\\|Function` / `sp rename <old> <new>` |\n| db_sql_execute / db_sql_execute_nonquery | `query \"<sql>\"` / `exec \"<sql>\"` (+ `--confirm-write`) |\n| skill_get / skill_reference_get / skill_sync | `skill get` / `skill ref <path...>` / `skill sync` |\n| design_list / design_read | `design list` / `design read <brand>` |\n\nEvery Fui MCP 1.4.0 tool has a fui command (the SSE-only `workspace_*` and `*_stage` tools are out of scope).\n\n## Modules\n\n1. `fui module get /project/module` (also a live URL or display name).\n - It archives local files that differ, deletes `.vue` files the server no longer has, and makes the module the session target.\n2. Edit the files in `<workspace>/<project>/modules/<module>/`. Running `fui` from inside that folder targets it.\n3. Check, cheapest first:\n - `fui module validate`: fix every error (exit 1 while any remain); it only reads local files.\n - `fui module outline`: the layout tree (grid widths, bindings, actions, dialogs) to confirm the structure is what you meant.\n - `fui module simulate --scenario '[{\"click\":\"Save\"},{\"assert\":\"vueData.list.length > 0\"}]'`: runs the module in a simulated runtime; exit 1 when an assert fails. `--mocks` wins over live data. An unmocked static API may call the real tAPI when its local `.sql` body reads as read-only (max 8 calls, cached in `_testdata/`). For an existing SP take mocks from `fui sp verify`, never invent them.\n - `fui component preview <uc-name> --cases '[{\"name\":\"empty\",\"props\":{\"items\":[]}}]'`: one local `.vue` alone in a real browser; do it before wiring a component in and before publishing a project-scope one.\n - `fui module simulate --render [--device mobile]`: the real page in headless Chromium, last and only for problems that need pixels. Project-level assets come from the server, so publish project files first. It prints screenshot paths: open and read them, then fix what you see. Needs Playwright once: `bun add -g playwright && bunx playwright install chromium`.\n4. Publish:\n - **Whole module, including header/body:** `fui module preview` \u2192 show the changes to the user \u2192 after approval `fui module publish html --approval <token>`.\n - The token breaks if any payload file changes after the preview.\n - **One file:** `fui module publish json|script|css`, `fui component update <name>`, or `fui file push <path...>`, which routes each path to the right publisher.\n - Unchanged files are skipped via the publish ledger.\n5. `fui file status` lists what is still unpublished.\n\n`fui remote module --part ui|html|script|css|load|components|imports` and `fui remote project --part config|modules|\u2026` show the server copy without touching the workspace. `fui remote diff <path...>` compares local files with the server.\n\nAsk the user before `module delete`, `component delete`, `import delete` and `user remove`.\n\n## Data\n\n```bash\nfui query \"SELECT TOP 20 * FROM tblStudent WHERE ClassID = 5\" --db lhu-docs/docs\nfui query -f report.sql --limit 0 # default cap 200 rows; 0 = all\necho \"SELECT 1 AS x\" | fui query -\n```\n\nNarrow with `TOP`/`WHERE` first. Every row you read costs tokens.\n\n## Structure (MSSQL)\n\n```bash\nfui schema --search studentid # compact DDL lines; --search matches the whole line (columns, params)\nfui schema --type API --search Student # Table View API \"API File\" Procedure Function\nfui schema pull # refresh schema.json + every local .sql body\nfui sp list --search Student # columns: db name kind count local\n```\n\n- With no cache, `schema` fails and tells you to run `fui schema pull`. It never pulls on its own.\n- A `# warning: ... last synced N days ago` note means pull before trusting the names.\n- `schema pull` archives and deletes local `.sql` files whose objects were dropped on the server.\n- It only lists drafts that exist in neither schema. `--prune-local-only` archives those too, but only once the user confirms they are stale.\n\n## Stored procedures (MSSQL)\n\n1. `fui sp get <name>` saves the live body to `_db/<alias>/<name>.sql`. A differing local copy is archived first.\n - For a new SP: `fui sp path <name> --db <ref>`, or `fui sp save <name> -f file.sql --db <ref>`.\n2. Edit the file. `CREATE` versus `ALTER` does not matter.\n3. `fui sp deploy <name>`:\n - saves the live body as a rollback copy\n - fixes the verb\n - grants `EXECUTE` to `[public]` on new procedures and checks it\n - clears the tAPI cache\n4. If the grant failed, do **not** redeploy. Run the printed `fui exec \"GRANT ...\"`.\n\nBefore writing `apiMocks` for an SP that exists, run `fui sp verify <name>`. It reads the real body (never calls the endpoint) and prints the params, the response shape, the error contract, read/write and a mock snippet. `sp delete` and `sp rename` change the server: ask the user first.\n\n## Writes need the user\n\n- Writes to real data exit `3` until you add `--confirm-write`. That covers `INSERT`, `UPDATE`, `DELETE`, `MERGE` and `TRUNCATE` in `query` or `exec`, and `ALTER TABLE ... ALTER COLUMN`.\n- Add `--confirm-write` **only after the user approved that exact write in chat.**\n- Always blocked, with no flag to unlock them. Show the SQL to the user to run by hand:\n - `DROP TABLE`\n - `ALTER TABLE ... DROP COLUMN` or `SWITCH`\n - `DELETE`/`UPDATE` without `WHERE` in `exec`\n- Never write or call a live API SP \"to test\" it. Read its body.\n\n## Workspace files (local, no network)\n\n```bash\nfui file where # workspace, project, module, dirs, and why\nfui file status [--all] # files changed since the last publish/sync, with the command to push each\nfui file history <path> # archived versions in _history, newest first\nfui file restore <path> [--at YYYYMMDD_HHMMSS] # the current file is archived first\nfui file ledger <path...> [--mark|--forget]\n```\n\n## Compose with shell\n\nFilter and loop in the shell. Piped output costs no tokens. Every table also takes `--filter text`, `--where col=value` and `--fields a,b` (applied after the request, so nothing else changes).\n\n```bash\nfui sp list --no-header --type API | cut -f2 # API SP names\nfui sp list --no-header | awk -F'\\t' '$5==\"yes\"{print $2}' # SPs with a local copy\nfui schema --type Procedure | grep -i tblStudent | cut -d' ' -f3 # procedures mentioning a word\nfui sp list --no-header --search Student | cut -f2 | xargs -n1 fui sp get\nfui query \"SELECT TOP 5 ID, Name FROM tblX\" --json | jq -r '.[].Name'\n```\n\n- Check exit codes. An exit `3` inside a loop means ask the user; do not retry.\n- Never put `--confirm-write` in a loop or `xargs`.\n\n## Connections and secrets\n\n```bash\nprintf %s \"$TOKEN\" | fui db add docs -p lhu-docs --token - --api-domain tapi.lhu.edu.vn --user-token-env UT\nfui db add local -p demo --conn-env PG_URL # postgres:// mysql:// sqlite:// connect directly\nfui db default lhu-docs/docs\nfui db test --db lhu-docs/docs\nfui token decode --db lhu-docs/docs # never shows the password\n```\n\nSecrets are accepted only from stdin (`-`) or `--*-env VAR`. Never echo or paste token values.\n\n- Never `cat`, `jq` or otherwise print `_db/_connections.json`, `_db/config.json` or MCP config files. Use `fui db list`, `fui token decode` or `fui config`; they never print secrets.\n- No token at hand? `fui db user-token` lists fingerprints of the tokens already in the workspace; copy one with `--from <project/alias>`. The value never appears.\n- Know the alias but not the connection string? `fui db alias list`, then `fui db add-by-name <alias>`: it borrows the SQL login of a connection on the same apiDomain.\n- `right *` and `db alias new` write to the shared acc system. Ask the user first, especially for `delete`.\n- `fui db list` shows a pre-1.4 `_db/config.json` as `engine=legacy`. Migrate it with `fui db import-legacy -p <project>`.\n\n## FUI conventions\n\n- tAPI routes:\n - `spAPI_<Fn>` \u2192 `/<alias>/<Fn>`\n - `spAPI_AUTH_<Fn>` \u2192 `/<alias>/auth/<Fn>`\n - `spAPIFILE_*` \u2192 file endpoints\n- tAPI parameters must not have default values (`@x int = 0`). Deploy blocks them.\n- Every new procedure needs `GRANT EXECUTE ... TO [public]`. `sp deploy` does it; raw `exec` only reports it.\n\n`fui --help` lists commands. `fui <command> --help` shows usage for one command.\n";async function su(e){if(e.targets.cwdTarget())return e.paths.root;return(await e.session.getTarget())?.workspaceRoot}var Ah={name:"skill",usage:"fui skill",summary:"Print the fui CLI agent guide",async run(){k(Ns)}},Nh={name:"skill get",mcpName:"skill_get",usage:"fui skill get",summary:"Print the FUI domain skill (SKILL.md + Tier-A refs with an active target, else the summary); marks it loaded",async run(e){let t=await Ja(await su(e));await e.session.markSkillLoaded(),E(`# source: ${t.source}; more: fui skill ref <references/x.md ...>`),k(`${t.text}
|
|
352
|
+
`)}},_h={name:"skill ref",mcpName:"skill_reference_get",usage:"fui skill ref <path> [path...] e.g. references/db-workflow.md",summary:"Print skill reference files; marks the skill loaded",async run(e){if(e.positionals.length===0)throw new f("missing <path>; see references/INDEX.md");await e.session.markSkillLoaded();let t=await su(e),n=t?Xn(t,"_skills",Dt):await sn();E(`# skill dir: ${n}`),k(`${(await Va(n,e.positionals)).trimStart()}
|
|
353
|
+
`)}},Dh={name:"skill loaded",usage:"fui skill loaded",summary:"Record that the skill was read (after reading installed skill files yourself); satisfies strict-mode gates",async run({session:e}){await e.markSkillLoaded(),k(`skill marked loaded for this session
|
|
354
|
+
`)}},xh={name:"skill sync",mcpName:"skill_sync",usage:"fui skill sync",summary:"Re-copy the bundled fui-skill into <workspace>/_skills/",async run({paths:e}){let t=await Io(e.root);k(`${t.skillDir} (${t.source})
|
|
355
|
+
`)}},Oh={name:"skill install",usage:"fui skill install [--dir <skills-root>]",summary:"Install the fui and fui-skill Claude skills into ~/.claude/skills (or --dir)",options:{dir:{type:"string"}},async run({values:e}){let t=h(e,"dir")??Xn(Th(),".claude","skills"),n=Xn(t,"fui","SKILL.md");await Bun.write(n,Ns);let o=Xn(t,Dt);await Eh(o,{recursive:!0,force:!0}),await Ph(t,{recursive:!0}),await Rh(await sn(),o,{recursive:!0}),k(`${n}
|
|
356
|
+
${o}
|
|
357
|
+
`)}},Mh={name:"design list",mcpName:"design_list",usage:"fui design list",summary:"List bundled design systems (brands) for fui design read",async run({render:e}){let t=await Zr();E(`# ${t.length} brands`),k(e.json?`${JSON.stringify(t)}
|
|
358
|
+
`:`${t.join(`
|
|
359
|
+
`)}
|
|
360
|
+
`)}},Fh={name:"design read",mcpName:"design_read",usage:"fui design read <brand>",summary:"Print a brand's DESIGN.md",async run({positionals:e}){let t=e[0];if(!t)throw new f("missing <brand>; run: fui design list");let n=await Zr();if(!n.includes(t))throw new f(`unknown brand "${t}"; available: ${n.join(", ")}`);let o=Xn(await sn(),"design-md",t,"DESIGN.md");E(`# source: ${o}`),k(await H(o))}},iu=[Ah,Nh,_h,Dh,xh,Oh,Mh,Fh];function Ut(e){let t=e[0];if(!t)throw new f("missing <name>");return t}var Lh={name:"sp list",mcpName:"db_sp_list",usage:'fui sp list [--db ref] [--type Table|View|API|"API File"|Procedure|Function] [--search name]',summary:"List objects from the schema cache (every connection unless --db); count = params or columns",options:{type:{type:"string"},search:{type:"string"}},async run(e){let{rows:t,notes:n}=await Xi(await z(e),{db:h(e.values,"db"),objectType:As(h(e.values,"type")),search:h(e.values,"search")});for(let r of n)E(`# ${r}`);let o=new Map,s=[];for(let r of t){let i=o.get(r.db);if(!i){let[a,c]=r.db.split("/");i=new Set((await e.store.localSqlNames(a,c)).map((l)=>l.toLowerCase())),o.set(r.db,i)}s.push({...r,local:i.has(r.name.toLowerCase())?"yes":"no"})}k(X(s,e.render))}},Uh={name:"sp get",mcpName:"db_sp_get",usage:"fui sp get <name> [--db ref] [--print | -o file]",summary:"Fetch a definition into _db/<alias>/<name>.sql (a differing local copy is archived first)",options:{print:{type:"boolean"},out:{type:"string",short:"o"}},async run(e){let t=await Yi(await z(e),Ut(e.positionals),h(e.values,"db"));for(let o of t.notes)E(`# ${o}`);let n=h(e.values,"out");if(n)await Bun.write(n,t.body),k(`${n}
|
|
361
|
+
`);else k(e.values.print===!0?t.body:`${t.path}
|
|
362
|
+
`)}},Bh={name:"sp save",mcpName:"db_sp_save",usage:'fui sp save <name> (--sql "<text>" | -f file | -) [--db ref]',summary:"Write a script to _db/<alias>/<name>.sql locally; nothing reaches the database",options:{sql:{type:"string"},file:{type:"string",short:"f"}},async run(e){let t=Ut(e.positionals),n=h(e.values,"sql"),o=e.positionals[1];if(n!==void 0&&o!==void 0)throw new f("pass --sql or - / -f, not both");let s=await vn(n??o,h(e.values,"file")),r=await Qi(await z(e),t,s,h(e.values,"db"));for(let i of r.notes)E(`# ${i}`);k(`${r.path}
|
|
363
|
+
next: fui sp deploy ${t} --db ${r.alias}
|
|
364
|
+
`)}},Hh={name:"sp path",usage:"fui sp path <name> [--db ref]",summary:"Print the local .sql path to edit (file may not exist yet)",async run(e){let t=pt(Ut(e.positionals)),n=await z(e),o=await e.store.resolve({db:h(e.values,"db"),project:n.project,contextProject:n.contextProject,objectName:t});k(`${e.store.paths.spFile(o.projectId,o.alias,t)}
|
|
365
|
+
`)}},Wh={name:"sp deploy",mcpName:"db_sp_deploy",usage:"fui sp deploy <name> [--db ref]",summary:"Deploy the local .sql: rollback copy, CREATE/ALTER fix, grant, tAPI cache clear",async run(e){let t=await Zi(await z(e),Ut(e.positionals),h(e.values,"db"));k(`${t.notes.join(`
|
|
366
|
+
`)}
|
|
367
|
+
`)}},qh={name:"sp help",mcpName:"db_sp_help",usage:"fui sp help <name|Fn> [--db ref] [--full]",summary:"Call tAPI /help: shows params and clears the tAPI cache (never fails)",options:{full:{type:"boolean"}},async run(e){let t=await ea(await z(e),Ut(e.positionals),h(e.values,"db"),e.values.full===!0?0:Ji);for(let n of t.notes)E(`# ${n}`);k(t.data??"")}};function Jh(e){if(e===void 0)return;let t;try{t=JSON.parse(e)}catch(n){throw new f(`--params is not valid JSON: ${n.message}`)}if(!t||typeof t!=="object"||Array.isArray(t))throw new f("--params must be a JSON object");return t}var Vh={name:"sp verify",mcpName:"db_sp_verify",usage:`fui sp verify <name|Fn> [--db ref] [--params '{"ClassID":"C01"}'] [--url-param v]... [--check-grant]`,summary:"Static contract check of an API SP from its real body (params, response shape, errors, read/write, mock); never calls it",options:{params:{type:"string"},"url-param":{type:"string",multiple:!0},"check-grant":{type:"boolean"}},async run(e){let t=e.values["url-param"],n=await oa(await z(e),{name:Ut(e.positionals),db:h(e.values,"db"),params:Jh(h(e.values,"params")),urlParams:Array.isArray(t)?t.map(String):void 0,checkGrant:e.values["check-grant"]===!0});k(`${n.notes.join(`
|
|
67
368
|
`)}
|
|
68
|
-
`)}},
|
|
369
|
+
`)}},Kh={name:"sp delete",mcpName:"db_sp_delete",usage:"fui sp delete <name> --type Procedure|Function [--db ref]",summary:"DROP a procedure or function on the server (ask the user first); archives the local .sql",options:{type:{type:"string"}},async run(e){let t=h(e.values,"type");if(t!=="Procedure"&&t!=="Function")throw new f("--type must be Procedure or Function");let n=await ra(await z(e),Ut(e.positionals),t,h(e.values,"db"));k(`${n.notes.join(`
|
|
69
370
|
`)}
|
|
70
|
-
`)}},
|
|
71
|
-
`)}},nt=[on,sn];var an=/^[A-Za-z0-9_]+$/,rt=2000;function Q(e){let t=e[0];if(!t)throw new a("missing <name>");if(!an.test(t))throw new a(`invalid object name "${t}": letters, digits and "_" only`);return t}async function V({store:e,values:t},n){let o=await e.resolve({db:u(t,"db"),project:u(t,"project"),objectName:n});return U(o,"sp"),o}async function ot(e,t){let n=await e.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(${F(t)})), '') AS sqlText`);return String(n[0]?.sqlText??"")}async function cn(e,t){try{let n=await e.query(Xe(t)),o=Number(n[0]?.granted??Number.NaN);return Number.isFinite(o)?o>0:null}catch{return null}}var un={name:"sp list",usage:"fui sp list [--db ref] [--filter text]",summary:"List procedures/functions from the schema cache plus local-only .sql files",options:{filter:{type:"string"}},async run(e){let{store:t,values:n,json:o}=e,r=await t.resolve({db:u(n,"db"),project:u(n,"project")});U(r,"sp list");let s=await fe(t,r),i=new Set((await t.localSqlNames(r.projectId,r.alias)).map((p)=>p.toLowerCase())),c=u(n,"filter")?.toLowerCase(),l=[],d=new Set;for(let p of s.procedures){let m=G(p);if(!m)continue;d.add(m.toLowerCase()),l.push({name:m,kind:K(p),local:i.has(m.toLowerCase())?"yes":"no"})}for(let p of await t.localSqlNames(r.projectId,r.alias))if(!d.has(p.toLowerCase()))l.push({name:p,kind:"local-only",local:"yes"});let g=l.filter((p)=>!c||p.name.toLowerCase().includes(c));h(`# ${r.projectId}/${r.alias} count=${g.length}`),f(T(g,o))}},ln={name:"sp get",usage:"fui sp get <name> [--db ref] [--print]",summary:"Fetch a definition from the server into _db/<alias>/<name>.sql",options:{print:{type:"boolean"}},async run(e){let t=Q(e.positionals),n=await V(e,t),o=await E(n,(i)=>ot(i,t));if(!o.trim())throw Error(`"${t}" not found on ${n.projectId}/${n.alias}`);let r=e.store.paths.spFile(n.projectId,n.alias,t),s=Bun.file(r);if(await s.exists()&&await s.text()!==o)h(`# old local copy archived: ${await R(e.store.paths,r)}`);await Bun.write(r,o),f(e.values.print===!0?o:`${r}
|
|
72
|
-
`)}},dn={name:"sp path",usage:"fui sp path <name> [--db ref]",summary:"Print the local .sql path to edit (file may not exist yet)",async run(e){let t=Q(e.positionals),n=await V(e,t);f(`${e.store.paths.spFile(n.projectId,n.alias,t)}
|
|
73
|
-
`)}},pn={name:"sp deploy",usage:"fui sp deploy <name> [--db ref]",summary:"Deploy the local .sql: archive live body, align CREATE/ALTER, grant, clear tAPI cache",async run(e){let t=Q(e.positionals),n=await V(e,t),{projectId:o,alias:r}=n,s=e.store.paths,i=s.spFile(o,r,t),c=Bun.file(i);if(!await c.exists())throw new a(`file not found: ${i}; run: fui sp get ${t} or create it`);let l=await c.text();if(!l.trim())throw new a(`file is empty: ${i}`);let d=Je(l)??t,g=me(d);if(g){let w=Ye(l);if(w.length>0)throw new P(`tAPI SP parameters cannot have default values; remove "= ..." then deploy again:
|
|
74
|
-
`+w.map((y)=>` ${y}`).join(`
|
|
75
|
-
`))}let p=`${o}/${r}`,m=[];await E(n,async(w)=>{let y=null,N="";try{N=(await ot(w,d)).trim(),y=N.length>0}catch{}if(N){let b=await R(s,i,{content:N});m.push(`rollback copy: ${b}`)}let k=Ve(l,y);await w.exec(k.sql),m.unshift(`ok deployed ${d} to ${p}${k.changed?` (ran as ${k.to}, file unchanged)`:""}`);let M=Qe(l),he=y===null?/\bCREATE\s+(?:PROC|PROCEDURE)\b/i.test(l):!y;if(he&&!M)try{await w.exec(J(d)),m.push("grant EXECUTE to [public]: applied")}catch(b){m.push(`grant FAILED (${b.message}); do not redeploy, run: fui exec "${J(d)}" --db ${p}`)}if(!M){let b=await cn(w,d);if(b===!1)m.push(`[public] lacks EXECUTE; tAPI calls will fail. run: fui exec "${J(d)}" --db ${p}`);else if(b===!0&&!he)m.push("[public] has EXECUTE")}if(g)try{let b=await ae(D(n.config.apiDomain),r,g.isAuth,g.fnName,n.config.userToken);m.push(b.ok?`tAPI cache cleared (HTTP ${b.status})`:`tAPI /help HTTP ${b.status}; ignored`)}catch(b){m.push(`tAPI /help failed (${b.message}); ignored`)}}),f(`${m.join(`
|
|
371
|
+
`)}},Gh={name:"sp rename",mcpName:"db_sp_rename",usage:"fui sp rename <old> <new> [--db ref]",summary:"sp_rename a procedure, function or table on the server; renames the local .sql to match",async run(e){let[t,n]=e.positionals;if(!t||!n)throw new f("usage: fui sp rename <old> <new>");let o=await sa(await z(e),t,n,h(e.values,"db"));k(`${o.notes.join(`
|
|
76
372
|
`)}
|
|
77
|
-
`)}},
|
|
78
|
-
`)
|
|
79
|
-
|
|
80
|
-
|
|
373
|
+
`)}},au=[Lh,Uh,Bh,Hh,Wh,qh,Vh,Kh,Gh];var zh={name:"token decode",usage:"fui token decode [--db ref]",summary:"Show engine, server, database and user of a stored token (password never shown)",async run({store:e,values:t,render:n}){let o=await e.resolve({db:h(t,"db"),project:h(t,"project")}),s={db:`${o.projectId}/${o.alias}`,...io(o.dbToken)};k(X([s],n))}},Xh={name:"token encode",usage:"fui token encode --server S --database D --uid U --pwd-env VAR",summary:"Print a Fui MSSQL dbToken (Base64) built like Fui MCP",options:{server:{type:"string"},database:{type:"string"},uid:{type:"string"},"pwd-env":{type:"string"}},async run({values:e}){let t=h(e,"server"),n=h(e,"database"),o=h(e,"uid"),s=await Pe(void 0,h(e,"pwd-env"),"--pwd");if(!t||!n||!o||!s)throw new f("need --server, --database, --uid and --pwd-env");k(`${Jt(t,n,o,s)}
|
|
374
|
+
`)}},cu=[zh,Xh];var Yh={name:"update",usage:"fui update [--check]",summary:"Update fui to the latest npm release via bun add -g (--check only reports)",options:{check:{type:"boolean"},background:{type:"boolean"}},async run({values:e}){if(e.background===!0){await Mr().catch(()=>{return});return}if(e.check===!0){let n=await xr();k(Or(n)?`update available: ${je} -> ${n}; run: fui update
|
|
375
|
+
`:`fui ${je} is current (latest ${n})
|
|
376
|
+
`);return}let t=await Mr();if(t.action==="current")k(`fui ${je} is current
|
|
377
|
+
`);else if(t.action==="installed")k(`updated ${je} -> ${t.latest}; refresh installed skills: fui skill install
|
|
378
|
+
`);else if(t.action==="manual")E(`# ${Bun.main} is not a bun global install (bun link checkout or compiled binary); it cannot replace itself`),k(`update available: ${je} -> ${t.latest}; install: ${t.detail}
|
|
379
|
+
`);else return E(t.detail??""),k(`update to ${t.latest} failed; retry: bun add -g ${On}@${t.latest}
|
|
380
|
+
`),Oe.Runtime}},lu=[Yh];var _s=[...Ca,...ba,...cu,...ou,...ru,...au,...cc,...Pl,...Vl,...nu,...Pa,...iu,...Zl,...Ia,...lu],du=`global: -p/--project <id> --db <alias|project/alias> --workdir <dir> --json --no-header -h/--help -v/--version
|
|
381
|
+
lists: --filter text --where col=value (repeatable) --fields a,b,c (client-side; the request is unchanged)
|
|
382
|
+
exit: 0 ok, 1 runtime error, 2 usage error, 3 guard blocked`;function uu(){let e=Math.max(..._s.map((n)=>n.name.length)),t=_s.map((n)=>` ${n.name.padEnd(e)} ${n.summary}`);return`fui ${je}: FUI platform CLI
|
|
81
383
|
|
|
82
384
|
commands:
|
|
83
|
-
${
|
|
385
|
+
${t.join(`
|
|
84
386
|
`)}
|
|
85
387
|
|
|
86
|
-
${
|
|
388
|
+
${du}
|
|
87
389
|
run "fui skill" for the agent guide
|
|
88
|
-
`}function
|
|
390
|
+
`}function Zh(e){return`${e.usage}
|
|
89
391
|
${e.summary}
|
|
90
|
-
${
|
|
91
|
-
`}function
|
|
92
|
-
`),
|
|
93
|
-
|
|
392
|
+
${du}
|
|
393
|
+
`}var eg=3;function tg(e){let t=[];for(let n of e.slice(0,eg)){if(n.startsWith("-"))break;t.push(n)}for(let n=t.length;n>0;n--){let o=t.slice(0,n).join(" "),s=_s.find((r)=>r.name===o);if(s)return{command:s,rest:e.slice(n)}}return null}async function ng(e,t,n,o){if(n instanceof $e&&n.severity==="soft")return;let s=n instanceof $e?n.logKind:"error",r=n instanceof lt?n.report:`\u274C ${o}`;await Gl(e,t.mcpName??`fui ${t.name}`,r,s)}async function og(e){let t=tg(e);if(!t){let r=e[0];if(!r||r==="-h"||r==="--help"||r==="help")return k(uu()),Oe.Ok;if(r==="-v"||r==="--version")return k(`${je}
|
|
394
|
+
`),Oe.Ok;return E(`unknown command: ${e.slice(0,2).join(" ")}
|
|
395
|
+
`),E(uu()),Oe.Usage}let{command:n,rest:o}=t,s;try{let r;try{r=Qh({args:o,options:{...ni,...n.options},allowPositionals:!0,strict:!0})}catch(i){throw new f(`${i.message}
|
|
396
|
+
usage: ${n.usage}`)}if(r.values.help===!0)return k(Zh(n)),Oe.Ok;return s=await oi(r.values,r.positionals),await n.run(s)??Oe.Ok}catch(r){let i=Ms(r),a=i===Oe.Guard?"guard":"error",c=r.message;if(E(`${a}: ${c}`),s)await ng(s.paths.root,n,r,c);return i}}if(import.meta.main){let e=await Sa().catch(()=>null);if(e)E(e);process.exitCode=await og(Bun.argv.slice(2))}export{og as main};
|