@zhama/tego-plugin-sdk 1.37.2 → 1.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/agent/guides/host-api-versions.md +3 -0
  2. package/agent/reference/plugin-sdk-api.md +1 -0
  3. package/dist/access.d.cts +1 -0
  4. package/dist/access.d.ts +1 -0
  5. package/dist/{chunk-EIVA5BR6.js → chunk-2KXEYXM2.js} +1 -1
  6. package/dist/{chunk-I5Z3ECC4.js → chunk-44LPR67H.js} +1 -1
  7. package/dist/chunk-66LDWN4T.cjs +1 -0
  8. package/dist/{chunk-HZ4YWRYX.cjs → chunk-6EKQMOYT.cjs} +2 -2
  9. package/dist/{chunk-SKNPNPJP.cjs → chunk-H6GXDFLP.cjs} +1 -1
  10. package/dist/{chunk-6HNG5OZI.cjs → chunk-IOZFHJ4F.cjs} +1 -1
  11. package/dist/chunk-JV2G2Z33.js +1 -0
  12. package/dist/{chunk-DRHBXEPK.cjs → chunk-QQOUNXLC.cjs} +7 -7
  13. package/dist/{chunk-IQNSBHBI.js → chunk-UZBWS5AR.js} +1 -1
  14. package/dist/{chunk-56QOZQTY.js → chunk-XOXUS4VI.js} +1 -1
  15. package/dist/decision-card-IF2c4wvM.d.cts +161 -0
  16. package/dist/decision-card-IF2c4wvM.d.ts +161 -0
  17. package/dist/dev.cjs +3 -3
  18. package/dist/dev.js +1 -1
  19. package/dist/doctor.cjs +6 -6
  20. package/dist/doctor.js +13 -13
  21. package/dist/hono.d.cts +1 -0
  22. package/dist/hono.d.ts +1 -0
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.d.cts +14 -1
  25. package/dist/index.d.ts +14 -1
  26. package/dist/index.js +1 -1
  27. package/dist/manifest.cjs +1 -1
  28. package/dist/manifest.d.cts +654 -654
  29. package/dist/manifest.d.ts +654 -654
  30. package/dist/manifest.js +1 -1
  31. package/dist/pack.cjs +1 -1
  32. package/dist/pack.js +1 -1
  33. package/dist/publish.cjs +1 -1
  34. package/dist/publish.d.cts +6 -3
  35. package/dist/publish.d.ts +6 -3
  36. package/dist/publish.js +1 -1
  37. package/dist/scaffold.cjs +2 -2
  38. package/dist/scaffold.js +1 -1
  39. package/dist/server-runtime.cjs +1 -1
  40. package/dist/server-runtime.d.cts +1 -0
  41. package/dist/server-runtime.d.ts +1 -0
  42. package/dist/server-runtime.js +1 -1
  43. package/dist/server.cjs +1 -1
  44. package/dist/server.d.cts +58 -33
  45. package/dist/server.d.ts +58 -33
  46. package/dist/server.js +1 -1
  47. package/dist/testing.d.cts +1 -0
  48. package/dist/testing.d.ts +1 -0
  49. package/dist/vite.cjs +1 -1
  50. package/dist/vite.js +1 -1
  51. package/package.json +1 -1
  52. /package/dist/{chunk-QKSPPMVN.js → chunk-CDJQ7OPA.js} +0 -0
  53. /package/dist/{chunk-BN6TGHSB.cjs → chunk-SR5V7ZC2.cjs} +0 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * A decision card: something a plugin asks one person to decide inside a
