@zhama/tego-plugin-sdk 1.37.1 → 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 +8 -3
- package/dist/dev.d.cts +33 -2
- package/dist/dev.d.ts +33 -2
- package/dist/dev.js +13 -8
- 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 g(){await promises.rm(e.outDir,{recursive:!0,force:!0}),await chunk4LKRXDYY_cjs.buildPluginBackend(t,{root:e.root,outDir:e.outDir});const f=await applyDevMigrations({databaseUrl:e.databaseUrl,schema:n,migrationsDir:i,tenantId:DEV_TENANT_ID});for(const E of f)r(`[backend] applied migration ${E}`)}o(g,"build");function v(){const f=child_process.spawn(process.execPath,[u],{env:s,stdio:["ignore","pipe","pipe"]});l=f,f.stdout?.on("data",E=>process.stdout.write(E)),f.stderr?.on("data",E=>process.stderr.write(E)),f.on("exit",E=>{l===f&&!d&&(l=null,r(`[backend] exited (code ${E??"null"}); it starts again on the next save`))})}o(v,"start"),await g(),v(),r(`[backend] ${e.pluginId} on 127.0.0.1:${a}, schema ${n} in ${redactUrl(e.databaseUrl)}`);let y=null,m=null,N=!1;const D=o(async()=>{if(m){N=!0;return}m=(async()=>{try{await g();const f=l;l=null,await stopChild(f),d||(v(),r("[backend] rebuilt and restarted"))}catch(f){r(`[backend] rebuild failed, the previous build keeps running: ${f instanceof Error?f.message:String(f)}`)}})(),await m,m=null,N&&!d&&(N=!1,await D())},"rebuild"),A=[];for(const f of[t.dir,"shared"]){const E=path.resolve(e.root,f);fs.existsSync(E)&&A.push(fs.watch(E,{recursive:!0},(x,k)=>{k&&/(^|[\\/])(node_modules|dist)([\\/]|$)/.test(String(k))||(y&&clearTimeout(y),y=setTimeout(()=>void D(),REBUILD_DEBOUNCE_MS))}))}return{port:a,schema:n,async close(){d=!0,y&&clearTimeout(y);for(const f of A)f.close();await m,await stopChild(l)}}}o(startDevBackend,"startDevBackend");var FAKE_HOST_PREFIX="/__tego/host",FAKE_FILES_PREFIX="/__tego/files",MAX_JSON_BYTES=1024*1024;async function readBody(e,t=1/0){const r=[];let a=0;for await(const n of e){if(a+=n.length,a>t)throw Object.assign(new Error("body too large"),{status:413});r.push(n)}return Buffer.concat(r)}o(readBody,"readBody");function send(e,t,r){if(r===void 0){e.writeHead(t),e.end();return}e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r))}o(send,"send");function storagePath(e,t){const r=path.resolve(e),a=path.resolve(r,t);if(!t||path.isAbsolute(t)||!a.startsWith(r+path.sep))throw Object.assign(new Error(`invalid storage key ${JSON.stringify(t)}`),{status:400});return a}o(storagePath,"storagePath");var metaOf=o(e=>`${e}.tego-meta.json`,"metaOf");async function head(e){const t=await promises.stat(e).catch(()=>null);if(!t?.isFile())return null;const r=await promises.readFile(metaOf(e),"utf8").then(a=>JSON.parse(a),()=>({}));return{size:t.size,contentType:r.contentType??null,versionTag:crypto.createHash("sha1").update(`${t.size}:${t.mtimeMs}`).digest("hex")}}o(head,"head");async function put(e,t,r){await promises.mkdir(path.dirname(e),{recursive:!0}),await promises.writeFile(e,t),await promises.writeFile(metaOf(e),JSON.stringify({contentType:r??null}))}o(put,"put");async function handleFakeHost(e,t,r){const a=new URL(e.url??"/","http://preview.local");try{if(a.pathname.startsWith(FAKE_FILES_PREFIX)){const s=storagePath(r.storageDir,a.searchParams.get("key")??""),l=await head(s);if(!l)return send(t,404);t.writeHead(200,{"content-type":l.contentType??"application/octet-stream"}),t.end(await promises.readFile(s));return}if(e.headers.authorization!==`Bearer ${r.token}`)return send(t,401,{error:"bad host API token"});const n=a.pathname.slice(FAKE_HOST_PREFIX.length)||"/",i=o(async()=>JSON.parse((await readBody(e,MAX_JSON_BYTES)).toString("utf8")||"{}"),"json"),u=o(s=>storagePath(r.storageDir,String(s??"")),"keyFile");switch(n){case"/config":return send(t,200,{data:{values:r.config()}});case"/log":{const{level:s,message:l,fields:d}=await i();return r.log(`[backend ${String(s??"info")}] ${String(l??"")}${d?` ${JSON.stringify(d)}`:""}`),send(t,200,{data:{}})}case"/events":{const{name:s,payload:l}=await i();return r.log(`[event] ${String(s)} ${JSON.stringify(l??null)}`),send(t,200,{data:{}})}case"/notifications":return r.log(`[notification] ${JSON.stringify(await i())}`),send(t,200,{data:{id:crypto.randomUUID()}});case"/directory/identity":{const{userId:s}=await i(),l=r.users().get(String(s));return send(t,200,{data:{active:!!l,roleNames:l??[],memberships:[],checkedAt:new Date().toISOString()}})}case"/directory/list":{const{kind:s,search:l,ids:d}=await i(),g=typeof l=="string"?l.toLowerCase():"",v=Array.isArray(d)?new Set(d.map(String)):void 0,y=s==="users"?[...r.users().keys()].filter(m=>(!v||v.has(m))&&m.toLowerCase().includes(g)).map(m=>({id:m,displayName:m,active:!0})):[];return send(t,200,{data:{items:y,hasMore:!1}})}case"/directory/match":{const{userId:s,principals:l}=await i(),d=Array.isArray(l)?l:[];return send(t,200,{data:{active:r.users().has(String(s)),matched:d.flatMap((g,v)=>g.kind==="user"&&g.id===s?[v]:[]),checkedAt:new Date().toISOString()}})}case"/directory/validate":{const{principals:s}=await i(),l=Array.isArray(s)?s:[],d=r.users();return send(t,200,{data:{invalid:l.flatMap((g,v)=>g.kind==="user"&&d.has(String(g.id))?[]:[v])}})}case"/storage/get":case"/storage/stream":{const s=u(a.searchParams.get("key"));if(e.method==="POST"&&n==="/storage/stream")return await put(s,await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});const l=await head(s);if(!l)return send(t,404);let d=await promises.readFile(s);const g=Number(a.searchParams.get("bytes"));Number.isFinite(g)&&g>0&&(d=d.subarray(0,g)),t.writeHead(200,{"content-type":l.contentType??"application/octet-stream"}),t.end(d);return}case"/storage/put":return await put(u(a.searchParams.get("key")),await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});case"/storage/head":{const s=await head(u((await i()).key));return s?send(t,200,{data:s}):send(t,404)}case"/storage/delete":{const s=u((await i()).key);return await promises.rm(s,{force:!0}),await promises.rm(metaOf(s),{force:!0}),send(t,200,{data:{}})}case"/storage/copy":{const{sourceKey:s,destinationKey:l}=await i(),d=u(s),g=u(l);return await head(d)?(await promises.mkdir(path.dirname(g),{recursive:!0}),await promises.copyFile(d,g),await promises.copyFile(metaOf(d),metaOf(g)).catch(()=>{}),send(t,200,{data:await head(g)})):send(t,404)}case"/storage/signed-url":{const{key:s}=await i();u(s);const l=`${FAKE_FILES_PREFIX}?key=${encodeURIComponent(String(s))}`;return send(t,200,{data:{url:`${r.origin()}${l}`,path:l}})}default:return r.log(`[host] ${n} is not available in local preview`),send(t,501,{error:{code:"not_available_in_preview",message:`${n} is not available in local preview`}})}}catch(n){const i=n.status??500;send(t,i,{error:{message:n instanceof Error?n.message:String(n)}})}}o(handleFakeHost,"handleFakeHost");var SDK_PACKAGE="@tego/plugin-sdk",SDK_IMPORT=JSON.stringify(SDK_PACKAGE),SDK_FRONTEND_IMPORT=JSON.stringify(`${SDK_PACKAGE}/frontend`);function shellFiles(e){return[{path:"index.html",contents:indexHtml(e)},{path:"main.tsx",contents:mainTsx(e)},{path:"shell.css",contents:SHELL_CSS},{path:"plugin-surface.tsx",contents:PLUGIN_SURFACE_STUB}]}o(shellFiles,"shellFiles");var escapeHtml=o(e=>e.replace(/[&<>"]/g,t=>({"&":"&","<":"<",">":">",'"':"""})[t]),"escapeHtml");function indexHtml({displayName:e}){return`<!doctype html>
|
|
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
|
|
|
@@ -378,4 +382,5 @@ if (frontend) {
|
|
|
378
382
|
DEV.pluginId +
|
|
379
383
|
'/</p>';
|
|
380
384
|
}
|
|
381
|
-
`}o(mainTsx,"mainTsx");var CSP_REPORT_PATH="/__tego/csp-report",CSP_REPORT_PREFIX="TEGO_DEV_CSP ",TARGET_COOKIE="tego-dev-target",DESKTOP_MAX_RESPONSE_BYTES=100*1024*1024,SHARED=["default-src 'self'","object-src 'none'","base-uri 'self'"],PREVIEW_CSP={web:[...SHARED,"script-src 'self' 'wasm-unsafe-eval'","style-src 'self' 'unsafe-inline'","img-src 'self' data: blob: https:","font-src 'self' data:","connect-src 'self' wss: ws:","frame-src 'self'","worker-src 'self' blob:"].join("; "),desktop:[...SHARED,"script-src 'self' 'wasm-unsafe-eval'","style-src 'self' 'unsafe-inline' https:","img-src 'self' data: blob: http: https:","font-src 'self' data: blob: https:","media-src 'self' data: blob:","worker-src 'self' blob:","connect-src 'self' http: https: ws: wss:","frame-src 'self'"].join("; ")};function isPreviewTarget(e){return e==="web"||e==="desktop"}o(isPreviewTarget,"isPreviewTarget");function targetOf(e,t){const r=new URL(e.url??"/","http://preview.local").searchParams.get("target");if(isPreviewTarget(r))return r;const a=/(?:^|;\s*)tego-dev-target=(web|desktop)(?:;|$)/.exec(String(e.headers.cookie??""))?.[1];return isPreviewTarget(a)?a:t}o(targetOf,"targetOf");function parseCspReport(e){try{const t=JSON.parse(e)["csp-report"];if(!t)return;const r=String(t["effective-directive"]??t["violated-directive"]??""),a=String(t["blocked-uri"]??"");if(!r)return;const n=typeof t["source-file"]=="string"?t["source-file"]:void 0,i=typeof t["line-number"]=="number"?t["line-number"]:void 0;return{directive:r,blocked:a,...n?{source:n}:{},...i?{line:i}:{}}}catch{return}}o(parseCspReport,"parseCspReport");var DEFAULT_DEV_PORT=5280,PORT_ATTEMPTS=20,DEV_READY_PREFIX="TEGO_DEV_READY ",DEV_DIR=".tego-dev",PLUGIN_API_PREFIX="/api/v1/tego-os/plugin-api/";function displayNameOf(e){const{displayName:t}=e;return typeof t=="string"?t:t.en??t["zh-CN"]??Object.values(t)[0]??e.id}o(displayNameOf,"displayNameOf");function packageDir(e,t){for(let r=path.resolve(e);;r=path.dirname(r)){const a=path.join(r,"node_modules",...t.split("/"));if(fs.existsSync(path.join(a,"package.json")))return fs.realpathSync(a);if(path.dirname(r)===r)return}}o(packageDir,"packageDir");function sendJson(e,t,r){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r))}o(sendJson,"sendJson");function listen(e,t,r){return new Promise((a,n)=>{const i=o(s=>{e.off("listening",u),n(s)},"onError"),u=o(()=>{e.off("error",i);const s=e.address();a(typeof s=="object"&&s?s.port:t)},"onListening");e.once("error",i),e.once("listening",u),e.listen(t,r)})}o(listen,"listen");async function shellTailwind(e,t){const r=module$1.createRequire(path.join(e,"vite.config.ts"));let a,n;try{a=r("@tego/ui/tailwind-preset"),n=path.dirname(r.resolve("@tego/ui"))}catch(u){throw new Error(`The preview cannot load @tego/ui's Tailwind preset from ${e}: ${u instanceof Error?u.message:String(u)}. Install the plugin's dependencies.`,{cause:u})}const i=(await import("tailwindcss")).default;return i({presets:[a],content:[path.join(n,"**/*.{js,cjs}"),path.join(t,"*.tsx")]})}o(shellTailwind,"shellTailwind");function perStylesheet(e,t,r){return{postcssPlugin:"tego-dev-stylesheets",async Once(a,n){const i=a.source?.input.file,u=i&&path.resolve(i)===e?[t]:r;if(u.length===0)return;const s=await n.postcss(u).process(a,{from:i});n.result.messages.push(...s.messages)}}}o(perStylesheet,"perStylesheet");function servePlugins(e){return Array.isArray(e)?e.flat(1/0).map(t=>{if(!t||typeof t!="object")return t;const{buildStart:r}=t;return typeof r!="function"?t:{...t,buildStart(...a){const n=new Proxy(this,{get(i,u){if(u==="emitFile")return()=>"";const s=Reflect.get(i,u);return typeof s=="function"?s.bind(i):s}});return r.apply(n,a)}}}):e}o(servePlugins,"servePlugins");async function startDevServer(e){const t=path.resolve(e.root),r=e.log??(c=>console.log(c)),a=await chunk4LKRXDYY_cjs.readManifest(t),n=chunk2P6UEAPV_cjs.resolvePluginParts(a),i=path.join(t,DEV_DIR),u=path.join(i,"shell"),s=crypto.randomBytes(32).toString("hex"),l=crypto.randomBytes(24).toString("hex");let d="";const g=new Map;let v,y={};const m=n.frontend?path.resolve(t,n.frontend.dir):t,{loadConfigFromFile:N,createServer:D,mergeConfig:A}=await import("vite");if(n.frontend){const c=path.join(m,"vite.config.ts");fs.existsSync(c)&&(y=(await N({command:"serve",mode:"development"},c,t))?.config??{});const p=y.build?.rollupOptions?.input,w=path.resolve(t,typeof p=="string"?p:`${n.frontend.dir}/src/index.tsx`);if(!fs.existsSync(w))throw new Error(`The frontend entry ${path.relative(t,w)} does not exist.`);v=path.relative(u,w).split(path.sep).join("/"),v.startsWith(".")||(v=`./${v}`)}const f=o(()=>n.frontend?chunk4LKRXDYY_cjs.readPluginLocales(t,a,n.frontend.i18nDirPath):Promise.resolve(new Map),"localeFiles"),E=[...(await f()).keys()].map(c=>c.replace(/\.json$/,""));await promises.mkdir(u,{recursive:!0});for(const c of shellFiles({pluginId:a.id,displayName:displayNameOf(a),nav:n.frontend?.nav??[],config:n.frontend?.config?chunkMWMGJJ74_cjs.applyConfigDefaults(n.frontend.config.schema,{}):{},entry:v,locales:E,defaultTarget:e.target??"web"}))await promises.writeFile(path.join(u,c.path),c.contents);let x,k;const G=o(()=>n.backend?.config?chunkMWMGJJ74_cjs.applyConfigDefaults(n.backend.config.schema,{}):{},"backendConfig"),R=http.createServer((c,p)=>void M(c,p));async function M(c,p){const w=(c.url??"/").split("?")[0],T=targetOf(c,e.target??"web");try{if(w==="/favicon.ico"){p.writeHead(204),p.end();return}if(w===CSP_REPORT_PATH){const h=await new Promise(O=>{let I="";c.setEncoding("utf8"),c.on("data",P=>I+=P),c.on("end",()=>O(I))}),b=parseCspReport(h);b&&(r(`[csp:${T}] blocked ${b.blocked||"(inline)"} by ${b.directive}${b.source?` in ${b.source}${b.line?`:${b.line}`:""}`:""}`),r(`${CSP_REPORT_PREFIX}${JSON.stringify({target:T,...b})}`)),p.writeHead(204),p.end();return}if(w.startsWith(FAKE_HOST_PREFIX)||w.startsWith(FAKE_FILES_PREFIX)){await handleFakeHost(c,p,{token:l,storageDir:path.join(i,"storage"),config:G,origin:o(()=>d,"origin"),users:o(()=>g,"users"),log:r});return}const S=/^\/i18n\/([A-Za-z-]+)\.json$/.exec(w);if(S){const h=(await f()).get(`${S[1]}.json`);if(h===void 0)return sendJson(p,404,{error:`no ${S[1]}.json`});p.writeHead(200,{"content-type":"application/json"}),p.end(h);return}const _=`${PLUGIN_API_PREFIX}${a.id}`;if(w===_||w.startsWith(`${_}/`)){q(c,p,(c.url??"/").slice(_.length)||"/",T);return}if(String(c.headers.accept??"").includes("text/html")){p.setHeader("Content-Security-Policy",`${PREVIEW_CSP[T]}; report-uri ${CSP_REPORT_PATH}`);const h=new URL(c.url??"/","http://preview.local").searchParams.get("target");isPreviewTarget(h)&&p.setHeader("Set-Cookie",`${TARGET_COOKIE}=${h}; Path=/; SameSite=Lax`)}U.middlewares(c,p,()=>sendJson(p,404,{error:`${w} not found`}))}catch(S){sendJson(p,500,{error:S instanceof Error?S.message:String(S)})}}o(M,"route");function q(c,p,w,T){if(!x){sendJson(p,503,{error:k??"The plugin has no backend part."});return}const S=devCallerOf(c.headers);g.set(S.userId,S.roles);const _=http.request({host:"127.0.0.1",port:x.port,method:c.method,path:w,headers:{...gatewayHeaders(c.headers,S,s),...T==="desktop"?{"accept-encoding":"identity"}:{}}},h=>{if(T!=="desktop"){p.writeHead(h.statusCode??502,h.headers),h.pipe(p);return}const b=[];let O=0,I=!1;h.on("data",P=>{if(!I){if(O+=P.length,O>DESKTOP_MAX_RESPONSE_BYTES){I=!0,h.destroy(),r(`[desktop] ${c.method} ${w}: response over 100 MB refused (413)`),sendJson(p,413,{error:"Response over 100 MB: the desktop app refuses it. Serve large files through storage paths."});return}b.push(P)}}),h.on("end",()=>{if(I)return;const P=Buffer.concat(b),F={...h.headers};delete F["transfer-encoding"],F["content-length"]=String(P.length),p.writeHead(h.statusCode??502,F),p.end(P)})});_.on("error",h=>{p.headersSent?p.destroy():sendJson(p,502,{error:`The backend is not answering (${h.message}); see the terminal.`})}),c.pipe(_)}o(q,"proxy");const K=path.join(u,"shell.css"),J=(()=>{const c=y.css?.postcss;return c&&typeof c=="object"?c.plugins??[]:[]})(),$=(()=>{const c=packageDir(m,"react-i18next");return(c&&packageDir(c,"i18next"))??packageDir(m,"i18next")})(),{css:X,mode:Y,build:z,...C}=y;C.plugins=servePlugins(C.plugins);const U=await D(A(C,{configFile:!1,root:u,mode:"development",appType:"spa",clearScreen:!1,logLevel:"info",esbuild:{jsx:"automatic",jsxDev:!1},css:{postcss:{plugins:[perStylesheet(K,await shellTailwind(m,u),J)]}},resolve:{alias:[{find:"@tego/chat/plugin-surface",replacement:path.join(u,"plugin-surface.tsx")},...$?[{find:/^i18next$/,replacement:$}]:[]]},server:{middlewareMode:!0,hmr:{server:R},fs:{allow:[t,u,...$?[$]:[]]}},optimizeDeps:{entries:[path.join(u,"index.html")]}})),L=e.host??"127.0.0.1";let j;const H=e.port??DEFAULT_DEV_PORT;for(let c=0;j===void 0;c++)try{j=await listen(R,H===0?0:H+c,L)}catch(p){if(p.code!=="EADDRINUSE"||H===0||c>=PORT_ATTEMPTS)throw await U.close(),p}const V=L==="0.0.0.0"||L==="::"?"127.0.0.1":L;if(d=`http://${V.includes(":")?`[${V}]`:V}:${j}`,!n.backend)k="The plugin has no backend part.";else if(e.backend===!1)k="The backend is off (--no-backend).";else if(!e.databaseUrl)k="The backend needs a PostgreSQL: start the preview with DATABASE_URL=postgres://\u2026 (each plugin gets its own schema).";else try{x=await startDevBackend({root:t,pluginId:a.id,version:a.version,part:n.backend,databaseUrl:e.databaseUrl,outDir:path.join(i,"backend"),hostApiUrl:`${d}${FAKE_HOST_PREFIX}`,hostApiToken:l,gatewayKey:s,log:r})}catch(c){k=`The backend did not start: ${c instanceof Error?c.message:String(c)}`}k&&n.backend&&r(`[backend] ${k}`);const B=`${d}/p/${a.id}/`;return r(`${a.id} ${a.version}: local preview at ${B}`),e.databaseUrl&&x&&r(` database ${redactUrl(e.databaseUrl)}, schema ${x.schema}`),r(`${DEV_READY_PREFIX}${JSON.stringify({url:B})}`),{url:B,async close(){await x?.close(),await U.close(),R.closeAllConnections(),await new Promise(c=>R.close(()=>c()))}}}o(startDevServer,"startDevServer"),exports.CSP_REPORT_PREFIX=CSP_REPORT_PREFIX,exports.DEFAULT_DEV_PORT=DEFAULT_DEV_PORT,exports.DEV_DIR=DEV_DIR,exports.DEV_READY_PREFIX=DEV_READY_PREFIX,exports.DEV_ROLES_HEADER=DEV_ROLES_HEADER,exports.DEV_TENANT_ID=DEV_TENANT_ID,exports.DEV_USER_HEADER=DEV_USER_HEADER,exports.PREVIEW_CSP=PREVIEW_CSP,exports.applyDevMigrations=applyDevMigrations,exports.devCallerOf=devCallerOf,exports.devDatabaseUrl=devDatabaseUrl,exports.devSchemaName=devSchemaName,exports.gatewayHeaders=gatewayHeaders,exports.handleFakeHost=handleFakeHost,exports.parseCspReport=parseCspReport,exports.shellFiles=shellFiles,exports.startDevServer=startDevServer,exports.storagePath=storagePath,exports.targetOf=targetOf;
|
|
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 };
|
package/dist/dev.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
var Me=Object.defineProperty;var s=(e,t)=>Me(e,"name",{value:t,configurable:!0});import{signPluginContext as Fe}from"./chunk-WLTASJF6.js";import{readManifest as Ke,readPluginLocales as Je,buildPluginBackend as We}from"./chunk-FCYAAOHC.js";import"./chunk-I5Z3ECC4.js";import{applyConfigDefaults as ge}from"./chunk-DEGQDJF6.js";import"./chunk-EIVA5BR6.js";import"./chunk-2NTAWYCG.js";import{resolvePluginParts as ze}from"./chunk-IVJECPGG.js";import"./chunk-INCA3GFW.js";import"./chunk-56QOZQTY.js";import{PLUGIN_CONTEXT_HEADERS as L}from"./chunk-QKSPPMVN.js";import"./chunk-VMCGDDGP.js";import"./chunk-SNUCO2MK.js";import{randomUUID as qe,randomBytes as fe,createHash as Xe}from"crypto";import{existsSync as K,realpathSync as Ye,watch as Qe}from"fs";import{readdir as Ze,readFile as J,mkdir as ee,copyFile as pe,rm as te,writeFile as ne,stat as et}from"fs/promises";import{createServer as tt,request as nt}from"http";import{createRequire as rt}from"module";import{join as w,resolve as I,isAbsolute as at,sep as he,dirname as C,relative as me}from"path";import{spawn as ot}from"child_process";import{createServer as st}from"net";var re="x-tego-dev-user-id",ae="x-tego-dev-roles",O="dev-tenant",ve="00000000-0000-4000-8000-000000000001";function we(e){const t=s(r=>(Array.isArray(r)?r[0]:r)?.trim()??"","single"),n=t(e[re])||ve;let a=[];try{const r=JSON.parse(t(e[ae])||"[]");Array.isArray(r)&&(a=r.filter(i=>typeof i=="string"))}catch{}return{userId:n,roles:a}}s(we,"devCallerOf");function ye(e,t,n,a=Date.now()){const r={};for(const[o,l]of Object.entries(e)){const u=o.toLowerCase();l===void 0||u.startsWith("x-tego-")||u==="cookie"||u==="authorization"||u==="host"||u==="connection"||(r[u]=Array.isArray(l)?l.join(", "):l)}const i=JSON.stringify(t.roles),d=String(a);return r[L.userId]=t.userId,r[L.tenantId]=O,r[L.roles]=i,r[L.issuedAt]=d,r[L.signature]=Fe({userId:t.userId,tenantId:O,rolesHeader:i,avatarId:"",issuedAt:d},n),r}s(ye,"gatewayHeaders");var it="tego_dev_migrations";function be(e){return`tego_dev_${e.toLowerCase().replace(/[^a-z0-9_]/g,"_")}`.slice(0,63)}s(be,"devSchemaName");function Se(e,t){const n=new URL(e);n.searchParams.delete("options");const a=n.search.replace(/^\?/,"");return n.search=`?${a?`${a}&`:""}options=${encodeURIComponent(`-c search_path=${t}`)}`,n.toString()}s(Se,"devDatabaseUrl");async function ct(e){let t;try{t=await import("pg")}catch(r){throw new Error(`The local backend needs the "pg" driver; add it to the plugin's dependencies.`,{cause:r})}const n=t.default?.Client??t.Client;if(!n)throw new Error('The "pg" module has no Client constructor.');const a=new n({connectionString:e});try{await a.connect()}catch(r){throw new Error(`Cannot connect to DATABASE_URL (${oe(e)}): ${r instanceof Error?r.message:String(r)}`,{cause:r})}return a}s(ct,"connect");function oe(e){try{const t=new URL(e);return t.password&&(t.password="***"),t.toString()}catch{return"<unparseable DATABASE_URL>"}}s(oe,"redactUrl");var Ee=s(e=>`"${e.replace(/"/g,'""')}"`,"quote");async function Te(e){const t=await Ze(e.migrationsDir).then(i=>i.filter(d=>d.endsWith(".sql")).sort(),i=>{if(i.code==="ENOENT")return[];throw i}),n=await ct(e.databaseUrl),a=Ee(e.schema),r=`${a}.${Ee(it)}`;try{await n.query(`CREATE SCHEMA IF NOT EXISTS ${a}`),await n.query(`CREATE TABLE IF NOT EXISTS ${r} (name text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())`);const i=new Set((await n.query(`SELECT name FROM ${r}`)).rows.map(o=>String(o.name))),d=[];for(const o of t){if(i.has(o))continue;const l=await J(w(e.migrationsDir,o),"utf8");await n.query("BEGIN");try{await n.query(`SET LOCAL search_path TO ${a}`),await n.query("SELECT set_config('app.tenant_id', $1, true)",[e.tenantId]),await n.query(l),await n.query(`INSERT INTO ${r} (name) VALUES ($1)`,[o]),await n.query("COMMIT")}catch(u){throw await n.query("ROLLBACK").catch(()=>{}),new Error(`Migration ${o} failed: ${u instanceof Error?u.message:String(u)}`,{cause:u})}d.push(o)}return d}finally{await n.end().catch(()=>{})}}s(Te,"applyDevMigrations");var lt=250,dt=5e3;function ut(){return new Promise((e,t)=>{const n=st();n.once("error",t),n.listen(0,"127.0.0.1",()=>{const a=n.address(),r=typeof a=="object"&&a?a.port:0;n.close(()=>e(r))})})}s(ut,"freePort");function ke(e){return!e||e.exitCode!==null||e.signalCode!==null?Promise.resolve():new Promise(t=>{const n=setTimeout(()=>{e.kill("SIGKILL"),t()},dt);e.once("exit",()=>{clearTimeout(n),t()}),e.kill("SIGTERM")})}s(ke,"stopChild");async function gt(e){const{part:t,log:n}=e;if(t.runtime==="python")throw new Error("The local preview runs TypeScript backends only; a python backend runs through the platform.");const a=await ut(),r=be(e.pluginId),i=I(e.root,t.migrationsDirPath),d=w(e.outDir,t.entryPath),o={...process.env,NODE_ENV:"development",TEGO_PLUGIN_ID:e.pluginId,TEGO_PLUGIN_VERSION:e.version,TEGO_PLUGIN_INSTALL_ID:"dev-install",TEGO_TENANT_ID:O,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:Se(e.databaseUrl,r)};let l=null,u=!1;async function f(){await te(e.outDir,{recursive:!0,force:!0}),await We(t,{root:e.root,outDir:e.outDir});const h=await Te({databaseUrl:e.databaseUrl,schema:r,migrationsDir:i,tenantId:O});for(const E of h)n(`[backend] applied migration ${E}`)}s(f,"build");function y(){const h=ot(process.execPath,[d],{env:o,stdio:["ignore","pipe","pipe"]});l=h,h.stdout?.on("data",E=>process.stdout.write(E)),h.stderr?.on("data",E=>process.stderr.write(E)),h.on("exit",E=>{l===h&&!u&&(l=null,n(`[backend] exited (code ${E??"null"}); it starts again on the next save`))})}s(y,"start"),await f(),y(),n(`[backend] ${e.pluginId} on 127.0.0.1:${a}, schema ${r} in ${oe(e.databaseUrl)}`);let S=null,v=null,R=!1;const H=s(async()=>{if(v){R=!0;return}v=(async()=>{try{await f();const h=l;l=null,await ke(h),u||(y(),n("[backend] rebuilt and restarted"))}catch(h){n(`[backend] rebuild failed, the previous build keeps running: ${h instanceof Error?h.message:String(h)}`)}})(),await v,v=null,R&&!u&&(R=!1,await H())},"rebuild"),V=[];for(const h of[t.dir,"shared"]){const E=I(e.root,h);K(E)&&V.push(Qe(E,{recursive:!0},(_,x)=>{x&&/(^|[\\/])(node_modules|dist)([\\/]|$)/.test(String(x))||(S&&clearTimeout(S),S=setTimeout(()=>void H(),lt))}))}return{port:a,schema:r,async close(){u=!0,S&&clearTimeout(S);for(const h of V)h.close();await v,await ke(l)}}}s(gt,"startDevBackend");var se="/__tego/host",ie="/__tego/files",ft=1024*1024;async function ce(e,t=1/0){const n=[];let a=0;for await(const r of e){if(a+=r.length,a>t)throw Object.assign(new Error("body too large"),{status:413});n.push(r)}return Buffer.concat(n)}s(ce,"readBody");function p(e,t,n){if(n===void 0){e.writeHead(t),e.end();return}e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(n))}s(p,"send");function le(e,t){const n=I(e),a=I(n,t);if(!t||at(t)||!a.startsWith(n+he))throw Object.assign(new Error(`invalid storage key ${JSON.stringify(t)}`),{status:400});return a}s(le,"storagePath");var U=s(e=>`${e}.tego-meta.json`,"metaOf");async function B(e){const t=await et(e).catch(()=>null);if(!t?.isFile())return null;const n=await J(U(e),"utf8").then(a=>JSON.parse(a),()=>({}));return{size:t.size,contentType:n.contentType??null,versionTag:Xe("sha1").update(`${t.size}:${t.mtimeMs}`).digest("hex")}}s(B,"head");async function xe(e,t,n){await ee(C(e),{recursive:!0}),await ne(e,t),await ne(U(e),JSON.stringify({contentType:n??null}))}s(xe,"put");async function Ie(e,t,n){const a=new URL(e.url??"/","http://preview.local");try{if(a.pathname.startsWith(ie)){const o=le(n.storageDir,a.searchParams.get("key")??""),l=await B(o);if(!l)return p(t,404);t.writeHead(200,{"content-type":l.contentType??"application/octet-stream"}),t.end(await J(o));return}if(e.headers.authorization!==`Bearer ${n.token}`)return p(t,401,{error:"bad host API token"});const r=a.pathname.slice(se.length)||"/",i=s(async()=>JSON.parse((await ce(e,ft)).toString("utf8")||"{}"),"json"),d=s(o=>le(n.storageDir,String(o??"")),"keyFile");switch(r){case"/config":return p(t,200,{data:{values:n.config()}});case"/log":{const{level:o,message:l,fields:u}=await i();return n.log(`[backend ${String(o??"info")}] ${String(l??"")}${u?` ${JSON.stringify(u)}`:""}`),p(t,200,{data:{}})}case"/events":{const{name:o,payload:l}=await i();return n.log(`[event] ${String(o)} ${JSON.stringify(l??null)}`),p(t,200,{data:{}})}case"/notifications":return n.log(`[notification] ${JSON.stringify(await i())}`),p(t,200,{data:{id:qe()}});case"/directory/identity":{const{userId:o}=await i(),l=n.users().get(String(o));return p(t,200,{data:{active:!!l,roleNames:l??[],memberships:[],checkedAt:new Date().toISOString()}})}case"/directory/list":{const{kind:o,search:l,ids:u}=await i(),f=typeof l=="string"?l.toLowerCase():"",y=Array.isArray(u)?new Set(u.map(String)):void 0,S=o==="users"?[...n.users().keys()].filter(v=>(!y||y.has(v))&&v.toLowerCase().includes(f)).map(v=>({id:v,displayName:v,active:!0})):[];return p(t,200,{data:{items:S,hasMore:!1}})}case"/directory/match":{const{userId:o,principals:l}=await i(),u=Array.isArray(l)?l:[];return p(t,200,{data:{active:n.users().has(String(o)),matched:u.flatMap((f,y)=>f.kind==="user"&&f.id===o?[y]:[]),checkedAt:new Date().toISOString()}})}case"/directory/validate":{const{principals:o}=await i(),l=Array.isArray(o)?o:[],u=n.users();return p(t,200,{data:{invalid:l.flatMap((f,y)=>f.kind==="user"&&u.has(String(f.id))?[]:[y])}})}case"/storage/get":case"/storage/stream":{const o=d(a.searchParams.get("key"));if(e.method==="POST"&&r==="/storage/stream")return await xe(o,await ce(e),e.headers["content-type"]),p(t,200,{data:{}});const l=await B(o);if(!l)return p(t,404);let u=await J(o);const f=Number(a.searchParams.get("bytes"));Number.isFinite(f)&&f>0&&(u=u.subarray(0,f)),t.writeHead(200,{"content-type":l.contentType??"application/octet-stream"}),t.end(u);return}case"/storage/put":return await xe(d(a.searchParams.get("key")),await ce(e),e.headers["content-type"]),p(t,200,{data:{}});case"/storage/head":{const o=await B(d((await i()).key));return o?p(t,200,{data:o}):p(t,404)}case"/storage/delete":{const o=d((await i()).key);return await te(o,{force:!0}),await te(U(o),{force:!0}),p(t,200,{data:{}})}case"/storage/copy":{const{sourceKey:o,destinationKey:l}=await i(),u=d(o),f=d(l);return await B(u)?(await ee(C(f),{recursive:!0}),await pe(u,f),await pe(U(u),U(f)).catch(()=>{}),p(t,200,{data:await B(f)})):p(t,404)}case"/storage/signed-url":{const{key:o}=await i();d(o);const l=`${ie}?key=${encodeURIComponent(String(o))}`;return p(t,200,{data:{url:`${n.origin()}${l}`,path:l}})}default:return n.log(`[host] ${r} is not available in local preview`),p(t,501,{error:{code:"not_available_in_preview",message:`${r} is not available in local preview`}})}}catch(r){const i=r.status??500;p(t,i,{error:{message:r instanceof Error?r.message:String(r)}})}}s(Ie,"handleFakeHost");var Pe="@tego/plugin-sdk",pt=JSON.stringify(Pe),ht=JSON.stringify(`${Pe}/frontend`);function _e(e){return[{path:"index.html",contents:vt(e)},{path:"main.tsx",contents:bt(e)},{path:"shell.css",contents:wt},{path:"plugin-surface.tsx",contents:yt}]}s(_e,"shellFiles");var mt=s(e=>e.replace(/[&<>"]/g,t=>({"&":"&","<":"<",">":">",'"':"""})[t]),"escapeHtml");function vt({displayName:e}){return`<!doctype html>
|
|
1
|
+
var at=Object.defineProperty;var s=(e,t)=>at(e,"name",{value:t,configurable:!0});import{signPluginContext as ot}from"./chunk-WLTASJF6.js";import{readManifest as st,readPluginLocales as it,buildPluginBackend as ct}from"./chunk-IQNSBHBI.js";import"./chunk-I5Z3ECC4.js";import{applyConfigDefaults as ve}from"./chunk-DEGQDJF6.js";import"./chunk-EIVA5BR6.js";import"./chunk-2NTAWYCG.js";import{resolvePluginParts as lt}from"./chunk-IVJECPGG.js";import"./chunk-INCA3GFW.js";import"./chunk-56QOZQTY.js";import{PLUGIN_CONTEXT_HEADERS as M}from"./chunk-QKSPPMVN.js";import"./chunk-VMCGDDGP.js";import"./chunk-SNUCO2MK.js";import{randomUUID as ut,randomBytes as we,createHash as dt}from"crypto";import{existsSync as q,watch as ye,realpathSync as ft}from"fs";import{readdir as be,readFile as X,mkdir as oe,copyFile as Ee,rm as Y,writeFile as Q,stat as Se,mkdtemp as gt}from"fs/promises";import{createServer as pt,request as ht}from"http";import{createRequire as mt}from"module";import{join as m,resolve as P,isAbsolute as vt,sep as Te,dirname as j,relative as ke}from"path";import{spawn as wt}from"child_process";import{createServer as yt}from"net";import{tmpdir as bt}from"os";var se="x-tego-dev-user-id",ie="x-tego-dev-roles",V="dev-tenant",Pe="00000000-0000-4000-8000-000000000001";function xe(e){const t=s(a=>(Array.isArray(a)?a[0]:a)?.trim()??"","single"),n=t(e[se])||Pe;let r=[];try{const a=JSON.parse(t(e[ie])||"[]");Array.isArray(a)&&(r=a.filter(c=>typeof c=="string"))}catch{}return{userId:n,roles:r}}s(xe,"devCallerOf");function Ie(e,t,n,r=Date.now()){const a={};for(const[o,i]of Object.entries(e)){const d=o.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(r);return a[M.userId]=t.userId,a[M.tenantId]=V,a[M.roles]=c,a[M.issuedAt]=u,a[M.signature]=ot({userId:t.userId,tenantId:V,rolesHeader:c,avatarId:"",issuedAt:u},n),a}s(Ie,"gatewayHeaders");var Et="tego_dev_migrations";function _e(e){return`tego_dev_${e.toLowerCase().replace(/[^a-z0-9_]/g,"_")}`.slice(0,63)}s(_e,"devSchemaName");function $e(e,t){const n=new URL(e);n.searchParams.delete("options");const r=n.search.replace(/^\?/,"");return n.search=`?${r?`${r}&`:""}options=${encodeURIComponent(`-c search_path=${t}`)}`,n.toString()}s($e,"devDatabaseUrl");async function Ae(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 n=t.default?.Client??t.Client;if(!n)throw new Error('The "pg" module has no Client constructor.');const r=new n({connectionString:e});try{await r.connect()}catch(a){throw new Error(`Cannot connect to DATABASE_URL (${ce(e)}): ${De(a)}`,{cause:a})}return r}s(Ae,"connect");function De(e){const t=e?.code,n=e instanceof Error?e.message:String(e);switch(t){case"ECONNREFUSED":return`${n} \u2014 is PostgreSQL running at that host and port?`;case"ENOTFOUND":return`${n} \u2014 the host name does not resolve.`;case"28P01":case"28000":return`${n} \u2014 check the user name and password.`;case"3D000":return`${n} \u2014 create the database first (createdb <name>).`;default:return n}}s(De,"explainDatabaseError");async function Ne(e){const t=await Ae(e);try{await t.query("select 1")}finally{await t.end().catch(()=>{})}}s(Ne,"checkDevDatabase");function ce(e){try{const t=new URL(e);return t.password&&(t.password="***"),t.toString()}catch{return"<unparseable DATABASE_URL>"}}s(ce,"redactUrl");var Le=s(e=>`"${e.replace(/"/g,'""')}"`,"quote");async function Re(e){const t=await be(e.migrationsDir).then(c=>c.filter(u=>u.endsWith(".sql")).sort(),c=>{if(c.code==="ENOENT")return[];throw c}),n=await Ae(e.databaseUrl),r=Le(e.schema),a=`${r}.${Le(Et)}`;try{await n.query(`CREATE SCHEMA IF NOT EXISTS ${r}`),await n.query(`CREATE TABLE IF NOT EXISTS ${a} (name text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())`);const c=new Set((await n.query(`SELECT name FROM ${a}`)).rows.map(o=>String(o.name))),u=[];for(const o of t){if(c.has(o))continue;const i=await X(m(e.migrationsDir,o),"utf8");await n.query("BEGIN");try{await n.query(`SET LOCAL search_path TO ${r}`),await n.query("SELECT set_config('app.tenant_id', $1, true)",[e.tenantId]),await n.query(i),await n.query(`INSERT INTO ${a} (name) VALUES ($1)`,[o]),await n.query("COMMIT")}catch(d){throw await n.query("ROLLBACK").catch(()=>{}),new Error(`Migration ${o} failed: ${d instanceof Error?d.message:String(d)}`,{cause:d})}u.push(o)}return u}finally{await n.end().catch(()=>{})}}s(Re,"applyDevMigrations");var le=1e3,St=800,Oe=new Set(["node_modules","dist",".git",".tego-dev","build"]);async function Ce(e){const t=new Map,n=s(async r=>{let a;try{a=await be(r,{withFileTypes:!0})}catch{return}for(const c of a){const u=m(r,c.name);if(c.isDirectory())Oe.has(c.name)||await n(u);else if(c.isFile())try{const o=await Se(u);t.set(u,`${o.mtimeMs}:${o.size}`)}catch{}}},"walk");return await n(e),t}s(Ce,"treeSnapshot");function Tt(e,t){if(e.size!==t.size)return!1;for(const[n,r]of e)if(t.get(n)!==r)return!1;return!0}s(Tt,"sameSnapshot");function kt(e,t,n){let r=!1,a,c;const u=s(async()=>{let i=await Ce(e);const d=s(async()=>{if(r)return;const p=await Ce(e);!r&&!Tt(i,p)&&(i=p,t()),r||(c=setTimeout(()=>void d(),le))},"tick");r||(c=setTimeout(()=>void d(),le))},"poll"),o=s(i=>{a?.close(),a=void 0,!r&&(n.log(`[watch] ${n.label}: file events unavailable (${i}); checking for changes every ${le/1e3}s`),u())},"fallBack");if(n.poll)u();else try{a=ye(e,{recursive:!0},(i,d)=>{String(d??"").split(/[\\/]/).some(y=>Oe.has(y))||t()}),a.on("error",i=>o(i.message))}catch(i){o(i instanceof Error?i.message:String(i))}return{close(){r=!0,a?.close(),c&&clearTimeout(c)}}}s(kt,"watchTree");async function Pt(){const e=await gt(m(bt(),"tego-dev-watch-"));try{return await new Promise(t=>{let n,r=!1;const a=s(u=>{clearTimeout(c),n?.close(),t(u)},"finish"),c=setTimeout(()=>a(r),St);try{n=ye(e,{recursive:!0},()=>r=!0),n.on("error",()=>a(!1))}catch{a(!1);return}Q(m(e,"probe"),"x").catch(()=>a(!1))})}finally{await Y(e,{recursive:!0,force:!0})}}s(Pt,"fileEventsAvailable");var xt=250,It=5e3,Ue=3e4,_t=200,$t=40,At=9229;function Dt(){return new Promise((e,t)=>{const n=yt();n.once("error",t),n.listen(0,"127.0.0.1",()=>{const r=n.address(),a=typeof r=="object"&&r?r.port:0;n.close(()=>e(a))})})}s(Dt,"freePort");function Be(e,t){return["--enable-source-maps",...t?[`--inspect=127.0.0.1:${t}`]:[],e]}s(Be,"backendNodeArgs");function He(e,t){let n="";return{push(r){const a=(n+r).split(/\r?\n/);n=a.pop()??"";for(const c of a)t(`${e}${c}`)},flush(){n&&t(`${e}${n}`),n=""}}}s(He,"linePrefixer");function ue(e){return!e||e.exitCode!==null||e.signalCode!==null?Promise.resolve():new Promise(t=>{const n=setTimeout(()=>{e.kill("SIGKILL"),t()},It);e.once("exit",()=>{clearTimeout(n),t()}),e.kill("SIGTERM")})}s(ue,"stopChild");async function Nt(e){const{part:t,log:n}=e;if(t.runtime==="python")throw new Error("The local preview runs TypeScript backends only; a python backend runs through the platform.");const r=await Dt(),a=_e(e.pluginId),c=P(e.root,t.migrationsDirPath),u=m(e.outDir,t.entryPath),o={...process.env,NODE_ENV:"development",TEGO_PLUGIN_ID:e.pluginId,TEGO_PLUGIN_VERSION:e.version,TEGO_PLUGIN_INSTALL_ID:"dev-install",TEGO_TENANT_ID:V,TEGO_PLUGIN_PORT:String(r),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:$e(e.databaseUrl,a)};let i=null,d=!1;const p=[],y=s(f=>{p.push(f),p.length>$t&&p.shift(),n(f)},"remember");async function I(){await Y(e.outDir,{recursive:!0,force:!0}),await ct(t,{root:e.root,outDir:e.outDir,sourcemap:!0});const f=await Re({databaseUrl:e.databaseUrl,schema:a,migrationsDir:c,tenantId:V});for(const w of f)n(`[backend] applied migration ${w}`)}s(I,"build");function E(){const f=wt(process.execPath,Be(u,e.inspect),{env:o,stdio:["ignore","pipe","pipe"]});i=f;for(const w of[f.stdout,f.stderr]){const L=He("[backend] ",y);w?.setEncoding("utf8"),w?.on("data",S=>L.push(S)),w?.on("end",()=>L.flush())}return f.on("exit",w=>{i===f&&!d&&(i=null,n(`[backend] exited (code ${w??"null"}); fix the code and save to restart`))}),f}s(E,"start");async function D(f){const w=`http://127.0.0.1:${r}${t.healthPath}`,L=Date.now()+Ue;for(;Date.now()<L;){if(f.exitCode!==null||f.signalCode!==null)throw new Error(`The backend exited while starting (code ${f.exitCode??f.signalCode}):
|
|
2
|
+
${p.join(`
|
|
3
|
+
`)}`);try{if((await fetch(w)).ok)return}catch{}await new Promise(S=>setTimeout(S,_t))}throw new Error(`The backend did not answer ${t.healthPath} within ${Ue/1e3}s:
|
|
4
|
+
${p.join(`
|
|
5
|
+
`)}`)}s(D,"healthy"),await I();const N=E();try{await D(N)}catch(f){throw d=!0,await ue(i),f}n(`[backend] ${e.pluginId} on 127.0.0.1:${r}, schema ${a} in ${ce(e.databaseUrl)}`),e.inspect&&n(`[backend] debugger: attach to 127.0.0.1:${e.inspect} (chrome://inspect, or your editor's Node attach)`);let _=null,$=null,U=!1;const B=s(async()=>{if($){U=!0;return}$=(async()=>{try{await I();const f=i;i=null,await ue(f),d||(E(),n("[backend] rebuilt and restarted"))}catch(f){n(`[backend] rebuild failed, the previous build keeps running: ${f instanceof Error?f.message:String(f)}`)}})(),await $,$=null,U&&!d&&(U=!1,await B())},"rebuild"),K=[];for(const f of[t.dir,"shared"]){const w=P(e.root,f);q(w)&&K.push(kt(w,()=>{_&&clearTimeout(_),_=setTimeout(()=>void B(),xt)},{poll:e.poll===!0,log:n,label:f}))}return{port:r,schema:a,async close(){d=!0,_&&clearTimeout(_);for(const f of K)f.close();await $,await ue(i)}}}s(Nt,"startDevBackend");var de="/__tego/host",fe="/__tego/files",Lt=1024*1024;async function ge(e,t=1/0){const n=[];let r=0;for await(const a of e){if(r+=a.length,r>t)throw Object.assign(new Error("body too large"),{status:413});n.push(a)}return Buffer.concat(n)}s(ge,"readBody");function h(e,t,n){if(n===void 0){e.writeHead(t),e.end();return}e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(n))}s(h,"send");function pe(e,t){const n=P(e),r=P(n,t);if(!t||vt(t)||!r.startsWith(n+Te))throw Object.assign(new Error(`invalid storage key ${JSON.stringify(t)}`),{status:400});return r}s(pe,"storagePath");var F=s(e=>`${e}.tego-meta.json`,"metaOf");async function G(e){const t=await Se(e).catch(()=>null);if(!t?.isFile())return null;const n=await X(F(e),"utf8").then(r=>JSON.parse(r),()=>({}));return{size:t.size,contentType:n.contentType??null,versionTag:dt("sha1").update(`${t.size}:${t.mtimeMs}`).digest("hex")}}s(G,"head");async function Me(e,t,n){await oe(j(e),{recursive:!0}),await Q(e,t),await Q(F(e),JSON.stringify({contentType:n??null}))}s(Me,"put");async function je(e,t,n){const r=new URL(e.url??"/","http://preview.local");try{if(r.pathname.startsWith(fe)){const o=pe(n.storageDir,r.searchParams.get("key")??""),i=await G(o);if(!i)return h(t,404);t.writeHead(200,{"content-type":i.contentType??"application/octet-stream"}),t.end(await X(o));return}if(e.headers.authorization!==`Bearer ${n.token}`)return h(t,401,{error:"bad host API token"});const a=r.pathname.slice(de.length)||"/",c=s(async()=>JSON.parse((await ge(e,Lt)).toString("utf8")||"{}"),"json"),u=s(o=>pe(n.storageDir,String(o??"")),"keyFile");switch(a){case"/config":return h(t,200,{data:{values:n.config()}});case"/log":{const{level:o,message:i,fields:d}=await c();return n.log(`[backend ${String(o??"info")}] ${String(i??"")}${d?` ${JSON.stringify(d)}`:""}`),h(t,200,{data:{}})}case"/events":{const{name:o,payload:i}=await c();return n.log(`[event] ${String(o)} ${JSON.stringify(i??null)}`),h(t,200,{data:{}})}case"/notifications":return n.log(`[notification] ${JSON.stringify(await c())}`),h(t,200,{data:{id:ut()}});case"/directory/identity":{const{userId:o}=await c(),i=n.users().get(String(o));return h(t,200,{data:{active:!!i,roleNames:i??[],memberships:[],checkedAt:new Date().toISOString()}})}case"/directory/list":{const{kind:o,search:i,ids:d}=await c(),p=typeof i=="string"?i.toLowerCase():"",y=Array.isArray(d)?new Set(d.map(String)):void 0,I=o==="users"?[...n.users().keys()].filter(E=>(!y||y.has(E))&&E.toLowerCase().includes(p)).map(E=>({id:E,displayName:E,active:!0})):[];return h(t,200,{data:{items:I,hasMore:!1}})}case"/directory/match":{const{userId:o,principals:i}=await c(),d=Array.isArray(i)?i:[];return h(t,200,{data:{active:n.users().has(String(o)),matched:d.flatMap((p,y)=>p.kind==="user"&&p.id===o?[y]:[]),checkedAt:new Date().toISOString()}})}case"/directory/validate":{const{principals:o}=await c(),i=Array.isArray(o)?o:[],d=n.users();return h(t,200,{data:{invalid:i.flatMap((p,y)=>p.kind==="user"&&d.has(String(p.id))?[]:[y])}})}case"/storage/get":case"/storage/stream":{const o=u(r.searchParams.get("key"));if(e.method==="POST"&&a==="/storage/stream")return await Me(o,await ge(e),e.headers["content-type"]),h(t,200,{data:{}});const i=await G(o);if(!i)return h(t,404);let d=await X(o);const p=Number(r.searchParams.get("bytes"));Number.isFinite(p)&&p>0&&(d=d.subarray(0,p)),t.writeHead(200,{"content-type":i.contentType??"application/octet-stream"}),t.end(d);return}case"/storage/put":return await Me(u(r.searchParams.get("key")),await ge(e),e.headers["content-type"]),h(t,200,{data:{}});case"/storage/head":{const o=await G(u((await c()).key));return o?h(t,200,{data:o}):h(t,404)}case"/storage/delete":{const o=u((await c()).key);return await Y(o,{force:!0}),await Y(F(o),{force:!0}),h(t,200,{data:{}})}case"/storage/copy":{const{sourceKey:o,destinationKey:i}=await c(),d=u(o),p=u(i);return await G(d)?(await oe(j(p),{recursive:!0}),await Ee(d,p),await Ee(F(d),F(p)).catch(()=>{}),h(t,200,{data:await G(p)})):h(t,404)}case"/storage/signed-url":{const{key:o}=await c();u(o);const i=`${fe}?key=${encodeURIComponent(String(o))}`;return h(t,200,{data:{url:`${n.origin()}${i}`,path:i}})}default:return n.log(`[host] ${a} is not available in local preview`),h(t,501,{error:{code:"not_available_in_preview",message:`${a} is not available in local preview`}})}}catch(a){const c=a.status??500;h(t,c,{error:{message:a instanceof Error?a.message:String(a)}})}}s(je,"handleFakeHost");var Ve="@tego/plugin-sdk",Rt=JSON.stringify(Ve),Ot=JSON.stringify(`${Ve}/frontend`);function Fe(e){return[{path:"index.html",contents:Ut(e)},{path:"main.tsx",contents:Mt(e)},{path:"shell.css",contents:Bt},{path:"plugin-surface.tsx",contents:Ht}]}s(Fe,"shellFiles");var Ct=s(e=>e.replace(/[&<>"]/g,t=>({"&":"&","<":"<",">":">",'"':"""})[t]),"escapeHtml");function Ut({displayName:e}){return`<!doctype html>
|
|
2
6
|
<html lang="en" class="h-full">
|
|
3
7
|
<head>
|
|
4
8
|
<meta charset="UTF-8" />
|
|
5
9
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>${
|
|
10
|
+
<title>${Ct(e)} \xB7 local preview</title>
|
|
7
11
|
</head>
|
|
8
12
|
<body class="h-full">
|
|
9
13
|
<div id="tego-dev-bar"></div>
|
|
@@ -11,7 +15,7 @@ var Me=Object.defineProperty;var s=(e,t)=>Me(e,"name",{value:t,configurable:!0})
|
|
|
11
15
|
<script type="module" src="/main.tsx"><\/script>
|
|
12
16
|
</body>
|
|
13
17
|
</html>
|
|
14
|
-
`}s(
|
|
18
|
+
`}s(Ut,"indexHtml");var Bt=`@tailwind base;
|
|
15
19
|
@tailwind components;
|
|
16
20
|
@tailwind utilities;
|
|
17
21
|
|
|
@@ -31,7 +35,7 @@ body {
|
|
|
31
35
|
min-height: 0;
|
|
32
36
|
overflow: auto;
|
|
33
37
|
}
|
|
34
|
-
`,
|
|
38
|
+
`,Ht=`import type { ReactNode } from 'react';
|
|
35
39
|
|
|
36
40
|
function Unavailable(): ReactNode {
|
|
37
41
|
return (
|
|
@@ -46,12 +50,12 @@ export const ChatPanel = Unavailable;
|
|
|
46
50
|
export function useTopic() {
|
|
47
51
|
return { topic: undefined, topics: [], loading: false };
|
|
48
52
|
}
|
|
49
|
-
`;function
|
|
53
|
+
`;function Mt(e){const t=JSON.stringify({pluginId:e.pluginId,displayName:e.displayName,nav:e.nav.map(r=>({key:r.key,labelKey:r.labelKey,path:r.path??"/"})),config:e.config,locales:e.locales,defaultTarget:e.defaultTarget??"web",userHeader:se,rolesHeader:ie,defaultUser:Pe},null,2),n=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
|
|
|
53
|
-
import { pluginI18nNamespace } from ${
|
|
54
|
-
import type { PluginFrontendModule, PluginHostContext, PluginRoute } from ${
|
|
57
|
+
import { pluginI18nNamespace } from ${Rt};
|
|
58
|
+
import type { PluginFrontendModule, PluginHostContext, PluginRoute } from ${Ot};
|
|
55
59
|
import {
|
|
56
60
|
Button,
|
|
57
61
|
Input,
|
|
@@ -378,4 +382,5 @@ if (frontend) {
|
|
|
378
382
|
DEV.pluginId +
|
|
379
383
|
'/</p>';
|
|
380
384
|
}
|
|
381
|
-
`}s(
|
|
385
|
+
`}s(Mt,"mainTsx");var Ge="/__tego/csp-report",Ke="TEGO_DEV_CSP ",jt="tego-dev-target",Vt=100*1024*1024,Je=["default-src 'self'","object-src 'none'","base-uri 'self'"],ze={web:[...Je,"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:[...Je,"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 he(e){return e==="web"||e==="desktop"}s(he,"isPreviewTarget");function We(e,t){const n=new URL(e.url??"/","http://preview.local").searchParams.get("target");if(he(n))return n;const r=/(?:^|;\s*)tego-dev-target=(web|desktop)(?:;|$)/.exec(String(e.headers.cookie??""))?.[1];return he(r)?r:t}s(We,"targetOf");function qe(e){try{const t=JSON.parse(e)["csp-report"];if(!t)return;const n=String(t["effective-directive"]??t["violated-directive"]??""),r=String(t["blocked-uri"]??"");if(!n)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:n,blocked:r,...a?{source:a}:{},...c?{line:c}:{}}}catch{return}}s(qe,"parseCspReport");var Xe=5280,Ft=20,Ye="TEGO_DEV_READY ",Qe=".tego-dev",Gt="/api/v1/tego-os/plugin-api/";function Ze(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
|
+
`)}s(Ze,"missingDatabaseMessage");function Kt(e){const{displayName:t}=e;return typeof t=="string"?t:t.en??t["zh-CN"]??Object.values(t)[0]??e.id}s(Kt,"displayNameOf");function me(e,t){for(let n=P(e);;n=j(n)){const r=m(n,"node_modules",...t.split("/"));if(q(m(r,"package.json")))return ft(r);if(j(n)===n)return}}s(me,"packageDir");function C(e,t,n){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(n))}s(C,"sendJson");function Jt(e,t,n){return new Promise((r,a)=>{const c=s(o=>{e.off("listening",u),a(o)},"onError"),u=s(()=>{e.off("error",c);const o=e.address();r(typeof o=="object"&&o?o.port:t)},"onListening");e.once("error",c),e.once("listening",u),e.listen(t,n)})}s(Jt,"listen");async function zt(e,t){const n=mt(m(e,"vite.config.ts"));let r,a;try{r=n("@tego/ui/tailwind-preset"),a=j(n.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:[r],content:[m(a,"**/*.{js,cjs}"),m(t,"*.tsx")]})}s(zt,"shellTailwind");function Wt(e,t,n){return{postcssPlugin:"tego-dev-stylesheets",async Once(r,a){const c=r.source?.input.file,u=c&&P(c)===e?[t]:n;if(u.length===0)return;const o=await a.postcss(u).process(r,{from:c});a.result.messages.push(...o.messages)}}}s(Wt,"perStylesheet");function qt(e){return Array.isArray(e)?e.flat(1/0).map(t=>{if(!t||typeof t!="object")return t;const{buildStart:n}=t;return typeof n!="function"?t:{...t,buildStart(...r){const a=new Proxy(this,{get(c,u){if(u==="emitFile")return()=>"";const o=Reflect.get(c,u);return typeof o=="function"?o.bind(c):o}});return n.apply(a,r)}}}):e}s(qt,"servePlugins");async function Xt(e){const t=P(e.root),n=e.log??(l=>console.log(l)),r=await st(t),a=lt(r),c=process.env.TEGO_DEV_POLL==="1"||!await Pt();c&&n("[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(Ze(r.id));await Ne(e.databaseUrl)}const o=m(t,Qe),i=m(o,"shell"),d=we(32).toString("hex"),p=we(24).toString("hex");let y="";const I=new Map;let E,D={};const N=a.frontend?P(t,a.frontend.dir):t,{loadConfigFromFile:_,createServer:$,mergeConfig:U}=await import("vite");if(a.frontend){const l=m(N,"vite.config.ts");q(l)&&(D=(await _({command:"serve",mode:"development"},l,t))?.config??{});const g=D.build?.rollupOptions?.input,b=P(t,typeof g=="string"?g:`${a.frontend.dir}/src/index.tsx`);if(!q(b))throw new Error(`The frontend entry ${ke(t,b)} does not exist.`);E=ke(i,b).split(Te).join("/"),E.startsWith(".")||(E=`./${E}`)}const B=s(()=>a.frontend?it(t,r,a.frontend.i18nDirPath):Promise.resolve(new Map),"localeFiles"),K=[...(await B()).keys()].map(l=>l.replace(/\.json$/,""));await oe(i,{recursive:!0});for(const l of Fe({pluginId:r.id,displayName:Kt(r),nav:a.frontend?.nav??[],config:a.frontend?.config?ve(a.frontend.config.schema,{}):{},entry:E,locales:K,defaultTarget:e.target??"web"}))await Q(m(i,l.path),l.contents);let f,w;const L=s(()=>a.backend?.config?ve(a.backend.config.schema,{}):{},"backendConfig"),S=pt((l,g)=>void et(l,g));async function et(l,g){const b=(l.url??"/").split("?")[0],x=We(l,e.target??"web");try{if(b==="/favicon.ico"){g.writeHead(204),g.end();return}if(b===Ge){const v=await new Promise(W=>{let O="";l.setEncoding("utf8"),l.on("data",A=>O+=A),l.on("end",()=>W(O))}),k=qe(v);k&&(n(`[csp:${x}] blocked ${k.blocked||"(inline)"} by ${k.directive}${k.source?` in ${k.source}${k.line?`:${k.line}`:""}`:""}`),n(`${Ke}${JSON.stringify({target:x,...k})}`)),g.writeHead(204),g.end();return}if(b.startsWith(de)||b.startsWith(fe)){await je(l,g,{token:p,storageDir:m(o,"storage"),config:L,origin:s(()=>y,"origin"),users:s(()=>I,"users"),log:n});return}const T=/^\/i18n\/([A-Za-z-]+)\.json$/.exec(b);if(T){const v=(await B()).get(`${T[1]}.json`);if(v===void 0)return C(g,404,{error:`no ${T[1]}.json`});g.writeHead(200,{"content-type":"application/json"}),g.end(v);return}const R=`${Gt}${r.id}`;if(b===R||b.startsWith(`${R}/`)){tt(l,g,(l.url??"/").slice(R.length)||"/",x);return}if(String(l.headers.accept??"").includes("text/html")){g.setHeader("Content-Security-Policy",`${ze[x]}; report-uri ${Ge}`);const v=new URL(l.url??"/","http://preview.local").searchParams.get("target");he(v)&&g.setHeader("Set-Cookie",`${jt}=${v}; Path=/; SameSite=Lax`)}H.middlewares(l,g,()=>C(g,404,{error:`${b} not found`}))}catch(T){C(g,500,{error:T instanceof Error?T.message:String(T)})}}s(et,"route");function tt(l,g,b,x){if(!f){C(g,503,{error:w??"The plugin has no backend part."});return}const T=xe(l.headers);I.set(T.userId,T.roles);const R=ht({host:"127.0.0.1",port:f.port,method:l.method,path:b,headers:{...Ie(l.headers,T,d),...x==="desktop"?{"accept-encoding":"identity"}:{}}},v=>{if(x!=="desktop"){g.writeHead(v.statusCode??502,v.headers),v.pipe(g);return}const k=[];let W=0,O=!1;v.on("data",A=>{if(!O){if(W+=A.length,W>Vt){O=!0,v.destroy(),n(`[desktop] ${l.method} ${b}: response over 100 MB refused (413)`),C(g,413,{error:"Response over 100 MB: the desktop app refuses it. Serve large files through storage paths."});return}k.push(A)}}),v.on("end",()=>{if(O)return;const A=Buffer.concat(k),ae={...v.headers};delete ae["transfer-encoding"],ae["content-length"]=String(A.length),g.writeHead(v.statusCode??502,ae),g.end(A)})});R.on("error",v=>{g.headersSent?g.destroy():C(g,502,{error:`The backend is not answering (${v.message}); see the terminal.`})}),l.pipe(R)}s(tt,"proxy");const nt=m(i,"shell.css"),rt=(()=>{const l=D.css?.postcss;return l&&typeof l=="object"?l.plugins??[]:[]})(),J=(()=>{const l=me(N,"react-i18next");return(l&&me(l,"i18next"))??me(N,"i18next")})(),{css:Yt,mode:Qt,build:Zt,...Z}=D;Z.plugins=qt(Z.plugins);const H=await $(U(Z,{configFile:!1,root:i,mode:"development",appType:"spa",clearScreen:!1,logLevel:"info",esbuild:{jsx:"automatic",jsxDev:!1},css:{postcss:{plugins:[Wt(nt,await zt(N,i),rt)]}},resolve:{alias:[{find:"@tego/chat/plugin-surface",replacement:m(i,"plugin-surface.tsx")},...J?[{find:/^i18next$/,replacement:J}]:[]]},server:{middlewareMode:!0,hmr:{server:S},fs:{allow:[t,i,...J?[J]:[]]},watch:{ignored:["**/.tego-dev/backend/**","**/build/**","**/dist/**"],...c?{usePolling:!0,interval:500}:{}}},optimizeDeps:{entries:[m(i,"index.html")]}}));H.watcher.on("error",l=>n(`[watch] frontend: ${l instanceof Error?l.message:String(l)}; restart the preview if saves stop reloading`));const z=e.host??"127.0.0.1";let ee;const te=e.port??Xe;for(let l=0;ee===void 0;l++)try{ee=await Jt(S,te===0?0:te+l,z)}catch(g){if(g.code!=="EADDRINUSE"||te===0||l>=Ft)throw await H.close(),g}const ne=z==="0.0.0.0"||z==="::"?"127.0.0.1":z;if(y=`http://${ne.includes(":")?`[${ne}]`:ne}:${ee}`,!a.backend)w="The plugin has no backend part.";else if(!u)w="The backend is off (--no-backend).",n(`[backend] ${w}`);else try{f=await Nt({root:t,pluginId:r.id,version:r.version,part:a.backend,databaseUrl:e.databaseUrl,outDir:m(o,"backend"),hostApiUrl:`${y}${de}`,hostApiToken:p,gatewayKey:d,...e.inspect?{inspect:e.inspect}:{},poll:c,log:n})}catch(l){throw await H.close(),S.closeAllConnections(),await new Promise(g=>S.close(()=>g())),new Error(`The backend did not start: ${l instanceof Error?l.message:String(l)}`,{cause:l})}const re=`${y}/p/${r.id}/`;return n(`${r.id} ${r.version}: local preview at ${re}`),e.databaseUrl&&f&&n(` database ${ce(e.databaseUrl)}, schema ${f.schema}`),n(`${Ye}${JSON.stringify({url:re})}`),{url:re,async close(){await f?.close(),await H.close(),S.closeAllConnections(),await new Promise(l=>S.close(()=>l()))}}}s(Xt,"startDevServer");export{Ke as CSP_REPORT_PREFIX,Xe as DEFAULT_DEV_PORT,At as DEFAULT_INSPECT_PORT,Qe as DEV_DIR,Ye as DEV_READY_PREFIX,ie as DEV_ROLES_HEADER,V as DEV_TENANT_ID,se as DEV_USER_HEADER,ze as PREVIEW_CSP,Re as applyDevMigrations,Be as backendNodeArgs,Ne as checkDevDatabase,xe as devCallerOf,$e as devDatabaseUrl,_e as devSchemaName,De as explainDatabaseError,Ie as gatewayHeaders,je as handleFakeHost,He as linePrefixer,Ze as missingDatabaseMessage,qe as parseCspReport,Fe as shellFiles,Xt as startDevServer,pe as storagePath,We as targetOf};
|