@zhama/tego-plugin-sdk 1.37.0 → 1.37.2

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.
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
- var v=Object.defineProperty;var l=(r,o)=>v(r,"name",{value:o,configurable:!0});import{readFileSync as x}from"node:fs";const c=process.argv.slice(2),p=c[0];if(p==="create"){const{createStandalonePlugin:r}=await import("../dist/scaffold.js"),o=c.indexOf("--dir");try{const e=await r(x(0,"utf8"),{parent:o===-1?".":c[o+1]??".",cwd:process.cwd()});process.stdout.write(`${JSON.stringify(e)}
3
- `)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1}process.exit()}if(p==="migrations"){const{syncMigrations:r}=await import("../dist/migrations-sync.js"),o=c[1];o!=="sync"&&o!=="check"&&(console.error("usage: tego-plugin migrations sync|check"),process.exit(2));const e=await r({root:process.cwd(),check:o==="check"});for(const{from:s,to:t}of e.copied)console.log(`${s} -> ${t}`);for(const s of e.problems)console.error(`\u2716 ${s}`);e.problems.length===0&&e.copied.length===0&&console.log("\u2705 migrations are in sync"),process.exit(e.problems.length>0?1:0)}if(p==="doctor"){const{runDoctor:r,formatDoctorReport:o}=await import("../dist/doctor.js"),e=c.slice(1),s=e.indexOf("--files");try{const t=await r({root:process.cwd(),files:s===-1?void 0:(e[s+1]??"").split(",").filter(Boolean)});process.stdout.write(e.includes("--json")?`${JSON.stringify(t)}
2
+ var v=Object.defineProperty;var f=(s,o)=>v(s,"name",{value:o,configurable:!0});import{readFileSync as $}from"node:fs";const l=process.argv.slice(2),d=l[0];if(d==="create"){const{createStandalonePlugin:s}=await import("../dist/scaffold.js"),o=l.indexOf("--dir");try{const e=await s($(0,"utf8"),{parent:o===-1?".":l[o+1]??".",cwd:process.cwd()});process.stdout.write(`${JSON.stringify(e)}
3
+ `)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1}process.exit()}if(d==="migrations"){const{syncMigrations:s}=await import("../dist/migrations-sync.js"),o=l[1];o!=="sync"&&o!=="check"&&(console.error("usage: tego-plugin migrations sync|check"),process.exit(2));const e=await s({root:process.cwd(),check:o==="check"});for(const{from:r,to:t}of e.copied)console.log(`${r} -> ${t}`);for(const r of e.problems)console.error(`\u2716 ${r}`);e.problems.length===0&&e.copied.length===0&&console.log("\u2705 migrations are in sync"),process.exit(e.problems.length>0?1:0)}if(d==="doctor"){const{runDoctor:s,formatDoctorReport:o}=await import("../dist/doctor.js"),e=l.slice(1),r=e.indexOf("--files");try{const t=await s({root:process.cwd(),files:r===-1?void 0:(e[r+1]??"").split(",").filter(Boolean)});process.stdout.write(e.includes("--json")?`${JSON.stringify(t)}
4
4
  `:`${o(t)}
5
- `),process.exit(t.errors>0?1:0)}catch(t){console.error(`\u2716 ${t instanceof Error?t.message:String(t)}`),process.exit(2)}}if(p==="dev"){const{startDevServer:r}=await import("../dist/dev.js"),o=c.slice(1),e=l(a=>{const u=o.indexOf(`--${a}`);return u===-1?void 0:o[u+1]},"value"),s=e("port");s!==void 0&&!/^\d+$/.test(s)&&(console.error(`\u2716 --port must be a number, not ${s}`),process.exit(2));let t;try{t=await r({root:process.cwd(),port:s===void 0?void 0:Number(s),host:e("host"),backend:!o.includes("--no-backend"),target:e("target")==="desktop"?"desktop":"web",databaseUrl:process.env.DATABASE_URL||void 0})}catch(a){console.error(`\u2716 ${a instanceof Error?a.message:String(a)}`),process.exit(1)}let g=!1;const f=l(async()=>{g||(g=!0,setTimeout(()=>process.exit(0),5e3).unref(),await t.close().catch(()=>{}),process.exit(0))},"stop");process.once("SIGINT",f),process.once("SIGTERM",f),await new Promise(()=>{})}else p!=="publish"&&(console.error("usage: tego-plugin create [--dir <parent>] | tego-plugin dev [--port <n>] [--no-backend] | tego-plugin doctor [--json] | tego-plugin publish [options]"),process.exit(2));const{publishPlugin:b}=await import("../dist/publish.js"),i=c.slice(1);function n(r){const o=i.indexOf(`--${r}`);return o===-1?void 0:i[o+1]}l(n,"flag");function $(r){const o=[];for(let e=0;e<i.length;e++)i[e]===`--${r}`&&o.push(i[e+1]);return o}l($,"all");const m=n("server")??process.env.TEGO_SERVER_URL,h=n("token")??process.env.TEGO_ADMIN_TOKEN;(!m||!h)&&(console.error("\u2716 --server (TEGO_SERVER_URL) and --token (TEGO_ADMIN_TOKEN) are both required"),process.exit(2));const w=$("avatar").filter(Boolean),d={};n("category")&&(d.category=n("category")),n("homepage")&&(d.homepage=n("homepage")),n("icon")&&(d.icon=n("icon"));try{const r=await b({root:n("root")??process.cwd(),server:m,token:h,...w.length>0?{avatarIds:w}:{},...Object.keys(d).length>0?{meta:d}:{},approve:!i.includes("--no-approve"),install:!i.includes("--no-install")}),o=r.release.semanticVersion;console.log(`\u2705 ${r.release.pluginId}@${o} \u2014 release ${r.release.status}`)}catch(r){console.error(`\u2716 ${r instanceof Error?r.message:String(r)}`),process.exitCode=1}
5
+ `),process.exit(t.errors>0?1:0)}catch(t){console.error(`\u2716 ${t instanceof Error?t.message:String(t)}`),process.exit(2)}}if(d==="check"){const{runChecks:s,formatCheckReport:o,DEFAULT_CHECK_STEPS:e}=await import("../dist/doctor.js"),r=l.slice(1),t=f(i=>{const a=r.indexOf(`--${i}`);return a===-1?void 0:(r[a+1]??"").split(",").filter(Boolean)},"list"),n=t("steps")??[...e,...r.includes("--build")?["build"]:[]],g=["doctor","lint","type-check","test","build"],m=n.filter(i=>!g.includes(i));m.length>0&&(console.error(`\u2716 unknown step(s): ${m.join(", ")}; use ${g.join(", ")}`),process.exit(2));try{const i=t("files"),a=await s({root:process.cwd(),steps:n,...i?{files:i}:{}});process.stdout.write(r.includes("--json")?`${JSON.stringify(a)}
6
+ `:`${o(a)}
7
+ `),process.exit(a.passed?0:1)}catch(i){console.error(`\u2716 ${i instanceof Error?i.message:String(i)}`),process.exit(2)}}if(d==="rules"){const{ruleCatalog:s}=await import("../dist/doctor.js"),{resolvePluginParts:o}=await import("../dist/index.js");let e={};try{const t=JSON.parse($("tego.plugin.json","utf8")),n=o(t);e={frontend:n.frontend?.dir,backend:n.backend?.dir,claw:n.claw?.dir}}catch{}const r=s(e);if(l.includes("--json"))process.stdout.write(`${JSON.stringify(r)}
8
+ `);else for(const t of r.rules)process.stdout.write(`${t.id} ${t.severity.padEnd(7)} ${t.title} (${t.guide}; ${t.appliesTo.join(", ")||"whole plugin"})
9
+ `);process.exit(0)}if(d==="dev"){const{startDevServer:s}=await import("../dist/dev.js"),o=l.slice(1),e=f(c=>{const h=o.indexOf(`--${c}`);return h===-1?void 0:o[h+1]},"value"),r=e("port");r!==void 0&&!/^\d+$/.test(r)&&(console.error(`\u2716 --port must be a number, not ${r}`),process.exit(2));const t=o.find(c=>c==="--inspect"||c.startsWith("--inspect=")),n=t===void 0?process.env.TEGO_DEV_INSPECT||void 0:t.slice(10)||"default";let g;if(n!==void 0){n!=="default"&&!/^\d+$/.test(n)&&(console.error(`\u2716 --inspect takes a port number, not ${n}`),process.exit(2));const{DEFAULT_INSPECT_PORT:c}=await import("../dist/dev.js");g=n==="default"?c:Number(n)}let m;try{m=await s({root:process.cwd(),port:r===void 0?void 0:Number(r),host:e("host"),backend:!o.includes("--no-backend"),target:e("target")==="desktop"?"desktop":"web",databaseUrl:process.env.DATABASE_URL||void 0,inspect:g})}catch(c){console.error(`\u2716 ${c instanceof Error?c.message:String(c)}`),process.exit(1)}let i=!1;const a=f(async()=>{i||(i=!0,setTimeout(()=>process.exit(0),5e3).unref(),await m.close().catch(()=>{}),process.exit(0))},"stop");process.once("SIGINT",a),process.once("SIGTERM",a),await new Promise(()=>{})}else d!=="publish"&&(console.error("usage: tego-plugin create [--dir <parent>] | tego-plugin dev [--port <n>] [--inspect[=<port>]] [--no-backend] | tego-plugin doctor [--json] | tego-plugin publish [options]"),process.exit(2));const{publishPlugin:k}=await import("../dist/publish.js"),u=l.slice(1);function p(s){const o=u.indexOf(`--${s}`);return o===-1?void 0:u[o+1]}f(p,"flag");function y(s){const o=[];for(let e=0;e<u.length;e++)u[e]===`--${s}`&&o.push(u[e+1]);return o}f(y,"all");const x=p("server")??process.env.TEGO_SERVER_URL,b=p("token")??process.env.TEGO_ADMIN_TOKEN;(!x||!b)&&(console.error("\u2716 --server (TEGO_SERVER_URL) and --token (TEGO_ADMIN_TOKEN) are both required"),process.exit(2));const E=y("avatar").filter(Boolean),w={};p("category")&&(w.category=p("category")),p("homepage")&&(w.homepage=p("homepage")),p("icon")&&(w.icon=p("icon"));try{const s=await k({root:p("root")??process.cwd(),server:x,token:b,...E.length>0?{avatarIds:E}:{},...Object.keys(w).length>0?{meta:w}:{},approve:!u.includes("--no-approve"),install:!u.includes("--no-install")}),o=s.release.semanticVersion;console.log(`\u2705 ${s.release.pluginId}@${o} \u2014 release ${s.release.status}`)}catch(s){console.error(`\u2716 ${s instanceof Error?s.message:String(s)}`),process.exitCode=1}
@@ -14,7 +14,7 @@ ${t}`)}}c(uv,"uv");async function buildSdkWheel(e,n){await uv(["build","--quiet"
14
14
 
15
15
  `)},"notify"),clientCount:c(()=>t.size,"clientCount")}}c(serveBuildDir,"serveBuildDir");function contentTypeFor(e){return e.endsWith(".js")?"text/javascript; charset=utf-8":e.endsWith(".json")?"application/json; charset=utf-8":e.endsWith(".css")?"text/css; charset=utf-8":"application/octet-stream"}c(contentTypeFor,"contentTypeFor");async function readManifest(e){const n=path.join(e,chunk2P6UEAPV_cjs.PLUGIN_MANIFEST_FILENAME);let i;try{i=await promises.readFile(n,"utf8")}catch{throw new Error(`${chunk2P6UEAPV_cjs.PLUGIN_MANIFEST_FILENAME} not found at ${n}`)}const t=chunkSKNPNPJP_cjs.parseTegoPluginManifest(JSON.parse(i));if(!t.ok||!t.manifest)throw new Error([`${chunk2P6UEAPV_cjs.PLUGIN_MANIFEST_FILENAME} is invalid:`,...t.errors].join(`
16
16
  `));return t.manifest}c(readManifest,"readManifest");async function stageFrontend(e,n,i,t,a){const o=path.resolve(e,a?.viteConfig??`${t.dir}/vite.config.ts`);await assertExists(o,"frontend vite config");const{build:r}=await import("vite");await r({root:e,configFile:o,logLevel:"warn"});const s=path.resolve(e,a?.buildDir??`${t.dir}/dist`);await assertExists(path.join(s,path.basename(t.entryPath)),`frontend bundle (expected the vite build to write ${path.basename(t.entryPath)})`),await promises.mkdir(path.join(n,path.dirname(t.entryPath)),{recursive:!0}),await promises.cp(s,path.join(n,path.dirname(t.entryPath)),{recursive:!0});const l=await readPluginLocales(e,i,t.i18nDirPath);l.size>0&&await promises.mkdir(path.join(n,t.i18nDirPath),{recursive:!0});for(const[u,f]of l)await promises.writeFile(path.join(n,t.i18nDirPath,u),f,"utf8")}c(stageFrontend,"stageFrontend");async function stageAccessCatalog(e,n,i){const t=i.access?.catalog;if(!t)return;const a=path.resolve(e,t);await assertExists(a,`access catalog ${t}`);const o=await promises.readFile(a,"utf8"),r=chunkSKNPNPJP_cjs.checkPluginAccessCatalog(i,JSON.parse(o));if(!r.ok)throw new Error(["The access catalog is invalid:",...r.errors].join(`
17
- `));await promises.mkdir(path.dirname(path.join(n,t)),{recursive:!0}),await promises.writeFile(path.join(n,t),o,"utf8")}c(stageAccessCatalog,"stageAccessCatalog");async function stagePublicFrontend(e,n,i,t){const a=path.resolve(e,t?.viteConfig??`${i.dir}/vite.config.ts`);await assertExists(a,"public pages vite config");const{build:o}=await import("vite");await o({root:path.resolve(e,i.dir),configFile:a,logLevel:"warn"});const r=path.resolve(e,t?.buildDir??`${i.dir}/dist`);await assertExists(path.join(r,path.basename(i.entryPath)),`public pages (expected the vite build to write ${path.basename(i.entryPath)})`),await promises.mkdir(path.join(n,i.dir),{recursive:!0}),await promises.cp(r,path.join(n,i.dir),{recursive:!0})}c(stagePublicFrontend,"stagePublicFrontend");async function stageBackend(e,n,i,t){await buildPluginBackend(i,{root:e,outDir:n,...t?.entry?{entry:t.entry}:{},...t?.external?{external:t.external}:{}})}c(stageBackend,"stageBackend");async function buildPluginBackend(e,n){const{root:i,outDir:t}=n,a=path.resolve(i,n.entry??`${e.dir}/src/index.ts`);await assertExists(a,"backend entry");const{build:o}=await import("esbuild");await o({entryPoints:[a],outfile:path.join(t,e.entryPath),bundle:!0,platform:"node",target:"node22",format:"esm",banner:{js:"import { createRequire as __tegoCreateRequire } from 'node:module'; const require = __tegoCreateRequire(import.meta.url);"},external:[...BACKEND_EXTERNAL,...n.external??[]],logLevel:"warning"});for(const s of e.compatibilityRepairs??[]){const l=`${e.dir}/${s.sqlFile}`;await assertExists(path.resolve(i,l),"schema repair"),await promises.mkdir(path.dirname(path.join(t,l)),{recursive:!0}),await promises.cp(path.resolve(i,l),path.join(t,l))}const r=path.resolve(i,e.migrationsDirPath);await exists2(r)&&await promises.cp(r,path.join(t,e.migrationsDirPath),{recursive:!0}),await promises.writeFile(path.join(t,e.dir,"package.json"),`${JSON.stringify({type:"module"},null,2)}
17
+ `));await promises.mkdir(path.dirname(path.join(n,t)),{recursive:!0}),await promises.writeFile(path.join(n,t),o,"utf8")}c(stageAccessCatalog,"stageAccessCatalog");async function stagePublicFrontend(e,n,i,t){const a=path.resolve(e,t?.viteConfig??`${i.dir}/vite.config.ts`);await assertExists(a,"public pages vite config");const{build:o}=await import("vite");await o({root:path.resolve(e,i.dir),configFile:a,logLevel:"warn"});const r=path.resolve(e,t?.buildDir??`${i.dir}/dist`);await assertExists(path.join(r,path.basename(i.entryPath)),`public pages (expected the vite build to write ${path.basename(i.entryPath)})`),await promises.mkdir(path.join(n,i.dir),{recursive:!0}),await promises.cp(r,path.join(n,i.dir),{recursive:!0})}c(stagePublicFrontend,"stagePublicFrontend");async function stageBackend(e,n,i,t){await buildPluginBackend(i,{root:e,outDir:n,...t?.entry?{entry:t.entry}:{},...t?.external?{external:t.external}:{}})}c(stageBackend,"stageBackend");async function buildPluginBackend(e,n){const{root:i,outDir:t}=n,a=path.resolve(i,n.entry??`${e.dir}/src/index.ts`);await assertExists(a,"backend entry");const{build:o}=await import("esbuild");await o({entryPoints:[a],outfile:path.join(t,e.entryPath),bundle:!0,platform:"node",target:"node22",format:"esm",banner:{js:"import { createRequire as __tegoCreateRequire } from 'node:module'; const require = __tegoCreateRequire(import.meta.url);"},external:[...BACKEND_EXTERNAL,...n.external??[]],sourcemap:n.sourcemap?"inline":!1,logLevel:"warning"});for(const s of e.compatibilityRepairs??[]){const l=`${e.dir}/${s.sqlFile}`;await assertExists(path.resolve(i,l),"schema repair"),await promises.mkdir(path.dirname(path.join(t,l)),{recursive:!0}),await promises.cp(path.resolve(i,l),path.join(t,l))}const r=path.resolve(i,e.migrationsDirPath);await exists2(r)&&await promises.cp(r,path.join(t,e.migrationsDirPath),{recursive:!0}),await promises.writeFile(path.join(t,e.dir,"package.json"),`${JSON.stringify({type:"module"},null,2)}
18
18
  `,"utf8")}c(buildPluginBackend,"buildPluginBackend");async function stageClaw(e,n,i,t,a){const o=path.resolve(e,a?.entry??`${i.dir}/src/index.ts`);await assertExists(o,"claw entry");const{build:r}=await import("esbuild");await r({entryPoints:[o],outfile:path.join(n,i.dir,"index.js"),bundle:!0,platform:"node",target:"node22",format:"esm",external:[...CLAW_EXTERNAL,...a?.external??[]],logLevel:"warning"});const s=path.resolve(e,i.manifestPath);await assertExists(s,chunk2P6UEAPV_cjs.CLAW_MANIFEST_FILENAME);const l=JSON.parse(await promises.readFile(s,"utf8"));await promises.writeFile(path.join(n,i.manifestPath),`${JSON.stringify({...l,version:t.version},null,2)}
19
19
  `,"utf8");const u=path.resolve(e,i.dir,"package.json"),f=await exists2(u)?JSON.parse(await promises.readFile(u,"utf8")):{name:t.id,private:!0,type:"module"};await promises.writeFile(path.join(n,i.dir,"package.json"),`${JSON.stringify({...f,version:t.version,openclaw:{extensions:["./index.js"]}},null,2)}
20
20
  `,"utf8");const d=path.resolve(e,a?.skillsDir??`${i.dir}/skills`);await exists2(d)&&await promises.cp(d,path.join(n,i.dir,"skills"),{recursive:!0})}c(stageClaw,"stageClaw");async function zipDirectory(e,n){const i=new JSZip__default.default;await addDirectory(i,e,"");const t=await i.generateAsync({type:"nodebuffer",compression:"DEFLATE",compressionOptions:{level:9}});return await promises.writeFile(n,t),t.byteLength}c(zipDirectory,"zipDirectory");async function addDirectory(e,n,i){const t=await promises.readdir(n,{withFileTypes:!0});t.sort((a,o)=>a.name.localeCompare(o.name));for(const a of t){const o=`${i}${a.name}`;a.isDirectory()?(e.file(`${o}/`,null,{dir:!0,date:ZIP_EPOCH}),await addDirectory(e,path.join(n,a.name),`${o}/`)):e.file(o,await promises.readFile(path.join(n,a.name)),{date:ZIP_EPOCH,createFolders:!1})}}c(addDirectory,"addDirectory");async function exists2(e){try{return await promises.stat(e),!0}catch{return!1}}c(exists2,"exists2");async function assertExists(e,n){if(!await exists2(e))throw new Error(`${n} not found at ${e}`)}c(assertExists,"assertExists");function formatBytes(e){return e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/1024/1024).toFixed(2)} MB`}c(formatBytes,"formatBytes"),exports.buildAgentTemplate=buildAgentTemplate,exports.buildPluginBackend=buildPluginBackend,exports.buildPluginTemplates=buildPluginTemplates,exports.lockPlugin=lockPlugin,exports.packPlugin=packPlugin,exports.readManifest=readManifest,exports.readPluginLocales=readPluginLocales,exports.watchPluginFrontend=watchPluginFrontend;
@@ -14,7 +14,7 @@ ${e}`)}}l(F,"uv");async function at(t,n){await F(["build","--quiet","--wheel","-
14
14
 
15
15
  `)},"notify"),clientCount:l(()=>e.size,"clientCount")}}l(Vt,"serveBuildDir");function ct(t){return t.endsWith(".js")?"text/javascript; charset=utf-8":t.endsWith(".json")?"application/json; charset=utf-8":t.endsWith(".css")?"text/css; charset=utf-8":"application/octet-stream"}l(ct,"contentTypeFor");async function N(t){const n=o(t,C);let a;try{a=await m(n,"utf8")}catch{throw new Error(`${C} not found at ${n}`)}const e=wt(JSON.parse(a));if(!e.ok||!e.manifest)throw new Error([`${C} is invalid:`,...e.errors].join(`
16
16
  `));return e.manifest}l(N,"readManifest");async function Yt(t,n,a,e,i){const s=d(t,i?.viteConfig??`${e.dir}/vite.config.ts`);await p(s,"frontend vite config");const{build:r}=await import("vite");await r({root:t,configFile:s,logLevel:"warn"});const c=d(t,i?.buildDir??`${e.dir}/dist`);await p(o(c,x(e.entryPath)),`frontend bundle (expected the vite build to write ${x(e.entryPath)})`),await g(o(n,v(e.entryPath)),{recursive:!0}),await y(c,o(n,v(e.entryPath)),{recursive:!0});const u=await M(t,a,e.i18nDirPath);u.size>0&&await g(o(n,e.i18nDirPath),{recursive:!0});for(const[f,h]of u)await b(o(n,e.i18nDirPath,f),h,"utf8")}l(Yt,"stageFrontend");async function zt(t,n,a){const e=a.access?.catalog;if(!e)return;const i=d(t,e);await p(i,`access catalog ${e}`);const s=await m(i,"utf8"),r=ht(a,JSON.parse(s));if(!r.ok)throw new Error(["The access catalog is invalid:",...r.errors].join(`
17
- `));await g(v(o(n,e)),{recursive:!0}),await b(o(n,e),s,"utf8")}l(zt,"stageAccessCatalog");async function Xt(t,n,a,e){const i=d(t,e?.viteConfig??`${a.dir}/vite.config.ts`);await p(i,"public pages vite config");const{build:s}=await import("vite");await s({root:d(t,a.dir),configFile:i,logLevel:"warn"});const r=d(t,e?.buildDir??`${a.dir}/dist`);await p(o(r,x(a.entryPath)),`public pages (expected the vite build to write ${x(a.entryPath)})`),await g(o(n,a.dir),{recursive:!0}),await y(r,o(n,a.dir),{recursive:!0})}l(Xt,"stagePublicFrontend");async function Zt(t,n,a,e){await lt(a,{root:t,outDir:n,...e?.entry?{entry:e.entry}:{},...e?.external?{external:e.external}:{}})}l(Zt,"stageBackend");async function lt(t,n){const{root:a,outDir:e}=n,i=d(a,n.entry??`${t.dir}/src/index.ts`);await p(i,"backend entry");const{build:s}=await import("esbuild");await s({entryPoints:[i],outfile:o(e,t.entryPath),bundle:!0,platform:"node",target:"node22",format:"esm",banner:{js:"import { createRequire as __tegoCreateRequire } from 'node:module'; const require = __tegoCreateRequire(import.meta.url);"},external:[...Ht,...n.external??[]],logLevel:"warning"});for(const c of t.compatibilityRepairs??[]){const u=`${t.dir}/${c.sqlFile}`;await p(d(a,u),"schema repair"),await g(v(o(e,u)),{recursive:!0}),await y(d(a,u),o(e,u))}const r=d(a,t.migrationsDirPath);await L(r)&&await y(r,o(e,t.migrationsDirPath),{recursive:!0}),await b(o(e,t.dir,"package.json"),`${JSON.stringify({type:"module"},null,2)}
17
+ `));await g(v(o(n,e)),{recursive:!0}),await b(o(n,e),s,"utf8")}l(zt,"stageAccessCatalog");async function Xt(t,n,a,e){const i=d(t,e?.viteConfig??`${a.dir}/vite.config.ts`);await p(i,"public pages vite config");const{build:s}=await import("vite");await s({root:d(t,a.dir),configFile:i,logLevel:"warn"});const r=d(t,e?.buildDir??`${a.dir}/dist`);await p(o(r,x(a.entryPath)),`public pages (expected the vite build to write ${x(a.entryPath)})`),await g(o(n,a.dir),{recursive:!0}),await y(r,o(n,a.dir),{recursive:!0})}l(Xt,"stagePublicFrontend");async function Zt(t,n,a,e){await lt(a,{root:t,outDir:n,...e?.entry?{entry:e.entry}:{},...e?.external?{external:e.external}:{}})}l(Zt,"stageBackend");async function lt(t,n){const{root:a,outDir:e}=n,i=d(a,n.entry??`${t.dir}/src/index.ts`);await p(i,"backend entry");const{build:s}=await import("esbuild");await s({entryPoints:[i],outfile:o(e,t.entryPath),bundle:!0,platform:"node",target:"node22",format:"esm",banner:{js:"import { createRequire as __tegoCreateRequire } from 'node:module'; const require = __tegoCreateRequire(import.meta.url);"},external:[...Ht,...n.external??[]],sourcemap:n.sourcemap?"inline":!1,logLevel:"warning"});for(const c of t.compatibilityRepairs??[]){const u=`${t.dir}/${c.sqlFile}`;await p(d(a,u),"schema repair"),await g(v(o(e,u)),{recursive:!0}),await y(d(a,u),o(e,u))}const r=d(a,t.migrationsDirPath);await L(r)&&await y(r,o(e,t.migrationsDirPath),{recursive:!0}),await b(o(e,t.dir,"package.json"),`${JSON.stringify({type:"module"},null,2)}
18
18
  `,"utf8")}l(lt,"buildPluginBackend");async function Qt(t,n,a,e,i){const s=d(t,i?.entry??`${a.dir}/src/index.ts`);await p(s,"claw entry");const{build:r}=await import("esbuild");await r({entryPoints:[s],outfile:o(n,a.dir,"index.js"),bundle:!0,platform:"node",target:"node22",format:"esm",external:[...Wt,...i?.external??[]],logLevel:"warning"});const c=d(t,a.manifestPath);await p(c,$t);const u=JSON.parse(await m(c,"utf8"));await b(o(n,a.manifestPath),`${JSON.stringify({...u,version:e.version},null,2)}
19
19
  `,"utf8");const f=d(t,a.dir,"package.json"),h=await L(f)?JSON.parse(await m(f,"utf8")):{name:e.id,private:!0,type:"module"};await b(o(n,a.dir,"package.json"),`${JSON.stringify({...h,version:e.version,openclaw:{extensions:["./index.js"]}},null,2)}
20
20
  `,"utf8");const w=d(t,i?.skillsDir??`${a.dir}/skills`);await L(w)&&await y(w,o(n,a.dir,"skills"),{recursive:!0})}l(Qt,"stageClaw");async function te(t,n){const a=new Et;await ut(a,t,"");const e=await a.generateAsync({type:"nodebuffer",compression:"DEFLATE",compressionOptions:{level:9}});return await b(n,e),e.byteLength}l(te,"zipDirectory");async function ut(t,n,a){const e=await k(n,{withFileTypes:!0});e.sort((i,s)=>i.name.localeCompare(s.name));for(const i of e){const s=`${a}${i.name}`;i.isDirectory()?(t.file(`${s}/`,null,{dir:!0,date:rt}),await ut(t,o(n,i.name),`${s}/`)):t.file(s,await m(o(n,i.name)),{date:rt,createFolders:!1})}}l(ut,"addDirectory");async function L(t){try{return await G(t),!0}catch{return!1}}l(L,"exists2");async function p(t,n){if(!await L(t))throw new Error(`${n} not found at ${t}`)}l(p,"assertExists");function ee(t){return t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/1024/1024).toFixed(2)} MB`}l(ee,"formatBytes");export{z as buildAgentTemplate,lt as buildPluginBackend,Bt as buildPluginTemplates,Jt as lockPlugin,Ut as packPlugin,N as readManifest,M as readPluginLocales,qt as watchPluginFrontend};
package/dist/dev.cjs CHANGED
@@ -1,4 +1,8 @@
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 f(){await promises.rm(e.outDir,{recursive:!0,force:!0}),await chunk4LKRXDYY_cjs.buildPluginBackend(t,{root:e.root,outDir:e.outDir});const g=await applyDevMigrations({databaseUrl:e.databaseUrl,schema:n,migrationsDir:i,tenantId:DEV_TENANT_ID});for(const E of g)r(`[backend] applied migration ${E}`)}o(f,"build");function v(){const g=child_process.spawn(process.execPath,[u],{env:s,stdio:["ignore","pipe","pipe"]});l=g,g.stdout?.on("data",E=>process.stdout.write(E)),g.stderr?.on("data",E=>process.stderr.write(E)),g.on("exit",E=>{l===g&&!d&&(l=null,r(`[backend] exited (code ${E??"null"}); it starts again on the next save`))})}o(v,"start"),await f(),v(),r(`[backend] ${e.pluginId} on 127.0.0.1:${a}, schema ${n} in ${redactUrl(e.databaseUrl)}`);let y=null,m=null,D=!1;const A=o(async()=>{if(m){D=!0;return}m=(async()=>{try{await f();const g=l;l=null,await stopChild(g),d||(v(),r("[backend] rebuilt and restarted"))}catch(g){r(`[backend] rebuild failed, the previous build keeps running: ${g instanceof Error?g.message:String(g)}`)}})(),await m,m=null,D&&!d&&(D=!1,await A())},"rebuild"),N=[];for(const g of[t.dir,"shared"]){const E=path.resolve(e.root,g);fs.existsSync(E)&&N.push(fs.watch(E,{recursive:!0},(_,k)=>{k&&/(^|[\\/])(node_modules|dist)([\\/]|$)/.test(String(k))||(y&&clearTimeout(y),y=setTimeout(()=>void A(),REBUILD_DEBOUNCE_MS))}))}return{port:a,schema:n,async close(){d=!0,y&&clearTimeout(y);for(const g of N)g.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(),f=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(f)).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((f,v)=>f.kind==="user"&&f.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((f,v)=>f.kind==="user"&&d.has(String(f.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 f=Number(a.searchParams.get("bytes"));Number.isFinite(f)&&f>0&&(d=d.subarray(0,f)),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),f=u(l);return await head(d)?(await promises.mkdir(path.dirname(f),{recursive:!0}),await promises.copyFile(d,f),await promises.copyFile(metaOf(d),metaOf(f)).catch(()=>{}),send(t,200,{data:await head(f)})):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=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t]),"escapeHtml");function indexHtml({displayName:e}){return`<!doctype html>
1
+ "use strict";var z=Object.defineProperty;var o=(e,t)=>z(e,"name",{value:t,configurable:!0});var chunkIMAVYQ4U_cjs=require("./chunk-IMAVYQ4U.cjs"),chunkDRHBXEPK_cjs=require("./chunk-DRHBXEPK.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"),os=require("os"),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(a=>(Array.isArray(a)?a[0]:a)?.trim()??"","single"),r=t(e[DEV_USER_HEADER])||DEFAULT_DEV_USER_ID;let n=[];try{const a=JSON.parse(t(e[DEV_ROLES_HEADER])||"[]");Array.isArray(a)&&(n=a.filter(c=>typeof c=="string"))}catch{}return{userId:r,roles:n}}o(devCallerOf,"devCallerOf");function gatewayHeaders(e,t,r,n=Date.now()){const a={};for(const[s,i]of Object.entries(e)){const d=s.toLowerCase();i===void 0||d.startsWith("x-tego-")||d==="cookie"||d==="authorization"||d==="host"||d==="connection"||(a[d]=Array.isArray(i)?i.join(", "):i)}const c=JSON.stringify(t.roles),u=String(n);return a[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.userId]=t.userId,a[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.tenantId]=DEV_TENANT_ID,a[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.roles]=c,a[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.issuedAt]=u,a[chunkBN6TGHSB_cjs.PLUGIN_CONTEXT_HEADERS.signature]=chunkIMAVYQ4U_cjs.signPluginContext({userId:t.userId,tenantId:DEV_TENANT_ID,rolesHeader:c,avatarId:"",issuedAt:u},r),a}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 n=r.search.replace(/^\?/,"");return r.search=`?${n?`${n}&`:""}options=${encodeURIComponent(`-c search_path=${t}`)}`,r.toString()}o(devDatabaseUrl,"devDatabaseUrl");async function connect(e){let t;try{t=await import("pg")}catch(a){throw new Error(`The local backend needs the "pg" driver; add it to the plugin's dependencies.`,{cause:a})}const r=t.default?.Client??t.Client;if(!r)throw new Error('The "pg" module has no Client constructor.');const n=new r({connectionString:e});try{await n.connect()}catch(a){throw new Error(`Cannot connect to DATABASE_URL (${redactUrl(e)}): ${explainDatabaseError(a)}`,{cause:a})}return n}o(connect,"connect");function explainDatabaseError(e){const t=e?.code,r=e instanceof Error?e.message:String(e);switch(t){case"ECONNREFUSED":return`${r} \u2014 is PostgreSQL running at that host and port?`;case"ENOTFOUND":return`${r} \u2014 the host name does not resolve.`;case"28P01":case"28000":return`${r} \u2014 check the user name and password.`;case"3D000":return`${r} \u2014 create the database first (createdb <name>).`;default:return r}}o(explainDatabaseError,"explainDatabaseError");async function checkDevDatabase(e){const t=await connect(e);try{await t.query("select 1")}finally{await t.end().catch(()=>{})}}o(checkDevDatabase,"checkDevDatabase");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(c=>c.filter(u=>u.endsWith(".sql")).sort(),c=>{if(c.code==="ENOENT")return[];throw c}),r=await connect(e.databaseUrl),n=quote(e.schema),a=`${n}.${quote(DEV_MIGRATIONS_TABLE)}`;try{await r.query(`CREATE SCHEMA IF NOT EXISTS ${n}`),await r.query(`CREATE TABLE IF NOT EXISTS ${a} (name text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())`);const c=new Set((await r.query(`SELECT name FROM ${a}`)).rows.map(s=>String(s.name))),u=[];for(const s of t){if(c.has(s))continue;const i=await promises.readFile(path.join(e.migrationsDir,s),"utf8");await r.query("BEGIN");try{await r.query(`SET LOCAL search_path TO ${n}`),await r.query("SELECT set_config('app.tenant_id', $1, true)",[e.tenantId]),await r.query(i),await r.query(`INSERT INTO ${a} (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 POLL_INTERVAL_MS=1e3,PROBE_TIMEOUT_MS=800,SKIPPED=new Set(["node_modules","dist",".git",".tego-dev","build"]);async function treeSnapshot(e){const t=new Map,r=o(async n=>{let a;try{a=await promises.readdir(n,{withFileTypes:!0})}catch{return}for(const c of a){const u=path.join(n,c.name);if(c.isDirectory())SKIPPED.has(c.name)||await r(u);else if(c.isFile())try{const s=await promises.stat(u);t.set(u,`${s.mtimeMs}:${s.size}`)}catch{}}},"walk");return await r(e),t}o(treeSnapshot,"treeSnapshot");function sameSnapshot(e,t){if(e.size!==t.size)return!1;for(const[r,n]of e)if(t.get(r)!==n)return!1;return!0}o(sameSnapshot,"sameSnapshot");function watchTree(e,t,r){let n=!1,a,c;const u=o(async()=>{let i=await treeSnapshot(e);const d=o(async()=>{if(n)return;const h=await treeSnapshot(e);!n&&!sameSnapshot(i,h)&&(i=h,t()),n||(c=setTimeout(()=>void d(),POLL_INTERVAL_MS))},"tick");n||(c=setTimeout(()=>void d(),POLL_INTERVAL_MS))},"poll"),s=o(i=>{a?.close(),a=void 0,!n&&(r.log(`[watch] ${r.label}: file events unavailable (${i}); checking for changes every ${POLL_INTERVAL_MS/1e3}s`),u())},"fallBack");if(r.poll)u();else try{a=fs.watch(e,{recursive:!0},(i,d)=>{String(d??"").split(/[\\/]/).some(v=>SKIPPED.has(v))||t()}),a.on("error",i=>s(i.message))}catch(i){s(i instanceof Error?i.message:String(i))}return{close(){n=!0,a?.close(),c&&clearTimeout(c)}}}o(watchTree,"watchTree");async function fileEventsAvailable(){const e=await promises.mkdtemp(path.join(os.tmpdir(),"tego-dev-watch-"));try{return await new Promise(t=>{let r,n=!1;const a=o(u=>{clearTimeout(c),r?.close(),t(u)},"finish"),c=setTimeout(()=>a(n),PROBE_TIMEOUT_MS);try{r=fs.watch(e,{recursive:!0},()=>n=!0),r.on("error",()=>a(!1))}catch{a(!1);return}promises.writeFile(path.join(e,"probe"),"x").catch(()=>a(!1))})}finally{await promises.rm(e,{recursive:!0,force:!0})}}o(fileEventsAvailable,"fileEventsAvailable");var REBUILD_DEBOUNCE_MS=250,STOP_TIMEOUT_MS=5e3,HEALTH_TIMEOUT_MS=3e4,HEALTH_POLL_MS=200,OUTPUT_TAIL_LINES=40,DEFAULT_INSPECT_PORT=9229;function freePort(){return new Promise((e,t)=>{const r=net.createServer();r.once("error",t),r.listen(0,"127.0.0.1",()=>{const n=r.address(),a=typeof n=="object"&&n?n.port:0;r.close(()=>e(a))})})}o(freePort,"freePort");function backendNodeArgs(e,t){return["--enable-source-maps",...t?[`--inspect=127.0.0.1:${t}`]:[],e]}o(backendNodeArgs,"backendNodeArgs");function linePrefixer(e,t){let r="";return{push(n){const a=(r+n).split(/\r?\n/);r=a.pop()??"";for(const c of a)t(`${e}${c}`)},flush(){r&&t(`${e}${r}`),r=""}}}o(linePrefixer,"linePrefixer");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 n=await freePort(),a=devSchemaName(e.pluginId),c=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(n),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,a)};let i=null,d=!1;const h=[],v=o(f=>{h.push(f),h.length>OUTPUT_TAIL_LINES&&h.shift(),r(f)},"remember");async function k(){await promises.rm(e.outDir,{recursive:!0,force:!0}),await chunkDRHBXEPK_cjs.buildPluginBackend(t,{root:e.root,outDir:e.outDir,sourcemap:!0});const f=await applyDevMigrations({databaseUrl:e.databaseUrl,schema:a,migrationsDir:c,tenantId:DEV_TENANT_ID});for(const m of f)r(`[backend] applied migration ${m}`)}o(k,"build");function y(){const f=child_process.spawn(process.execPath,backendNodeArgs(u,e.inspect),{env:s,stdio:["ignore","pipe","pipe"]});i=f;for(const m of[f.stdout,f.stderr]){const A=linePrefixer("[backend] ",v);m?.setEncoding("utf8"),m?.on("data",E=>A.push(E)),m?.on("end",()=>A.flush())}return f.on("exit",m=>{i===f&&!d&&(i=null,r(`[backend] exited (code ${m??"null"}); fix the code and save to restart`))}),f}o(y,"start");async function I(f){const m=`http://127.0.0.1:${n}${t.healthPath}`,A=Date.now()+HEALTH_TIMEOUT_MS;for(;Date.now()<A;){if(f.exitCode!==null||f.signalCode!==null)throw new Error(`The backend exited while starting (code ${f.exitCode??f.signalCode}):
2
+ ${h.join(`
3
+ `)}`);try{if((await fetch(m)).ok)return}catch{}await new Promise(E=>setTimeout(E,HEALTH_POLL_MS))}throw new Error(`The backend did not answer ${t.healthPath} within ${HEALTH_TIMEOUT_MS/1e3}s:
4
+ ${h.join(`
5
+ `)}`)}o(I,"healthy"),await k();const D=y();try{await I(D)}catch(f){throw d=!0,await stopChild(i),f}r(`[backend] ${e.pluginId} on 127.0.0.1:${n}, schema ${a} in ${redactUrl(e.databaseUrl)}`),e.inspect&&r(`[backend] debugger: attach to 127.0.0.1:${e.inspect} (chrome://inspect, or your editor's Node attach)`);let P=null,x=null,R=!1;const L=o(async()=>{if(x){R=!0;return}x=(async()=>{try{await k();const f=i;i=null,await stopChild(f),d||(y(),r("[backend] rebuilt and restarted"))}catch(f){r(`[backend] rebuild failed, the previous build keeps running: ${f instanceof Error?f.message:String(f)}`)}})(),await x,x=null,R&&!d&&(R=!1,await L())},"rebuild"),C=[];for(const f of[t.dir,"shared"]){const m=path.resolve(e.root,f);fs.existsSync(m)&&C.push(watchTree(m,()=>{P&&clearTimeout(P),P=setTimeout(()=>void L(),REBUILD_DEBOUNCE_MS)},{poll:e.poll===!0,log:r,label:f}))}return{port:n,schema:a,async close(){d=!0,P&&clearTimeout(P);for(const f of C)f.close();await x,await stopChild(i)}}}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 n=0;for await(const a of e){if(n+=a.length,n>t)throw Object.assign(new Error("body too large"),{status:413});r.push(a)}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),n=path.resolve(r,t);if(!t||path.isAbsolute(t)||!n.startsWith(r+path.sep))throw Object.assign(new Error(`invalid storage key ${JSON.stringify(t)}`),{status:400});return n}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(n=>JSON.parse(n),()=>({}));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 n=new URL(e.url??"/","http://preview.local");try{if(n.pathname.startsWith(FAKE_FILES_PREFIX)){const s=storagePath(r.storageDir,n.searchParams.get("key")??""),i=await head(s);if(!i)return send(t,404);t.writeHead(200,{"content-type":i.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 a=n.pathname.slice(FAKE_HOST_PREFIX.length)||"/",c=o(async()=>JSON.parse((await readBody(e,MAX_JSON_BYTES)).toString("utf8")||"{}"),"json"),u=o(s=>storagePath(r.storageDir,String(s??"")),"keyFile");switch(a){case"/config":return send(t,200,{data:{values:r.config()}});case"/log":{const{level:s,message:i,fields:d}=await c();return r.log(`[backend ${String(s??"info")}] ${String(i??"")}${d?` ${JSON.stringify(d)}`:""}`),send(t,200,{data:{}})}case"/events":{const{name:s,payload:i}=await c();return r.log(`[event] ${String(s)} ${JSON.stringify(i??null)}`),send(t,200,{data:{}})}case"/notifications":return r.log(`[notification] ${JSON.stringify(await c())}`),send(t,200,{data:{id:crypto.randomUUID()}});case"/directory/identity":{const{userId:s}=await c(),i=r.users().get(String(s));return send(t,200,{data:{active:!!i,roleNames:i??[],memberships:[],checkedAt:new Date().toISOString()}})}case"/directory/list":{const{kind:s,search:i,ids:d}=await c(),h=typeof i=="string"?i.toLowerCase():"",v=Array.isArray(d)?new Set(d.map(String)):void 0,k=s==="users"?[...r.users().keys()].filter(y=>(!v||v.has(y))&&y.toLowerCase().includes(h)).map(y=>({id:y,displayName:y,active:!0})):[];return send(t,200,{data:{items:k,hasMore:!1}})}case"/directory/match":{const{userId:s,principals:i}=await c(),d=Array.isArray(i)?i:[];return send(t,200,{data:{active:r.users().has(String(s)),matched:d.flatMap((h,v)=>h.kind==="user"&&h.id===s?[v]:[]),checkedAt:new Date().toISOString()}})}case"/directory/validate":{const{principals:s}=await c(),i=Array.isArray(s)?s:[],d=r.users();return send(t,200,{data:{invalid:i.flatMap((h,v)=>h.kind==="user"&&d.has(String(h.id))?[]:[v])}})}case"/storage/get":case"/storage/stream":{const s=u(n.searchParams.get("key"));if(e.method==="POST"&&a==="/storage/stream")return await put(s,await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});const i=await head(s);if(!i)return send(t,404);let d=await promises.readFile(s);const h=Number(n.searchParams.get("bytes"));Number.isFinite(h)&&h>0&&(d=d.subarray(0,h)),t.writeHead(200,{"content-type":i.contentType??"application/octet-stream"}),t.end(d);return}case"/storage/put":return await put(u(n.searchParams.get("key")),await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});case"/storage/head":{const s=await head(u((await c()).key));return s?send(t,200,{data:s}):send(t,404)}case"/storage/delete":{const s=u((await c()).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:i}=await c(),d=u(s),h=u(i);return await head(d)?(await promises.mkdir(path.dirname(h),{recursive:!0}),await promises.copyFile(d,h),await promises.copyFile(metaOf(d),metaOf(h)).catch(()=>{}),send(t,200,{data:await head(h)})):send(t,404)}case"/storage/signed-url":{const{key:s}=await c();u(s);const i=`${FAKE_FILES_PREFIX}?key=${encodeURIComponent(String(s))}`;return send(t,200,{data:{url:`${r.origin()}${i}`,path:i}})}default:return r.log(`[host] ${a} is not available in local preview`),send(t,501,{error:{code:"not_available_in_preview",message:`${a} is not available in local preview`}})}}catch(a){const c=a.status??500;send(t,c,{error:{message:a instanceof Error?a.message:String(a)}})}}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=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t]),"escapeHtml");function indexHtml({displayName:e}){return`<!doctype html>
2
6
  <html lang="en" class="h-full">
3
7
  <head>
4
8
  <meta charset="UTF-8" />
@@ -46,7 +50,7 @@ export const ChatPanel = Unavailable;
46
50
  export function useTopic() {
47
51
  return { topic: undefined, topics: [], loading: false };
48
52
  }
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.
53
+ `;function mainTsx(e){const t=JSON.stringify({pluginId:e.pluginId,displayName:e.displayName,nav:e.nav.map(n=>({key:n.key,labelKey:n.labelKey,path:n.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
54
  import '@tego/ui/styles/tokens.css';
51
55
  import './shell.css';
52
56
 
@@ -55,12 +59,17 @@ import type { PluginFrontendModule, PluginHostContext, PluginRoute } from ${SDK_
55
59
  import {
56
60
  Button,
57
61
  Input,
62
+ Label,
63
+ Popover,
64
+ PopoverContent,
65
+ PopoverTrigger,
58
66
  Select,
59
67
  SelectContent,
60
68
  SelectItem,
61
69
  SelectTrigger,
62
70
  SelectValue,
63
71
  Toaster,
72
+ Switch,
64
73
  TooltipProvider,
65
74
  } from '@tego/ui';
66
75
  import i18next from 'i18next';
@@ -247,55 +256,39 @@ function DevBar() {
247
256
  store('roles', roles);
248
257
  location.reload();
249
258
  };
259
+ // One quiet row: what the preview imitates, the plugin's pages, CSP problems, and the
260
+ // rarely changed settings (language, theme, who is signed in) behind one button.
250
261
  return (
251
- <div className='flex shrink-0 items-center gap-2 border-b border-border bg-card px-3 py-2 text-sm'>
252
- <span className='shrink-0 font-medium'>{DEV.displayName}</span>
253
- <span className='shrink-0 rounded-control bg-sunken px-1.5 py-0.5 font-mono text-xs text-muted-foreground'>preview</span>
254
- <nav className='flex min-w-0 flex-1 items-center gap-1 overflow-x-auto'>
262
+ <div className='flex h-10 shrink-0 items-center gap-2 border-b border-border bg-card px-2 text-sm'>
263
+ <div className='flex shrink-0 items-center rounded-control bg-sunken p-0.5' role='group' aria-label='Shell'>
264
+ {(['web', 'desktop'] as const).map(target => (
265
+ <button
266
+ key={target}
267
+ type='button'
268
+ aria-pressed={TARGET === target}
269
+ onClick={() => TARGET !== target && switchTarget(target)}
270
+ className={
271
+ 'h-6 rounded-control px-2 text-xs font-medium transition-colors ' +
272
+ (TARGET === target ? 'bg-card text-foreground shadow-card' : 'text-muted-foreground hover:text-foreground')
273
+ }
274
+ >
275
+ {target === 'web' ? 'Web' : 'Desktop'}
276
+ </button>
277
+ ))}
278
+ </div>
279
+ <nav className='flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto' aria-label='Pages'>
255
280
  {DEV.nav.map(entry => (
256
281
  <Button
257
282
  key={entry.key}
258
283
  size='sm'
259
284
  variant={current.path === entry.path ? 'secondary' : 'ghost'}
285
+ className='h-7 shrink-0'
260
286
  onClick={() => navigate(entry.path)}
261
287
  >
262
288
  {i18next.t(entry.labelKey, { ns: NAMESPACE, lng: lang })}
263
289
  </Button>
264
290
  ))}
265
291
  </nav>
266
- <Select
267
- value={lang}
268
- onValueChange={async next => {
269
- await loadLocale(next);
270
- await i18next.changeLanguage(next);
271
- store('lang', next);
272
- setLang(next);
273
- }}
274
- >
275
- <SelectTrigger className='h-8 w-28' aria-label='Language'>
276
- <SelectValue />
277
- </SelectTrigger>
278
- <SelectContent>
279
- {DEV.locales.map(tag => (
280
- <SelectItem key={tag} value={tag}>
281
- {tag}
282
- </SelectItem>
283
- ))}
284
- </SelectContent>
285
- </Select>
286
- <div className='flex shrink-0 items-center rounded-control border border-border p-0.5' role='group' aria-label='Shell'>
287
- {(['web', 'desktop'] as const).map(target => (
288
- <Button
289
- key={target}
290
- size='sm'
291
- variant={TARGET === target ? 'secondary' : 'ghost'}
292
- aria-pressed={TARGET === target}
293
- onClick={() => TARGET !== target && switchTarget(target)}
294
- >
295
- {target === 'web' ? 'Web' : 'Desktop'}
296
- </Button>
297
- ))}
298
- </div>
299
292
  {blocked > 0 && (
300
293
  <span
301
294
  className='shrink-0 rounded-control bg-destructive-soft px-1.5 py-0.5 text-xs text-destructive'
@@ -304,36 +297,70 @@ function DevBar() {
304
297
  CSP blocked {blocked}
305
298
  </span>
306
299
  )}
307
- <Button
308
- size='sm'
309
- variant='ghost'
310
- onClick={() => {
311
- document.documentElement.classList.toggle('dark', !dark);
312
- store('theme', dark ? 'light' : 'dark');
313
- setDark(!dark);
314
- }}
315
- >
316
- {dark ? 'Light' : 'Dark'}
317
- </Button>
318
- <form
319
- className='flex shrink-0 items-center gap-1'
320
- onSubmit={event => {
321
- event.preventDefault();
322
- switchUser();
323
- }}
324
- >
325
- <Input className='h-8 w-32' aria-label='User id' value={userId} onChange={event => setUserId(event.target.value)} />
326
- <Input
327
- className='h-8 w-36'
328
- aria-label='Roles, comma-separated'
329
- placeholder='roles'
330
- value={roles}
331
- onChange={event => setRoles(event.target.value)}
332
- />
333
- <Button size='sm' variant='outline' type='submit'>
334
- Sign in as
335
- </Button>
336
- </form>
300
+ <Popover>
301
+ <PopoverTrigger asChild>
302
+ <Button size='sm' variant='ghost' className='h-7 shrink-0 font-mono text-xs text-muted-foreground'>
303
+ {lang} \xB7 {user.userId.slice(0, 8)}
304
+ </Button>
305
+ </PopoverTrigger>
306
+ <PopoverContent align='end' className='w-72 space-y-3'>
307
+ <div className='space-y-1.5'>
308
+ <Label>Language</Label>
309
+ <Select
310
+ value={lang}
311
+ onValueChange={async next => {
312
+ await loadLocale(next);
313
+ await i18next.changeLanguage(next);
314
+ store('lang', next);
315
+ setLang(next);
316
+ }}
317
+ >
318
+ <SelectTrigger className='h-8' aria-label='Language'>
319
+ <SelectValue />
320
+ </SelectTrigger>
321
+ <SelectContent>
322
+ {DEV.locales.map(tag => (
323
+ <SelectItem key={tag} value={tag}>
324
+ {tag}
325
+ </SelectItem>
326
+ ))}
327
+ </SelectContent>
328
+ </Select>
329
+ </div>
330
+ <div className='flex items-center justify-between'>
331
+ <Label htmlFor='tego-dev-dark'>Dark theme</Label>
332
+ <Switch
333
+ id='tego-dev-dark'
334
+ checked={dark}
335
+ onCheckedChange={next => {
336
+ document.documentElement.classList.toggle('dark', next);
337
+ store('theme', next ? 'dark' : 'light');
338
+ setDark(next);
339
+ }}
340
+ />
341
+ </div>
342
+ <form
343
+ className='space-y-1.5 border-t border-border pt-3'
344
+ onSubmit={event => {
345
+ event.preventDefault();
346
+ switchUser();
347
+ }}
348
+ >
349
+ <Label>Signed in as</Label>
350
+ <Input className='h-8 font-mono text-xs' aria-label='User id' value={userId} onChange={event => setUserId(event.target.value)} />
351
+ <Input
352
+ className='h-8'
353
+ aria-label='Roles, comma-separated'
354
+ placeholder='Roles, comma-separated'
355
+ value={roles}
356
+ onChange={event => setRoles(event.target.value)}
357
+ />
358
+ <Button size='sm' variant='outline' type='submit' className='w-full'>
359
+ Sign in
360
+ </Button>
361
+ </form>
362
+ </PopoverContent>
363
+ </Popover>
337
364
  </div>
338
365
  );
339
366
  }
@@ -355,4 +382,5 @@ if (frontend) {
355
382
  DEV.pluginId +
356
383
  '/</p>';
357
384
  }
358
- `}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 f=new Map;let v,y={};const m=n.frontend?path.resolve(t,n.frontend.dir):t,{loadConfigFromFile:D,createServer:A,mergeConfig:N}=await import("vite");if(n.frontend){const c=path.join(m,"vite.config.ts");fs.existsSync(c)&&(y=(await D({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 g=o(()=>n.frontend?chunk4LKRXDYY_cjs.readPluginLocales(t,a,n.frontend.i18nDirPath):Promise.resolve(new Map),"localeFiles"),E=[...(await g()).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 _,k;const F=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(L=>{let P="";c.setEncoding("utf8"),c.on("data",x=>P+=x),c.on("end",()=>L(P))}),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:F,origin:o(()=>d,"origin"),users:o(()=>f,"users"),log:r});return}const S=/^\/i18n\/([A-Za-z-]+)\.json$/.exec(w);if(S){const h=(await g()).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 I=`${PLUGIN_API_PREFIX}${a.id}`;if(w===I||w.startsWith(`${I}/`)){q(c,p,(c.url??"/").slice(I.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`)}C.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(!_){sendJson(p,503,{error:k??"The plugin has no backend part."});return}const S=devCallerOf(c.headers);f.set(S.userId,S.roles);const I=http.request({host:"127.0.0.1",port:_.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 L=0,P=!1;h.on("data",x=>{if(!P){if(L+=x.length,L>DESKTOP_MAX_RESPONSE_BYTES){P=!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(x)}}),h.on("end",()=>{if(P)return;const x=Buffer.concat(b),G={...h.headers};delete G["transfer-encoding"],G["content-length"]=String(x.length),p.writeHead(h.statusCode??502,G),p.end(x)})});I.on("error",h=>{p.headersSent?p.destroy():sendJson(p,502,{error:`The backend is not answering (${h.message}); see the terminal.`})}),c.pipe(I)}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,...U}=y;U.plugins=servePlugins(U.plugins);const C=await A(N(U,{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")]}})),O=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,O)}catch(p){if(p.code!=="EADDRINUSE"||H===0||c>=PORT_ATTEMPTS)throw await C.close(),p}const V=O==="0.0.0.0"||O==="::"?"127.0.0.1":O;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{_=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&&_&&r(` database ${redactUrl(e.databaseUrl)}, schema ${_.schema}`),r(`${DEV_READY_PREFIX}${JSON.stringify({url:B})}`),{url:B,async close(){await _?.close(),await C.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;
385
+ `}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 n=/(?:^|;\s*)tego-dev-target=(web|desktop)(?:;|$)/.exec(String(e.headers.cookie??""))?.[1];return isPreviewTarget(n)?n: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"]??""),n=String(t["blocked-uri"]??"");if(!r)return;const a=typeof t["source-file"]=="string"?t["source-file"]:void 0,c=typeof t["line-number"]=="number"?t["line-number"]:void 0;return{directive:r,blocked:n,...a?{source:a}:{},...c?{line:c}:{}}}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 missingDatabaseMessage(e){return[`${e} has a backend, and the preview runs it for real against a PostgreSQL: set DATABASE_URL`,"(DATABASE_URL=postgres://user:password@localhost:5432/tego_dev pnpm dev; each plugin gets its own schema).","In Tego Studio: Settings \u2192 Plugins \u2192 Tego plugin development \u2192 Dev database URL.","To look at the frontend alone, run with --no-backend."].join(`
386
+ `)}o(missingDatabaseMessage,"missingDatabaseMessage");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 n=path.join(r,"node_modules",...t.split("/"));if(fs.existsSync(path.join(n,"package.json")))return fs.realpathSync(n);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((n,a)=>{const c=o(s=>{e.off("listening",u),a(s)},"onError"),u=o(()=>{e.off("error",c);const s=e.address();n(typeof s=="object"&&s?s.port:t)},"onListening");e.once("error",c),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 n,a;try{n=r("@tego/ui/tailwind-preset"),a=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 c=(await import("tailwindcss")).default;return c({presets:[n],content:[path.join(a,"**/*.{js,cjs}"),path.join(t,"*.tsx")]})}o(shellTailwind,"shellTailwind");function perStylesheet(e,t,r){return{postcssPlugin:"tego-dev-stylesheets",async Once(n,a){const c=n.source?.input.file,u=c&&path.resolve(c)===e?[t]:r;if(u.length===0)return;const s=await a.postcss(u).process(n,{from:c});a.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(...n){const a=new Proxy(this,{get(c,u){if(u==="emitFile")return()=>"";const s=Reflect.get(c,u);return typeof s=="function"?s.bind(c):s}});return r.apply(a,n)}}}):e}o(servePlugins,"servePlugins");async function startDevServer(e){const t=path.resolve(e.root),r=e.log??(l=>console.log(l)),n=await chunkDRHBXEPK_cjs.readManifest(t),a=chunk2P6UEAPV_cjs.resolvePluginParts(n),c=process.env.TEGO_DEV_POLL==="1"||!await fileEventsAvailable();c&&r("[watch] file events are unavailable on this machine (too many watchers open); the preview polls for changes");const u=!!a.backend&&e.backend!==!1;if(u){if(!e.databaseUrl)throw new Error(missingDatabaseMessage(n.id));await checkDevDatabase(e.databaseUrl)}const s=path.join(t,DEV_DIR),i=path.join(s,"shell"),d=crypto.randomBytes(32).toString("hex"),h=crypto.randomBytes(24).toString("hex");let v="";const k=new Map;let y,I={};const D=a.frontend?path.resolve(t,a.frontend.dir):t,{loadConfigFromFile:P,createServer:x,mergeConfig:R}=await import("vite");if(a.frontend){const l=path.join(D,"vite.config.ts");fs.existsSync(l)&&(I=(await P({command:"serve",mode:"development"},l,t))?.config??{});const p=I.build?.rollupOptions?.input,w=path.resolve(t,typeof p=="string"?p:`${a.frontend.dir}/src/index.tsx`);if(!fs.existsSync(w))throw new Error(`The frontend entry ${path.relative(t,w)} does not exist.`);y=path.relative(i,w).split(path.sep).join("/"),y.startsWith(".")||(y=`./${y}`)}const L=o(()=>a.frontend?chunkDRHBXEPK_cjs.readPluginLocales(t,n,a.frontend.i18nDirPath):Promise.resolve(new Map),"localeFiles"),C=[...(await L()).keys()].map(l=>l.replace(/\.json$/,""));await promises.mkdir(i,{recursive:!0});for(const l of shellFiles({pluginId:n.id,displayName:displayNameOf(n),nav:a.frontend?.nav??[],config:a.frontend?.config?chunkMWMGJJ74_cjs.applyConfigDefaults(a.frontend.config.schema,{}):{},entry:y,locales:C,defaultTarget:e.target??"web"}))await promises.writeFile(path.join(i,l.path),l.contents);let f,m;const A=o(()=>a.backend?.config?chunkMWMGJJ74_cjs.applyConfigDefaults(a.backend.config.schema,{}):{},"backendConfig"),E=http.createServer((l,p)=>void K(l,p));async function K(l,p){const w=(l.url??"/").split("?")[0],T=targetOf(l,e.target??"web");try{if(w==="/favicon.ico"){p.writeHead(204),p.end();return}if(w===CSP_REPORT_PATH){const g=await new Promise(H=>{let $="";l.setEncoding("utf8"),l.on("data",_=>$+=_),l.on("end",()=>H($))}),S=parseCspReport(g);S&&(r(`[csp:${T}] blocked ${S.blocked||"(inline)"} by ${S.directive}${S.source?` in ${S.source}${S.line?`:${S.line}`:""}`:""}`),r(`${CSP_REPORT_PREFIX}${JSON.stringify({target:T,...S})}`)),p.writeHead(204),p.end();return}if(w.startsWith(FAKE_HOST_PREFIX)||w.startsWith(FAKE_FILES_PREFIX)){await handleFakeHost(l,p,{token:h,storageDir:path.join(s,"storage"),config:A,origin:o(()=>v,"origin"),users:o(()=>k,"users"),log:r});return}const b=/^\/i18n\/([A-Za-z-]+)\.json$/.exec(w);if(b){const g=(await L()).get(`${b[1]}.json`);if(g===void 0)return sendJson(p,404,{error:`no ${b[1]}.json`});p.writeHead(200,{"content-type":"application/json"}),p.end(g);return}const N=`${PLUGIN_API_PREFIX}${n.id}`;if(w===N||w.startsWith(`${N}/`)){J(l,p,(l.url??"/").slice(N.length)||"/",T);return}if(String(l.headers.accept??"").includes("text/html")){p.setHeader("Content-Security-Policy",`${PREVIEW_CSP[T]}; report-uri ${CSP_REPORT_PATH}`);const g=new URL(l.url??"/","http://preview.local").searchParams.get("target");isPreviewTarget(g)&&p.setHeader("Set-Cookie",`${TARGET_COOKIE}=${g}; Path=/; SameSite=Lax`)}O.middlewares(l,p,()=>sendJson(p,404,{error:`${w} not found`}))}catch(b){sendJson(p,500,{error:b instanceof Error?b.message:String(b)})}}o(K,"route");function J(l,p,w,T){if(!f){sendJson(p,503,{error:m??"The plugin has no backend part."});return}const b=devCallerOf(l.headers);k.set(b.userId,b.roles);const N=http.request({host:"127.0.0.1",port:f.port,method:l.method,path:w,headers:{...gatewayHeaders(l.headers,b,d),...T==="desktop"?{"accept-encoding":"identity"}:{}}},g=>{if(T!=="desktop"){p.writeHead(g.statusCode??502,g.headers),g.pipe(p);return}const S=[];let H=0,$=!1;g.on("data",_=>{if(!$){if(H+=_.length,H>DESKTOP_MAX_RESPONSE_BYTES){$=!0,g.destroy(),r(`[desktop] ${l.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}S.push(_)}}),g.on("end",()=>{if($)return;const _=Buffer.concat(S),q={...g.headers};delete q["transfer-encoding"],q["content-length"]=String(_.length),p.writeHead(g.statusCode??502,q),p.end(_)})});N.on("error",g=>{p.headersSent?p.destroy():sendJson(p,502,{error:`The backend is not answering (${g.message}); see the terminal.`})}),l.pipe(N)}o(J,"proxy");const W=path.join(i,"shell.css"),X=(()=>{const l=I.css?.postcss;return l&&typeof l=="object"?l.plugins??[]:[]})(),U=(()=>{const l=packageDir(D,"react-i18next");return(l&&packageDir(l,"i18next"))??packageDir(D,"i18next")})(),{css:Y,mode:Z,build:Q,...B}=I;B.plugins=servePlugins(B.plugins);const O=await x(R(B,{configFile:!1,root:i,mode:"development",appType:"spa",clearScreen:!1,logLevel:"info",esbuild:{jsx:"automatic",jsxDev:!1},css:{postcss:{plugins:[perStylesheet(W,await shellTailwind(D,i),X)]}},resolve:{alias:[{find:"@tego/chat/plugin-surface",replacement:path.join(i,"plugin-surface.tsx")},...U?[{find:/^i18next$/,replacement:U}]:[]]},server:{middlewareMode:!0,hmr:{server:E},fs:{allow:[t,i,...U?[U]:[]]},watch:{ignored:["**/.tego-dev/backend/**","**/build/**","**/dist/**"],...c?{usePolling:!0,interval:500}:{}}},optimizeDeps:{entries:[path.join(i,"index.html")]}}));O.watcher.on("error",l=>r(`[watch] frontend: ${l instanceof Error?l.message:String(l)}; restart the preview if saves stop reloading`));const j=e.host??"127.0.0.1";let M;const F=e.port??DEFAULT_DEV_PORT;for(let l=0;M===void 0;l++)try{M=await listen(E,F===0?0:F+l,j)}catch(p){if(p.code!=="EADDRINUSE"||F===0||l>=PORT_ATTEMPTS)throw await O.close(),p}const V=j==="0.0.0.0"||j==="::"?"127.0.0.1":j;if(v=`http://${V.includes(":")?`[${V}]`:V}:${M}`,!a.backend)m="The plugin has no backend part.";else if(!u)m="The backend is off (--no-backend).",r(`[backend] ${m}`);else try{f=await startDevBackend({root:t,pluginId:n.id,version:n.version,part:a.backend,databaseUrl:e.databaseUrl,outDir:path.join(s,"backend"),hostApiUrl:`${v}${FAKE_HOST_PREFIX}`,hostApiToken:h,gatewayKey:d,...e.inspect?{inspect:e.inspect}:{},poll:c,log:r})}catch(l){throw await O.close(),E.closeAllConnections(),await new Promise(p=>E.close(()=>p())),new Error(`The backend did not start: ${l instanceof Error?l.message:String(l)}`,{cause:l})}const G=`${v}/p/${n.id}/`;return r(`${n.id} ${n.version}: local preview at ${G}`),e.databaseUrl&&f&&r(` database ${redactUrl(e.databaseUrl)}, schema ${f.schema}`),r(`${DEV_READY_PREFIX}${JSON.stringify({url:G})}`),{url:G,async close(){await f?.close(),await O.close(),E.closeAllConnections(),await new Promise(l=>E.close(()=>l()))}}}o(startDevServer,"startDevServer"),exports.CSP_REPORT_PREFIX=CSP_REPORT_PREFIX,exports.DEFAULT_DEV_PORT=DEFAULT_DEV_PORT,exports.DEFAULT_INSPECT_PORT=DEFAULT_INSPECT_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.backendNodeArgs=backendNodeArgs,exports.checkDevDatabase=checkDevDatabase,exports.devCallerOf=devCallerOf,exports.devDatabaseUrl=devDatabaseUrl,exports.devSchemaName=devSchemaName,exports.explainDatabaseError=explainDatabaseError,exports.gatewayHeaders=gatewayHeaders,exports.handleFakeHost=handleFakeHost,exports.linePrefixer=linePrefixer,exports.missingDatabaseMessage=missingDatabaseMessage,exports.parseCspReport=parseCspReport,exports.shellFiles=shellFiles,exports.startDevServer=startDevServer,exports.storagePath=storagePath,exports.targetOf=targetOf;
package/dist/dev.d.cts CHANGED
@@ -41,6 +41,16 @@ declare function devSchemaName(pluginId: string): string;
41
41
  * libpq reads a `+` literally).
42
42
  */
43
43
  declare function devDatabaseUrl(databaseUrl: string, schema: string): string;
44
+ /**
45
+ * A connection failure as the developer should read it: what went wrong and
46
+ * what to do, keyed on the driver's error code.
47
+ */
48
+ declare function explainDatabaseError(error: unknown): string;
49
+ /**
50
+ * Connects once and runs `select 1`, so a wrong URL fails before anything
51
+ * else starts, with the reason and without the password.
52
+ */
53
+ declare function checkDevDatabase(databaseUrl: string): Promise<void>;
44
54
  /** Applies the files not yet in the ledger, in name order; returns their names. */
45
55
  declare function applyDevMigrations(options: {
46
56
  databaseUrl: string;
@@ -49,6 +59,19 @@ declare function applyDevMigrations(options: {
49
59
  tenantId: string;
50
60
  }): Promise<string[]>;
51
61
 
62
+ declare const DEFAULT_INSPECT_PORT = 9229;
63
+ /** `node` arguments for the backend: source-mapped stack traces, and the inspector when asked. */
64
+ declare function backendNodeArgs(entry: string, inspect?: number): string[];
65
+ /**
66
+ * Splits a stream into lines, each written with `prefix` (so the backend's
67
+ * output reads apart from the preview's own). A trailing partial line waits
68
+ * for the rest.
69
+ */
70
+ declare function linePrefixer(prefix: string, write: (line: string) => void): {
71
+ push(chunk: string): void;
72
+ flush(): void;
73
+ };
74
+
52
75
  /** Who the preview shell says is calling; sent by the shell on every API request. */
53
76
  declare const DEV_USER_HEADER = "x-tego-dev-user-id";
54
77
  declare const DEV_ROLES_HEADER = "x-tego-dev-roles";
@@ -128,8 +151,14 @@ interface DevServerOptions {
128
151
  /** 0 for any free port; defaults to {@link DEFAULT_DEV_PORT} or the next free one. */
129
152
  port?: number;
130
153
  host?: string;
131
- /** Run the backend part (needs `databaseUrl`); default true. */
154
+ /**
155
+ * Run the backend part; default true. A plugin with a backend then needs
156
+ * `databaseUrl`: the preview fails rather than half-work without it.
157
+ * `false` previews the frontend alone (API calls answer 503).
158
+ */
132
159
  backend?: boolean;
160
+ /** Start the backend under the Node inspector on this port. */
161
+ inspect?: number;
133
162
  /** Which shell to imitate by default (CSP, network proxy); the page can switch. Default `web`. */
134
163
  target?: PreviewTarget;
135
164
  databaseUrl?: string;
@@ -139,6 +168,8 @@ interface DevServer {
139
168
  url: string;
140
169
  close(): Promise<void>;
141
170
  }
171
+ /** Why the preview will not start a plugin with a backend without a database, and what to do. */
172
+ declare function missingDatabaseMessage(pluginId: string): string;
142
173
  declare function startDevServer(options: DevServerOptions): Promise<DevServer>;
143
174
 
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 };
175
+ export { CSP_REPORT_PREFIX, type CspViolation, DEFAULT_DEV_PORT, DEFAULT_INSPECT_PORT, DEV_DIR, DEV_READY_PREFIX, DEV_ROLES_HEADER, DEV_TENANT_ID, DEV_USER_HEADER, type DevServer, type DevServerOptions, PREVIEW_CSP, type PreviewTarget, applyDevMigrations, backendNodeArgs, checkDevDatabase, devCallerOf, devDatabaseUrl, devSchemaName, explainDatabaseError, gatewayHeaders, handleFakeHost, linePrefixer, missingDatabaseMessage, parseCspReport, shellFiles, startDevServer, storagePath, targetOf };
package/dist/dev.d.ts CHANGED
@@ -41,6 +41,16 @@ declare function devSchemaName(pluginId: string): string;
41
41
  * libpq reads a `+` literally).
42
42
  */
43
43
  declare function devDatabaseUrl(databaseUrl: string, schema: string): string;
44
+ /**
45
+ * A connection failure as the developer should read it: what went wrong and
46
+ * what to do, keyed on the driver's error code.
47
+ */
48
+ declare function explainDatabaseError(error: unknown): string;
49
+ /**
50
+ * Connects once and runs `select 1`, so a wrong URL fails before anything
51
+ * else starts, with the reason and without the password.
52
+ */
53
+ declare function checkDevDatabase(databaseUrl: string): Promise<void>;
44
54
  /** Applies the files not yet in the ledger, in name order; returns their names. */
45
55
  declare function applyDevMigrations(options: {
46
56
  databaseUrl: string;
@@ -49,6 +59,19 @@ declare function applyDevMigrations(options: {
49
59
  tenantId: string;
50
60
  }): Promise<string[]>;
51
61
 
62
+ declare const DEFAULT_INSPECT_PORT = 9229;
63
+ /** `node` arguments for the backend: source-mapped stack traces, and the inspector when asked. */
64
+ declare function backendNodeArgs(entry: string, inspect?: number): string[];
65
+ /**
66
+ * Splits a stream into lines, each written with `prefix` (so the backend's
67
+ * output reads apart from the preview's own). A trailing partial line waits
68
+ * for the rest.
69
+ */
70
+ declare function linePrefixer(prefix: string, write: (line: string) => void): {
71
+ push(chunk: string): void;
72
+ flush(): void;
73
+ };
74
+
52
75
  /** Who the preview shell says is calling; sent by the shell on every API request. */
53
76
  declare const DEV_USER_HEADER = "x-tego-dev-user-id";
54
77
  declare const DEV_ROLES_HEADER = "x-tego-dev-roles";
@@ -128,8 +151,14 @@ interface DevServerOptions {
128
151
  /** 0 for any free port; defaults to {@link DEFAULT_DEV_PORT} or the next free one. */
129
152
  port?: number;
130
153
  host?: string;
131
- /** Run the backend part (needs `databaseUrl`); default true. */
154
+ /**
155
+ * Run the backend part; default true. A plugin with a backend then needs
156
+ * `databaseUrl`: the preview fails rather than half-work without it.
157
+ * `false` previews the frontend alone (API calls answer 503).
158
+ */
132
159
  backend?: boolean;
160
+ /** Start the backend under the Node inspector on this port. */
161
+ inspect?: number;
133
162
  /** Which shell to imitate by default (CSP, network proxy); the page can switch. Default `web`. */
134
163
  target?: PreviewTarget;
135
164
  databaseUrl?: string;
@@ -139,6 +168,8 @@ interface DevServer {
139
168
  url: string;
140
169
  close(): Promise<void>;
141
170
  }
171
+ /** Why the preview will not start a plugin with a backend without a database, and what to do. */
172
+ declare function missingDatabaseMessage(pluginId: string): string;
142
173
  declare function startDevServer(options: DevServerOptions): Promise<DevServer>;
143
174
 
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 };
175
+ export { CSP_REPORT_PREFIX, type CspViolation, DEFAULT_DEV_PORT, DEFAULT_INSPECT_PORT, DEV_DIR, DEV_READY_PREFIX, DEV_ROLES_HEADER, DEV_TENANT_ID, DEV_USER_HEADER, type DevServer, type DevServerOptions, PREVIEW_CSP, type PreviewTarget, applyDevMigrations, backendNodeArgs, checkDevDatabase, devCallerOf, devDatabaseUrl, devSchemaName, explainDatabaseError, gatewayHeaders, handleFakeHost, linePrefixer, missingDatabaseMessage, parseCspReport, shellFiles, startDevServer, storagePath, targetOf };