3
+ * conversation.
4
+ *
5
+ * The employee only ever puts an intent id into the message. The client fetches
6
+ * the card from the plugin with the person's own token, and a commit goes straight
7
+ * back the same way, past the employee. That is what makes it a person's decision
8
+ * rather than an employee's report of one: the employee can neither write what the
9
+ * card says nor send what it submits.
10
+ *
11
+ * Content is rendered by the plugin in the viewer's language, and the same shape
12
+ * is drawn natively by every client, so nothing here can carry markup or actions
13
+ * beyond what is declared.
14
+ */
15
+ /** Which one of a card's two buttons the person pressed. */
16
+ type DecisionCardButtonId = 'primary' | 'secondary';
17
+ type DecisionCardState =
18
+ /** Can be submitted, including one partly carried out with items still waiting. */
19
+ 'open'
20
+ /** Being carried out; poll rather than submit again. */
21
+ | 'committing'
22
+ /** Every item has its final outcome. */
23
+ | 'committed'
24
+ /** A newer card replaced this one. */
25
+ | 'superseded' | 'expired';
26
+ type DecisionItemStatus = 'done'
27
+ /** Needs something only a person can supply; see `message`. */
28
+ | 'needs_input'
29
+ /** Changed since the card was drawn, so nothing was done. */
30
+ | 'stale' | 'refused' | 'failed'
31
+ /** The change has been reversed. */
32
+ | 'undone';
33
+ interface DecisionCardSource {
34
+ quote: string;
35
+ }
36
+ interface DecisionCardField {
37
+ key: string;
38
+ label: string;
39
+ /**
40
+ * `datetime` carries an ISO timestamp with its offset, and sends one back.
41
+ * `boolean` is a checkbox and sends true or false. `choice` picks from `options`.
42
+ */
43
+ type: 'text' | 'number' | 'date' | 'datetime' | 'boolean' | 'choice';
44
+ value?: string | number | boolean | null;
45
+ options?: {
46
+ value: string;
47
+ label: string;
48
+ }[];
49
+ /** Without a value the item cannot be carried out. */
50
+ required?: boolean;
51
+ /** Shown for checking and sent back by nobody. */
52
+ readOnly?: boolean;
53
+ }
54
+ /** One value to pick from, such as which existing record this means. */
55
+ interface DecisionCardChoice {
56
+ key: string;
57
+ label: string;
58
+ options: {
59
+ value: string;
60
+ label: string;
61
+ }[];
62
+ value?: string | null;
63
+ /** Without a value the item cannot be carried out. */
64
+ required: boolean;
65
+ }
66
+ interface DecisionCardItemResult {
67
+ status: DecisionItemStatus;
68
+ message?: string;
69
+ undoable?: boolean;
70
+ /**
71
+ * Nothing more can be done with this item on this card. Without it the item can be
72
+ * ticked and submitted again, as `needs_input` and `failed` can.
73
+ */
74
+ final?: boolean;
75
+ }
76
+ interface DecisionCardItem {
77
+ id: string;
78
+ title: string;
79
+ detail?: string;
80
+ /** What this will do, such as "Create new" or "Link to existing". */
81
+ badge?: string;
82
+ /** The source words behind it, so a person can check before agreeing. */
83
+ sources?: DecisionCardSource[];
84
+ /** How many sources this one item stands for. */
85
+ sourceCount?: number;
86
+ /** Ticked when drawn. A plugin leaves off anything a person should look at first. */
87
+ selected: boolean;
88
+ /** Set when it cannot be agreed to, with the reason to show. */
89
+ disabledReason?: string;
90
+ /**
91
+ * With `disabledReason`: it may still be ticked for the declining button. A person
92
+ * can always say no to something they could not say yes to.
93
+ */
94
+ declinable?: boolean;
95
+ fields?: DecisionCardField[];
96
+ choice?: DecisionCardChoice;
97
+ result?: DecisionCardItemResult;
98
+ }
99
+ interface DecisionCardGroup {
100
+ label: string;
101
+ items: DecisionCardItem[];
102
+ }
103
+ interface DecisionCardButton {
104
+ id: DecisionCardButtonId;
105
+ label: string;
106
+ /** Rejecting or cancelling gets a confirmation step and a warning colour. */
107
+ destructive?: boolean;
108
+ }
109
+ interface DecisionCard {
110
+ version: 1;
111
+ id: string;
112
+ state: DecisionCardState;
113
+ title: string;
114
+ /** Sent back on commit, so what is carried out is what was drawn. */
115
+ planHash: string;
116
+ expiresAt: string;
117
+ groups: DecisionCardGroup[];
118
+ buttons: DecisionCardButton[];
119
+ /** Shown once there is an outcome, such as "3 done, 1 needs you". */
120
+ summary?: string;
121
+ /** Someone else's to decide, shown as far as this viewer may see. No buttons. */
122
+ readOnly?: boolean;
123
+ }
124
+ interface DecisionCardCommit {
125
+ planHash: string;
126
+ button: DecisionCardButtonId;
127
+ /** Item ids to act on. */
128
+ selected: string[];
129
+ /** Values the person changed, by item id then field key. */
130
+ edits?: Record<string, Record<string, string | number | boolean | null>>;
131
+ /**
132
+ * Retrying the same request with the same key returns the first attempt's outcome.
133
+ * A new press is a new key, or a card left waiting on something could not be retried.
134
+ */
135
+ idempotencyKey: string;
136
+ }
137
+ /** What a plugin tool returns so the host draws a card in the message. */
138
+ interface DecisionCardBlock {
139
+ type: 'plugin-decision';
140
+ pluginId: string;
141
+ intentId: string;
142
+ }
143
+ /** The route a client reads and commits a card at, under the plugin's API. */
144
+ declare const decisionCardPath: (intentId: string) => string;
145
+ declare function isDecisionCardBlock(value: unknown): value is DecisionCardBlock;
146
+ /**
147
+ * The last line of a message that carries a card, for a client too old to draw one, so
148
+ * it tells the person what to do instead. It is fixed text, and only these sentences are
149
+ * ever hidden: a client that draws the card takes it off, and nothing an employee writes
150
+ * can be hidden this way.
151
+ */
152
+ declare const DECISION_CARD_FALLBACKS: readonly string[];
153
+ /** What to show of a message whose card this client can draw. */
154
+ declare function stripDecisionCardFallback(content: string): string;
155
+ /** Separates cards from the A2UI messages they travel beside in a message. */
156
+ declare function splitDecisionCardBlocks<T>(blocks: readonly T[]): {
157
+ decisions: DecisionCardBlock[];
158
+ rest: T[];
159
+ };
160
+
161
+ export { type DecisionCard as D, type DecisionCardBlock as a, type DecisionCardButton as b, type DecisionCardButtonId as c, type DecisionCardChoice as d, type DecisionCardCommit as e, type DecisionCardField as f, type DecisionCardGroup as g, type DecisionCardItem as h, type DecisionCardItemResult as i, type DecisionCardSource as j, type DecisionCardState as k, type DecisionItemStatus as l, decisionCardPath as m, DECISION_CARD_FALLBACKS as n, isDecisionCardBlock as o, stripDecisionCardFallback as p, splitDecisionCardBlocks as s };
@@ -0,0 +1,161 @@
1
+ /**
2
+ * A decision card: something a plugin asks one person to decide inside a
3
+ * conversation.
4
+ *
5
+ * The employee only ever puts an intent id into the message. The client fetches
6
+ * the card from the plugin with the person's own token, and a commit goes straight
7
+ * back the same way, past the employee. That is what makes it a person's decision
8
+ * rather than an employee's report of one: the employee can neither write what the
9
+ * card says nor send what it submits.
10
+ *
11
+ * Content is rendered by the plugin in the viewer's language, and the same shape
12
+ * is drawn natively by every client, so nothing here can carry markup or actions
13
+ * beyond what is declared.
14
+ */
15
+ /** Which one of a card's two buttons the person pressed. */
16
+ type DecisionCardButtonId = 'primary' | 'secondary';
17
+ type DecisionCardState =
18
+ /** Can be submitted, including one partly carried out with items still waiting. */
19
+ 'open'
20
+ /** Being carried out; poll rather than submit again. */
21
+ | 'committing'
22
+ /** Every item has its final outcome. */
23
+ | 'committed'
24
+ /** A newer card replaced this one. */
25
+ | 'superseded' | 'expired';
26
+ type DecisionItemStatus = 'done'
27
+ /** Needs something only a person can supply; see `message`. */
28
+ | 'needs_input'
29
+ /** Changed since the card was drawn, so nothing was done. */
30
+ | 'stale' | 'refused' | 'failed'
31
+ /** The change has been reversed. */
32
+ | 'undone';
33
+ interface DecisionCardSource {
34
+ quote: string;
35
+ }
36
+ interface DecisionCardField {
37
+ key: string;
38
+ label: string;
39
+ /**
40
+ * `datetime` carries an ISO timestamp with its offset, and sends one back.
41
+ * `boolean` is a checkbox and sends true or false. `choice` picks from `options`.
42
+ */
43
+ type: 'text' | 'number' | 'date' | 'datetime' | 'boolean' | 'choice';
44
+ value?: string | number | boolean | null;
45
+ options?: {
46
+ value: string;
47
+ label: string;
48
+ }[];
49
+ /** Without a value the item cannot be carried out. */
50
+ required?: boolean;
51
+ /** Shown for checking and sent back by nobody. */
52
+ readOnly?: boolean;
53
+ }
54
+ /** One value to pick from, such as which existing record this means. */
55
+ interface DecisionCardChoice {
56
+ key: string;
57
+ label: string;
58
+ options: {
59
+ value: string;
60
+ label: string;
61
+ }[];
62
+ value?: string | null;
63
+ /** Without a value the item cannot be carried out. */
64
+ required: boolean;
65
+ }
66
+ interface DecisionCardItemResult {
67
+ status: DecisionItemStatus;
68
+ message?: string;
69
+ undoable?: boolean;
70
+ /**
71
+ * Nothing more can be done with this item on this card. Without it the item can be
72
+ * ticked and submitted again, as `needs_input` and `failed` can.
73
+ */
74
+ final?: boolean;
75
+ }
76
+ interface DecisionCardItem {
77
+ id: string;
78
+ title: string;
79
+ detail?: string;
80
+ /** What this will do, such as "Create new" or "Link to existing". */
81
+ badge?: string;
82
+ /** The source words behind it, so a person can check before agreeing. */
83
+ sources?: DecisionCardSource[];
84
+ /** How many sources this one item stands for. */
85
+ sourceCount?: number;
86
+ /** Ticked when drawn. A plugin leaves off anything a person should look at first. */
87
+ selected: boolean;
88
+ /** Set when it cannot be agreed to, with the reason to show. */
89
+ disabledReason?: string;
90
+ /**
91
+ * With `disabledReason`: it may still be ticked for the declining button. A person
92
+ * can always say no to something they could not say yes to.
93
+ */
94
+ declinable?: boolean;
95
+ fields?: DecisionCardField[];
96
+ choice?: DecisionCardChoice;
97
+ result?: DecisionCardItemResult;
98
+ }
99
+ interface DecisionCardGroup {
100
+ label: string;
101
+ items: DecisionCardItem[];
102
+ }
103
+ interface DecisionCardButton {
104
+ id: DecisionCardButtonId;
105
+ label: string;
106
+ /** Rejecting or cancelling gets a confirmation step and a warning colour. */
107
+ destructive?: boolean;
108
+ }
109
+ interface DecisionCard {
110
+ version: 1;
111
+ id: string;
112
+ state: DecisionCardState;
113
+ title: string;
114
+ /** Sent back on commit, so what is carried out is what was drawn. */
115
+ planHash: string;
116
+ expiresAt: string;
117
+ groups: DecisionCardGroup[];
118
+ buttons: DecisionCardButton[];
119
+ /** Shown once there is an outcome, such as "3 done, 1 needs you". */
120
+ summary?: string;
121
+ /** Someone else's to decide, shown as far as this viewer may see. No buttons. */
122
+ readOnly?: boolean;
123
+ }
124
+ interface DecisionCardCommit {
125
+ planHash: string;
126
+ button: DecisionCardButtonId;
127
+ /** Item ids to act on. */
128
+ selected: string[];
129
+ /** Values the person changed, by item id then field key. */
130
+ edits?: Record<string, Record<string, string | number | boolean | null>>;
131
+ /**
132
+ * Retrying the same request with the same key returns the first attempt's outcome.
133
+ * A new press is a new key, or a card left waiting on something could not be retried.
134
+ */
135
+ idempotencyKey: string;
136
+ }
137
+ /** What a plugin tool returns so the host draws a card in the message. */
138
+ interface DecisionCardBlock {
139
+ type: 'plugin-decision';
140
+ pluginId: string;
141
+ intentId: string;
142
+ }
143
+ /** The route a client reads and commits a card at, under the plugin's API. */
144
+ declare const decisionCardPath: (intentId: string) => string;
145
+ declare function isDecisionCardBlock(value: unknown): value is DecisionCardBlock;
146
+ /**
147
+ * The last line of a message that carries a card, for a client too old to draw one, so
148
+ * it tells the person what to do instead. It is fixed text, and only these sentences are
149
+ * ever hidden: a client that draws the card takes it off, and nothing an employee writes
150
+ * can be hidden this way.
151
+ */
152
+ declare const DECISION_CARD_FALLBACKS: readonly string[];
153
+ /** What to show of a message whose card this client can draw. */
154
+ declare function stripDecisionCardFallback(content: string): string;
155
+ /** Separates cards from the A2UI messages they travel beside in a message. */
156
+ declare function splitDecisionCardBlocks<T>(blocks: readonly T[]): {
157
+ decisions: DecisionCardBlock[];
158
+ rest: T[];
159
+ };
160
+
161
+ export { type DecisionCard as D, type DecisionCardBlock as a, type DecisionCardButton as b, type DecisionCardButtonId as c, type DecisionCardChoice as d, type DecisionCardCommit as e, type DecisionCardField as f, type DecisionCardGroup as g, type DecisionCardItem as h, type DecisionCardItemResult as i, type DecisionCardSource as j, type DecisionCardState as k, type DecisionItemStatus as l, decisionCardPath as m, DECISION_CARD_FALLBACKS as n, isDecisionCardBlock as o, stripDecisionCardFallback as p, splitDecisionCardBlocks as s };
package/dist/dev.cjs CHANGED
@@ -1,8 +1,8 @@
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}):
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"),chunkQQOUNXLC_cjs=require("./chunk-QQOUNXLC.cjs");require("./chunk-6EKQMOYT.cjs");var chunkMWMGJJ74_cjs=require("./chunk-MWMGJJ74.cjs");require("./chunk-H6GXDFLP.cjs"),require("./chunk-45Y4CR4R.cjs");var chunk2P6UEAPV_cjs=require("./chunk-2P6UEAPV.cjs");require("./chunk-N4SXZZPY.cjs"),require("./chunk-IOZFHJ4F.cjs");var chunkSR5V7ZC2_cjs=require("./chunk-SR5V7ZC2.cjs");require("./chunk-66LDWN4T.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[chunkSR5V7ZC2_cjs.PLUGIN_CONTEXT_HEADERS.userId]=t.userId,a[chunkSR5V7ZC2_cjs.PLUGIN_CONTEXT_HEADERS.tenantId]=DEV_TENANT_ID,a[chunkSR5V7ZC2_cjs.PLUGIN_CONTEXT_HEADERS.roles]=c,a[chunkSR5V7ZC2_cjs.PLUGIN_CONTEXT_HEADERS.issuedAt]=u,a[chunkSR5V7ZC2_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 chunkQQOUNXLC_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
2
  ${h.join(`
3
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
4
  ${h.join(`
5
- `)}`)}o(I,"healthy"),await k();const D=y();try{await I(D)}catch(f){throw d=!0,await stopChild(i),f}r(`[backend] ${e.pluginId} on 127.0.0.1:${n}, schema ${a} in ${redactUrl(e.databaseUrl)}`),e.inspect&&r(`[backend] debugger: attach to 127.0.0.1:${e.inspect} (chrome://inspect, or your editor's Node attach)`);let P=null,x=null,R=!1;const L=o(async()=>{if(x){R=!0;return}x=(async()=>{try{await k();const f=i;i=null,await stopChild(f),d||(y(),r("[backend] rebuilt and restarted"))}catch(f){r(`[backend] rebuild failed, the previous build keeps running: ${f instanceof Error?f.message:String(f)}`)}})(),await x,x=null,R&&!d&&(R=!1,await L())},"rebuild"),C=[];for(const f of[t.dir,"shared"]){const m=path.resolve(e.root,f);fs.existsSync(m)&&C.push(watchTree(m,()=>{P&&clearTimeout(P),P=setTimeout(()=>void L(),REBUILD_DEBOUNCE_MS)},{poll:e.poll===!0,log:r,label:f}))}return{port:n,schema:a,async close(){d=!0,P&&clearTimeout(P);for(const f of C)f.close();await x,await stopChild(i)}}}o(startDevBackend,"startDevBackend");var FAKE_HOST_PREFIX="/__tego/host",FAKE_FILES_PREFIX="/__tego/files",MAX_JSON_BYTES=1024*1024;async function readBody(e,t=1/0){const r=[];let n=0;for await(const a of e){if(n+=a.length,n>t)throw Object.assign(new Error("body too large"),{status:413});r.push(a)}return Buffer.concat(r)}o(readBody,"readBody");function send(e,t,r){if(r===void 0){e.writeHead(t),e.end();return}e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r))}o(send,"send");function storagePath(e,t){const r=path.resolve(e),n=path.resolve(r,t);if(!t||path.isAbsolute(t)||!n.startsWith(r+path.sep))throw Object.assign(new Error(`invalid storage key ${JSON.stringify(t)}`),{status:400});return n}o(storagePath,"storagePath");var metaOf=o(e=>`${e}.tego-meta.json`,"metaOf");async function head(e){const t=await promises.stat(e).catch(()=>null);if(!t?.isFile())return null;const r=await promises.readFile(metaOf(e),"utf8").then(n=>JSON.parse(n),()=>({}));return{size:t.size,contentType:r.contentType??null,versionTag:crypto.createHash("sha1").update(`${t.size}:${t.mtimeMs}`).digest("hex")}}o(head,"head");async function put(e,t,r){await promises.mkdir(path.dirname(e),{recursive:!0}),await promises.writeFile(e,t),await promises.writeFile(metaOf(e),JSON.stringify({contentType:r??null}))}o(put,"put");async function handleFakeHost(e,t,r){const n=new URL(e.url??"/","http://preview.local");try{if(n.pathname.startsWith(FAKE_FILES_PREFIX)){const s=storagePath(r.storageDir,n.searchParams.get("key")??""),i=await head(s);if(!i)return send(t,404);t.writeHead(200,{"content-type":i.contentType??"application/octet-stream"}),t.end(await promises.readFile(s));return}if(e.headers.authorization!==`Bearer ${r.token}`)return send(t,401,{error:"bad host API token"});const a=n.pathname.slice(FAKE_HOST_PREFIX.length)||"/",c=o(async()=>JSON.parse((await readBody(e,MAX_JSON_BYTES)).toString("utf8")||"{}"),"json"),u=o(s=>storagePath(r.storageDir,String(s??"")),"keyFile");switch(a){case"/config":return send(t,200,{data:{values:r.config()}});case"/log":{const{level:s,message:i,fields:d}=await c();return r.log(`[backend ${String(s??"info")}] ${String(i??"")}${d?` ${JSON.stringify(d)}`:""}`),send(t,200,{data:{}})}case"/events":{const{name:s,payload:i}=await c();return r.log(`[event] ${String(s)} ${JSON.stringify(i??null)}`),send(t,200,{data:{}})}case"/notifications":return r.log(`[notification] ${JSON.stringify(await c())}`),send(t,200,{data:{id:crypto.randomUUID()}});case"/directory/identity":{const{userId:s}=await c(),i=r.users().get(String(s));return send(t,200,{data:{active:!!i,roleNames:i??[],memberships:[],checkedAt:new Date().toISOString()}})}case"/directory/list":{const{kind:s,search:i,ids:d}=await c(),h=typeof i=="string"?i.toLowerCase():"",v=Array.isArray(d)?new Set(d.map(String)):void 0,k=s==="users"?[...r.users().keys()].filter(y=>(!v||v.has(y))&&y.toLowerCase().includes(h)).map(y=>({id:y,displayName:y,active:!0})):[];return send(t,200,{data:{items:k,hasMore:!1}})}case"/directory/match":{const{userId:s,principals:i}=await c(),d=Array.isArray(i)?i:[];return send(t,200,{data:{active:r.users().has(String(s)),matched:d.flatMap((h,v)=>h.kind==="user"&&h.id===s?[v]:[]),checkedAt:new Date().toISOString()}})}case"/directory/validate":{const{principals:s}=await c(),i=Array.isArray(s)?s:[],d=r.users();return send(t,200,{data:{invalid:i.flatMap((h,v)=>h.kind==="user"&&d.has(String(h.id))?[]:[v])}})}case"/storage/get":case"/storage/stream":{const s=u(n.searchParams.get("key"));if(e.method==="POST"&&a==="/storage/stream")return await put(s,await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});const i=await head(s);if(!i)return send(t,404);let d=await promises.readFile(s);const h=Number(n.searchParams.get("bytes"));Number.isFinite(h)&&h>0&&(d=d.subarray(0,h)),t.writeHead(200,{"content-type":i.contentType??"application/octet-stream"}),t.end(d);return}case"/storage/put":return await put(u(n.searchParams.get("key")),await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});case"/storage/head":{const s=await head(u((await c()).key));return s?send(t,200,{data:s}):send(t,404)}case"/storage/delete":{const s=u((await c()).key);return await promises.rm(s,{force:!0}),await promises.rm(metaOf(s),{force:!0}),send(t,200,{data:{}})}case"/storage/copy":{const{sourceKey:s,destinationKey:i}=await c(),d=u(s),h=u(i);return await head(d)?(await promises.mkdir(path.dirname(h),{recursive:!0}),await promises.copyFile(d,h),await promises.copyFile(metaOf(d),metaOf(h)).catch(()=>{}),send(t,200,{data:await head(h)})):send(t,404)}case"/storage/signed-url":{const{key:s}=await c();u(s);const i=`${FAKE_FILES_PREFIX}?key=${encodeURIComponent(String(s))}`;return send(t,200,{data:{url:`${r.origin()}${i}`,path:i}})}default:return r.log(`[host] ${a} is not available in local preview`),send(t,501,{error:{code:"not_available_in_preview",message:`${a} is not available in local preview`}})}}catch(a){const c=a.status??500;send(t,c,{error:{message:a instanceof Error?a.message:String(a)}})}}o(handleFakeHost,"handleFakeHost");var SDK_PACKAGE="@tego/plugin-sdk",SDK_IMPORT=JSON.stringify(SDK_PACKAGE),SDK_FRONTEND_IMPORT=JSON.stringify(`${SDK_PACKAGE}/frontend`);function shellFiles(e){return[{path:"index.html",contents:indexHtml(e)},{path:"main.tsx",contents:mainTsx(e)},{path:"shell.css",contents:SHELL_CSS},{path:"plugin-surface.tsx",contents:PLUGIN_SURFACE_STUB}]}o(shellFiles,"shellFiles");var escapeHtml=o(e=>e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t]),"escapeHtml");function indexHtml({displayName:e}){return`<!doctype html>
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 x=null,P=null,R=!1;const L=o(async()=>{if(P){R=!0;return}P=(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 P,P=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,()=>{x&&clearTimeout(x),x=setTimeout(()=>void L(),REBUILD_DEBOUNCE_MS)},{poll:e.poll===!0,log:r,label:f}))}return{port:n,schema:a,async close(){d=!0,x&&clearTimeout(x);for(const f of C)f.close();await P,await stopChild(i)}}}o(startDevBackend,"startDevBackend");var FAKE_HOST_PREFIX="/__tego/host",FAKE_FILES_PREFIX="/__tego/files",MAX_JSON_BYTES=1024*1024;async function readBody(e,t=1/0){const r=[];let n=0;for await(const a of e){if(n+=a.length,n>t)throw Object.assign(new Error("body too large"),{status:413});r.push(a)}return Buffer.concat(r)}o(readBody,"readBody");function send(e,t,r){if(r===void 0){e.writeHead(t),e.end();return}e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r))}o(send,"send");function storagePath(e,t){const r=path.resolve(e),n=path.resolve(r,t);if(!t||path.isAbsolute(t)||!n.startsWith(r+path.sep))throw Object.assign(new Error(`invalid storage key ${JSON.stringify(t)}`),{status:400});return n}o(storagePath,"storagePath");var metaOf=o(e=>`${e}.tego-meta.json`,"metaOf");async function head(e){const t=await promises.stat(e).catch(()=>null);if(!t?.isFile())return null;const r=await promises.readFile(metaOf(e),"utf8").then(n=>JSON.parse(n),()=>({}));return{size:t.size,contentType:r.contentType??null,versionTag:crypto.createHash("sha1").update(`${t.size}:${t.mtimeMs}`).digest("hex")}}o(head,"head");async function put(e,t,r){await promises.mkdir(path.dirname(e),{recursive:!0}),await promises.writeFile(e,t),await promises.writeFile(metaOf(e),JSON.stringify({contentType:r??null}))}o(put,"put");async function handleFakeHost(e,t,r){const n=new URL(e.url??"/","http://preview.local");try{if(n.pathname.startsWith(FAKE_FILES_PREFIX)){const s=storagePath(r.storageDir,n.searchParams.get("key")??""),i=await head(s);if(!i)return send(t,404);t.writeHead(200,{"content-type":i.contentType??"application/octet-stream"}),t.end(await promises.readFile(s));return}if(e.headers.authorization!==`Bearer ${r.token}`)return send(t,401,{error:"bad host API token"});const a=n.pathname.slice(FAKE_HOST_PREFIX.length)||"/",c=o(async()=>JSON.parse((await readBody(e,MAX_JSON_BYTES)).toString("utf8")||"{}"),"json"),u=o(s=>storagePath(r.storageDir,String(s??"")),"keyFile");switch(a){case"/config":return send(t,200,{data:{values:r.config()}});case"/log":{const{level:s,message:i,fields:d}=await c();return r.log(`[backend ${String(s??"info")}] ${String(i??"")}${d?` ${JSON.stringify(d)}`:""}`),send(t,200,{data:{}})}case"/events":{const{name:s,payload:i}=await c();return r.log(`[event] ${String(s)} ${JSON.stringify(i??null)}`),send(t,200,{data:{}})}case"/notifications":return r.log(`[notification] ${JSON.stringify(await c())}`),send(t,200,{data:{id:crypto.randomUUID()}});case"/directory/identity":{const{userId:s}=await c(),i=r.users().get(String(s));return send(t,200,{data:{active:!!i,roleNames:i??[],memberships:[],checkedAt:new Date().toISOString()}})}case"/directory/list":{const{kind:s,search:i,ids:d}=await c(),h=typeof i=="string"?i.toLowerCase():"",v=Array.isArray(d)?new Set(d.map(String)):void 0,k=s==="users"?[...r.users().keys()].filter(y=>(!v||v.has(y))&&y.toLowerCase().includes(h)).map(y=>({id:y,displayName:y,active:!0})):[];return send(t,200,{data:{items:k,hasMore:!1}})}case"/directory/match":{const{userId:s,principals:i}=await c(),d=Array.isArray(i)?i:[];return send(t,200,{data:{active:r.users().has(String(s)),matched:d.flatMap((h,v)=>h.kind==="user"&&h.id===s?[v]:[]),checkedAt:new Date().toISOString()}})}case"/directory/validate":{const{principals:s}=await c(),i=Array.isArray(s)?s:[],d=r.users();return send(t,200,{data:{invalid:i.flatMap((h,v)=>h.kind==="user"&&d.has(String(h.id))?[]:[v])}})}case"/storage/get":case"/storage/stream":{const s=u(n.searchParams.get("key"));if(e.method==="POST"&&a==="/storage/stream")return await put(s,await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});const i=await head(s);if(!i)return send(t,404);let d=await promises.readFile(s);const h=Number(n.searchParams.get("bytes"));Number.isFinite(h)&&h>0&&(d=d.subarray(0,h)),t.writeHead(200,{"content-type":i.contentType??"application/octet-stream"}),t.end(d);return}case"/storage/put":return await put(u(n.searchParams.get("key")),await readBody(e),e.headers["content-type"]),send(t,200,{data:{}});case"/storage/head":{const s=await head(u((await c()).key));return s?send(t,200,{data:s}):send(t,404)}case"/storage/delete":{const s=u((await c()).key);return await promises.rm(s,{force:!0}),await promises.rm(metaOf(s),{force:!0}),send(t,200,{data:{}})}case"/storage/copy":{const{sourceKey:s,destinationKey:i}=await c(),d=u(s),h=u(i);return await head(d)?(await promises.mkdir(path.dirname(h),{recursive:!0}),await promises.copyFile(d,h),await promises.copyFile(metaOf(d),metaOf(h)).catch(()=>{}),send(t,200,{data:await head(h)})):send(t,404)}case"/storage/signed-url":{const{key:s}=await c();u(s);const i=`${FAKE_FILES_PREFIX}?key=${encodeURIComponent(String(s))}`;return send(t,200,{data:{url:`${r.origin()}${i}`,path:i}})}default:return r.log(`[host] ${a} is not available in local preview`),send(t,501,{error:{code:"not_available_in_preview",message:`${a} is not available in local preview`}})}}catch(a){const c=a.status??500;send(t,c,{error:{message:a instanceof Error?a.message:String(a)}})}}o(handleFakeHost,"handleFakeHost");var SDK_PACKAGE="@tego/plugin-sdk",SDK_IMPORT=JSON.stringify(SDK_PACKAGE),SDK_FRONTEND_IMPORT=JSON.stringify(`${SDK_PACKAGE}/frontend`);function shellFiles(e){return[{path:"index.html",contents:indexHtml(e)},{path:"main.tsx",contents:mainTsx(e)},{path:"shell.css",contents:SHELL_CSS},{path:"plugin-surface.tsx",contents:PLUGIN_SURFACE_STUB}]}o(shellFiles,"shellFiles");var escapeHtml=o(e=>e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t]),"escapeHtml");function indexHtml({displayName:e}){return`<!doctype html>
6
6
  <html lang="en" class="h-full">
7
7
  <head>
8
8
  <meta charset="UTF-8" />
@@ -383,4 +383,4 @@ if (frontend) {
383
383
  '/</p>';
384
384
  }
385
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;
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 chunkQQOUNXLC_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:x,createServer:P,mergeConfig:R}=await import("vite");if(a.frontend){const l=path.join(D,"vite.config.ts");fs.existsSync(l)&&(I=(await x({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?chunkQQOUNXLC_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 J(l,p));async function J(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 N="";l.setEncoding("utf8"),l.on("data",_=>N+=_),l.on("end",()=>H(N))}),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 $=`${PLUGIN_API_PREFIX}${n.id}`;if(w===$||w.startsWith(`${$}/`)){K(l,p,(l.url??"/").slice($.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(J,"route");function K(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 $=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,N=!1;g.on("data",_=>{if(!N){if(H+=_.length,H>DESKTOP_MAX_RESPONSE_BYTES){N=!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(N)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(_)})});$.on("error",g=>{p.headersSent?p.destroy():sendJson(p,502,{error:`The backend is not answering (${g.message}); see the terminal.`})}),l.pipe($)}o(K,"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:Q,build:Z,...F}=I;F.plugins=servePlugins(F.plugins);const O=await P(R(F,{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 V=e.port??DEFAULT_DEV_PORT;for(let l=0;M===void 0;l++)try{M=await listen(E,V===0?0:V+l,j)}catch(p){if(p.code!=="EADDRINUSE"||V===0||l>=PORT_ATTEMPTS)throw await O.close(),p}const B=j==="0.0.0.0"||j==="::"?"127.0.0.1":j;if(v=`http://${B.includes(":")?`[${B}]`:B}:${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.js CHANGED
@@ -1,4 +1,4 @@
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}):
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-UZBWS5AR.js";import"./chunk-44LPR67H.js";import{applyConfigDefaults as ve}from"./chunk-DEGQDJF6.js";import"./chunk-2KXEYXM2.js";import"./chunk-2NTAWYCG.js";import{resolvePluginParts as lt}from"./chunk-IVJECPGG.js";import"./chunk-INCA3GFW.js";import"./chunk-XOXUS4VI.js";import{PLUGIN_CONTEXT_HEADERS as M}from"./chunk-CDJQ7OPA.js";import"./chunk-JV2G2Z33.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
2
  ${p.join(`
3
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
4
  ${p.join(`