@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.
- package/README.md +48 -9
- package/agent/RULES.md +34 -11
- package/agent/guides/agent-templates.md +51 -0
- package/agent/guides/backend.md +8 -0
- package/agent/rules.json +500 -0
- package/agent/skills/add-agent-template/SKILL.md +19 -0
- package/agent/skills/add-claw-tool/SKILL.md +21 -0
- package/agent/skills/add-i18n-strings/SKILL.md +19 -0
- package/agent/skills/add-migration/SKILL.md +22 -0
- package/agent/skills/add-route-with-access/SKILL.md +30 -0
- package/agent/skills/add-screen/SKILL.md +27 -0
- package/bin/tego-plugin.mjs +7 -3
- package/dist/{chunk-4LKRXDYY.cjs → chunk-DRHBXEPK.cjs} +1 -1
- package/dist/{chunk-FCYAAOHC.js → chunk-IQNSBHBI.js} +1 -1
- package/dist/dev.cjs +98 -70
- package/dist/dev.d.cts +33 -2
- package/dist/dev.d.ts +33 -2
- package/dist/dev.js +103 -75
- package/dist/doctor.cjs +15 -8
- package/dist/doctor.d.cts +99 -2
- package/dist/doctor.d.ts +99 -2
- package/dist/doctor.js +15 -8
- package/dist/eslint.cjs +1 -0
- package/dist/eslint.d.cts +29 -0
- package/dist/eslint.d.ts +29 -0
- package/dist/eslint.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/scaffold.cjs +52 -7
- package/dist/scaffold.js +59 -14
- package/package.json +8 -1
package/bin/tego-plugin.mjs
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var v=Object.defineProperty;var
|
|
3
|
-
`)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1}process.exit()}if(
|
|
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(
|
|
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=>({"&":"&","<":"<",">":">",'"':"""})[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=>({"&":"&","<":"<",">":">",'"':"""})[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(
|
|
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-
|
|
252
|
-
<
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
<
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 };
|