@zhama/tego-plugin-sdk 1.36.2 → 1.37.1
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/agent/RULES.md +123 -0
- package/agent/guides/access.md +39 -0
- package/agent/guides/backend.md +76 -0
- package/agent/guides/claw.md +18 -0
- package/agent/guides/desktop-web.md +48 -0
- package/agent/guides/frontend.md +83 -0
- package/agent/guides/host-api-versions.md +34 -0
- package/agent/guides/i18n.md +17 -0
- package/agent/guides/manifest.md +26 -0
- package/agent/guides/migrations.md +17 -0
- package/agent/guides/storage.md +18 -0
- package/agent/guides/testing.md +17 -0
- package/agent/guides/ui.md +22 -0
- package/agent/reference/plugin-sdk-api.md +561 -0
- package/bin/tego-plugin-pack.mjs +1 -1
- package/bin/tego-plugin.mjs +4 -2
- package/dist/chunk-2NTAWYCG.js +1 -0
- package/dist/chunk-2P6UEAPV.cjs +1 -0
- package/dist/chunk-45Y4CR4R.cjs +1 -0
- package/dist/{chunk-PWSTCQRN.cjs → chunk-4LKRXDYY.cjs} +13 -12
- package/dist/{chunk-7XHF3YSK.js → chunk-56QOZQTY.js} +1 -1
- package/dist/{chunk-C2OCOPZQ.cjs → chunk-6HNG5OZI.cjs} +1 -1
- package/dist/{chunk-2ORO36BY.js → chunk-DEGQDJF6.js} +1 -1
- package/dist/chunk-EIVA5BR6.js +1 -0
- package/dist/{chunk-P2Q2AKWP.js → chunk-FCYAAOHC.js} +17 -16
- package/dist/chunk-HZ4YWRYX.cjs +5 -0
- package/dist/chunk-I5Z3ECC4.js +5 -0
- package/dist/chunk-INCA3GFW.js +1 -0
- package/dist/chunk-IVJECPGG.js +1 -0
- package/dist/{chunk-KMN5RHLX.cjs → chunk-MWMGJJ74.cjs} +1 -1
- package/dist/chunk-N4SXZZPY.cjs +1 -0
- package/dist/chunk-SKNPNPJP.cjs +1 -0
- package/dist/dev.cjs +165 -59
- package/dist/dev.d.cts +37 -1
- package/dist/dev.d.ts +37 -1
- package/dist/dev.js +168 -62
- package/dist/doctor.cjs +8 -0
- package/dist/doctor.d.cts +62 -0
- package/dist/doctor.d.ts +62 -0
- package/dist/doctor.js +8 -0
- package/dist/frontend.d.cts +33 -1
- package/dist/frontend.d.ts +33 -1
- package/dist/hono.cjs +1 -0
- package/dist/hono.d.cts +84 -0
- package/dist/hono.d.ts +84 -0
- package/dist/hono.js +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1 -1
- package/dist/manifest.cjs +1 -1
- package/dist/manifest.d.cts +55 -2
- package/dist/manifest.d.ts +55 -2
- package/dist/manifest.js +1 -1
- package/dist/migrations-sync.cjs +1 -0
- package/dist/migrations-sync.d.cts +20 -0
- package/dist/migrations-sync.d.ts +20 -0
- package/dist/migrations-sync.js +1 -0
- package/dist/pack.cjs +1 -1
- package/dist/pack.d.cts +5 -0
- package/dist/pack.d.ts +5 -0
- package/dist/pack.js +1 -1
- package/dist/publish.cjs +1 -1
- package/dist/publish.js +1 -1
- package/dist/react.cjs +1 -0
- package/dist/react.d.cts +101 -0
- package/dist/react.d.ts +101 -0
- package/dist/react.js +1 -0
- package/dist/scaffold.cjs +671 -17
- package/dist/scaffold.d.cts +42 -18
- package/dist/scaffold.d.ts +42 -18
- package/dist/scaffold.js +673 -19
- package/dist/server-runtime.cjs +1 -1
- package/dist/server-runtime.d.cts +27 -2
- package/dist/server-runtime.d.ts +27 -2
- package/dist/server-runtime.js +1 -1
- package/dist/testing.cjs +6 -2
- package/dist/testing.d.cts +19 -1
- package/dist/testing.d.ts +19 -1
- package/dist/testing.js +6 -2
- package/dist/verify-FF2RD2MV.cjs +1 -0
- package/dist/verify-W7ZTUCO4.js +1 -0
- package/dist/vite.cjs +1 -1
- package/dist/vite.d.cts +27 -1
- package/dist/vite.d.ts +27 -1
- package/dist/vite.js +1 -1
- package/package.json +31 -3
- package/dist/chunk-3WA3M347.cjs +0 -1
- package/dist/chunk-5CU33JVY.js +0 -1
- package/dist/chunk-7K37PVGV.js +0 -5
- package/dist/chunk-BTHXQWHT.js +0 -1
- package/dist/chunk-ROFUQLOZ.cjs +0 -1
- package/dist/chunk-W7C357A6.cjs +0 -5
package/dist/dev.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var V=Object.defineProperty;var i=(e,t)=>V(e,"name",{value:t,configurable:!0});var chunkIMAVYQ4U_cjs=require("./chunk-IMAVYQ4U.cjs"),chunkPWSTCQRN_cjs=require("./chunk-PWSTCQRN.cjs");require("./chunk-W7C357A6.cjs");var chunkKMN5RHLX_cjs=require("./chunk-KMN5RHLX.cjs");require("./chunk-3WA3M347.cjs");var chunkROFUQLOZ_cjs=require("./chunk-ROFUQLOZ.cjs");require("./chunk-C2OCOPZQ.cjs");var chunkBN6TGHSB_cjs=require("./chunk-BN6TGHSB.cjs");require("./chunk-AJY7ZHXK.cjs"),require("./chunk-OP64Y4IN.cjs");var crypto=require("crypto"),fs=require("fs"),promises=require("fs/promises"),http=require("http"),module$1=require("module"),path=require("path"),child_process=require("child_process"),net=require("net"),DEV_USER_HEADER="x-tego-dev-user-id",DEV_ROLES_HEADER="x-tego-dev-roles",DEV_TENANT_ID="dev-tenant",DEFAULT_DEV_USER_ID="00000000-0000-4000-8000-000000000001";function devCallerOf(e){const t=i(r=>(Array.isArray(r)?r[0]:r)?.trim()??"","single"),n=t(e[DEV_USER_HEADER])||DEFAULT_DEV_USER_ID;let a=[];try{const r=JSON.parse(t(e[DEV_ROLES_HEADER])||"[]");Array.isArray(r)&&(a=r.filter(o=>typeof o=="string"))}catch{}return{userId:n,roles:a}}i(devCallerOf,"devCallerOf");function gatewayHeaders(e,t,n,a=Date.now()){const r={};for(const[s,c]of Object.entries(e)){const u=s.toLowerCase();c===void 0||u.startsWith("x-tego-")||u==="cookie"||u==="authorization"||u==="host"||u==="connection"||(r[u]=Array.isArray(c)?c.join(", "):c)}const o=JSON.stringify(t.roles),l=String(a);return r[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.userId]=t.userId,r[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.tenantId]=DEV_TENANT_ID,r[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.roles]=o,r[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.issuedAt]=l,r[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.signature]=chunkIMAVYQ4U_cjs.signPluginContext({userId:t.userId,tenantId:DEV_TENANT_ID,rolesHeader:o,avatarId:"",issuedAt:l},n),r}i(gatewayHeaders,"gatewayHeaders");var DEV_MIGRATIONS_TABLE="tego_dev_migrations";function devSchemaName(e){return`tego_dev_${e.toLowerCase().replace(/[^a-z0-9_]/g,"_")}`.slice(0,63)}i(devSchemaName,"devSchemaName");function devDatabaseUrl(e,t){const n=new URL(e);n.searchParams.delete("options");const a=n.search.replace(/^\?/,"");return n.search=`?${a?`${a}&`:""}options=${encodeURIComponent(`-c search_path=${t}`)}`,n.toString()}i(devDatabaseUrl,"devDatabaseUrl");async function connect(e){let t;try{t=await import("pg")}catch(r){throw new Error(`The local backend needs the "pg" driver; add it to the plugin's dependencies.`,{cause:r})}const n=t.default?.Client??t.Client;if(!n)throw new Error('The "pg" module has no Client constructor.');const a=new n({connectionString:e});try{await a.connect()}catch(r){throw new Error(`Cannot connect to DATABASE_URL (${redactUrl(e)}): ${r instanceof Error?r.message:String(r)}`,{cause:r})}return a}i(connect,"connect");function redactUrl(e){try{const t=new URL(e);return t.password&&(t.password="***"),t.toString()}catch{return"<unparseable DATABASE_URL>"}}i(redactUrl,"redactUrl");var quote=i(e=>`"${e.replace(/"/g,'""')}"`,"quote");async function applyDevMigrations(e){const t=await promises.readdir(e.migrationsDir).then(o=>o.filter(l=>l.endsWith(".sql")).sort(),o=>{if(o.code==="ENOENT")return[];throw o}),n=await connect(e.databaseUrl),a=quote(e.schema),r=`${a}.${quote(DEV_MIGRATIONS_TABLE)}`;try{await n.query(`CREATE SCHEMA IF NOT EXISTS ${a}`),await n.query(`CREATE TABLE IF NOT EXISTS ${r} (name text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())`);const o=new Set((await n.query(`SELECT name FROM ${r}`)).rows.map(s=>String(s.name))),l=[];for(const s of t){if(o.has(s))continue;const c=await promises.readFile(path.join(e.migrationsDir,s),"utf8");await n.query("BEGIN");try{await n.query(`SET LOCAL search_path TO ${a}`),await n.query("SELECT set_config('app.tenant_id', $1, true)",[e.tenantId]),await n.query(c),await n.query(`INSERT INTO ${r} (name) VALUES ($1)`,[s]),await n.query("COMMIT")}catch(u){throw await n.query("ROLLBACK").catch(()=>{}),new Error(`Migration ${s} failed: ${u instanceof Error?u.message:String(u)}`,{cause:u})}l.push(s)}return l}finally{await n.end().catch(()=>{})}}i(applyDevMigrations,"applyDevMigrations");var REBUILD_DEBOUNCE_MS=250,STOP_TIMEOUT_MS=5e3;function freePort(){return new Promise((e,t)=>{const n=net.createServer();n.once("error",t),n.listen(0,"127.0.0.1",()=>{const a=n.address(),r=typeof a=="object"&&a?a.port:0;n.close(()=>e(r))})})}i(freePort,"freePort");function stopChild(e){return!e||e.exitCode!==null||e.signalCode!==null?Promise.resolve():new Promise(t=>{const n=setTimeout(()=>{e.kill("SIGKILL"),t()},STOP_TIMEOUT_MS);e.once("exit",()=>{clearTimeout(n),t()}),e.kill("SIGTERM")})}i(stopChild,"stopChild");async function startDevBackend(e){const{part:t,log:n}=e;if(t.runtime==="python")throw new Error("The local preview runs TypeScript backends only; a python backend runs through the platform.");const a=await freePort(),r=devSchemaName(e.pluginId),o=path.resolve(e.root,t.migrationsDirPath),l=path.join(e.outDir,t.entryPath),s={...process.env,NODE_ENV:"development",TEGO_PLUGIN_ID:e.pluginId,TEGO_PLUGIN_VERSION:e.version,TEGO_PLUGIN_INSTALL_ID:"dev-install",TEGO_TENANT_ID:DEV_TENANT_ID,TEGO_PLUGIN_PORT:String(a),TEGO_PLUGIN_HEALTH_PATH:t.healthPath,TEGO_PLUGIN_HOST_API_URL:e.hostApiUrl,TEGO_PLUGIN_HOST_API_TOKEN:e.hostApiToken,TEGO_PLUGIN_GATEWAY_KEY:e.gatewayKey,DATABASE_URL:devDatabaseUrl(e.databaseUrl,r)};let c=null,u=!1;async function p(){await promises.rm(e.outDir,{recursive:!0,force:!0}),await chunkPWSTCQRN_cjs.buildPluginBackend(t,{root:e.root,outDir:e.outDir});const f=await applyDevMigrations({databaseUrl:e.databaseUrl,schema:r,migrationsDir:o,tenantId:DEV_TENANT_ID});for(const y of f)n(`[backend] applied migration ${y}`)}i(p,"build");function m(){const f=child_process.spawn(process.execPath,[l],{env:s,stdio:["ignore","pipe","pipe"]});c=f,f.stdout?.on("data",y=>process.stdout.write(y)),f.stderr?.on("data",y=>process.stderr.write(y)),f.on("exit",y=>{c===f&&!u&&(c=null,n(`[backend] exited (code ${y??"null"}); it starts again on the next save`))})}i(m,"start"),await p(),m(),n(`[backend] ${e.pluginId} on 127.0.0.1:${a}, schema ${r} in ${redactUrl(e.databaseUrl)}`);let v=null,h=null,_=!1;const k=i(async()=>{if(h){_=!0;return}h=(async()=>{try{await p();const f=c;c=null,await stopChild(f),u||(m(),n("[backend] rebuilt and restarted"))}catch(f){n(`[backend] rebuild failed, the previous build keeps running: ${f instanceof Error?f.message:String(f)}`)}})(),await h,h=null,_&&!u&&(_=!1,await k())},"rebuild"),x=[];for(const f of[t.dir,"shared"]){const y=path.resolve(e.root,f);fs.existsSync(y)&&x.push(fs.watch(y,{recursive:!0},(I,S)=>{S&&/(^|[\\/])(node_modules|dist)([\\/]|$)/.test(String(S))||(v&&clearTimeout(v),v=setTimeout(()=>void k(),REBUILD_DEBOUNCE_MS))}))}return{port:a,schema:r,async close(){u=!0,v&&clearTimeout(v);for(const f of x)f.close();await h,await stopChild(c)}}}i(startDevBackend,"startDevBackend");var FAKE_HOST_PREFIX="/__tego/host",FAKE_FILES_PREFIX="/__tego/files",MAX_JSON_BYTES=1024*1024;async function readBody(e,t=1/0){const n=[];let a=0;for await(const r of e){if(a+=r.length,a>t)throw Object.assign(new Error("body too large"),{status:413});n.push(r)}return Buffer.concat(n)}i(readBody,"readBody");function send(e,t,n){if(n===void 0){e.writeHead(t),e.end();return}e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(n))}i(send,"send");function storagePath(e,t){const n=path.resolve(e),a=path.resolve(n,t);if(!t||path.isAbsolute(t)||!a.startsWith(n+path.sep))throw Object.assign(new Error(`invalid storage key ${JSON.stringify(t)}`),{status:400});return a}i(storagePath,"storagePath");var metaOf=i(e=>`${e}.tego-meta.json`,"metaOf");async function head(e){const t=await promises.stat(e).catch(()=>null);if(!t?.isFile())return null;const n=await promises.readFile(metaOf(e),"utf8").then(a=>JSON.parse(a),()=>({}));return{size:t.size,contentType:n.contentType??null,versionTag:crypto.createHash("sha1").update(`${t.size}:${t.mtimeMs}`).digest("hex")}}i(head,"head");async function put(e,t,n){await promises.mkdir(path.dirname(e),{recursive:!0}),await promises.writeFile(e,t),await promises.writeFile(metaOf(e),JSON.stringify({contentType:n??null}))}i(put,"put");async function handleFakeHost(e,t,n){const a=new URL(e.url??"/","http://preview.local");try{if(a.pathname.startsWith(FAKE_FILES_PREFIX)){const s=storagePath(n.storageDir,a.searchParams.get("key")??""),c=await head(s);if(!c)return send(t,404);t.writeHead(200,{"content-type":c.contentType??"application/octet-stream"}),t.end(await promises.readFile(s));return}if(e.headers.authorization!==`Bearer ${n.token}`)return send(t,401,{error:"bad host API token"});const r=a.pathname.slice(FAKE_HOST_PREFIX.length)||"/",o=i(async()=>JSON.parse((await readBody(e,MAX_JSON_BYTES)).toString("utf8")||"{}"),"json"),l=i(s=>storagePath(n.storageDir,String(s??"")),"keyFile");switch(r){case"/config":return send(t,200,{data:{values:n.config()}});case"/log":{const{level:s,message:c,fields:u}=await o();return n.log(`[backend ${String(s??"info")}] ${String(c??"")}${u?` ${JSON.stringify(u)}`:""}`),send(t,200,{data:{}})}case"/events":{const{name:s,payload:c}=await o();return n.log(`[event] ${String(s)} ${JSON.stringify(c??null)}`),send(t,200,{data:{}})}case"/notifications":return n.log(`[notification] ${JSON.stringify(await o())}`),send(t,200,{data:{id:crypto.randomUUID()}});case"/directory/identity":{const{userId:s}=await o(),c=n.users().get(String(s));return send(t,200,{data:{active:!!c,roleNames:c??[],memberships:[],checkedAt:new Date().toISOString()}})}case"/directory/list":{const{kind:s,search:c,ids:u}=await o(),p=typeof c=="string"?c.toLowerCase():"",m=Array.isArray(u)?new Set(u.map(String)):void 0,v=s==="users"?[...n.users().keys()].filter(h=>(!m||m.has(h))&&h.toLowerCase().includes(p)).map(h=>({id:h,displayName:h,active:!0})):[];return send(t,200,{data:{items:v,hasMore:!1}})}case"/directory/match":{const{userId:s,principals:c}=await o(),u=Array.isArray(c)?c:[];return send(t,200,{data:{active:n.users().has(String(s)),matched:u.flatMap((p,m)=>p.kind==="user"&&p.id===s?[m]:[]),checkedAt:new Date().toISOString()}})}case"/directory/validate":{const{principals:s}=await o(),c=Array.isArray(s)?s:[],u=n.users();return send(t,200,{data:{invalid:c.flatMap((p,m)=>p.kind==="user"&&u.has(String(p.id))?[]:[m])}})}case"/storage/get":case"/storage/stream":{const s=l(a.searchParams.get("key"));if(e.method==="POST"&&r==="/storage/stream")return await put(s,await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});const c=await head(s);if(!c)return send(t,404);let u=await promises.readFile(s);const p=Number(a.searchParams.get("bytes"));Number.isFinite(p)&&p>0&&(u=u.subarray(0,p)),t.writeHead(200,{"content-type":c.contentType??"application/octet-stream"}),t.end(u);return}case"/storage/put":return await put(l(a.searchParams.get("key")),await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});case"/storage/head":{const s=await head(l((await o()).key));return s?send(t,200,{data:s}):send(t,404)}case"/storage/delete":{const s=l((await o()).key);return await promises.rm(s,{force:!0}),await promises.rm(metaOf(s),{force:!0}),send(t,200,{data:{}})}case"/storage/copy":{const{sourceKey:s,destinationKey:c}=await o(),u=l(s),p=l(c);return await head(u)?(await promises.mkdir(path.dirname(p),{recursive:!0}),await promises.copyFile(u,p),await promises.copyFile(metaOf(u),metaOf(p)).catch(()=>{}),send(t,200,{data:await head(p)})):send(t,404)}case"/storage/signed-url":{const{key:s}=await o();l(s);const c=`${FAKE_FILES_PREFIX}?key=${encodeURIComponent(String(s))}`;return send(t,200,{data:{url:`${n.origin()}${c}`,path:c}})}default:return n.log(`[host] ${r} is not available in local preview`),send(t,501,{error:{code:"not_available_in_preview",message:`${r} is not available in local preview`}})}}catch(r){const o=r.status??500;send(t,o,{error:{message:r instanceof Error?r.message:String(r)}})}}i(handleFakeHost,"handleFakeHost");var SDK_PACKAGE="@tego/plugin-sdk",SDK_IMPORT=JSON.stringify(SDK_PACKAGE),SDK_FRONTEND_IMPORT=JSON.stringify(`${SDK_PACKAGE}/frontend`);function shellFiles(e){return[{path:"index.html",contents:indexHtml(e)},{path:"main.tsx",contents:mainTsx(e)},{path:"shell.css",contents:SHELL_CSS},{path:"plugin-surface.tsx",contents:PLUGIN_SURFACE_STUB}]}i(shellFiles,"shellFiles");var escapeHtml=i(e=>e.replace(/[&<>"]/g,t=>({"&":"&","<":"<",">":">",'"':"""})[t]),"escapeHtml");function indexHtml({displayName:e}){return`<!doctype html>
|
|
1
|
+
"use strict";var W=Object.defineProperty;var o=(e,t)=>W(e,"name",{value:t,configurable:!0});var chunkIMAVYQ4U_cjs=require("./chunk-IMAVYQ4U.cjs"),chunk4LKRXDYY_cjs=require("./chunk-4LKRXDYY.cjs");require("./chunk-HZ4YWRYX.cjs");var chunkMWMGJJ74_cjs=require("./chunk-MWMGJJ74.cjs");require("./chunk-SKNPNPJP.cjs"),require("./chunk-45Y4CR4R.cjs");var chunk2P6UEAPV_cjs=require("./chunk-2P6UEAPV.cjs");require("./chunk-N4SXZZPY.cjs"),require("./chunk-6HNG5OZI.cjs");var chunkBN6TGHSB_cjs=require("./chunk-BN6TGHSB.cjs");require("./chunk-AJY7ZHXK.cjs"),require("./chunk-OP64Y4IN.cjs");var crypto=require("crypto"),fs=require("fs"),promises=require("fs/promises"),http=require("http"),module$1=require("module"),path=require("path"),child_process=require("child_process"),net=require("net"),DEV_USER_HEADER="x-tego-dev-user-id",DEV_ROLES_HEADER="x-tego-dev-roles",DEV_TENANT_ID="dev-tenant",DEFAULT_DEV_USER_ID="00000000-0000-4000-8000-000000000001";function devCallerOf(e){const t=o(n=>(Array.isArray(n)?n[0]:n)?.trim()??"","single"),r=t(e[DEV_USER_HEADER])||DEFAULT_DEV_USER_ID;let a=[];try{const n=JSON.parse(t(e[DEV_ROLES_HEADER])||"[]");Array.isArray(n)&&(a=n.filter(i=>typeof i=="string"))}catch{}return{userId:r,roles:a}}o(devCallerOf,"devCallerOf");function gatewayHeaders(e,t,r,a=Date.now()){const n={};for(const[s,l]of Object.entries(e)){const d=s.toLowerCase();l===void 0||d.startsWith("x-tego-")||d==="cookie"||d==="authorization"||d==="host"||d==="connection"||(n[d]=Array.isArray(l)?l.join(", "):l)}const i=JSON.stringify(t.roles),u=String(a);return n[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.userId]=t.userId,n[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.tenantId]=DEV_TENANT_ID,n[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.roles]=i,n[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.issuedAt]=u,n[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.signature]=chunkIMAVYQ4U_cjs.signPluginContext({userId:t.userId,tenantId:DEV_TENANT_ID,rolesHeader:i,avatarId:"",issuedAt:u},r),n}o(gatewayHeaders,"gatewayHeaders");var DEV_MIGRATIONS_TABLE="tego_dev_migrations";function devSchemaName(e){return`tego_dev_${e.toLowerCase().replace(/[^a-z0-9_]/g,"_")}`.slice(0,63)}o(devSchemaName,"devSchemaName");function devDatabaseUrl(e,t){const r=new URL(e);r.searchParams.delete("options");const a=r.search.replace(/^\?/,"");return r.search=`?${a?`${a}&`:""}options=${encodeURIComponent(`-c search_path=${t}`)}`,r.toString()}o(devDatabaseUrl,"devDatabaseUrl");async function connect(e){let t;try{t=await import("pg")}catch(n){throw new Error(`The local backend needs the "pg" driver; add it to the plugin's dependencies.`,{cause:n})}const r=t.default?.Client??t.Client;if(!r)throw new Error('The "pg" module has no Client constructor.');const a=new r({connectionString:e});try{await a.connect()}catch(n){throw new Error(`Cannot connect to DATABASE_URL (${redactUrl(e)}): ${n instanceof Error?n.message:String(n)}`,{cause:n})}return a}o(connect,"connect");function redactUrl(e){try{const t=new URL(e);return t.password&&(t.password="***"),t.toString()}catch{return"<unparseable DATABASE_URL>"}}o(redactUrl,"redactUrl");var quote=o(e=>`"${e.replace(/"/g,'""')}"`,"quote");async function applyDevMigrations(e){const t=await promises.readdir(e.migrationsDir).then(i=>i.filter(u=>u.endsWith(".sql")).sort(),i=>{if(i.code==="ENOENT")return[];throw i}),r=await connect(e.databaseUrl),a=quote(e.schema),n=`${a}.${quote(DEV_MIGRATIONS_TABLE)}`;try{await r.query(`CREATE SCHEMA IF NOT EXISTS ${a}`),await r.query(`CREATE TABLE IF NOT EXISTS ${n} (name text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())`);const i=new Set((await r.query(`SELECT name FROM ${n}`)).rows.map(s=>String(s.name))),u=[];for(const s of t){if(i.has(s))continue;const l=await promises.readFile(path.join(e.migrationsDir,s),"utf8");await r.query("BEGIN");try{await r.query(`SET LOCAL search_path TO ${a}`),await r.query("SELECT set_config('app.tenant_id', $1, true)",[e.tenantId]),await r.query(l),await r.query(`INSERT INTO ${n} (name) VALUES ($1)`,[s]),await r.query("COMMIT")}catch(d){throw await r.query("ROLLBACK").catch(()=>{}),new Error(`Migration ${s} failed: ${d instanceof Error?d.message:String(d)}`,{cause:d})}u.push(s)}return u}finally{await r.end().catch(()=>{})}}o(applyDevMigrations,"applyDevMigrations");var REBUILD_DEBOUNCE_MS=250,STOP_TIMEOUT_MS=5e3;function freePort(){return new Promise((e,t)=>{const r=net.createServer();r.once("error",t),r.listen(0,"127.0.0.1",()=>{const a=r.address(),n=typeof a=="object"&&a?a.port:0;r.close(()=>e(n))})})}o(freePort,"freePort");function stopChild(e){return!e||e.exitCode!==null||e.signalCode!==null?Promise.resolve():new Promise(t=>{const r=setTimeout(()=>{e.kill("SIGKILL"),t()},STOP_TIMEOUT_MS);e.once("exit",()=>{clearTimeout(r),t()}),e.kill("SIGTERM")})}o(stopChild,"stopChild");async function startDevBackend(e){const{part:t,log:r}=e;if(t.runtime==="python")throw new Error("The local preview runs TypeScript backends only; a python backend runs through the platform.");const a=await freePort(),n=devSchemaName(e.pluginId),i=path.resolve(e.root,t.migrationsDirPath),u=path.join(e.outDir,t.entryPath),s={...process.env,NODE_ENV:"development",TEGO_PLUGIN_ID:e.pluginId,TEGO_PLUGIN_VERSION:e.version,TEGO_PLUGIN_INSTALL_ID:"dev-install",TEGO_TENANT_ID:DEV_TENANT_ID,TEGO_PLUGIN_PORT:String(a),TEGO_PLUGIN_HEALTH_PATH:t.healthPath,TEGO_PLUGIN_HOST_API_URL:e.hostApiUrl,TEGO_PLUGIN_HOST_API_TOKEN:e.hostApiToken,TEGO_PLUGIN_GATEWAY_KEY:e.gatewayKey,DATABASE_URL:devDatabaseUrl(e.databaseUrl,n)};let l=null,d=!1;async function g(){await promises.rm(e.outDir,{recursive:!0,force:!0}),await chunk4LKRXDYY_cjs.buildPluginBackend(t,{root:e.root,outDir:e.outDir});const f=await applyDevMigrations({databaseUrl:e.databaseUrl,schema:n,migrationsDir:i,tenantId:DEV_TENANT_ID});for(const E of f)r(`[backend] applied migration ${E}`)}o(g,"build");function v(){const f=child_process.spawn(process.execPath,[u],{env:s,stdio:["ignore","pipe","pipe"]});l=f,f.stdout?.on("data",E=>process.stdout.write(E)),f.stderr?.on("data",E=>process.stderr.write(E)),f.on("exit",E=>{l===f&&!d&&(l=null,r(`[backend] exited (code ${E??"null"}); it starts again on the next save`))})}o(v,"start"),await g(),v(),r(`[backend] ${e.pluginId} on 127.0.0.1:${a}, schema ${n} in ${redactUrl(e.databaseUrl)}`);let y=null,m=null,N=!1;const D=o(async()=>{if(m){N=!0;return}m=(async()=>{try{await g();const f=l;l=null,await stopChild(f),d||(v(),r("[backend] rebuilt and restarted"))}catch(f){r(`[backend] rebuild failed, the previous build keeps running: ${f instanceof Error?f.message:String(f)}`)}})(),await m,m=null,N&&!d&&(N=!1,await D())},"rebuild"),A=[];for(const f of[t.dir,"shared"]){const E=path.resolve(e.root,f);fs.existsSync(E)&&A.push(fs.watch(E,{recursive:!0},(x,k)=>{k&&/(^|[\\/])(node_modules|dist)([\\/]|$)/.test(String(k))||(y&&clearTimeout(y),y=setTimeout(()=>void D(),REBUILD_DEBOUNCE_MS))}))}return{port:a,schema:n,async close(){d=!0,y&&clearTimeout(y);for(const f of A)f.close();await m,await stopChild(l)}}}o(startDevBackend,"startDevBackend");var FAKE_HOST_PREFIX="/__tego/host",FAKE_FILES_PREFIX="/__tego/files",MAX_JSON_BYTES=1024*1024;async function readBody(e,t=1/0){const r=[];let a=0;for await(const n of e){if(a+=n.length,a>t)throw Object.assign(new Error("body too large"),{status:413});r.push(n)}return Buffer.concat(r)}o(readBody,"readBody");function send(e,t,r){if(r===void 0){e.writeHead(t),e.end();return}e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r))}o(send,"send");function storagePath(e,t){const r=path.resolve(e),a=path.resolve(r,t);if(!t||path.isAbsolute(t)||!a.startsWith(r+path.sep))throw Object.assign(new Error(`invalid storage key ${JSON.stringify(t)}`),{status:400});return a}o(storagePath,"storagePath");var metaOf=o(e=>`${e}.tego-meta.json`,"metaOf");async function head(e){const t=await promises.stat(e).catch(()=>null);if(!t?.isFile())return null;const r=await promises.readFile(metaOf(e),"utf8").then(a=>JSON.parse(a),()=>({}));return{size:t.size,contentType:r.contentType??null,versionTag:crypto.createHash("sha1").update(`${t.size}:${t.mtimeMs}`).digest("hex")}}o(head,"head");async function put(e,t,r){await promises.mkdir(path.dirname(e),{recursive:!0}),await promises.writeFile(e,t),await promises.writeFile(metaOf(e),JSON.stringify({contentType:r??null}))}o(put,"put");async function handleFakeHost(e,t,r){const a=new URL(e.url??"/","http://preview.local");try{if(a.pathname.startsWith(FAKE_FILES_PREFIX)){const s=storagePath(r.storageDir,a.searchParams.get("key")??""),l=await head(s);if(!l)return send(t,404);t.writeHead(200,{"content-type":l.contentType??"application/octet-stream"}),t.end(await promises.readFile(s));return}if(e.headers.authorization!==`Bearer ${r.token}`)return send(t,401,{error:"bad host API token"});const n=a.pathname.slice(FAKE_HOST_PREFIX.length)||"/",i=o(async()=>JSON.parse((await readBody(e,MAX_JSON_BYTES)).toString("utf8")||"{}"),"json"),u=o(s=>storagePath(r.storageDir,String(s??"")),"keyFile");switch(n){case"/config":return send(t,200,{data:{values:r.config()}});case"/log":{const{level:s,message:l,fields:d}=await i();return r.log(`[backend ${String(s??"info")}] ${String(l??"")}${d?` ${JSON.stringify(d)}`:""}`),send(t,200,{data:{}})}case"/events":{const{name:s,payload:l}=await i();return r.log(`[event] ${String(s)} ${JSON.stringify(l??null)}`),send(t,200,{data:{}})}case"/notifications":return r.log(`[notification] ${JSON.stringify(await i())}`),send(t,200,{data:{id:crypto.randomUUID()}});case"/directory/identity":{const{userId:s}=await i(),l=r.users().get(String(s));return send(t,200,{data:{active:!!l,roleNames:l??[],memberships:[],checkedAt:new Date().toISOString()}})}case"/directory/list":{const{kind:s,search:l,ids:d}=await i(),g=typeof l=="string"?l.toLowerCase():"",v=Array.isArray(d)?new Set(d.map(String)):void 0,y=s==="users"?[...r.users().keys()].filter(m=>(!v||v.has(m))&&m.toLowerCase().includes(g)).map(m=>({id:m,displayName:m,active:!0})):[];return send(t,200,{data:{items:y,hasMore:!1}})}case"/directory/match":{const{userId:s,principals:l}=await i(),d=Array.isArray(l)?l:[];return send(t,200,{data:{active:r.users().has(String(s)),matched:d.flatMap((g,v)=>g.kind==="user"&&g.id===s?[v]:[]),checkedAt:new Date().toISOString()}})}case"/directory/validate":{const{principals:s}=await i(),l=Array.isArray(s)?s:[],d=r.users();return send(t,200,{data:{invalid:l.flatMap((g,v)=>g.kind==="user"&&d.has(String(g.id))?[]:[v])}})}case"/storage/get":case"/storage/stream":{const s=u(a.searchParams.get("key"));if(e.method==="POST"&&n==="/storage/stream")return await put(s,await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});const l=await head(s);if(!l)return send(t,404);let d=await promises.readFile(s);const g=Number(a.searchParams.get("bytes"));Number.isFinite(g)&&g>0&&(d=d.subarray(0,g)),t.writeHead(200,{"content-type":l.contentType??"application/octet-stream"}),t.end(d);return}case"/storage/put":return await put(u(a.searchParams.get("key")),await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});case"/storage/head":{const s=await head(u((await i()).key));return s?send(t,200,{data:s}):send(t,404)}case"/storage/delete":{const s=u((await i()).key);return await promises.rm(s,{force:!0}),await promises.rm(metaOf(s),{force:!0}),send(t,200,{data:{}})}case"/storage/copy":{const{sourceKey:s,destinationKey:l}=await i(),d=u(s),g=u(l);return await head(d)?(await promises.mkdir(path.dirname(g),{recursive:!0}),await promises.copyFile(d,g),await promises.copyFile(metaOf(d),metaOf(g)).catch(()=>{}),send(t,200,{data:await head(g)})):send(t,404)}case"/storage/signed-url":{const{key:s}=await i();u(s);const l=`${FAKE_FILES_PREFIX}?key=${encodeURIComponent(String(s))}`;return send(t,200,{data:{url:`${r.origin()}${l}`,path:l}})}default:return r.log(`[host] ${n} is not available in local preview`),send(t,501,{error:{code:"not_available_in_preview",message:`${n} is not available in local preview`}})}}catch(n){const i=n.status??500;send(t,i,{error:{message:n instanceof Error?n.message:String(n)}})}}o(handleFakeHost,"handleFakeHost");var SDK_PACKAGE="@tego/plugin-sdk",SDK_IMPORT=JSON.stringify(SDK_PACKAGE),SDK_FRONTEND_IMPORT=JSON.stringify(`${SDK_PACKAGE}/frontend`);function shellFiles(e){return[{path:"index.html",contents:indexHtml(e)},{path:"main.tsx",contents:mainTsx(e)},{path:"shell.css",contents:SHELL_CSS},{path:"plugin-surface.tsx",contents:PLUGIN_SURFACE_STUB}]}o(shellFiles,"shellFiles");var escapeHtml=o(e=>e.replace(/[&<>"]/g,t=>({"&":"&","<":"<",">":">",'"':"""})[t]),"escapeHtml");function indexHtml({displayName:e}){return`<!doctype html>
|
|
2
2
|
<html lang="en" class="h-full">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script type="module" src="/main.tsx"><\/script>
|
|
12
12
|
</body>
|
|
13
13
|
</html>
|
|
14
|
-
`}
|
|
14
|
+
`}o(indexHtml,"indexHtml");var SHELL_CSS=`@tailwind base;
|
|
15
15
|
@tailwind components;
|
|
16
16
|
@tailwind utilities;
|
|
17
17
|
|
|
@@ -46,7 +46,7 @@ export const ChatPanel = Unavailable;
|
|
|
46
46
|
export function useTopic() {
|
|
47
47
|
return { topic: undefined, topics: [], loading: false };
|
|
48
48
|
}
|
|
49
|
-
`;function mainTsx(e){const t=JSON.stringify({pluginId:e.pluginId,displayName:e.displayName,nav:e.nav.map(a=>({key:a.key,labelKey:a.labelKey,path:a.path??"/"})),config:e.config,locales:e.locales,userHeader:DEV_USER_HEADER,rolesHeader:DEV_ROLES_HEADER,defaultUser:DEFAULT_DEV_USER_ID},null,2),
|
|
49
|
+
`;function mainTsx(e){const t=JSON.stringify({pluginId:e.pluginId,displayName:e.displayName,nav:e.nav.map(a=>({key:a.key,labelKey:a.labelKey,path:a.path??"/"})),config:e.config,locales:e.locales,defaultTarget:e.defaultTarget??"web",userHeader:DEV_USER_HEADER,rolesHeader:DEV_ROLES_HEADER,defaultUser:DEFAULT_DEV_USER_ID},null,2),r=e.entry?`import plugin from ${JSON.stringify(e.entry)};`:"const plugin = undefined;";return`// Generated by \`tego-plugin dev\` on every start; edits here are overwritten.
|
|
50
50
|
import '@tego/ui/styles/tokens.css';
|
|
51
51
|
import './shell.css';
|
|
52
52
|
|
|
@@ -55,12 +55,17 @@ import type { PluginFrontendModule, PluginHostContext, PluginRoute } from ${SDK_
|
|
|
55
55
|
import {
|
|
56
56
|
Button,
|
|
57
57
|
Input,
|
|
58
|
+
Label,
|
|
59
|
+
Popover,
|
|
60
|
+
PopoverContent,
|
|
61
|
+
PopoverTrigger,
|
|
58
62
|
Select,
|
|
59
63
|
SelectContent,
|
|
60
64
|
SelectItem,
|
|
61
65
|
SelectTrigger,
|
|
62
66
|
SelectValue,
|
|
63
67
|
Toaster,
|
|
68
|
+
Switch,
|
|
64
69
|
TooltipProvider,
|
|
65
70
|
} from '@tego/ui';
|
|
66
71
|
import i18next from 'i18next';
|
|
@@ -68,9 +73,14 @@ import { useState, useSyncExternalStore } from 'react';
|
|
|
68
73
|
import { createRoot } from 'react-dom/client';
|
|
69
74
|
import { initReactI18next } from 'react-i18next';
|
|
70
75
|
|
|
71
|
-
${
|
|
76
|
+
${r}
|
|
72
77
|
|
|
73
78
|
const DEV = ${t};
|
|
79
|
+
const TARGET: 'web' | 'desktop' = /(?:^|;\\s*)tego-dev-target=desktop(?:;|$)/.test(document.cookie)
|
|
80
|
+
? 'desktop'
|
|
81
|
+
: /(?:^|;\\s*)tego-dev-target=web(?:;|$)/.test(document.cookie)
|
|
82
|
+
? 'web'
|
|
83
|
+
: DEV.defaultTarget;
|
|
74
84
|
const BASE = '/p/' + DEV.pluginId;
|
|
75
85
|
const NAMESPACE = pluginI18nNamespace(DEV.pluginId);
|
|
76
86
|
|
|
@@ -168,8 +178,59 @@ const context: PluginHostContext = {
|
|
|
168
178
|
config: DEV.config,
|
|
169
179
|
navigate,
|
|
170
180
|
can: navKey => navKeys.includes(navKey),
|
|
181
|
+
// Host API 1.37, as the shells provide it: the preview prints what a plugin
|
|
182
|
+
// asked for, then does the browser equivalent.
|
|
183
|
+
platform: TARGET,
|
|
184
|
+
openExternal: async url => {
|
|
185
|
+
if (!/^https?:\\/\\//i.test(url)) throw new Error('openExternal takes an http(s) URL');
|
|
186
|
+
console.info('[preview] openExternal', url);
|
|
187
|
+
window.open(url, '_blank', 'noopener');
|
|
188
|
+
},
|
|
189
|
+
download: async request => {
|
|
190
|
+
const anchor = document.createElement('a');
|
|
191
|
+
let revoke: string | undefined;
|
|
192
|
+
if ('blob' in request) {
|
|
193
|
+
revoke = URL.createObjectURL(request.blob);
|
|
194
|
+
anchor.href = revoke;
|
|
195
|
+
anchor.download = request.fileName;
|
|
196
|
+
} else {
|
|
197
|
+
if (!request.path.startsWith('/')) throw new Error('download takes a root-relative host path');
|
|
198
|
+
anchor.href = context.hostUrl(request.path);
|
|
199
|
+
if (request.fileName) anchor.download = request.fileName;
|
|
200
|
+
}
|
|
201
|
+
console.info('[preview] download', 'blob' in request ? request.fileName : request.path);
|
|
202
|
+
document.body.append(anchor);
|
|
203
|
+
anchor.click();
|
|
204
|
+
anchor.remove();
|
|
205
|
+
if (revoke) setTimeout(() => URL.revokeObjectURL(revoke), 30_000);
|
|
206
|
+
},
|
|
207
|
+
copyText: async text => {
|
|
208
|
+
try {
|
|
209
|
+
await navigator.clipboard.writeText(text);
|
|
210
|
+
return true;
|
|
211
|
+
} catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
171
215
|
};
|
|
172
216
|
|
|
217
|
+
// What the imitated shell's CSP refused on this page (the server prints them too).
|
|
218
|
+
const violations: string[] = [];
|
|
219
|
+
const violationListeners = new Set<() => void>();
|
|
220
|
+
document.addEventListener('securitypolicyviolation', event => {
|
|
221
|
+
violations.push(event.effectiveDirective + ': ' + (event.blockedURI || 'inline'));
|
|
222
|
+
for (const listener of violationListeners) listener();
|
|
223
|
+
});
|
|
224
|
+
function useViolations(): number {
|
|
225
|
+
return useSyncExternalStore(
|
|
226
|
+
listener => {
|
|
227
|
+
violationListeners.add(listener);
|
|
228
|
+
return () => violationListeners.delete(listener);
|
|
229
|
+
},
|
|
230
|
+
() => violations.length
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
173
234
|
function useRoute(): PluginRoute {
|
|
174
235
|
return useSyncExternalStore(context.route.subscribe, context.route.current);
|
|
175
236
|
}
|
|
@@ -180,77 +241,122 @@ function DevBar() {
|
|
|
180
241
|
const [dark, setDark] = useState(document.documentElement.classList.contains('dark'));
|
|
181
242
|
const [userId, setUserId] = useState(user.userId);
|
|
182
243
|
const [roles, setRoles] = useState(user.roles.join(', '));
|
|
244
|
+
const blocked = useViolations();
|
|
245
|
+
const switchTarget = (next: 'web' | 'desktop') => {
|
|
246
|
+
const url = new URL(location.href);
|
|
247
|
+
url.searchParams.set('target', next);
|
|
248
|
+
location.assign(url.toString());
|
|
249
|
+
};
|
|
183
250
|
const switchUser = () => {
|
|
184
251
|
store('userId', userId.trim() || DEV.defaultUser);
|
|
185
252
|
store('roles', roles);
|
|
186
253
|
location.reload();
|
|
187
254
|
};
|
|
255
|
+
// One quiet row: what the preview imitates, the plugin's pages, CSP problems, and the
|
|
256
|
+
// rarely changed settings (language, theme, who is signed in) behind one button.
|
|
188
257
|
return (
|
|
189
|
-
<div className='flex shrink-0 items-center gap-2 border-b border-border bg-card px-
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
258
|
+
<div className='flex h-10 shrink-0 items-center gap-2 border-b border-border bg-card px-2 text-sm'>
|
|
259
|
+
<div className='flex shrink-0 items-center rounded-control bg-sunken p-0.5' role='group' aria-label='Shell'>
|
|
260
|
+
{(['web', 'desktop'] as const).map(target => (
|
|
261
|
+
<button
|
|
262
|
+
key={target}
|
|
263
|
+
type='button'
|
|
264
|
+
aria-pressed={TARGET === target}
|
|
265
|
+
onClick={() => TARGET !== target && switchTarget(target)}
|
|
266
|
+
className={
|
|
267
|
+
'h-6 rounded-control px-2 text-xs font-medium transition-colors ' +
|
|
268
|
+
(TARGET === target ? 'bg-card text-foreground shadow-card' : 'text-muted-foreground hover:text-foreground')
|
|
269
|
+
}
|
|
270
|
+
>
|
|
271
|
+
{target === 'web' ? 'Web' : 'Desktop'}
|
|
272
|
+
</button>
|
|
273
|
+
))}
|
|
274
|
+
</div>
|
|
275
|
+
<nav className='flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto' aria-label='Pages'>
|
|
193
276
|
{DEV.nav.map(entry => (
|
|
194
277
|
<Button
|
|
195
278
|
key={entry.key}
|
|
196
279
|
size='sm'
|
|
197
280
|
variant={current.path === entry.path ? 'secondary' : 'ghost'}
|
|
281
|
+
className='h-7 shrink-0'
|
|
198
282
|
onClick={() => navigate(entry.path)}
|
|
199
283
|
>
|
|
200
284
|
{i18next.t(entry.labelKey, { ns: NAMESPACE, lng: lang })}
|
|
201
285
|
</Button>
|
|
202
286
|
))}
|
|
203
287
|
</nav>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
>
|
|
213
|
-
<
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
</
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
288
|
+
{blocked > 0 && (
|
|
289
|
+
<span
|
|
290
|
+
className='shrink-0 rounded-control bg-destructive-soft px-1.5 py-0.5 text-xs text-destructive'
|
|
291
|
+
title={violations.join('\\n')}
|
|
292
|
+
>
|
|
293
|
+
CSP blocked {blocked}
|
|
294
|
+
</span>
|
|
295
|
+
)}
|
|
296
|
+
<Popover>
|
|
297
|
+
<PopoverTrigger asChild>
|
|
298
|
+
<Button size='sm' variant='ghost' className='h-7 shrink-0 font-mono text-xs text-muted-foreground'>
|
|
299
|
+
{lang} \xB7 {user.userId.slice(0, 8)}
|
|
300
|
+
</Button>
|
|
301
|
+
</PopoverTrigger>
|
|
302
|
+
<PopoverContent align='end' className='w-72 space-y-3'>
|
|
303
|
+
<div className='space-y-1.5'>
|
|
304
|
+
<Label>Language</Label>
|
|
305
|
+
<Select
|
|
306
|
+
value={lang}
|
|
307
|
+
onValueChange={async next => {
|
|
308
|
+
await loadLocale(next);
|
|
309
|
+
await i18next.changeLanguage(next);
|
|
310
|
+
store('lang', next);
|
|
311
|
+
setLang(next);
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
<SelectTrigger className='h-8' aria-label='Language'>
|
|
315
|
+
<SelectValue />
|
|
316
|
+
</SelectTrigger>
|
|
317
|
+
<SelectContent>
|
|
318
|
+
{DEV.locales.map(tag => (
|
|
319
|
+
<SelectItem key={tag} value={tag}>
|
|
320
|
+
{tag}
|
|
321
|
+
</SelectItem>
|
|
322
|
+
))}
|
|
323
|
+
</SelectContent>
|
|
324
|
+
</Select>
|
|
325
|
+
</div>
|
|
326
|
+
<div className='flex items-center justify-between'>
|
|
327
|
+
<Label htmlFor='tego-dev-dark'>Dark theme</Label>
|
|
328
|
+
<Switch
|
|
329
|
+
id='tego-dev-dark'
|
|
330
|
+
checked={dark}
|
|
331
|
+
onCheckedChange={next => {
|
|
332
|
+
document.documentElement.classList.toggle('dark', next);
|
|
333
|
+
store('theme', next ? 'dark' : 'light');
|
|
334
|
+
setDark(next);
|
|
335
|
+
}}
|
|
336
|
+
/>
|
|
337
|
+
</div>
|
|
338
|
+
<form
|
|
339
|
+
className='space-y-1.5 border-t border-border pt-3'
|
|
340
|
+
onSubmit={event => {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
switchUser();
|
|
343
|
+
}}
|
|
344
|
+
>
|
|
345
|
+
<Label>Signed in as</Label>
|
|
346
|
+
<Input className='h-8 font-mono text-xs' aria-label='User id' value={userId} onChange={event => setUserId(event.target.value)} />
|
|
347
|
+
<Input
|
|
348
|
+
className='h-8'
|
|
349
|
+
aria-label='Roles, comma-separated'
|
|
350
|
+
placeholder='Roles, comma-separated'
|
|
351
|
+
value={roles}
|
|
352
|
+
onChange={event => setRoles(event.target.value)}
|
|
353
|
+
/>
|
|
354
|
+
<Button size='sm' variant='outline' type='submit' className='w-full'>
|
|
355
|
+
Sign in
|
|
356
|
+
</Button>
|
|
357
|
+
</form>
|
|
358
|
+
</PopoverContent>
|
|
359
|
+
</Popover>
|
|
254
360
|
</div>
|
|
255
361
|
);
|
|
256
362
|
}
|
|
@@ -272,4 +378,4 @@ if (frontend) {
|
|
|
272
378
|
DEV.pluginId +
|
|
273
379
|
'/</p>';
|
|
274
380
|
}
|
|
275
|
-
`}i(mainTsx,"mainTsx");var DEFAULT_DEV_PORT=5280,PORT_ATTEMPTS=20,DEV_READY_PREFIX="TEGO_DEV_READY ",DEV_DIR=".tego-dev",PLUGIN_API_PREFIX="/api/v1/tego-os/plugin-api/";function displayNameOf(e){const{displayName:t}=e;return typeof t=="string"?t:t.en??t["zh-CN"]??Object.values(t)[0]??e.id}i(displayNameOf,"displayNameOf");function packageDir(e,t){for(let n=path.resolve(e);;n=path.dirname(n)){const a=path.join(n,"node_modules",...t.split("/"));if(fs.existsSync(path.join(a,"package.json")))return fs.realpathSync(a);if(path.dirname(n)===n)return}}i(packageDir,"packageDir");function sendJson(e,t,n){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(n))}i(sendJson,"sendJson");function listen(e,t,n){return new Promise((a,r)=>{const o=i(s=>{e.off("listening",l),r(s)},"onError"),l=i(()=>{e.off("error",o);const s=e.address();a(typeof s=="object"&&s?s.port:t)},"onListening");e.once("error",o),e.once("listening",l),e.listen(t,n)})}i(listen,"listen");async function shellTailwind(e,t){const n=module$1.createRequire(path.join(e,"vite.config.ts"));let a,r;try{a=n("@tego/ui/tailwind-preset"),r=path.dirname(n.resolve("@tego/ui"))}catch(l){throw new Error(`The preview cannot load @tego/ui's Tailwind preset from ${e}: ${l instanceof Error?l.message:String(l)}. Install the plugin's dependencies.`,{cause:l})}const o=(await import("tailwindcss")).default;return o({presets:[a],content:[path.join(r,"**/*.{js,cjs}"),path.join(t,"*.tsx")]})}i(shellTailwind,"shellTailwind");function perStylesheet(e,t,n){return{postcssPlugin:"tego-dev-stylesheets",async Once(a,r){const o=a.source?.input.file,l=o&&path.resolve(o)===e?[t]:n;if(l.length===0)return;const s=await r.postcss(l).process(a,{from:o});r.result.messages.push(...s.messages)}}}i(perStylesheet,"perStylesheet");function servePlugins(e){return Array.isArray(e)?e.flat(1/0).map(t=>{if(!t||typeof t!="object")return t;const{buildStart:n}=t;return typeof n!="function"?t:{...t,buildStart(...a){const r=new Proxy(this,{get(o,l){if(l==="emitFile")return()=>"";const s=Reflect.get(o,l);return typeof s=="function"?s.bind(o):s}});return n.apply(r,a)}}}):e}i(servePlugins,"servePlugins");async function startDevServer(e){const t=path.resolve(e.root),n=e.log??(d=>console.log(d)),a=await chunkPWSTCQRN_cjs.readManifest(t),r=chunkROFUQLOZ_cjs.resolvePluginParts(a),o=path.join(t,DEV_DIR),l=path.join(o,"shell"),s=crypto.randomBytes(32).toString("hex"),c=crypto.randomBytes(24).toString("hex");let u="";const p=new Map;let m,v={};const h=r.frontend?path.resolve(t,r.frontend.dir):t,{loadConfigFromFile:_,createServer:k,mergeConfig:x}=await import("vite");if(r.frontend){const d=path.join(h,"vite.config.ts");fs.existsSync(d)&&(v=(await _({command:"serve",mode:"development"},d,t))?.config??{});const g=v.build?.rollupOptions?.input,w=path.resolve(t,typeof g=="string"?g:`${r.frontend.dir}/src/index.tsx`);if(!fs.existsSync(w))throw new Error(`The frontend entry ${path.relative(t,w)} does not exist.`);m=path.relative(l,w).split(path.sep).join("/"),m.startsWith(".")||(m=`./${m}`)}const f=i(()=>r.frontend?chunkPWSTCQRN_cjs.readPluginLocales(t,a,r.frontend.i18nDirPath):Promise.resolve(new Map),"localeFiles"),y=[...(await f()).keys()].map(d=>d.replace(/\.json$/,""));await promises.mkdir(l,{recursive:!0});for(const d of shellFiles({pluginId:a.id,displayName:displayNameOf(a),nav:r.frontend?.nav??[],config:r.frontend?.config?chunkKMN5RHLX_cjs.applyConfigDefaults(r.frontend.config.schema,{}):{},entry:m,locales:y}))await promises.writeFile(path.join(l,d.path),d.contents);let I,S;const C=i(()=>r.backend?.config?chunkKMN5RHLX_cjs.applyConfigDefaults(r.backend.config.schema,{}):{},"backendConfig"),D=http.createServer((d,g)=>void j(d,g));async function j(d,g){const w=(d.url??"/").split("?")[0];try{if(w.startsWith(FAKE_HOST_PREFIX)||w.startsWith(FAKE_FILES_PREFIX)){await handleFakeHost(d,g,{token:c,storageDir:path.join(o,"storage"),config:C,origin:i(()=>u,"origin"),users:i(()=>p,"users"),log:n});return}const E=/^\/i18n\/([A-Za-z-]+)\.json$/.exec(w);if(E){const T=(await f()).get(`${E[1]}.json`);if(T===void 0)return sendJson(g,404,{error:`no ${E[1]}.json`});g.writeHead(200,{"content-type":"application/json"}),g.end(T);return}const b=`${PLUGIN_API_PREFIX}${a.id}`;if(w===b||w.startsWith(`${b}/`)){H(d,g,(d.url??"/").slice(b.length)||"/");return}$.middlewares(d,g,()=>sendJson(g,404,{error:`${w} not found`}))}catch(E){sendJson(g,500,{error:E instanceof Error?E.message:String(E)})}}i(j,"route");function H(d,g,w){if(!I){sendJson(g,503,{error:S??"The plugin has no backend part."});return}const E=devCallerOf(d.headers);p.set(E.userId,E.roles);const b=http.request({host:"127.0.0.1",port:I.port,method:d.method,path:w,headers:gatewayHeaders(d.headers,E,s)},T=>{g.writeHead(T.statusCode??502,T.headers),T.pipe(g)});b.on("error",T=>{g.headersSent?g.destroy():sendJson(g,502,{error:`The backend is not answering (${T.message}); see the terminal.`})}),d.pipe(b)}i(H,"proxy");const B=path.join(l,"shell.css"),F=(()=>{const d=v.css?.postcss;return d&&typeof d=="object"?d.plugins??[]:[]})(),A=(()=>{const d=packageDir(h,"react-i18next");return(d&&packageDir(d,"i18next"))??packageDir(h,"i18next")})(),{css:M,mode:G,build:K,...P}=v;P.plugins=servePlugins(P.plugins);const $=await k(x(P,{configFile:!1,root:l,mode:"development",appType:"spa",clearScreen:!1,logLevel:"info",esbuild:{jsx:"automatic",jsxDev:!1},css:{postcss:{plugins:[perStylesheet(B,await shellTailwind(h,l),F)]}},resolve:{alias:[{find:"@tego/chat/plugin-surface",replacement:path.join(l,"plugin-surface.tsx")},...A?[{find:/^i18next$/,replacement:A}]:[]]},server:{middlewareMode:!0,hmr:{server:D},fs:{allow:[t,l,...A?[A]:[]]}},optimizeDeps:{entries:[path.join(l,"index.html")]}})),N=e.host??"127.0.0.1";let R;const O=e.port??DEFAULT_DEV_PORT;for(let d=0;R===void 0;d++)try{R=await listen(D,O===0?0:O+d,N)}catch(g){if(g.code!=="EADDRINUSE"||O===0||d>=PORT_ATTEMPTS)throw await $.close(),g}const L=N==="0.0.0.0"||N==="::"?"127.0.0.1":N;if(u=`http://${L.includes(":")?`[${L}]`:L}:${R}`,!r.backend)S="The plugin has no backend part.";else if(e.backend===!1)S="The backend is off (--no-backend).";else if(!e.databaseUrl)S="The backend needs a PostgreSQL: start the preview with DATABASE_URL=postgres://\u2026 (each plugin gets its own schema).";else try{I=await startDevBackend({root:t,pluginId:a.id,version:a.version,part:r.backend,databaseUrl:e.databaseUrl,outDir:path.join(o,"backend"),hostApiUrl:`${u}${FAKE_HOST_PREFIX}`,hostApiToken:c,gatewayKey:s,log:n})}catch(d){S=`The backend did not start: ${d instanceof Error?d.message:String(d)}`}S&&r.backend&&n(`[backend] ${S}`);const U=`${u}/p/${a.id}/`;return n(`${a.id} ${a.version}: local preview at ${U}`),e.databaseUrl&&I&&n(` database ${redactUrl(e.databaseUrl)}, schema ${I.schema}`),n(`${DEV_READY_PREFIX}${JSON.stringify({url:U})}`),{url:U,async close(){await I?.close(),await $.close(),D.closeAllConnections(),await new Promise(d=>D.close(()=>d()))}}}i(startDevServer,"startDevServer"),exports.DEFAULT_DEV_PORT=DEFAULT_DEV_PORT,exports.DEV_DIR=DEV_DIR,exports.DEV_READY_PREFIX=DEV_READY_PREFIX,exports.DEV_ROLES_HEADER=DEV_ROLES_HEADER,exports.DEV_TENANT_ID=DEV_TENANT_ID,exports.DEV_USER_HEADER=DEV_USER_HEADER,exports.applyDevMigrations=applyDevMigrations,exports.devCallerOf=devCallerOf,exports.devDatabaseUrl=devDatabaseUrl,exports.devSchemaName=devSchemaName,exports.gatewayHeaders=gatewayHeaders,exports.handleFakeHost=handleFakeHost,exports.shellFiles=shellFiles,exports.startDevServer=startDevServer,exports.storagePath=storagePath;
|
|
381
|
+
`}o(mainTsx,"mainTsx");var CSP_REPORT_PATH="/__tego/csp-report",CSP_REPORT_PREFIX="TEGO_DEV_CSP ",TARGET_COOKIE="tego-dev-target",DESKTOP_MAX_RESPONSE_BYTES=100*1024*1024,SHARED=["default-src 'self'","object-src 'none'","base-uri 'self'"],PREVIEW_CSP={web:[...SHARED,"script-src 'self' 'wasm-unsafe-eval'","style-src 'self' 'unsafe-inline'","img-src 'self' data: blob: https:","font-src 'self' data:","connect-src 'self' wss: ws:","frame-src 'self'","worker-src 'self' blob:"].join("; "),desktop:[...SHARED,"script-src 'self' 'wasm-unsafe-eval'","style-src 'self' 'unsafe-inline' https:","img-src 'self' data: blob: http: https:","font-src 'self' data: blob: https:","media-src 'self' data: blob:","worker-src 'self' blob:","connect-src 'self' http: https: ws: wss:","frame-src 'self'"].join("; ")};function isPreviewTarget(e){return e==="web"||e==="desktop"}o(isPreviewTarget,"isPreviewTarget");function targetOf(e,t){const r=new URL(e.url??"/","http://preview.local").searchParams.get("target");if(isPreviewTarget(r))return r;const a=/(?:^|;\s*)tego-dev-target=(web|desktop)(?:;|$)/.exec(String(e.headers.cookie??""))?.[1];return isPreviewTarget(a)?a:t}o(targetOf,"targetOf");function parseCspReport(e){try{const t=JSON.parse(e)["csp-report"];if(!t)return;const r=String(t["effective-directive"]??t["violated-directive"]??""),a=String(t["blocked-uri"]??"");if(!r)return;const n=typeof t["source-file"]=="string"?t["source-file"]:void 0,i=typeof t["line-number"]=="number"?t["line-number"]:void 0;return{directive:r,blocked:a,...n?{source:n}:{},...i?{line:i}:{}}}catch{return}}o(parseCspReport,"parseCspReport");var DEFAULT_DEV_PORT=5280,PORT_ATTEMPTS=20,DEV_READY_PREFIX="TEGO_DEV_READY ",DEV_DIR=".tego-dev",PLUGIN_API_PREFIX="/api/v1/tego-os/plugin-api/";function displayNameOf(e){const{displayName:t}=e;return typeof t=="string"?t:t.en??t["zh-CN"]??Object.values(t)[0]??e.id}o(displayNameOf,"displayNameOf");function packageDir(e,t){for(let r=path.resolve(e);;r=path.dirname(r)){const a=path.join(r,"node_modules",...t.split("/"));if(fs.existsSync(path.join(a,"package.json")))return fs.realpathSync(a);if(path.dirname(r)===r)return}}o(packageDir,"packageDir");function sendJson(e,t,r){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r))}o(sendJson,"sendJson");function listen(e,t,r){return new Promise((a,n)=>{const i=o(s=>{e.off("listening",u),n(s)},"onError"),u=o(()=>{e.off("error",i);const s=e.address();a(typeof s=="object"&&s?s.port:t)},"onListening");e.once("error",i),e.once("listening",u),e.listen(t,r)})}o(listen,"listen");async function shellTailwind(e,t){const r=module$1.createRequire(path.join(e,"vite.config.ts"));let a,n;try{a=r("@tego/ui/tailwind-preset"),n=path.dirname(r.resolve("@tego/ui"))}catch(u){throw new Error(`The preview cannot load @tego/ui's Tailwind preset from ${e}: ${u instanceof Error?u.message:String(u)}. Install the plugin's dependencies.`,{cause:u})}const i=(await import("tailwindcss")).default;return i({presets:[a],content:[path.join(n,"**/*.{js,cjs}"),path.join(t,"*.tsx")]})}o(shellTailwind,"shellTailwind");function perStylesheet(e,t,r){return{postcssPlugin:"tego-dev-stylesheets",async Once(a,n){const i=a.source?.input.file,u=i&&path.resolve(i)===e?[t]:r;if(u.length===0)return;const s=await n.postcss(u).process(a,{from:i});n.result.messages.push(...s.messages)}}}o(perStylesheet,"perStylesheet");function servePlugins(e){return Array.isArray(e)?e.flat(1/0).map(t=>{if(!t||typeof t!="object")return t;const{buildStart:r}=t;return typeof r!="function"?t:{...t,buildStart(...a){const n=new Proxy(this,{get(i,u){if(u==="emitFile")return()=>"";const s=Reflect.get(i,u);return typeof s=="function"?s.bind(i):s}});return r.apply(n,a)}}}):e}o(servePlugins,"servePlugins");async function startDevServer(e){const t=path.resolve(e.root),r=e.log??(c=>console.log(c)),a=await chunk4LKRXDYY_cjs.readManifest(t),n=chunk2P6UEAPV_cjs.resolvePluginParts(a),i=path.join(t,DEV_DIR),u=path.join(i,"shell"),s=crypto.randomBytes(32).toString("hex"),l=crypto.randomBytes(24).toString("hex");let d="";const g=new Map;let v,y={};const m=n.frontend?path.resolve(t,n.frontend.dir):t,{loadConfigFromFile:N,createServer:D,mergeConfig:A}=await import("vite");if(n.frontend){const c=path.join(m,"vite.config.ts");fs.existsSync(c)&&(y=(await N({command:"serve",mode:"development"},c,t))?.config??{});const p=y.build?.rollupOptions?.input,w=path.resolve(t,typeof p=="string"?p:`${n.frontend.dir}/src/index.tsx`);if(!fs.existsSync(w))throw new Error(`The frontend entry ${path.relative(t,w)} does not exist.`);v=path.relative(u,w).split(path.sep).join("/"),v.startsWith(".")||(v=`./${v}`)}const f=o(()=>n.frontend?chunk4LKRXDYY_cjs.readPluginLocales(t,a,n.frontend.i18nDirPath):Promise.resolve(new Map),"localeFiles"),E=[...(await f()).keys()].map(c=>c.replace(/\.json$/,""));await promises.mkdir(u,{recursive:!0});for(const c of shellFiles({pluginId:a.id,displayName:displayNameOf(a),nav:n.frontend?.nav??[],config:n.frontend?.config?chunkMWMGJJ74_cjs.applyConfigDefaults(n.frontend.config.schema,{}):{},entry:v,locales:E,defaultTarget:e.target??"web"}))await promises.writeFile(path.join(u,c.path),c.contents);let x,k;const G=o(()=>n.backend?.config?chunkMWMGJJ74_cjs.applyConfigDefaults(n.backend.config.schema,{}):{},"backendConfig"),R=http.createServer((c,p)=>void M(c,p));async function M(c,p){const w=(c.url??"/").split("?")[0],T=targetOf(c,e.target??"web");try{if(w==="/favicon.ico"){p.writeHead(204),p.end();return}if(w===CSP_REPORT_PATH){const h=await new Promise(O=>{let I="";c.setEncoding("utf8"),c.on("data",P=>I+=P),c.on("end",()=>O(I))}),b=parseCspReport(h);b&&(r(`[csp:${T}] blocked ${b.blocked||"(inline)"} by ${b.directive}${b.source?` in ${b.source}${b.line?`:${b.line}`:""}`:""}`),r(`${CSP_REPORT_PREFIX}${JSON.stringify({target:T,...b})}`)),p.writeHead(204),p.end();return}if(w.startsWith(FAKE_HOST_PREFIX)||w.startsWith(FAKE_FILES_PREFIX)){await handleFakeHost(c,p,{token:l,storageDir:path.join(i,"storage"),config:G,origin:o(()=>d,"origin"),users:o(()=>g,"users"),log:r});return}const S=/^\/i18n\/([A-Za-z-]+)\.json$/.exec(w);if(S){const h=(await f()).get(`${S[1]}.json`);if(h===void 0)return sendJson(p,404,{error:`no ${S[1]}.json`});p.writeHead(200,{"content-type":"application/json"}),p.end(h);return}const _=`${PLUGIN_API_PREFIX}${a.id}`;if(w===_||w.startsWith(`${_}/`)){q(c,p,(c.url??"/").slice(_.length)||"/",T);return}if(String(c.headers.accept??"").includes("text/html")){p.setHeader("Content-Security-Policy",`${PREVIEW_CSP[T]}; report-uri ${CSP_REPORT_PATH}`);const h=new URL(c.url??"/","http://preview.local").searchParams.get("target");isPreviewTarget(h)&&p.setHeader("Set-Cookie",`${TARGET_COOKIE}=${h}; Path=/; SameSite=Lax`)}U.middlewares(c,p,()=>sendJson(p,404,{error:`${w} not found`}))}catch(S){sendJson(p,500,{error:S instanceof Error?S.message:String(S)})}}o(M,"route");function q(c,p,w,T){if(!x){sendJson(p,503,{error:k??"The plugin has no backend part."});return}const S=devCallerOf(c.headers);g.set(S.userId,S.roles);const _=http.request({host:"127.0.0.1",port:x.port,method:c.method,path:w,headers:{...gatewayHeaders(c.headers,S,s),...T==="desktop"?{"accept-encoding":"identity"}:{}}},h=>{if(T!=="desktop"){p.writeHead(h.statusCode??502,h.headers),h.pipe(p);return}const b=[];let O=0,I=!1;h.on("data",P=>{if(!I){if(O+=P.length,O>DESKTOP_MAX_RESPONSE_BYTES){I=!0,h.destroy(),r(`[desktop] ${c.method} ${w}: response over 100 MB refused (413)`),sendJson(p,413,{error:"Response over 100 MB: the desktop app refuses it. Serve large files through storage paths."});return}b.push(P)}}),h.on("end",()=>{if(I)return;const P=Buffer.concat(b),F={...h.headers};delete F["transfer-encoding"],F["content-length"]=String(P.length),p.writeHead(h.statusCode??502,F),p.end(P)})});_.on("error",h=>{p.headersSent?p.destroy():sendJson(p,502,{error:`The backend is not answering (${h.message}); see the terminal.`})}),c.pipe(_)}o(q,"proxy");const K=path.join(u,"shell.css"),J=(()=>{const c=y.css?.postcss;return c&&typeof c=="object"?c.plugins??[]:[]})(),$=(()=>{const c=packageDir(m,"react-i18next");return(c&&packageDir(c,"i18next"))??packageDir(m,"i18next")})(),{css:X,mode:Y,build:z,...C}=y;C.plugins=servePlugins(C.plugins);const U=await D(A(C,{configFile:!1,root:u,mode:"development",appType:"spa",clearScreen:!1,logLevel:"info",esbuild:{jsx:"automatic",jsxDev:!1},css:{postcss:{plugins:[perStylesheet(K,await shellTailwind(m,u),J)]}},resolve:{alias:[{find:"@tego/chat/plugin-surface",replacement:path.join(u,"plugin-surface.tsx")},...$?[{find:/^i18next$/,replacement:$}]:[]]},server:{middlewareMode:!0,hmr:{server:R},fs:{allow:[t,u,...$?[$]:[]]}},optimizeDeps:{entries:[path.join(u,"index.html")]}})),L=e.host??"127.0.0.1";let j;const H=e.port??DEFAULT_DEV_PORT;for(let c=0;j===void 0;c++)try{j=await listen(R,H===0?0:H+c,L)}catch(p){if(p.code!=="EADDRINUSE"||H===0||c>=PORT_ATTEMPTS)throw await U.close(),p}const V=L==="0.0.0.0"||L==="::"?"127.0.0.1":L;if(d=`http://${V.includes(":")?`[${V}]`:V}:${j}`,!n.backend)k="The plugin has no backend part.";else if(e.backend===!1)k="The backend is off (--no-backend).";else if(!e.databaseUrl)k="The backend needs a PostgreSQL: start the preview with DATABASE_URL=postgres://\u2026 (each plugin gets its own schema).";else try{x=await startDevBackend({root:t,pluginId:a.id,version:a.version,part:n.backend,databaseUrl:e.databaseUrl,outDir:path.join(i,"backend"),hostApiUrl:`${d}${FAKE_HOST_PREFIX}`,hostApiToken:l,gatewayKey:s,log:r})}catch(c){k=`The backend did not start: ${c instanceof Error?c.message:String(c)}`}k&&n.backend&&r(`[backend] ${k}`);const B=`${d}/p/${a.id}/`;return r(`${a.id} ${a.version}: local preview at ${B}`),e.databaseUrl&&x&&r(` database ${redactUrl(e.databaseUrl)}, schema ${x.schema}`),r(`${DEV_READY_PREFIX}${JSON.stringify({url:B})}`),{url:B,async close(){await x?.close(),await U.close(),R.closeAllConnections(),await new Promise(c=>R.close(()=>c()))}}}o(startDevServer,"startDevServer"),exports.CSP_REPORT_PREFIX=CSP_REPORT_PREFIX,exports.DEFAULT_DEV_PORT=DEFAULT_DEV_PORT,exports.DEV_DIR=DEV_DIR,exports.DEV_READY_PREFIX=DEV_READY_PREFIX,exports.DEV_ROLES_HEADER=DEV_ROLES_HEADER,exports.DEV_TENANT_ID=DEV_TENANT_ID,exports.DEV_USER_HEADER=DEV_USER_HEADER,exports.PREVIEW_CSP=PREVIEW_CSP,exports.applyDevMigrations=applyDevMigrations,exports.devCallerOf=devCallerOf,exports.devDatabaseUrl=devDatabaseUrl,exports.devSchemaName=devSchemaName,exports.gatewayHeaders=gatewayHeaders,exports.handleFakeHost=handleFakeHost,exports.parseCspReport=parseCspReport,exports.shellFiles=shellFiles,exports.startDevServer=startDevServer,exports.storagePath=storagePath,exports.targetOf=targetOf;
|
package/dist/dev.d.cts
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { P as PluginNavEntry } from './manifest-types-CUTNfJTa.cjs';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* The two shells a plugin page runs in, as the local preview imitates them.
|
|
6
|
+
*
|
|
7
|
+
* - `web`: the platform's nginx CSP (`docker/ai-studio/nginx.conf`) — the page
|
|
8
|
+
* may connect to the platform only. Its per-request nonce is left out: Vite's
|
|
9
|
+
* dev scripts cannot carry one, and what the nonce guards (injected inline
|
|
10
|
+
* scripts, `eval`) is refused here the same way.
|
|
11
|
+
* - `desktop`: the Tauri app's CSP (`apps/ai-studio/src-tauri/tauri.conf.json`)
|
|
12
|
+
* — no `eval`/`new Function`, no `blob:` scripts — and its protocol proxy,
|
|
13
|
+
* which buffers every `ctx.api` response whole, refuses one over 100 MB, and
|
|
14
|
+
* strips `accept-encoding`.
|
|
15
|
+
*
|
|
16
|
+
* Both report violations to the preview, which prints them, so a dependency
|
|
17
|
+
* that compiles strings or a call to a third-party URL shows up while
|
|
18
|
+
* developing instead of after release.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type PreviewTarget = 'web' | 'desktop';
|
|
22
|
+
/** The machine-readable line tools watch for (`TEGO_DEV_CSP {"target",…}`). */
|
|
23
|
+
declare const CSP_REPORT_PREFIX = "TEGO_DEV_CSP ";
|
|
24
|
+
declare const PREVIEW_CSP: Record<PreviewTarget, string>;
|
|
25
|
+
/** `?target=` wins, then the cookie the shell's switch sets, then the default. */
|
|
26
|
+
declare function targetOf(req: Pick<IncomingMessage, 'url' | 'headers'>, fallback: PreviewTarget): PreviewTarget;
|
|
27
|
+
interface CspViolation {
|
|
28
|
+
directive: string;
|
|
29
|
+
blocked: string;
|
|
30
|
+
source?: string;
|
|
31
|
+
line?: number;
|
|
32
|
+
}
|
|
33
|
+
/** A browser's `application/csp-report` body; `undefined` when it is not one. */
|
|
34
|
+
declare function parseCspReport(body: string): CspViolation | undefined;
|
|
35
|
+
|
|
4
36
|
/** `tego_dev_<id>` with every character Postgres would need quoted folded to `_`. */
|
|
5
37
|
declare function devSchemaName(pluginId: string): string;
|
|
6
38
|
/**
|
|
@@ -76,6 +108,8 @@ interface ShellOptions {
|
|
|
76
108
|
entry?: string | undefined;
|
|
77
109
|
/** Locales the plugin ships (`en.json` → `en`). */
|
|
78
110
|
locales: string[];
|
|
111
|
+
/** The shell imitated when the page has not chosen one. */
|
|
112
|
+
defaultTarget?: 'web' | 'desktop';
|
|
79
113
|
}
|
|
80
114
|
interface ShellFile {
|
|
81
115
|
path: string;
|
|
@@ -96,6 +130,8 @@ interface DevServerOptions {
|
|
|
96
130
|
host?: string;
|
|
97
131
|
/** Run the backend part (needs `databaseUrl`); default true. */
|
|
98
132
|
backend?: boolean;
|
|
133
|
+
/** Which shell to imitate by default (CSP, network proxy); the page can switch. Default `web`. */
|
|
134
|
+
target?: PreviewTarget;
|
|
99
135
|
databaseUrl?: string;
|
|
100
136
|
log?(line: string): void;
|
|
101
137
|
}
|
|
@@ -105,4 +141,4 @@ interface DevServer {
|
|
|
105
141
|
}
|
|
106
142
|
declare function startDevServer(options: DevServerOptions): Promise<DevServer>;
|
|
107
143
|
|
|
108
|
-
export { DEFAULT_DEV_PORT, DEV_DIR, DEV_READY_PREFIX, DEV_ROLES_HEADER, DEV_TENANT_ID, DEV_USER_HEADER, type DevServer, type DevServerOptions, applyDevMigrations, devCallerOf, devDatabaseUrl, devSchemaName, gatewayHeaders, handleFakeHost, shellFiles, startDevServer, storagePath };
|
|
144
|
+
export { CSP_REPORT_PREFIX, type CspViolation, DEFAULT_DEV_PORT, DEV_DIR, DEV_READY_PREFIX, DEV_ROLES_HEADER, DEV_TENANT_ID, DEV_USER_HEADER, type DevServer, type DevServerOptions, PREVIEW_CSP, type PreviewTarget, applyDevMigrations, devCallerOf, devDatabaseUrl, devSchemaName, gatewayHeaders, handleFakeHost, parseCspReport, shellFiles, startDevServer, storagePath, targetOf };
|
package/dist/dev.d.ts
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { P as PluginNavEntry } from './manifest-types-CUTNfJTa.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* The two shells a plugin page runs in, as the local preview imitates them.
|
|
6
|
+
*
|
|
7
|
+
* - `web`: the platform's nginx CSP (`docker/ai-studio/nginx.conf`) — the page
|
|
8
|
+
* may connect to the platform only. Its per-request nonce is left out: Vite's
|
|
9
|
+
* dev scripts cannot carry one, and what the nonce guards (injected inline
|
|
10
|
+
* scripts, `eval`) is refused here the same way.
|
|
11
|
+
* - `desktop`: the Tauri app's CSP (`apps/ai-studio/src-tauri/tauri.conf.json`)
|
|
12
|
+
* — no `eval`/`new Function`, no `blob:` scripts — and its protocol proxy,
|
|
13
|
+
* which buffers every `ctx.api` response whole, refuses one over 100 MB, and
|
|
14
|
+
* strips `accept-encoding`.
|
|
15
|
+
*
|
|
16
|
+
* Both report violations to the preview, which prints them, so a dependency
|
|
17
|
+
* that compiles strings or a call to a third-party URL shows up while
|
|
18
|
+
* developing instead of after release.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type PreviewTarget = 'web' | 'desktop';
|
|
22
|
+
/** The machine-readable line tools watch for (`TEGO_DEV_CSP {"target",…}`). */
|
|
23
|
+
declare const CSP_REPORT_PREFIX = "TEGO_DEV_CSP ";
|
|
24
|
+
declare const PREVIEW_CSP: Record<PreviewTarget, string>;
|
|
25
|
+
/** `?target=` wins, then the cookie the shell's switch sets, then the default. */
|
|
26
|
+
declare function targetOf(req: Pick<IncomingMessage, 'url' | 'headers'>, fallback: PreviewTarget): PreviewTarget;
|
|
27
|
+
interface CspViolation {
|
|
28
|
+
directive: string;
|
|
29
|
+
blocked: string;
|
|
30
|
+
source?: string;
|
|
31
|
+
line?: number;
|
|
32
|
+
}
|
|
33
|
+
/** A browser's `application/csp-report` body; `undefined` when it is not one. */
|
|
34
|
+
declare function parseCspReport(body: string): CspViolation | undefined;
|
|
35
|
+
|
|
4
36
|
/** `tego_dev_<id>` with every character Postgres would need quoted folded to `_`. */
|
|
5
37
|
declare function devSchemaName(pluginId: string): string;
|
|
6
38
|
/**
|
|
@@ -76,6 +108,8 @@ interface ShellOptions {
|
|
|
76
108
|
entry?: string | undefined;
|
|
77
109
|
/** Locales the plugin ships (`en.json` → `en`). */
|
|
78
110
|
locales: string[];
|
|
111
|
+
/** The shell imitated when the page has not chosen one. */
|
|
112
|
+
defaultTarget?: 'web' | 'desktop';
|
|
79
113
|
}
|
|
80
114
|
interface ShellFile {
|
|
81
115
|
path: string;
|
|
@@ -96,6 +130,8 @@ interface DevServerOptions {
|
|
|
96
130
|
host?: string;
|
|
97
131
|
/** Run the backend part (needs `databaseUrl`); default true. */
|
|
98
132
|
backend?: boolean;
|
|
133
|
+
/** Which shell to imitate by default (CSP, network proxy); the page can switch. Default `web`. */
|
|
134
|
+
target?: PreviewTarget;
|
|
99
135
|
databaseUrl?: string;
|
|
100
136
|
log?(line: string): void;
|
|
101
137
|
}
|
|
@@ -105,4 +141,4 @@ interface DevServer {
|
|
|
105
141
|
}
|
|
106
142
|
declare function startDevServer(options: DevServerOptions): Promise<DevServer>;
|
|
107
143
|
|
|
108
|
-
export { DEFAULT_DEV_PORT, DEV_DIR, DEV_READY_PREFIX, DEV_ROLES_HEADER, DEV_TENANT_ID, DEV_USER_HEADER, type DevServer, type DevServerOptions, applyDevMigrations, devCallerOf, devDatabaseUrl, devSchemaName, gatewayHeaders, handleFakeHost, shellFiles, startDevServer, storagePath };
|
|
144
|
+
export { CSP_REPORT_PREFIX, type CspViolation, DEFAULT_DEV_PORT, DEV_DIR, DEV_READY_PREFIX, DEV_ROLES_HEADER, DEV_TENANT_ID, DEV_USER_HEADER, type DevServer, type DevServerOptions, PREVIEW_CSP, type PreviewTarget, applyDevMigrations, devCallerOf, devDatabaseUrl, devSchemaName, gatewayHeaders, handleFakeHost, parseCspReport, shellFiles, startDevServer, storagePath, targetOf };
|