@slabui/theme-react 0.4.0 → 0.5.0
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/CHANGELOG.md +13 -0
- package/dist/editor/index.mjs +1 -1
- package/dist/editor/index.mjs.map +1 -1
- package/dist/next/index.mjs +1 -1
- package/dist/server-DeZ9Emfp.mjs +123 -0
- package/dist/server-DeZ9Emfp.mjs.map +1 -0
- package/dist/vite/index.mjs +1 -1
- package/package.json +2 -2
- package/dist/server-DfiIO3KF.mjs +0 -4
- package/dist/server-DfiIO3KF.mjs.map +0 -1
package/dist/next/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,i as t,n,r,t as i}from"../server-
|
|
1
|
+
import{a as e,i as t,n,r,t as i}from"../server-DeZ9Emfp.mjs";const a=/* @__PURE__ */ new Map;function o(o={},c={}){return async(l,u)=>{let d=typeof o==`function`?await o(l,u):o;if(l!==`phase-development-server`)return d;let f=i(process.cwd(),c.css),p=a.get(f);p||(p=r({css:f,reference:c.reference,root:process.cwd(),themes:n(process.cwd(),c),account:t({root:process.cwd()})}).then(e=>e.port),a.set(f,p));let m={source:`${e}/:path*`,destination:`http://127.0.0.1:${await p}/:path*`};return{...d,rewrites:async()=>s(await d.rewrites?.(),m)}}}function s(e,t){return e===void 0?{beforeFiles:[t]}:Array.isArray(e)?{beforeFiles:[t],afterFiles:e}:{...e,beforeFiles:[t,...e.beforeFiles??[]]}}export{o as withTheme};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import{existsSync as e,readFileSync as t,watch as n}from"node:fs";import{createServer as r}from"node:http";import i,{basename as a,dirname as o,isAbsolute as s,join as c,relative as l,resolve as u,sep as ee}from"node:path";import{createPathsMatcher as d,parseTsconfig as f}from"get-tsconfig";import{mkdir as p,readFile as m,readdir as te,rename as ne,rm as h,writeFile as g}from"node:fs/promises";import{createHash as _,createPublicKey as v,randomBytes as y,verify as b}from"node:crypto";import{homedir as x,hostname as S}from"node:os";import{ThemeValidationError as re,defaultTheme as C,isThemeName as w,readTheme as T,writeTheme as E}from"@slabui/theme-core";import{addThemeImport as ie,removeThemeImport as ae,removeTokens as oe,renameThemeImport as se,renameThemeSelectors as ce,themeFileHeader as le}from"@slabui/theme-core/files";function ue({status:e,title:t,message:n,close:r=!1}){let i=e===`success`?A:j;return`<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="color-scheme" content="light dark">
|
|
7
|
+
<title>${D(t)} · SlabUI</title>
|
|
8
|
+
<style>${O}</style>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<main>
|
|
12
|
+
${N}
|
|
13
|
+
<section class="card" data-status="${e}">
|
|
14
|
+
<span class="icon">${i}</span>
|
|
15
|
+
<h1>${D(t)}</h1>
|
|
16
|
+
<p>${D(n)}</p>
|
|
17
|
+
${r?`<button type="button" onclick="window.close()">Close tab</button>`:``}
|
|
18
|
+
</section>
|
|
19
|
+
</main>
|
|
20
|
+
</body>
|
|
21
|
+
</html>`}function D(e){return e.replace(/[&<>"']/g,e=>({"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`})[e]??e)}const O=`
|
|
22
|
+
:root {
|
|
23
|
+
--background: oklch(0.985 0 0);
|
|
24
|
+
--foreground: oklch(0.145 0 0);
|
|
25
|
+
--card: oklch(1 0 0);
|
|
26
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
27
|
+
--border: oklch(0.922 0 0);
|
|
28
|
+
--primary: oklch(70% 0.1435 227.76);
|
|
29
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
30
|
+
--success: oklch(0.728 0.125 139.6);
|
|
31
|
+
--danger: oklch(0.577 0.245 27.325);
|
|
32
|
+
--logo: #0b0c0e;
|
|
33
|
+
--radius: 0.65rem;
|
|
34
|
+
}
|
|
35
|
+
@media (prefers-color-scheme: dark) {
|
|
36
|
+
:root {
|
|
37
|
+
--background: oklch(0.145 0 0);
|
|
38
|
+
--foreground: oklch(0.985 0 0);
|
|
39
|
+
--card: oklch(0.205 0 0);
|
|
40
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
41
|
+
--border: oklch(1 0 0 / 10%);
|
|
42
|
+
--primary: oklch(66% 0.1435 227.76);
|
|
43
|
+
--danger: oklch(0.704 0.191 22.216);
|
|
44
|
+
--logo: #f4f4f2;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
* { box-sizing: border-box; }
|
|
48
|
+
body {
|
|
49
|
+
margin: 0;
|
|
50
|
+
min-height: 100vh;
|
|
51
|
+
display: grid;
|
|
52
|
+
place-items: center;
|
|
53
|
+
padding: 1.5rem 1rem;
|
|
54
|
+
background: var(--background);
|
|
55
|
+
color: var(--foreground);
|
|
56
|
+
font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
57
|
+
-webkit-font-smoothing: antialiased;
|
|
58
|
+
}
|
|
59
|
+
main {
|
|
60
|
+
width: 100%;
|
|
61
|
+
max-width: 24rem;
|
|
62
|
+
display: grid;
|
|
63
|
+
justify-items: center;
|
|
64
|
+
gap: 1.5rem;
|
|
65
|
+
}
|
|
66
|
+
.logo { height: 1.75rem; width: auto; }
|
|
67
|
+
.logo .ink { fill: var(--logo); }
|
|
68
|
+
.card {
|
|
69
|
+
width: 100%;
|
|
70
|
+
display: grid;
|
|
71
|
+
justify-items: center;
|
|
72
|
+
gap: 0.5rem;
|
|
73
|
+
padding: 2rem 1.5rem;
|
|
74
|
+
text-align: center;
|
|
75
|
+
background: var(--card);
|
|
76
|
+
border: 1px solid var(--border);
|
|
77
|
+
border-radius: calc(var(--radius) * 1.4);
|
|
78
|
+
box-shadow: 0 1px 2px oklch(0 0 0 / 5%);
|
|
79
|
+
}
|
|
80
|
+
.icon {
|
|
81
|
+
--tone: var(--success);
|
|
82
|
+
display: grid;
|
|
83
|
+
place-items: center;
|
|
84
|
+
width: 3rem;
|
|
85
|
+
height: 3rem;
|
|
86
|
+
margin-bottom: 0.5rem;
|
|
87
|
+
border-radius: 999px;
|
|
88
|
+
color: var(--tone);
|
|
89
|
+
background: color-mix(in oklch, var(--tone) 15%, transparent);
|
|
90
|
+
}
|
|
91
|
+
.card[data-status="error"] .icon { --tone: var(--danger); }
|
|
92
|
+
.icon svg { width: 1.5rem; height: 1.5rem; }
|
|
93
|
+
h1 { margin: 0; font-size: 1.25rem; line-height: 1.4; font-weight: 600; }
|
|
94
|
+
p { margin: 0; color: var(--muted-foreground); font-size: 0.875rem; }
|
|
95
|
+
button {
|
|
96
|
+
margin-top: 1rem;
|
|
97
|
+
height: 2.25rem;
|
|
98
|
+
padding: 0 1rem;
|
|
99
|
+
border: 0;
|
|
100
|
+
border-radius: var(--radius);
|
|
101
|
+
background: var(--primary);
|
|
102
|
+
color: var(--primary-foreground);
|
|
103
|
+
font: inherit;
|
|
104
|
+
font-size: 0.875rem;
|
|
105
|
+
font-weight: 500;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
}
|
|
108
|
+
button:hover { opacity: 0.9; }
|
|
109
|
+
button:focus-visible { outline: 2.5px solid var(--primary); outline-offset: 2px; }
|
|
110
|
+
`,k=e=>`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${e}</svg>`,A=k(`<path d="M20 6 9 17l-5-5"/>`),j=k(`<path d="M18 6 6 18M6 6l12 12"/>`),M=`x="-30" y="-30" width="60" height="60" rx="12"`,N=`<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 288 80" fill="none" role="img" aria-label="SlabUI">
|
|
111
|
+
<defs>
|
|
112
|
+
<linearGradient id="plate" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#00c1cc"/><stop offset="1" stop-color="#9296ff"/></linearGradient>
|
|
113
|
+
<linearGradient id="ui" gradientUnits="userSpaceOnUse" x1="228.77" y1="0" x2="287.52" y2="0"><stop offset="0" stop-color="#00c1cc"/><stop offset="1" stop-color="#9296ff"/></linearGradient>
|
|
114
|
+
</defs>
|
|
115
|
+
<rect ${M} transform="translate(40 52) scale(1 0.5299193) rotate(45)" class="ink" fill-opacity=".25"/>
|
|
116
|
+
<rect ${M} transform="translate(40 40) scale(1 0.5299193) rotate(45)" class="ink" fill-opacity=".6"/>
|
|
117
|
+
<rect ${M} transform="translate(40 28) scale(1 0.5299193) rotate(45)" fill="url(#plate)"/>
|
|
118
|
+
<path class="ink" d="M122.72 63.52L122.72 63.52Q116.83 63.52 112.61 61.54Q108.38 59.56 106.02 55.97Q103.65 52.39 103.33 47.52L103.33 47.52L113.12 47.08Q113.50 49.76 114.69 51.65Q115.87 53.54 117.89 54.50Q119.90 55.46 122.85 55.46L122.85 55.46Q125.28 55.46 126.98 54.88Q128.67 54.31 129.57 53.12Q130.46 51.94 130.46 50.21L130.46 50.21Q130.46 48.61 129.70 47.43Q128.93 46.24 126.75 45.25Q124.58 44.26 120.35 43.30L120.35 43.30Q114.53 41.96 110.98 40.29Q107.42 38.63 105.82 36.10Q104.22 33.57 104.22 29.73L104.22 29.73Q104.22 25.64 106.24 22.56Q108.26 19.49 112.10 17.76Q115.94 16.04 121.25 16.04L121.25 16.04Q126.88 16.04 130.78 18.02Q134.69 20.00 136.90 23.46Q139.10 26.92 139.55 31.40L139.55 31.40L129.82 31.91Q129.63 29.54 128.54 27.78Q127.46 26.02 125.60 25.06Q123.74 24.10 121.12 24.10L121.12 24.10Q117.79 24.10 115.90 25.57Q114.02 27.04 114.02 29.41L114.02 29.41Q114.02 31.08 114.88 32.20Q115.74 33.32 117.76 34.15Q119.78 34.98 123.49 35.75L123.49 35.75Q129.82 37.03 133.50 38.98Q137.18 40.93 138.72 43.62Q140.26 46.31 140.26 49.76L140.26 49.76Q140.26 53.99 138.11 57.09Q135.97 60.20 132.03 61.86Q128.10 63.52 122.72 63.52ZM159.26 62.50L153.57 62.50Q149.34 62.50 146.98 60.39Q144.61 58.28 144.61 53.54L144.61 53.54L144.61 17.06L154.21 17.06L154.21 52.52Q154.21 54.05 154.91 54.69Q155.62 55.33 156.96 55.33L156.96 55.33L159.26 55.33L159.26 62.50ZM171.49 63.27L171.49 63.27Q166.18 63.27 162.82 60.80Q159.46 58.34 159.46 53.92L159.46 53.92Q159.46 49.38 162.30 46.92Q165.15 44.45 170.72 43.30L170.72 43.30L181.79 41.12Q181.79 37.80 180.29 36.10Q178.78 34.40 175.97 34.40L175.97 34.40Q173.22 34.40 171.68 35.65Q170.14 36.90 169.70 39.33L169.70 39.33L160.03 38.88Q160.99 33.25 165.12 30.34Q169.25 27.43 175.97 27.43L175.97 27.43Q183.65 27.43 187.52 31.08Q191.39 34.72 191.39 41.76L191.39 41.76L191.39 53.48Q191.39 54.95 191.87 55.43Q192.35 55.91 193.31 55.91L193.31 55.91L194.27 55.91L194.27 62.50Q193.82 62.63 192.83 62.76Q191.84 62.88 190.88 62.88L190.88 62.88Q188.83 62.88 187.07 62.18Q185.31 61.48 184.22 59.75L184.22 59.75Q183.58 58.79 183.33 57.32L183.33 57.32Q182.69 58.53 181.73 59.62L181.73 59.62Q180.06 61.35 177.47 62.31Q174.88 63.27 171.49 63.27ZM173.79 56.68L173.79 56.68Q176.16 56.68 177.95 55.75Q179.74 54.82 180.77 53.03Q181.79 51.24 181.79 48.80L181.79 48.80L181.79 47.14L173.98 48.80Q171.68 49.25 170.50 50.31Q169.31 51.36 169.31 53.03L169.31 53.03Q169.31 54.76 170.46 55.72Q171.62 56.68 173.79 56.68ZM215.33 63.27L215.33 63.27Q211.74 63.27 209.09 61.73Q206.43 60.20 204.96 57.44L204.96 57.44L204.77 62.50L195.62 62.50L195.62 17.06L205.22 17.06L205.22 33.06Q206.56 30.69 209.18 29.06Q211.81 27.43 215.33 27.43L215.33 27.43Q219.68 27.43 222.88 29.64Q226.08 31.84 227.84 35.88Q229.60 39.91 229.60 45.35L229.60 45.35Q229.60 50.79 227.84 54.82Q226.08 58.85 222.88 61.06Q219.68 63.27 215.33 63.27ZM212.64 55.91L212.64 55.91Q215.84 55.91 217.79 53.12Q219.74 50.34 219.74 45.35L219.74 45.35Q219.74 40.36 217.82 37.57Q215.90 34.79 212.70 34.79L212.70 34.79Q210.34 34.79 208.64 36.04Q206.94 37.28 206.08 39.65Q205.22 42.02 205.22 45.35L205.22 45.35Q205.22 48.61 206.08 50.98Q206.94 53.35 208.64 54.63Q210.34 55.91 212.64 55.91Z"/>
|
|
119
|
+
<path fill="url(#ui)" d="M251.30 63.52L251.30 63.52Q245.54 63.52 241.34 61.41Q237.15 59.30 234.85 55.36Q232.54 51.43 232.54 46.12L232.54 46.12L232.54 17.06L242.27 17.06L242.27 46.12Q242.27 50.53 244.61 52.93Q246.94 55.33 251.30 55.33L251.30 55.33Q255.65 55.33 257.95 52.93Q260.26 50.53 260.26 46.12L260.26 46.12L260.26 17.06L269.98 17.06L269.98 46.12Q269.98 51.43 267.71 55.36Q265.44 59.30 261.22 61.41Q256.99 63.52 251.30 63.52ZM285.98 62.50L276.26 62.50L276.26 17.06L285.98 17.06L285.98 62.50Z"/>
|
|
120
|
+
</svg>`;function P(e=process.env,t=process.platform,n=x()){let r=t===`win32`?i.join(e.APPDATA??i.join(n,`AppData/Roaming`),`slabui`):i.join(e.XDG_CONFIG_HOME||i.join(n,`.config`),`slabui`);return i.join(r,`credentials.json`)}async function F(e=P()){try{let{token:t}=JSON.parse(await m(e,`utf8`));return typeof t==`string`&&t?t:void 0}catch{return}}async function I(e,t=P()){await p(i.dirname(t),{recursive:!0}),await g(t,`${JSON.stringify({token:e})}\n`,{mode:384})}async function L(e=P()){let t=await F(e)!==void 0;return await h(e,{force:!0}),t}function R(e){return _(`sha256`).update(e.trim()).digest(`hex`)}function z(e){return typeof e==`string`?v({key:Buffer.from(e,`base64`),format:`der`,type:`spki`}):e}function B(e,{token:t,publicKey:n,now:r=Date.now()}){if(typeof e!=`string`)return null;let[i,a,...o]=e.split(`.`);if(!i||!a||o.length>0)return null;try{if(!b(null,Buffer.from(i),z(n),Buffer.from(a,`base64url`)))return null;let e=JSON.parse(Buffer.from(i,`base64url`).toString(`utf8`)),o=r/1e3;return!Array.isArray(e.access)||e.sub!==R(t)||typeof e.exp!=`number`||e.exp<o||typeof e.iat!=`number`||e.iat>o+60?null:e}catch{return null}}async function V({registryUrl:e,token:t,fetch:n=globalThis.fetch,headers:r={}}){let i=`${e.replace(/\/+$/,``)}/whoami.json`,a=await n(i,{headers:{...r,authorization:`Bearer ${t.trim()}`},signal:AbortSignal.timeout(15e3)});if(a.status===401)return null;if(!a.ok)throw Error(`Error ${a.status} while fetching ${i}.`);let o=await a.json();return{...o,access:o.access??[]}}function H(){return y(24).toString(`base64url`)}function U({registryUrl:e,callback:t,state:n,client:r,host:i=S()}){let a=new URL(`/cli/authorize`,new URL(e).origin);return a.searchParams.set(`callback`,t),a.searchParams.set(`state`,n),a.searchParams.set(`client`,r),a.searchParams.set(`host`,i.replace(/\.local$/,``).slice(0,60)),a.toString()}const de=[`spacing`,`font-size`],fe=`/__slabui/theme`,W=/* @__PURE__ */ new Set([`localhost`,`127.0.0.1`,`[::1]`,`::1`]);function G({css:e,reference:t,root:r,themes:i=c(o(e),`themes`),account:s}){let u=/* @__PURE__ */ new Set,d,f,_,v,y=Promise.resolve(),b=()=>m(e,`utf8`),x=e=>c(i,`${e}.css`),S=t=>{let n=l(o(e),x(t)).split(ee).join(`/`);return n.startsWith(`.`)?n:`./${n}`},D=e=>{let t=y.then(e);return y=t.catch(()=>void 0),t},O=async()=>(await te(i).catch(()=>[])).filter(e=>e.endsWith(`.css`)).map(e=>e.slice(0,-4)).filter(w).sort(),k=async e=>{if(e!=null&&e!==``){if(typeof e!=`string`||!(await O()).includes(e))throw new K(404,`theme not found: ${String(e)}`);return e}},A=(e,t)=>{let n=`event: ${e}\ndata: ${JSON.stringify(t)}\n\n`;for(let e of u)e.write(n)},j=async()=>{A(`theme`,{tokens:T(await b().catch(()=>``))})},M=async()=>{A(`themes`,{themes:await O()})},N=()=>{if(!(f||u.size===0))try{f=n(i,(e,t)=>{(!t||t.endsWith(`.css`))&&(clearTimeout(v),v=setTimeout(()=>void M(),50))}),f.on(`error`,()=>{f?.close(),f=void 0})}catch{f=void 0}},P=()=>{if(N(),d)return;let t=a(e);d=n(o(e),(e,n)=>{n===t&&(clearTimeout(_),_=setTimeout(()=>void j(),50))})},F=()=>{clearTimeout(_),clearTimeout(v),d?.close(),f?.close(),d=void 0,f=void 0},I=(e,t)=>{t.writeHead(200,{"content-type":`text/event-stream`,"cache-control":`no-cache, no-transform`,connection:`keep-alive`}),t.write(`: connected
|
|
121
|
+
|
|
122
|
+
`),u.add(t),P(),e.on(`close`,()=>{u.delete(t),u.size===0&&F()})},L=async e=>{if(!s)return;let t=await s.get();if(t.status===`signed-out`)throw new K(401,`sign in to your SlabUI account to save`);if(e&&t.status!==`pro`)throw new K(403,`this needs SlabUI Pro`)},R=e=>de.some(t=>e.light?.[t]!==void 0||e.dark?.[t]!==void 0),z=(e,n)=>{let r=t??C;return Object.keys(e).some(t=>Object.keys(e[t]??{}).some(e=>n[t]?.[e]===void 0&&r[t]?.[e]===void 0))},B=async(n,r)=>{let i=await q(n);if(!_e(i))return Y(r,400,{error:`expected { tokens: { theme, light, dark } }`});let a=await k(i.theme),o=a?x(a):e;await L(!!a||R(i.tokens)||z(i.tokens,T(await m(o,`utf8`),{theme:a})));let s=D(async()=>{let e=await m(o,`utf8`),{css:n,changes:r}=await E(e,i.tokens,{reference:t,theme:a}),s=i.unset?oe(n,i.unset,{theme:a}):n;return s!==e&&await g(o,s),r});try{Y(r,200,{changes:await s})}catch(e){if(e instanceof re)return Y(r,422,{issues:e.issues});throw e}},V=e=>{if(typeof e!=`string`||!w(e))throw new K(400,`invalid theme name (use letters, numbers and -)`);return e},H=async(t,n)=>{await L(!0);let r=await q(t),a=V(J(r)?r.name:void 0),o=await k(J(r)?r.from:void 0);await D(async()=>{if((await O()).includes(a))throw new K(409,`theme already exists: ${a}`);let t=o?T(await m(x(o),`utf8`),{theme:o}):{},{css:n}=await E(le(a),t,{theme:a});await p(i,{recursive:!0}),N(),await g(x(a),n);let r=await b();await g(e,ie(r,S(a)))}),Y(n,201,{themes:await O()})},U=async(t,n,r)=>{let i=await k(t);if(!i)throw new K(404,`theme not found: ${t}`);await L(!0);let a=await q(n),o=V(J(a)?a.name:void 0);await D(async()=>{if(o!==i&&(await O()).includes(o))throw new K(409,`theme already exists: ${o}`);let t=await m(x(i),`utf8`);await ne(x(i),x(o)),await g(x(o),ce(t,i,o));let n=await b();await g(e,se(n,S(i),S(o)))}),Y(r,200,{themes:await O()})},W=async(t,n)=>{let r=await k(t);if(!r)throw new K(404,`theme not found: ${t}`);await L(!0),await D(async()=>{await h(x(r));let t=await b();await g(e,ae(t,S(r)))}),Y(n,200,{themes:await O()})},G=async(e,t,n,r)=>{if(e===`/account`&&t.method===`GET`)return Y(n,200,{...await r.get(),site:r.site});if(e===`/account/login`&&t.method===`POST`){let e=await q(t),i=J(e)?e.origin:void 0;if(typeof i!=`string`||!me(i))throw new K(400,`expected { origin } of a local page`);let a=await r.get({fresh:!0});if(a.status===`free`||a.status===`pro`){let e={...a,site:r.site};return A(`account`,e),Y(n,200,{account:e})}return Y(n,200,{url:r.login(`${i}${fe}/account/callback`)})}if(e===`/account/callback`&&t.method===`GET`){let e=new URL(t.url??`/`,`http://localhost`).searchParams,i=e.get(`token`),a=i!==null&&await r.complete(e.get(`state`)??``,i);a&&A(`account`,{...await r.get(),site:r.site}),n.writeHead(a?200:400,{"content-type":`text/html; charset=utf-8`}),n.end(ue(a?{status:`success`,title:`You're signed in`,message:`Go back to the theme editor. You can close this tab.`,close:!0}:e.get(`error`)?{status:`error`,title:`Sign-in cancelled`,message:`You can close this tab.`,close:!0}:{status:`error`,title:`This sign-in link has expired`,message:`Sign in again from the theme editor.`,close:!0}))}else{if(e===`/account/logout`&&t.method===`POST`){await r.logout();let e=await r.get();return A(`account`,{...e,site:r.site}),Y(n,200,{...e,site:r.site})}return Y(n,404,{error:`not found`})}};return{handle:async(n,i)=>{if(!pe(n))return Y(i,403,{error:`only local requests are allowed`});let a=new URL(n.url??`/`,`http://localhost`).pathname;try{if(a===`/events`&&n.method===`GET`)return I(n,i);if(a===`/account`||a.startsWith(`/account/`))return s?await G(a,n,i,s):Y(i,404,{error:`not found`});if(a===`/`&&n.method===`GET`){let e=new URL(n.url??`/`,`http://localhost`).searchParams,a=await k(e.get(`theme`)),o=T(await b());return Y(i,200,{tokens:a?T(await m(x(a),`utf8`),{theme:a}):o,...a&&{theme:a,base:o},defaults:t??C,installed:r?await ge(r):null,themes:await O()})}if(a===`/`&&n.method===`PATCH`)return await B(n,i);if(a===`/themes`&&n.method===`POST`)return await H(n,i);let e=a.match(/^\/themes\/([^/]+)$/)?.[1];if(e&&n.method===`PATCH`)return await U(decodeURIComponent(e),n,i);if(e&&n.method===`DELETE`)return await W(decodeURIComponent(e),i);Y(i,404,{error:`not found`})}catch(t){if(ve(t))return Y(i,404,{error:`CSS file not found: ${e}`});if(t instanceof K)return Y(i,t.status,{error:t.message});Y(i,500,{error:t instanceof Error?t.message:String(t)})}},close:()=>{F();for(let e of u)e.end();u.clear()}}}function pe(e){if(![e.headers[`x-forwarded-host`],e.headers.host].flat().filter(e=>typeof e==`string`).every(e=>W.has(he(e))))return!1;let t=e.headers.origin;if(t===void 0)return!0;try{return W.has(new URL(t).hostname)}catch{return!1}}function me(e){try{let t=new URL(e);return t.protocol===`http:`&&W.has(t.hostname)}catch{return!1}}function he(e){let t=e.split(`,`)[0]?.trim()??``;return t.startsWith(`[`)?t.slice(0,t.indexOf(`]`)+1):t.split(`:`)[0]??``}async function ge(e){try{let t=JSON.parse(await m(c(e,`slabui.json`),`utf8`));return Object.keys(t.items??{}).map(e=>e.replace(/^@[^/]+\//,``))}catch{return null}}var K=class extends Error{status;constructor(e,t){super(t),this.status=e}};async function q(e){let t=0,n=[];for await(let r of e){if(t+=r.length,t>1e5)throw new K(413,`body too large`);n.push(r)}try{return JSON.parse(Buffer.concat(n).toString(`utf8`))}catch{throw new K(400,`invalid JSON`)}}function _e(e){if(!J(e)||!J(e.tokens))return!1;let t=e.unset;return t!==void 0&&!(J(t)&&Object.entries(t).every(([e,t])=>[`light`,`dark`].includes(e)&&Array.isArray(t)&&t.every(e=>typeof e==`string`)))?!1:Object.entries(e.tokens).every(([e,t])=>[`theme`,`light`,`dark`].includes(e)&&J(t)&&Object.values(t).every(e=>typeof e==`string`))}function J(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ve(e){return e instanceof Error&&e.code===`ENOENT`}function Y(e,t,n){e.writeHead(t,{"content-type":`application/json`}),e.end(JSON.stringify(n))}const X=6e5;function ye({root:e,env:t=process.env,fetch:n=globalThis.fetch,file:r=P(t),registryUrl:i=`https://slabui.com/r`,publicKey:a=`MCowBQYDK2VwAyEACwMW1a+QMU+TLzvMDpFSulpcu3+K8WNHscFjRVNaNPY=`}={}){let o,s=0,c=/* @__PURE__ */ new Map,l=async()=>{let e=t.SLABUI_TOKEN||await F(r);if(!e)return{status:`signed-out`};try{let t=await V({registryUrl:i,token:e,fetch:n});if(t===null)return{status:`signed-out`};let{access:r,signature:o,...c}=t,l=r.includes(`pro`);if(a!==void 0){let t=B(o,{token:e,publicKey:a});l=t?.access.includes(`pro`)??!1,s=l&&t?t.exp*1e3:0}else s=l?1/0:0;return{status:l?`pro`:`free`,...c}}catch{let e=o?.state;return e&&e.status!==`signed-out`?{...e,status:e.status===`pro`&&Date.now()<s?`pro`:`offline`}:{status:`offline`}}};return{site:new URL(i).origin,async get({fresh:e=!1}={}){return(e||!o||o.expires<Date.now())&&(o={state:await l(),expires:Date.now()+3e5}),o.state},login(e){let t=Date.now();for(let[e,n]of c)t-n>X&&c.delete(e);let n=H();return c.set(n,t),U({registryUrl:i,callback:e,state:n,client:`SlabUI theme editor`})},async complete(e,t){let n=c.get(e);return n===void 0||Date.now()-n>X?!1:(c.delete(e),await I(t,r),o=void 0,!0)},async logout(){await L(r),o=void 0}}}function Z(t,n){if(!n.startsWith(`.`)&&!s(n))for(let r of[`tsconfig.json`,`jsconfig.json`]){let i=c(t,r);if(e(i)){try{let e=d({path:i,config:f(i)})?.(n)[0];if(e)return e}catch{}break}}return u(t,n)}function Q(e){try{return JSON.parse(t(c(e,`slabui.json`),`utf8`))}catch{return}}function $(e,t){if(t)return Z(e,t);let n=Q(e);if(typeof n?.css!=`string`||n.css===``)throw Error("@slabui/theme-react: set the `css` option or the `css` field in slabui.json.");return Z(e,n.css)}function be(e,t={}){if(t.dir)return Z(e,t.dir);let n=Q(e)?.aliases?.styles;return c(typeof n==`string`&&n!==``?Z(e,n):o($(e,t.css)),`themes`)}async function xe(e){let t=G(e),n=r((e,n)=>void t.handle(e,n));return await new Promise((t,r)=>{n.once(`error`,r),n.listen(e.port??0,`127.0.0.1`,t)}),n.unref(),{port:n.address().port,close:()=>(t.close(),new Promise(e=>n.close(()=>e())))}}export{fe as a,ye as i,be as n,G as o,xe as r,$ as t};
|
|
123
|
+
//# sourceMappingURL=server-DeZ9Emfp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-DeZ9Emfp.mjs","names":["hostname","path","callbackPage","icon","status","CHECK","CROSS","escapeHtml","title","STYLE","LOGO","message","close","value","char","ICON","PLATE","credentialsFile","homedir","dir","platform","env","home","readSavedToken","readFile","file","token","saveToken","mkdir","writeFile","removeToken","existed","rm","tokenHash","createHash","publicKeyOf","key","createPublicKey","verifyAccount","signed","payload","signature","rest","verify","publicKey","claims","seconds","now","fetchAccount","url","registryUrl","response","fetch","headers","account","newState","randomBytes","authorizeUrl","hostname","callback","state","client","host","PRO_TOKENS","THEME_PATH","LOCAL_HOSTS","createThemeHandler","join","dirname","css","clients","watcher","themesWatcher","timer","themesTimer","queue","read","themeFile","themes","name","importOf","relative","sep","enqueue","result","task","listThemes","readdir","isThemeName","themeOf","BodyError","emit","event","data","broadcast","readTheme","broadcastThemes","watchThemes","watch","filename","startWatching","basename","stopWatching","events","res","req","requireAccount","pro","proTokens","tokens","addsTokens","known","reference","defaultTheme","group","current","patch","body","readJson","isTokensBody","send","theme","written","writeTheme","next","removeTokens","changes","error","ThemeValidationError","newName","createTheme","isObject","from","content","themeFileHeader","globals","addThemeImport","renameTheme","rename","renameThemeSelectors","renameThemeImport","deleteTheme","removeThemeImport","accountRoutes","source","origin","isLocalOrigin","query","done","isLocalRequest","base","root","installedItems","themePath","isMissingFile","hostname$1","config","size","chunks","chunk","unset","names","vars","STATE_TTL_MS","createAccountSource","cached","proUntil","pending","resolve","\"SLABUI_TOKEN\"","profile","access","last","fresh","created","resolveProjectFile","isAbsolute","existsSync","match","createPathsMatcher","parseTsconfig","readConfig","readFileSync","resolveCssPath","resolveThemesDir","options","styles","startThemeServer","handler","server","createServer","fail"],"sources":["../../account/src/callback-page.ts","../../account/src/credentials.ts","../../account/src/signature.ts","../../account/src/index.ts","../src/server/handler.ts","../src/server/account.ts","../src/server/index.ts"],"sourcesContent":["// Página que o navegador mostra no fim do login (`slabui login` e \"Entrar\"\n// do editor de tema), com as cores do tema padrão da SlabUI. Tudo inline: o\n// servidor local não serve outros arquivos.\n\nexport type CallbackPageOptions = {\n status: \"success\" | \"error\"\n title: string\n message: string\n // Botão para fechar a aba: só funciona numa aba aberta por script (a do\n // editor). A aba aberta pelo CLI o navegador não deixa fechar.\n close?: boolean\n}\n\nexport function callbackPage({\n status,\n title,\n message,\n close = false,\n}: CallbackPageOptions) {\n const icon = status === \"success\" ? CHECK : CROSS\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"color-scheme\" content=\"light dark\">\n<title>${escapeHtml(title)} · SlabUI</title>\n<style>${STYLE}</style>\n</head>\n<body>\n<main>\n${LOGO}\n<section class=\"card\" data-status=\"${status}\">\n<span class=\"icon\">${icon}</span>\n<h1>${escapeHtml(title)}</h1>\n<p>${escapeHtml(message)}</p>\n${close ? '<button type=\"button\" onclick=\"window.close()\">Close tab</button>' : \"\"}\n</section>\n</main>\n</body>\n</html>`\n}\n\nfunction escapeHtml(value: string) {\n return value.replace(\n /[&<>\"']/g,\n (char) =>\n ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[\n char\n ] ?? char,\n )\n}\n\n// Cores do `default-theme` (theme-core), claro e escuro.\nconst STYLE = `\n:root {\n --background: oklch(0.985 0 0);\n --foreground: oklch(0.145 0 0);\n --card: oklch(1 0 0);\n --muted-foreground: oklch(0.556 0 0);\n --border: oklch(0.922 0 0);\n --primary: oklch(70% 0.1435 227.76);\n --primary-foreground: oklch(0.985 0 0);\n --success: oklch(0.728 0.125 139.6);\n --danger: oklch(0.577 0.245 27.325);\n --logo: #0b0c0e;\n --radius: 0.65rem;\n}\n@media (prefers-color-scheme: dark) {\n :root {\n --background: oklch(0.145 0 0);\n --foreground: oklch(0.985 0 0);\n --card: oklch(0.205 0 0);\n --muted-foreground: oklch(0.708 0 0);\n --border: oklch(1 0 0 / 10%);\n --primary: oklch(66% 0.1435 227.76);\n --danger: oklch(0.704 0.191 22.216);\n --logo: #f4f4f2;\n }\n}\n* { box-sizing: border-box; }\nbody {\n margin: 0;\n min-height: 100vh;\n display: grid;\n place-items: center;\n padding: 1.5rem 1rem;\n background: var(--background);\n color: var(--foreground);\n font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n -webkit-font-smoothing: antialiased;\n}\nmain {\n width: 100%;\n max-width: 24rem;\n display: grid;\n justify-items: center;\n gap: 1.5rem;\n}\n.logo { height: 1.75rem; width: auto; }\n.logo .ink { fill: var(--logo); }\n.card {\n width: 100%;\n display: grid;\n justify-items: center;\n gap: 0.5rem;\n padding: 2rem 1.5rem;\n text-align: center;\n background: var(--card);\n border: 1px solid var(--border);\n border-radius: calc(var(--radius) * 1.4);\n box-shadow: 0 1px 2px oklch(0 0 0 / 5%);\n}\n.icon {\n --tone: var(--success);\n display: grid;\n place-items: center;\n width: 3rem;\n height: 3rem;\n margin-bottom: 0.5rem;\n border-radius: 999px;\n color: var(--tone);\n background: color-mix(in oklch, var(--tone) 15%, transparent);\n}\n.card[data-status=\"error\"] .icon { --tone: var(--danger); }\n.icon svg { width: 1.5rem; height: 1.5rem; }\nh1 { margin: 0; font-size: 1.25rem; line-height: 1.4; font-weight: 600; }\np { margin: 0; color: var(--muted-foreground); font-size: 0.875rem; }\nbutton {\n margin-top: 1rem;\n height: 2.25rem;\n padding: 0 1rem;\n border: 0;\n border-radius: var(--radius);\n background: var(--primary);\n color: var(--primary-foreground);\n font: inherit;\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n}\nbutton:hover { opacity: 0.9; }\nbutton:focus-visible { outline: 2.5px solid var(--primary); outline-offset: 2px; }\n`\n\nconst ICON = (path: string) =>\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${path}</svg>`\nconst CHECK = ICON('<path d=\"M20 6 9 17l-5-5\"/>')\nconst CROSS = ICON('<path d=\"M18 6 6 18M6 6l12 12\"/>')\n\n// Logo da SlabUI (apps/www/components/logo.tsx).\nconst PLATE = 'x=\"-30\" y=\"-30\" width=\"60\" height=\"60\" rx=\"12\"'\nconst LOGO = `<svg class=\"logo\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 288 80\" fill=\"none\" role=\"img\" aria-label=\"SlabUI\">\n<defs>\n<linearGradient id=\"plate\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"#00c1cc\"/><stop offset=\"1\" stop-color=\"#9296ff\"/></linearGradient>\n<linearGradient id=\"ui\" gradientUnits=\"userSpaceOnUse\" x1=\"228.77\" y1=\"0\" x2=\"287.52\" y2=\"0\"><stop offset=\"0\" stop-color=\"#00c1cc\"/><stop offset=\"1\" stop-color=\"#9296ff\"/></linearGradient>\n</defs>\n<rect ${PLATE} transform=\"translate(40 52) scale(1 0.5299193) rotate(45)\" class=\"ink\" fill-opacity=\".25\"/>\n<rect ${PLATE} transform=\"translate(40 40) scale(1 0.5299193) rotate(45)\" class=\"ink\" fill-opacity=\".6\"/>\n<rect ${PLATE} transform=\"translate(40 28) scale(1 0.5299193) rotate(45)\" fill=\"url(#plate)\"/>\n<path class=\"ink\" d=\"M122.72 63.52L122.72 63.52Q116.83 63.52 112.61 61.54Q108.38 59.56 106.02 55.97Q103.65 52.39 103.33 47.52L103.33 47.52L113.12 47.08Q113.50 49.76 114.69 51.65Q115.87 53.54 117.89 54.50Q119.90 55.46 122.85 55.46L122.85 55.46Q125.28 55.46 126.98 54.88Q128.67 54.31 129.57 53.12Q130.46 51.94 130.46 50.21L130.46 50.21Q130.46 48.61 129.70 47.43Q128.93 46.24 126.75 45.25Q124.58 44.26 120.35 43.30L120.35 43.30Q114.53 41.96 110.98 40.29Q107.42 38.63 105.82 36.10Q104.22 33.57 104.22 29.73L104.22 29.73Q104.22 25.64 106.24 22.56Q108.26 19.49 112.10 17.76Q115.94 16.04 121.25 16.04L121.25 16.04Q126.88 16.04 130.78 18.02Q134.69 20.00 136.90 23.46Q139.10 26.92 139.55 31.40L139.55 31.40L129.82 31.91Q129.63 29.54 128.54 27.78Q127.46 26.02 125.60 25.06Q123.74 24.10 121.12 24.10L121.12 24.10Q117.79 24.10 115.90 25.57Q114.02 27.04 114.02 29.41L114.02 29.41Q114.02 31.08 114.88 32.20Q115.74 33.32 117.76 34.15Q119.78 34.98 123.49 35.75L123.49 35.75Q129.82 37.03 133.50 38.98Q137.18 40.93 138.72 43.62Q140.26 46.31 140.26 49.76L140.26 49.76Q140.26 53.99 138.11 57.09Q135.97 60.20 132.03 61.86Q128.10 63.52 122.72 63.52ZM159.26 62.50L153.57 62.50Q149.34 62.50 146.98 60.39Q144.61 58.28 144.61 53.54L144.61 53.54L144.61 17.06L154.21 17.06L154.21 52.52Q154.21 54.05 154.91 54.69Q155.62 55.33 156.96 55.33L156.96 55.33L159.26 55.33L159.26 62.50ZM171.49 63.27L171.49 63.27Q166.18 63.27 162.82 60.80Q159.46 58.34 159.46 53.92L159.46 53.92Q159.46 49.38 162.30 46.92Q165.15 44.45 170.72 43.30L170.72 43.30L181.79 41.12Q181.79 37.80 180.29 36.10Q178.78 34.40 175.97 34.40L175.97 34.40Q173.22 34.40 171.68 35.65Q170.14 36.90 169.70 39.33L169.70 39.33L160.03 38.88Q160.99 33.25 165.12 30.34Q169.25 27.43 175.97 27.43L175.97 27.43Q183.65 27.43 187.52 31.08Q191.39 34.72 191.39 41.76L191.39 41.76L191.39 53.48Q191.39 54.95 191.87 55.43Q192.35 55.91 193.31 55.91L193.31 55.91L194.27 55.91L194.27 62.50Q193.82 62.63 192.83 62.76Q191.84 62.88 190.88 62.88L190.88 62.88Q188.83 62.88 187.07 62.18Q185.31 61.48 184.22 59.75L184.22 59.75Q183.58 58.79 183.33 57.32L183.33 57.32Q182.69 58.53 181.73 59.62L181.73 59.62Q180.06 61.35 177.47 62.31Q174.88 63.27 171.49 63.27ZM173.79 56.68L173.79 56.68Q176.16 56.68 177.95 55.75Q179.74 54.82 180.77 53.03Q181.79 51.24 181.79 48.80L181.79 48.80L181.79 47.14L173.98 48.80Q171.68 49.25 170.50 50.31Q169.31 51.36 169.31 53.03L169.31 53.03Q169.31 54.76 170.46 55.72Q171.62 56.68 173.79 56.68ZM215.33 63.27L215.33 63.27Q211.74 63.27 209.09 61.73Q206.43 60.20 204.96 57.44L204.96 57.44L204.77 62.50L195.62 62.50L195.62 17.06L205.22 17.06L205.22 33.06Q206.56 30.69 209.18 29.06Q211.81 27.43 215.33 27.43L215.33 27.43Q219.68 27.43 222.88 29.64Q226.08 31.84 227.84 35.88Q229.60 39.91 229.60 45.35L229.60 45.35Q229.60 50.79 227.84 54.82Q226.08 58.85 222.88 61.06Q219.68 63.27 215.33 63.27ZM212.64 55.91L212.64 55.91Q215.84 55.91 217.79 53.12Q219.74 50.34 219.74 45.35L219.74 45.35Q219.74 40.36 217.82 37.57Q215.90 34.79 212.70 34.79L212.70 34.79Q210.34 34.79 208.64 36.04Q206.94 37.28 206.08 39.65Q205.22 42.02 205.22 45.35L205.22 45.35Q205.22 48.61 206.08 50.98Q206.94 53.35 208.64 54.63Q210.34 55.91 212.64 55.91Z\"/>\n<path fill=\"url(#ui)\" d=\"M251.30 63.52L251.30 63.52Q245.54 63.52 241.34 61.41Q237.15 59.30 234.85 55.36Q232.54 51.43 232.54 46.12L232.54 46.12L232.54 17.06L242.27 17.06L242.27 46.12Q242.27 50.53 244.61 52.93Q246.94 55.33 251.30 55.33L251.30 55.33Q255.65 55.33 257.95 52.93Q260.26 50.53 260.26 46.12L260.26 46.12L260.26 17.06L269.98 17.06L269.98 46.12Q269.98 51.43 267.71 55.36Q265.44 59.30 261.22 61.41Q256.99 63.52 251.30 63.52ZM285.98 62.50L276.26 62.50L276.26 17.06L285.98 17.06L285.98 62.50Z\"/>\n</svg>`\n","import { mkdir, readFile, rm, writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport path from \"node:path\"\n\n// Token da conta SlabUI gravado pelo `slabui login` ou pelo \"Entrar\" do\n// editor de tema, fora do projeto para não ir parar no git. O CLI e o editor\n// usam o mesmo arquivo.\n\ntype Env = Record<string, string | undefined>\n\nexport function credentialsFile(\n env: Env = process.env,\n platform: NodeJS.Platform = process.platform,\n home = homedir(),\n) {\n const dir =\n platform === \"win32\"\n ? path.join(env.APPDATA ?? path.join(home, \"AppData/Roaming\"), \"slabui\")\n : path.join(env.XDG_CONFIG_HOME || path.join(home, \".config\"), \"slabui\")\n return path.join(dir, \"credentials.json\")\n}\n\nexport async function readSavedToken(\n file = credentialsFile(),\n): Promise<string | undefined> {\n try {\n const { token } = JSON.parse(await readFile(file, \"utf8\")) as {\n token?: unknown\n }\n return typeof token === \"string\" && token ? token : undefined\n } catch {\n return undefined\n }\n}\n\nexport async function saveToken(token: string, file = credentialsFile()) {\n await mkdir(path.dirname(file), { recursive: true })\n await writeFile(file, `${JSON.stringify({ token })}\\n`, { mode: 0o600 })\n}\n\n// `false` quando não havia token gravado.\nexport async function removeToken(file = credentialsFile()): Promise<boolean> {\n const existed = (await readSavedToken(file)) !== undefined\n await rm(file, { force: true })\n return existed\n}\n","import {\n createHash,\n createPrivateKey,\n createPublicKey,\n type KeyObject,\n sign,\n verify,\n} from \"node:crypto\"\n\n// Assinatura do `whoami.json`: o site assina com Ed25519 o que dá acesso\n// (`access`), o hash do token e o prazo, e o editor confere com a chave\n// pública. Uma resposta inventada, ou a de outro token, não vale.\n\n// Chave pública (SPKI, base64) do slabui.com. A privada fica só no site, em\n// SLABUI_ACCOUNT_SIGNING_KEY.\nexport const ACCOUNT_PUBLIC_KEY =\n \"MCowBQYDK2VwAyEACwMW1a+QMU+TLzvMDpFSulpcu3+K8WNHscFjRVNaNPY=\"\n\n// Validade de uma assinatura: o editor consulta a cada 5 minutos, e sem rede\n// mantém o plano só até ela vencer.\nexport const SIGNATURE_TTL_S = 60 * 60\n\nexport type AccountClaims = {\n access: string[]\n // sha256 do token, em hex.\n sub: string\n // Segundos desde 1970.\n iat: number\n exp: number\n}\n\nexport function tokenHash(token: string): string {\n return createHash(\"sha256\").update(token.trim()).digest(\"hex\")\n}\n\nfunction privateKeyOf(key: string | KeyObject) {\n return typeof key === \"string\"\n ? createPrivateKey({\n key: Buffer.from(key, \"base64\"),\n format: \"der\",\n type: \"pkcs8\",\n })\n : key\n}\n\nfunction publicKeyOf(key: string | KeyObject) {\n return typeof key === \"string\"\n ? createPublicKey({\n key: Buffer.from(key, \"base64\"),\n format: \"der\",\n type: \"spki\",\n })\n : key\n}\n\n// `<claims em base64url>.<assinatura em base64url>`.\nexport function signAccount({\n access,\n token,\n privateKey,\n now = Date.now(),\n}: {\n access: string[]\n token: string\n privateKey: string | KeyObject\n now?: number\n}): string {\n const iat = Math.floor(now / 1000)\n const claims: AccountClaims = {\n access,\n sub: tokenHash(token),\n iat,\n exp: iat + SIGNATURE_TTL_S,\n }\n const payload = Buffer.from(JSON.stringify(claims)).toString(\"base64url\")\n const signature = sign(null, Buffer.from(payload), privateKeyOf(privateKey))\n return `${payload}.${signature.toString(\"base64url\")}`\n}\n\n// As claims de uma assinatura válida, do token e no prazo; senão `null`.\nexport function verifyAccount(\n signed: unknown,\n {\n token,\n publicKey,\n now = Date.now(),\n }: { token: string; publicKey: string | KeyObject; now?: number },\n): AccountClaims | null {\n if (typeof signed !== \"string\") {\n return null\n }\n const [payload, signature, ...rest] = signed.split(\".\")\n if (!payload || !signature || rest.length > 0) {\n return null\n }\n try {\n const valid = verify(\n null,\n Buffer.from(payload),\n publicKeyOf(publicKey),\n Buffer.from(signature, \"base64url\"),\n )\n if (!valid) {\n return null\n }\n const claims = JSON.parse(\n Buffer.from(payload, \"base64url\").toString(\"utf8\"),\n ) as AccountClaims\n const seconds = now / 1000\n if (\n !Array.isArray(claims.access) ||\n claims.sub !== tokenHash(token) ||\n typeof claims.exp !== \"number\" ||\n claims.exp < seconds ||\n typeof claims.iat !== \"number\" ||\n claims.iat > seconds + 60\n ) {\n return null\n }\n return claims\n } catch {\n return null\n }\n}\n","import { randomBytes } from \"node:crypto\"\nimport { readFileSync } from \"node:fs\"\nimport { hostname } from \"node:os\"\nimport path from \"node:path\"\n\nexport { type CallbackPageOptions, callbackPage } from \"./callback-page\"\nexport {\n credentialsFile,\n readSavedToken,\n removeToken,\n saveToken,\n} from \"./credentials\"\nexport {\n ACCOUNT_PUBLIC_KEY,\n type AccountClaims,\n SIGNATURE_TTL_S,\n signAccount,\n tokenHash,\n verifyAccount,\n} from \"./signature\"\n\n// Conta SlabUI do CLI e do editor de tema (só Node). O token identifica a\n// conta no registry padrão; o `whoami.json` diz o plano.\n\nexport const DEFAULT_REGISTRY_URL = \"https://slabui.com/r\"\n\n// Variável com o token, antes do arquivo de credenciais.\nexport const TOKEN_ENV = \"SLABUI_TOKEN\"\n\n// Rota do registry que diz de quem é o token.\nexport const WHOAMI_PATH = \"whoami.json\"\n\nexport type Account = {\n name?: string\n image?: string\n // Nome do plano (\"Free\", \"Pro Yearly\"…).\n plan?: string\n // `[\"pro\"]` com assinatura ativa; `[]` no plano free.\n access: string[]\n // `access` assinado pelo site (ver `verifyAccount`).\n signature?: string\n}\n\n// URL do registry padrão: a do `slabui.json` da raiz (`registries[\"@slabui\"]`,\n// para desenvolver contra um registry local) ou a de produção.\nexport function defaultRegistryUrl(root?: string): string {\n if (root === undefined) {\n return DEFAULT_REGISTRY_URL\n }\n try {\n const config = JSON.parse(\n readFileSync(path.join(root, \"slabui.json\"), \"utf8\"),\n ) as { registries?: Record<string, unknown> }\n const entry = config.registries?.[\"@slabui\"]\n const url =\n typeof entry === \"string\"\n ? entry\n : (entry as { url?: unknown } | undefined)?.url\n return typeof url === \"string\" && url ? url : DEFAULT_REGISTRY_URL\n } catch {\n return DEFAULT_REGISTRY_URL\n }\n}\n\n// Confere o token no `whoami.json`: a conta, ou `null` se o token não vale.\n// Erros de rede sobem para quem chamou decidir.\nexport async function fetchAccount({\n registryUrl,\n token,\n fetch = globalThis.fetch,\n headers = {},\n}: {\n registryUrl: string\n token: string\n fetch?: typeof globalThis.fetch\n headers?: Record<string, string>\n}): Promise<Account | null> {\n const url = `${registryUrl.replace(/\\/+$/, \"\")}/${WHOAMI_PATH}`\n const response = await fetch(url, {\n headers: { ...headers, authorization: `Bearer ${token.trim()}` },\n signal: AbortSignal.timeout(15_000),\n })\n if (response.status === 401) {\n return null\n }\n if (!response.ok) {\n throw new Error(`Error ${response.status} while fetching ${url}.`)\n }\n const account = (await response.json()) as Account\n return { ...account, access: account.access ?? [] }\n}\n\n// Valor aleatório que volta no callback, para só aceitar o login pedido aqui.\nexport function newState() {\n return randomBytes(24).toString(\"base64url\")\n}\n\n// Página do site (na origem do registry) em que a pessoa entra e autoriza;\n// o token volta para o `callback` local com o `state`.\nexport function authorizeUrl({\n registryUrl,\n callback,\n state,\n client,\n host = hostname(),\n}: {\n registryUrl: string\n callback: string\n state: string\n client: string\n host?: string\n}) {\n const url = new URL(\"/cli/authorize\", new URL(registryUrl).origin)\n url.searchParams.set(\"callback\", callback)\n url.searchParams.set(\"state\", state)\n url.searchParams.set(\"client\", client)\n url.searchParams.set(\"host\", host.replace(/\\.local$/, \"\").slice(0, 60))\n return url.toString()\n}\n","import { type FSWatcher, watch } from \"node:fs\"\nimport {\n mkdir,\n readdir,\n readFile,\n rename,\n rm,\n writeFile,\n} from \"node:fs/promises\"\nimport { basename, dirname, join, relative, sep } from \"node:path\"\n\nimport { callbackPage } from \"@slabui/account\"\nimport {\n defaultTheme,\n isThemeName,\n readTheme,\n ThemeValidationError,\n writeTheme,\n} from \"@slabui/theme-core\"\n\nimport {\n addThemeImport,\n removeThemeImport,\n removeTokens,\n renameThemeImport,\n renameThemeSelectors,\n themeFileHeader,\n type UnsetTokens,\n} from \"@slabui/theme-core/files\"\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\"\nimport type { ThemeTokens } from \"@slabui/theme-core\"\nimport type { AccountSource } from \"./account\"\n\n// Tokens que só a conta Pro grava (a seção Size do editor).\nconst PRO_TOKENS = [\"spacing\", \"font-size\"]\n\n// Caminho em que os plugins expõem o servidor, na origem do app.\nexport const THEME_PATH = \"/__slabui/theme\"\n\nconst MAX_BODY = 100_000\nconst LOCAL_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"])\n\nexport type ThemeHandlerOptions = {\n // Caminho absoluto do CSS global.\n css: string\n // Tema que define o tipo de cada token na validação.\n reference?: ThemeTokens\n // Raiz do projeto: os itens instalados vêm do `slabui.json` dela.\n root?: string\n // Pasta dos temas (`<nome>.css`). Padrão: `themes/` ao lado do CSS global.\n themes?: string\n // Conta SlabUI (os plugins passam `createAccountSource`). Com ela, salvar\n // exige login e vários temas e cores novas exigem Pro; sem ela, nada é\n // bloqueado e `/account` responde 404.\n account?: AccountSource\n}\n\nexport type ThemeHandler = {\n handle: (req: IncomingMessage, res: ServerResponse) => Promise<void>\n close: () => void\n}\n\n// Lê e grava o tema do CSS global por HTTP, com os caminhos relativos ao\n// ponto de montagem:\n// - `GET /`: `{ tokens, defaults, installed, themes }` (o tema do arquivo, o de\n// referência, os itens do `slabui.json` e os temas da pasta de temas)\n// - `GET /?theme=<nome>`: o mesmo, com `tokens` do arquivo do tema e `base`\n// (o CSS global, sobre o qual o tema é aplicado)\n// - `PATCH /` com `{ tokens, theme?, unset? }`: `{ changes }`, ou 422 com\n// `{ issues }` (`unset` tira variáveis do arquivo: num tema, voltam da base)\n// - `POST /themes` com `{ name, from? }`: cria o tema (vazio ou cópia de\n// `from`) e o `@import` no CSS global\n// - `PATCH /themes/<nome>` com `{ name }`: renomeia o arquivo, os seletores e\n// o `@import`\n// - `DELETE /themes/<nome>`: apaga o arquivo e o `@import`\n// - `GET /account`: a conta (`{ status, name?, image?, plan? }`)\n// - `POST /account/login` com `{ origin }`: `{ account }` se o token desta\n// máquina ainda vale; senão `{ url }` da autorização no site, que volta em\n// `GET /account/callback?state&token`\n// - `POST /account/logout`: apaga o token gravado\n// - `GET /events`: SSE com `event: theme` (`{ tokens }`) a cada mudança no\n// CSS global e `event: themes` (`{ themes }`) a cada mudança na pasta de\n// temas, e `event: account` a cada login ou logout\nexport function createThemeHandler({\n css,\n reference,\n root,\n themes = join(dirname(css), \"themes\"),\n account,\n}: ThemeHandlerOptions): ThemeHandler {\n const clients = new Set<ServerResponse>()\n let watcher: FSWatcher | undefined\n let themesWatcher: FSWatcher | undefined\n let timer: NodeJS.Timeout | undefined\n let themesTimer: NodeJS.Timeout | undefined\n // Uma gravação de cada vez, para duas mudanças seguidas não se atropelarem.\n let queue: Promise<unknown> = Promise.resolve()\n\n const read = () => readFile(css, \"utf8\")\n const themeFile = (name: string) => join(themes, `${name}.css`)\n // `@import` do tema no CSS global: caminho relativo a ele.\n const importOf = (name: string) => {\n const path = relative(dirname(css), themeFile(name)).split(sep).join(\"/\")\n return path.startsWith(\".\") ? path : `./${path}`\n }\n\n // Uma alteração de arquivos de cada vez (gravar, criar, renomear, apagar).\n const enqueue = <T>(task: () => Promise<T>): Promise<T> => {\n const result = queue.then(task)\n // Uma tarefa que falha não trava as próximas.\n queue = result.catch(() => undefined)\n return result\n }\n\n // Temas da pasta: os `<nome>.css` com nome válido, em ordem.\n const listThemes = async () =>\n (await readdir(themes).catch(() => [] as string[]))\n .filter((file) => file.endsWith(\".css\"))\n .map((file) => file.slice(0, -\".css\".length))\n .filter(isThemeName)\n .sort()\n\n // Tema pedido (`?theme=` ou `theme` no corpo): precisa existir na pasta.\n const themeOf = async (name: unknown) => {\n if (name === undefined || name === null || name === \"\") {\n return undefined\n }\n if (typeof name !== \"string\" || !(await listThemes()).includes(name)) {\n throw new BodyError(404, `theme not found: ${String(name)}`)\n }\n return name\n }\n\n const emit = (event: string, data: unknown) => {\n const message = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`\n for (const client of clients) {\n client.write(message)\n }\n }\n\n const broadcast = async () => {\n emit(\"theme\", { tokens: readTheme(await read().catch(() => \"\")) })\n }\n\n const broadcastThemes = async () => {\n emit(\"themes\", { themes: await listThemes() })\n }\n\n // A pasta de temas pode não existir ainda: volta a tentar ao criar um tema.\n const watchThemes = () => {\n if (themesWatcher || clients.size === 0) {\n return\n }\n try {\n themesWatcher = watch(themes, (_, filename) => {\n if (filename && !filename.endsWith(\".css\")) {\n return\n }\n clearTimeout(themesTimer)\n themesTimer = setTimeout(() => void broadcastThemes(), 50)\n })\n // Pasta apagada com o servidor de pé: para de observar.\n themesWatcher.on(\"error\", () => {\n themesWatcher?.close()\n themesWatcher = undefined\n })\n } catch {\n themesWatcher = undefined\n }\n }\n\n // Observa a pasta, e não o arquivo: editores que gravam trocando o arquivo\n // derrubariam um watch direto nele.\n const startWatching = () => {\n watchThemes()\n if (watcher) {\n return\n }\n const name = basename(css)\n watcher = watch(dirname(css), (_, filename) => {\n if (filename !== name) {\n return\n }\n clearTimeout(timer)\n timer = setTimeout(() => void broadcast(), 50)\n })\n }\n\n const stopWatching = () => {\n clearTimeout(timer)\n clearTimeout(themesTimer)\n watcher?.close()\n themesWatcher?.close()\n watcher = undefined\n themesWatcher = undefined\n }\n\n const events = (req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n // `no-transform`: sem isso, a compressão do Next segura os eventos.\n \"cache-control\": \"no-cache, no-transform\",\n connection: \"keep-alive\",\n })\n res.write(\": connected\\n\\n\")\n clients.add(res)\n startWatching()\n req.on(\"close\", () => {\n clients.delete(res)\n if (clients.size === 0) {\n stopWatching()\n }\n })\n }\n\n // Salvar exige login; `pro` exige a assinatura. Também conferido no\n // editor, que mostra o motivo antes de a pessoa tentar.\n const requireAccount = async (pro: boolean) => {\n if (!account) {\n return\n }\n const state = await account.get()\n if (state.status === \"signed-out\") {\n throw new BodyError(401, \"sign in to your SlabUI account to save\")\n }\n if (pro && state.status !== \"pro\") {\n throw new BodyError(403, \"this needs SlabUI Pro\")\n }\n }\n\n // Tokens da seção Size do editor (espaçamento e tamanho do texto), recurso\n // Pro como as cores novas e os temas. A mesma lista do `pro` da seção em\n // `editor/controls.ts`.\n const proTokens = (tokens: ThemeTokens) =>\n PRO_TOKENS.some(\n (name) =>\n tokens.light?.[name] !== undefined || tokens.dark?.[name] !== undefined,\n )\n\n // Variáveis que ainda não existem no arquivo nem no tema de referência:\n // uma cor nova (recurso Pro).\n const addsTokens = (tokens: ThemeTokens, current: ThemeTokens) => {\n const known: ThemeTokens = reference ?? defaultTheme\n return (Object.keys(tokens) as (keyof ThemeTokens)[]).some((group) =>\n Object.keys(tokens[group] ?? {}).some(\n (name) =>\n current[group]?.[name] === undefined &&\n known[group]?.[name] === undefined,\n ),\n )\n }\n\n const patch = async (req: IncomingMessage, res: ServerResponse) => {\n const body = await readJson(req)\n if (!isTokensBody(body)) {\n return send(res, 400, {\n error: \"expected { tokens: { theme, light, dark } }\",\n })\n }\n const theme = await themeOf(body.theme)\n const file = theme ? themeFile(theme) : css\n await requireAccount(\n Boolean(theme) ||\n proTokens(body.tokens) ||\n addsTokens(\n body.tokens,\n readTheme(await readFile(file, \"utf8\"), { theme }),\n ),\n )\n const result = enqueue(async () => {\n const current = await readFile(file, \"utf8\")\n const { css: written, changes } = await writeTheme(current, body.tokens, {\n reference,\n theme,\n })\n const next = body.unset\n ? removeTokens(written, body.unset, { theme })\n : written\n if (next !== current) {\n await writeFile(file, next)\n }\n return changes\n })\n try {\n send(res, 200, { changes: await result })\n } catch (error) {\n if (error instanceof ThemeValidationError) {\n return send(res, 422, { issues: error.issues })\n }\n throw error\n }\n }\n\n const newName = (name: unknown) => {\n if (typeof name !== \"string\" || !isThemeName(name)) {\n throw new BodyError(\n 400,\n \"invalid theme name (use letters, numbers and -)\",\n )\n }\n return name\n }\n\n const createTheme = async (req: IncomingMessage, res: ServerResponse) => {\n await requireAccount(true)\n const body = await readJson(req)\n const name = newName(isObject(body) ? body.name : undefined)\n const from = await themeOf(isObject(body) ? body.from : undefined)\n await enqueue(async () => {\n if ((await listThemes()).includes(name)) {\n throw new BodyError(409, `theme already exists: ${name}`)\n }\n const tokens = from\n ? readTheme(await readFile(themeFile(from), \"utf8\"), { theme: from })\n : {}\n const { css: content } = await writeTheme(themeFileHeader(name), tokens, {\n theme: name,\n })\n await mkdir(themes, { recursive: true })\n watchThemes()\n await writeFile(themeFile(name), content)\n const globals = await read()\n await writeFile(css, addThemeImport(globals, importOf(name)))\n })\n send(res, 201, { themes: await listThemes() })\n }\n\n const renameTheme = async (\n from: string,\n req: IncomingMessage,\n res: ServerResponse,\n ) => {\n const current = await themeOf(from)\n if (!current) {\n throw new BodyError(404, `theme not found: ${from}`)\n }\n await requireAccount(true)\n const body = await readJson(req)\n const name = newName(isObject(body) ? body.name : undefined)\n await enqueue(async () => {\n if (name !== current && (await listThemes()).includes(name)) {\n throw new BodyError(409, `theme already exists: ${name}`)\n }\n const content = await readFile(themeFile(current), \"utf8\")\n await rename(themeFile(current), themeFile(name))\n await writeFile(\n themeFile(name),\n renameThemeSelectors(content, current, name),\n )\n const globals = await read()\n await writeFile(\n css,\n renameThemeImport(globals, importOf(current), importOf(name)),\n )\n })\n send(res, 200, { themes: await listThemes() })\n }\n\n const deleteTheme = async (name: string, res: ServerResponse) => {\n const current = await themeOf(name)\n if (!current) {\n throw new BodyError(404, `theme not found: ${name}`)\n }\n await requireAccount(true)\n await enqueue(async () => {\n await rm(themeFile(current))\n const globals = await read()\n await writeFile(css, removeThemeImport(globals, importOf(current)))\n })\n send(res, 200, { themes: await listThemes() })\n }\n\n const accountRoutes = async (\n path: string,\n req: IncomingMessage,\n res: ServerResponse,\n source: AccountSource,\n ) => {\n if (path === \"/account\" && req.method === \"GET\") {\n return send(res, 200, { ...(await source.get()), site: source.site })\n }\n if (path === \"/account/login\" && req.method === \"POST\") {\n const body = await readJson(req)\n const origin = isObject(body) ? body.origin : undefined\n if (typeof origin !== \"string\" || !isLocalOrigin(origin)) {\n throw new BodyError(400, \"expected { origin } of a local page\")\n }\n // O token desta máquina ainda vale: entra com ele, sem pedir outro ao\n // site (um token novo revogaria este).\n const current = await source.get({ fresh: true })\n if (current.status === \"free\" || current.status === \"pro\") {\n const state = { ...current, site: source.site }\n emit(\"account\", state)\n return send(res, 200, { account: state })\n }\n return send(res, 200, {\n url: source.login(`${origin}${THEME_PATH}/account/callback`),\n })\n }\n if (path === \"/account/callback\" && req.method === \"GET\") {\n const query = new URL(req.url ?? \"/\", \"http://localhost\").searchParams\n const token = query.get(\"token\")\n const done =\n token !== null &&\n (await source.complete(query.get(\"state\") ?? \"\", token))\n if (done) {\n emit(\"account\", { ...(await source.get()), site: source.site })\n }\n res.writeHead(done ? 200 : 400, {\n \"content-type\": \"text/html; charset=utf-8\",\n })\n res.end(\n callbackPage(\n done\n ? {\n status: \"success\",\n title: \"You're signed in\",\n message: \"Go back to the theme editor. You can close this tab.\",\n close: true,\n }\n : query.get(\"error\")\n ? {\n status: \"error\",\n title: \"Sign-in cancelled\",\n message: \"You can close this tab.\",\n close: true,\n }\n : {\n status: \"error\",\n title: \"This sign-in link has expired\",\n message: \"Sign in again from the theme editor.\",\n close: true,\n },\n ),\n )\n return\n }\n if (path === \"/account/logout\" && req.method === \"POST\") {\n await source.logout()\n const state = await source.get()\n emit(\"account\", { ...state, site: source.site })\n return send(res, 200, { ...state, site: source.site })\n }\n return send(res, 404, { error: \"not found\" })\n }\n\n const handle = async (req: IncomingMessage, res: ServerResponse) => {\n if (!isLocalRequest(req)) {\n return send(res, 403, { error: \"only local requests are allowed\" })\n }\n const path = new URL(req.url ?? \"/\", \"http://localhost\").pathname\n try {\n if (path === \"/events\" && req.method === \"GET\") {\n return events(req, res)\n }\n if (path === \"/account\" || path.startsWith(\"/account/\")) {\n return account\n ? await accountRoutes(path, req, res, account)\n : send(res, 404, { error: \"not found\" })\n }\n if (path === \"/\" && req.method === \"GET\") {\n const query = new URL(req.url ?? \"/\", \"http://localhost\").searchParams\n const theme = await themeOf(query.get(\"theme\"))\n const base = readTheme(await read())\n return send(res, 200, {\n tokens: theme\n ? readTheme(await readFile(themeFile(theme), \"utf8\"), { theme })\n : base,\n ...(theme && { theme, base }),\n defaults: reference ?? defaultTheme,\n installed: root ? await installedItems(root) : null,\n themes: await listThemes(),\n })\n }\n if (path === \"/\" && req.method === \"PATCH\") {\n return await patch(req, res)\n }\n if (path === \"/themes\" && req.method === \"POST\") {\n return await createTheme(req, res)\n }\n const themePath = path.match(/^\\/themes\\/([^/]+)$/)?.[1]\n if (themePath && req.method === \"PATCH\") {\n return await renameTheme(decodeURIComponent(themePath), req, res)\n }\n if (themePath && req.method === \"DELETE\") {\n return await deleteTheme(decodeURIComponent(themePath), res)\n }\n send(res, 404, { error: \"not found\" })\n } catch (error) {\n if (isMissingFile(error)) {\n return send(res, 404, { error: `CSS file not found: ${css}` })\n }\n if (error instanceof BodyError) {\n return send(res, error.status, { error: error.message })\n }\n send(res, 500, {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n const close = () => {\n stopWatching()\n for (const client of clients) {\n client.end()\n }\n clients.clear()\n }\n\n return { handle, close }\n}\n\n// Só aceita pedidos para um host local, vindos de uma página local. Impede que\n// um site externo use o navegador (ou um DNS apontando para 127.0.0.1) para\n// gravar no arquivo.\nfunction isLocalRequest(req: IncomingMessage) {\n const hosts = [req.headers[\"x-forwarded-host\"], req.headers.host]\n .flat()\n .filter((host): host is string => typeof host === \"string\")\n if (!hosts.every((host) => LOCAL_HOSTS.has(hostname(host)))) {\n return false\n }\n const origin = req.headers.origin\n if (origin === undefined) {\n return true\n }\n try {\n return LOCAL_HOSTS.has(new URL(origin).hostname)\n } catch {\n return false\n }\n}\n\nfunction isLocalOrigin(origin: string) {\n try {\n const url = new URL(origin)\n return url.protocol === \"http:\" && LOCAL_HOSTS.has(url.hostname)\n } catch {\n return false\n }\n}\n\nfunction hostname(host: string) {\n const value = host.split(\",\")[0]?.trim() ?? \"\"\n return value.startsWith(\"[\")\n ? value.slice(0, value.indexOf(\"]\") + 1)\n : (value.split(\":\")[0] ?? \"\")\n}\n\n// Nomes dos itens do `slabui.json` (sem o namespace), ou `null` sem o arquivo.\nasync function installedItems(root: string): Promise<string[] | null> {\n try {\n const config = JSON.parse(\n await readFile(join(root, \"slabui.json\"), \"utf8\"),\n ) as { items?: Record<string, unknown> }\n return Object.keys(config.items ?? {}).map((name) =>\n name.replace(/^@[^/]+\\//, \"\"),\n )\n } catch {\n return null\n }\n}\n\nclass BodyError extends Error {\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message)\n }\n}\n\nasync function readJson(req: IncomingMessage): Promise<unknown> {\n let size = 0\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n size += chunk.length\n if (size > MAX_BODY) {\n throw new BodyError(413, \"body too large\")\n }\n chunks.push(chunk)\n }\n try {\n return JSON.parse(Buffer.concat(chunks).toString(\"utf8\"))\n } catch {\n throw new BodyError(400, \"invalid JSON\")\n }\n}\n\nfunction isTokensBody(\n body: unknown,\n): body is { tokens: ThemeTokens; theme?: unknown; unset?: UnsetTokens } {\n if (!isObject(body) || !isObject(body.tokens)) {\n return false\n }\n const unset = body.unset\n if (\n unset !== undefined &&\n !(\n isObject(unset) &&\n Object.entries(unset).every(\n ([group, names]) =>\n [\"light\", \"dark\"].includes(group) &&\n Array.isArray(names) &&\n names.every((name) => typeof name === \"string\"),\n )\n )\n ) {\n return false\n }\n return Object.entries(body.tokens).every(\n ([group, vars]) =>\n [\"theme\", \"light\", \"dark\"].includes(group) &&\n isObject(vars) &&\n Object.values(vars).every((value) => typeof value === \"string\"),\n )\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction isMissingFile(error: unknown) {\n return (\n error instanceof Error && (error as NodeJS.ErrnoException).code === \"ENOENT\"\n )\n}\n\nfunction send(res: ServerResponse, status: number, body: unknown) {\n res.writeHead(status, { \"content-type\": \"application/json\" })\n res.end(JSON.stringify(body))\n}\n","import {\n ACCOUNT_PUBLIC_KEY,\n type Account,\n authorizeUrl,\n credentialsFile,\n DEFAULT_REGISTRY_URL,\n defaultRegistryUrl,\n fetchAccount,\n newState,\n readSavedToken,\n removeToken,\n saveToken,\n TOKEN_ENV,\n verifyAccount,\n} from \"@slabui/account\"\n\nimport type { KeyObject } from \"node:crypto\"\n\n// O build do pacote troca por `true` (tsdown.config.ts). Publicado, o editor\n// consulta sempre o slabui.com e só aceita o plano assinado por ele. Pelo src\n// do monorepo vale o registry do `slabui.json` (para um registry local) e a\n// assinatura só é conferida com SLABUI_ACCOUNT_PUBLIC_KEY.\ndeclare const __SLABUI_PUBLISHED__: boolean\nconst PUBLISHED =\n typeof __SLABUI_PUBLISHED__ !== \"undefined\" && __SLABUI_PUBLISHED__\n\n// Conta SlabUI no editor: deslogado, free ou pro. `offline` é quem tem token\n// mas o site não respondeu (vale como free até a próxima consulta).\nexport type AccountState =\n | { status: \"signed-out\" }\n | ({ status: \"free\" | \"pro\" | \"offline\" } & Omit<Account, \"access\">)\n\nexport type AccountSource = {\n // Origem do site da conta (links \"Gerenciar conta\" e \"Assinar Pro\").\n site: string\n // `fresh` ignora o cache e consulta o site de novo.\n get(options?: { fresh?: boolean }): Promise<AccountState>\n // URL de autorização no site; o token volta em `callback` com o `state`.\n login(callback: string): string\n // Chamado pelo callback: `true` quando o `state` era o esperado.\n complete(state: string, token: string): Promise<boolean>\n logout(): Promise<void>\n}\n\nconst TTL_MS = 5 * 60 * 1000\nconst STATE_TTL_MS = 10 * 60 * 1000\n\n// Lê o token como o CLI (`SLABUI_TOKEN`, depois o arquivo do `slabui login`)\n// e consulta o `whoami.json` do registry padrão, com cache de 5 minutos.\n// Com `publicKey`, o Pro vale só com a assinatura do site para este token.\nexport function createAccountSource({\n root,\n env = process.env,\n fetch = globalThis.fetch,\n file = credentialsFile(env),\n registryUrl = PUBLISHED ? DEFAULT_REGISTRY_URL : defaultRegistryUrl(root),\n publicKey = PUBLISHED ? ACCOUNT_PUBLIC_KEY : env.SLABUI_ACCOUNT_PUBLIC_KEY,\n}: {\n root?: string\n env?: Record<string, string | undefined>\n fetch?: typeof globalThis.fetch\n file?: string\n registryUrl?: string\n publicKey?: string | KeyObject\n} = {}): AccountSource {\n let cached: { state: AccountState; expires: number } | undefined\n // Até quando o Pro vale sem rede: o prazo da última assinatura.\n let proUntil = 0\n // Logins pedidos por este servidor, até o callback chegar.\n const pending = new Map<string, number>()\n\n const resolve = async (): Promise<AccountState> => {\n const token = env[TOKEN_ENV] || (await readSavedToken(file))\n if (!token) {\n return { status: \"signed-out\" }\n }\n try {\n const account = await fetchAccount({ registryUrl, token, fetch })\n if (account === null) {\n return { status: \"signed-out\" }\n }\n const { access, signature, ...profile } = account\n let pro = access.includes(\"pro\")\n if (publicKey !== undefined) {\n const claims = verifyAccount(signature, { token, publicKey })\n pro = claims?.access.includes(\"pro\") ?? false\n proUntil = pro && claims ? claims.exp * 1000 : 0\n } else {\n proUntil = pro ? Number.POSITIVE_INFINITY : 0\n }\n return { status: pro ? \"pro\" : \"free\", ...profile }\n } catch {\n // Sem rede: mantém o Pro até a assinatura vencer, senão vale como free.\n const last = cached?.state\n return last && last.status !== \"signed-out\"\n ? {\n ...last,\n status:\n last.status === \"pro\" && Date.now() < proUntil\n ? \"pro\"\n : \"offline\",\n }\n : { status: \"offline\" }\n }\n }\n\n return {\n site: new URL(registryUrl).origin,\n\n async get({ fresh = false } = {}) {\n if (fresh || !cached || cached.expires < Date.now()) {\n cached = { state: await resolve(), expires: Date.now() + TTL_MS }\n }\n return cached.state\n },\n\n login(callback) {\n const now = Date.now()\n for (const [state, created] of pending) {\n if (now - created > STATE_TTL_MS) pending.delete(state)\n }\n const state = newState()\n pending.set(state, now)\n return authorizeUrl({\n registryUrl,\n callback,\n state,\n client: \"SlabUI theme editor\",\n })\n },\n\n async complete(state, token) {\n const created = pending.get(state)\n if (created === undefined || Date.now() - created > STATE_TTL_MS) {\n return false\n }\n pending.delete(state)\n await saveToken(token, file)\n cached = undefined\n return true\n },\n\n async logout() {\n await removeToken(file)\n cached = undefined\n },\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { createServer } from \"node:http\"\nimport { dirname, isAbsolute, join, resolve } from \"node:path\"\n\nimport { createPathsMatcher, parseTsconfig } from \"get-tsconfig\"\n\nimport type { AddressInfo } from \"node:net\"\n\nimport { createThemeHandler, type ThemeHandlerOptions } from \"./handler\"\n\nexport {\n type AccountSource,\n type AccountState,\n createAccountSource,\n} from \"./account\"\nexport {\n createThemeHandler,\n THEME_PATH,\n type ThemeHandler,\n type ThemeHandlerOptions,\n} from \"./handler\"\n\n// Caminho de um arquivo do projeto: com alias dos `paths` do tsconfig\n// (`@/styles/globals.css`), como o CLI resolve; sem alias, relativo à raiz.\nfunction resolveProjectFile(root: string, file: string): string {\n if (!file.startsWith(\".\") && !isAbsolute(file)) {\n for (const name of [\"tsconfig.json\", \"jsconfig.json\"]) {\n const config = join(root, name)\n if (!existsSync(config)) {\n continue\n }\n try {\n const match = createPathsMatcher({\n path: config,\n config: parseTsconfig(config),\n })?.(file)[0]\n if (match) {\n return match\n }\n } catch {\n // tsconfig ilegível: segue como caminho relativo à raiz.\n }\n break\n }\n }\n return resolve(root, file)\n}\n\n// Campos do `slabui.json` usados aqui; `undefined` sem o arquivo.\nfunction readConfig(\n root: string,\n): { css?: unknown; aliases?: { styles?: unknown } } | undefined {\n try {\n return JSON.parse(readFileSync(join(root, \"slabui.json\"), \"utf8\"))\n } catch {\n return undefined\n }\n}\n\n// Caminho absoluto do CSS global: a opção `css` ou o campo `css` do\n// `slabui.json`, relativos à raiz do projeto ou com alias do tsconfig.\nexport function resolveCssPath(root: string, css?: string): string {\n if (css) {\n return resolveProjectFile(root, css)\n }\n const config = readConfig(root)\n if (typeof config?.css !== \"string\" || config.css === \"\") {\n throw new Error(\n \"@slabui/theme-react: set the `css` option or the `css` field in slabui.json.\",\n )\n }\n return resolveProjectFile(root, config.css)\n}\n\n// Pasta absoluta dos temas (`<nome>.css`): a opção `dir`; senão `themes/`\n// dentro do alias `styles` do `slabui.json` (`@/styles` → `src/styles/themes`\n// pelos `paths` do tsconfig); senão `themes/` ao lado do CSS global.\nexport function resolveThemesDir(\n root: string,\n options: { css?: string; dir?: string } = {},\n): string {\n if (options.dir) {\n return resolveProjectFile(root, options.dir)\n }\n const styles = readConfig(root)?.aliases?.styles\n if (typeof styles === \"string\" && styles !== \"\") {\n return join(resolveProjectFile(root, styles), \"themes\")\n }\n return join(dirname(resolveCssPath(root, options.css)), \"themes\")\n}\n\nexport type ThemeServer = {\n port: number\n close: () => Promise<void>\n}\n\n// Servidor próprio, só em 127.0.0.1, para frameworks sem servidor de\n// desenvolvimento extensível (o Next). Não segura o processo aberto.\nexport async function startThemeServer(\n options: ThemeHandlerOptions & { port?: number },\n): Promise<ThemeServer> {\n const handler = createThemeHandler(options)\n const server = createServer((req, res) => void handler.handle(req, res))\n await new Promise<void>((done, fail) => {\n server.once(\"error\", fail)\n server.listen(options.port ?? 0, \"127.0.0.1\", done)\n })\n server.unref()\n return {\n port: (server.address() as AddressInfo).port,\n close: () => {\n handler.close()\n return new Promise((done) => server.close(() => done()))\n },\n }\n}\n"],"mappings":"A,o0BAaA,SAAgBE,GAAa,CAC3B,SACA,QACA,UACA,QAAQ,IACc,CACtB,IAAMC,EAAOC,IAAW,UAAYC,EAAQC,EAC5C,MAAO;;;;;;SAMAC,EAAWC,CAAK,EAAE;SAClBC,EAAM;;;;EAIbC,EAAK;qCAC8BN,EAAO;qBACvBD,EAAK;MACpBI,EAAWC,CAAK,EAAE;KACnBD,EAAWI,CAAO,EAAE;EACvBC,EAAQ,oEAAsE,GAAG;;;;QAKnF,CAEA,SAASL,EAAW,EAAe,CACjC,OAAOM,EAAM,QACX,WACC,IACE,CAAE,IAAK,QAAS,IAAK,OAAQ,IAAK,OAAQ,IAAK,SAAU,IAAK,OAAQ,EAAA,CACrEC,IACGA,CACT,CACF,CAGA,MAAML,EAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2FRM,EAAQ,GACZ,mJAAmJd,EAAK,QACpJI,EAAQU,EAAK,6BAA6B,EAC1CT,EAAQS,EAAK,kCAAkC,EAG/CC,EAAQ,iDACRN,EAAO;;;;;QAKLM,EAAM;QACNA,EAAM;QACNA,EAAM;;;QCrJd,SAAgBC,EACd,EAAW,QAAQ,IACnB,EAA4B,QAAQ,SACpC,EAAOC,EAAQ,EACf,CACA,IAAMC,EACJC,IAAa,QACTnB,EAAK,KAAKoB,EAAI,SAAWpB,EAAK,KAAKqB,EAAM,iBAAiB,EAAG,QAAQ,EACrErB,EAAK,KAAKoB,EAAI,iBAAmBpB,EAAK,KAAKqB,EAAM,SAAS,EAAG,QAAQ,EAC3E,OAAOrB,EAAK,KAAKkB,EAAK,kBAAkB,CAC1C,CAEA,eAAsBI,EACpB,EAAON,EAAgB,EACM,CAC7B,GAAI,CACF,GAAM,CAAE,SAAU,KAAK,MAAM,MAAMO,EAASC,EAAM,MAAM,CAAC,EAGzD,OAAO,OAAOC,GAAU,UAAYA,EAAQA,EAAQ,IAAA,EACtD,MAAQ,CACN,MACF,CACF,CAEA,eAAsBC,EAAU,EAAe,EAAOV,EAAgB,EAAG,CACvE,MAAMW,EAAM3B,EAAK,QAAQwB,CAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EACnD,MAAMI,EAAUJ,EAAM,GAAG,KAAK,UAAU,CAAE,OAAM,CAAC,EAAE,IAAK,CAAE,KAAM,GAAM,CAAC,CACzE,CAGA,eAAsBK,EAAY,EAAOb,EAAgB,EAAqB,CAC5E,IAAMc,EAAW,MAAMR,EAAeE,CAAI,IAAO,IAAA,GAEjD,OADA,MAAMO,EAAGP,EAAM,CAAE,MAAO,EAAK,CAAC,EACvBM,CACT,CCdA,SAAgBE,EAAU,EAAuB,CAC/C,OAAOC,EAAW,QAAQ,CAAC,CAAC,OAAOR,EAAM,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAC/D,CAYA,SAASS,EAAY,EAAyB,CAC5C,OAAO,OAAOC,GAAQ,SAClBC,EAAgB,CACd,IAAK,OAAO,KAAKD,EAAK,QAAQ,EAC9B,OAAQ,MACR,KAAM,MACR,CAAC,EACDA,CACN,CA2BA,SAAgBE,EACd,EACA,CACE,QACA,YACA,MAAM,KAAK,IAAI,GAEK,CACtB,GAAI,OAAOC,GAAW,SACpB,OAAO,KAET,GAAM,CAACC,EAASC,EAAW,GAAGC,GAAQH,EAAO,MAAM,GAAG,EACtD,GAAI,CAACC,GAAW,CAACC,GAAaC,EAAK,OAAS,EAC1C,OAAO,KAET,GAAI,CAOF,GAAI,CANUC,EACZ,KACA,OAAO,KAAKH,CAAO,EACnBL,EAAYS,CAAS,EACrB,OAAO,KAAKH,EAAW,WAAW,CAE3B,EACP,OAAO,KAET,IAAMI,EAAS,KAAK,MAClB,OAAO,KAAKL,EAAS,WAAW,CAAC,CAAC,SAAS,MAAM,CACnD,EACMM,EAAUC,EAAM,IAWtB,MATE,CAAC,MAAM,QAAQF,EAAO,MAAM,GAC5BA,EAAO,MAAQZ,EAAUP,CAAK,GAC9B,OAAOmB,EAAO,KAAQ,UACtBA,EAAO,IAAMC,GACb,OAAOD,EAAO,KAAQ,UACtBA,EAAO,IAAMC,EAAU,GAEhB,KAEFD,CACT,MAAQ,CACN,OAAO,IACT,CACF,CCzDA,eAAsBG,EAAa,CACjC,cACA,QACA,QAAQ,WAAW,MACnB,UAAU,CAAC,GAMe,CAC1B,IAAMC,EAAM,GAAGC,EAAY,QAAQ,OAAQ,EAAE,EAAE,cACzCC,EAAW,MAAMC,EAAMH,EAAK,CAChC,QAAS,CAAE,GAAGI,EAAS,cAAe,UAAU3B,EAAM,KAAK,GAAI,EAC/D,OAAQ,YAAY,QAAQ,IAAM,CACpC,CAAC,EACD,GAAIyB,EAAS,SAAW,IACtB,OAAO,KAET,GAAI,CAACA,EAAS,GACZ,MAAU,MAAM,SAASA,EAAS,OAAO,kBAAkBF,EAAI,EAAE,EAEnE,IAAMK,EAAW,MAAMH,EAAS,KAAK,EACrC,MAAO,CAAE,GAAGG,EAAS,OAAQA,EAAQ,QAAU,CAAC,CAAE,CACpD,CAGA,SAAgBC,GAAW,CACzB,OAAOC,EAAY,EAAE,CAAC,CAAC,SAAS,WAAW,CAC7C,CAIA,SAAgBC,EAAa,CAC3B,cACA,WACA,QACA,SACA,OAAOC,EAAS,GAOf,CACD,IAAMT,EAAM,IAAI,IAAI,iBAAkB,IAAI,IAAIC,CAAW,CAAC,CAAC,MAAM,EAKjE,OAJAD,EAAI,aAAa,IAAI,WAAYU,CAAQ,EACzCV,EAAI,aAAa,IAAI,QAASW,CAAK,EACnCX,EAAI,aAAa,IAAI,SAAUY,CAAM,EACrCZ,EAAI,aAAa,IAAI,OAAQa,EAAK,QAAQ,WAAY,EAAE,CAAC,CAAC,MAAM,EAAG,EAAE,CAAC,EAC/Db,EAAI,SAAS,CACtB,CCnFA,MAAMc,GAAa,CAAC,UAAW,WAAW,EAG7BC,GAAa,kBAGpBC,kBAAc,IAAI,IAAI,CAAC,YAAa,YAAa,QAAS,KAAK,CAAC,EA2CtE,SAAgBC,EAAmB,CACjC,MACA,YACA,OACA,SAASC,EAAKC,EAAQC,CAAG,EAAG,QAAQ,EACpC,WACoC,CACpC,IAAMC,kBAAU,IAAI,IAChBC,EACAC,EACAC,EACAC,EAEAC,EAA0B,QAAQ,QAAQ,EAExCC,MAAapD,EAAS6C,EAAK,MAAM,EACjCQ,EAAa,GAAiBV,EAAKW,EAAQ,GAAGC,EAAK,KAAK,EAExDC,EAAY,GAAiB,CACjC,IAAM/E,EAAOgF,EAASb,EAAQC,CAAG,EAAGQ,EAAUE,CAAI,CAAC,CAAC,CAAC,MAAMG,EAAG,CAAC,CAAC,KAAK,GAAG,EACxE,OAAOjF,EAAK,WAAW,GAAG,EAAIA,EAAO,KAAKA,GAC5C,EAGMkF,EAAc,GAAuC,CACzD,IAAMC,EAAST,EAAM,KAAKU,CAAI,EAG9B,MADA,GAAQD,EAAO,UAAY,IAAA,EAAS,EAC7BA,CACT,EAGME,EAAa,UAChB,MAAMC,GAAQT,CAAM,CAAC,CAAC,UAAY,CAAC,CAAa,EAAA,CAC9C,OAAQ,GAASrD,EAAK,SAAS,MAAM,CAAC,CAAC,CACvC,IAAK,GAASA,EAAK,MAAM,EAAG,EAAc,CAAC,CAAC,CAC5C,OAAO+D,CAAW,CAAC,CACnB,KAAK,EAGJC,EAAU,KAAO,IAAkB,CACvC,GAAIV,GAA+B,MAAQA,IAAS,GAGpD,IAAI,OAAOA,GAAS,UAAY,EAAE,MAAMO,EAAW,EAAA,CAAG,SAASP,CAAI,EACjE,MAAM,IAAIW,EAAU,IAAK,oBAAoB,OAAOX,CAAI,GAAG,EAE7D,OAAOA,CAFsD,CAG/D,EAEMY,GAAQ,EAAe,IAAkB,CAC7C,IAAMhF,EAAU,UAAUiF,EAAM,UAAU,KAAK,UAAUC,CAAI,EAAE,MAC/D,IAAK,IAAMhC,KAAUS,EACnBT,EAAO,MAAMlD,CAAO,CAExB,EAEMmF,EAAY,SAAY,CAC5BH,EAAK,QAAS,CAAE,OAAQI,EAAU,MAAMnB,EAAK,CAAC,CAAC,UAAY,EAAE,CAAC,CAAE,CAAC,CACnE,EAEMoB,EAAkB,SAAY,CAClCL,EAAK,SAAU,CAAE,OAAQ,MAAML,EAAW,CAAE,CAAC,CAC/C,EAGMW,MAAoB,CACxB,GAAI,KAAiB3B,EAAQ,OAAS,GAGtC,GAAI,CACF,EAAgB4B,EAAMpB,GAAS,EAAG,IAAa,EACzC,IAAaqB,EAAS,SAAS,MAAM,KAGzC,aAAazB,CAAW,EACxB,EAAc,eAAiB,KAAKsB,EAAgB,EAAG,EAAE,EAC3D,CAAC,EAEDxB,EAAc,GAAG,YAAe,CAC9BA,GAAe,MAAM,EACrB,EAAgB,IAAA,EAClB,CAAC,CACH,MAAQ,CACN,EAAgB,IAAA,EAClB,CACF,EAIM4B,MAAsB,CAE1B,GADAH,EAAY,EACR1B,EACF,OAEF,IAAMQ,EAAOsB,EAAShC,CAAG,EACzB,EAAU6B,EAAM9B,EAAQC,CAAG,GAAI,EAAG,IAAa,CACzC8B,IAAapB,IAGjB,aAAaN,CAAK,EAClB,EAAQ,eAAiB,KAAKqB,EAAU,EAAG,EAAE,EAC/C,CAAC,CACH,EAEMQ,MAAqB,CACzB,aAAa7B,CAAK,EAClB,aAAaC,CAAW,EACxBH,GAAS,MAAM,EACfC,GAAe,MAAM,EACrB,EAAU,IAAA,GACV,EAAgB,IAAA,EAClB,EAEM+B,GAAU,EAAsB,IAAwB,CAC5DC,EAAI,UAAU,IAAK,CACjB,eAAgB,oBAEhB,gBAAiB,yBACjB,WAAY,YACd,CAAC,EACDA,EAAI,MAAM;;CAAiB,EAC3BlC,EAAQ,IAAIkC,CAAG,EACfJ,EAAc,EACdK,EAAI,GAAG,YAAe,CACpBnC,EAAQ,OAAOkC,CAAG,EACdlC,EAAQ,OAAS,GACnBgC,EAAa,CAEjB,CAAC,CACH,EAIMI,EAAiB,KAAO,IAAiB,CAC7C,GAAI,CAACpD,EACH,OAEF,IAAMM,EAAQ,MAAMN,EAAQ,IAAI,EAChC,GAAIM,EAAM,SAAW,aACnB,MAAM,IAAI8B,EAAU,IAAK,wCAAwC,EAEnE,GAAIiB,GAAO/C,EAAM,SAAW,MAC1B,MAAM,IAAI8B,EAAU,IAAK,uBAAuB,CAEpD,EAKMkB,EAAa,GACjB7C,GAAW,KACR,GACC8C,EAAO,QAAQ9B,KAAU,IAAA,IAAa8B,EAAO,OAAO9B,KAAU,IAAA,EAClE,EAII+B,GAAc,EAAqB,IAAyB,CAChE,IAAMC,EAAqBC,GAAaC,EACxC,OAAQ,OAAO,KAAKJ,CAAM,CAAC,CAA2B,KAAM,GAC1D,OAAO,KAAKA,EAAOK,IAAU,CAAC,CAAC,CAAC,CAAC,KAC9B,GACCC,EAAQD,EAAM,GAAGnC,KAAU,IAAA,IAC3BgC,EAAMG,EAAM,GAAGnC,KAAU,IAAA,EAC7B,CACF,CACF,EAEMqC,EAAQ,MAAO,EAAsB,IAAwB,CACjE,IAAMC,EAAO,MAAMC,EAASb,CAAG,EAC/B,GAAI,CAACc,GAAaF,CAAI,EACpB,OAAOG,EAAKhB,EAAK,IAAK,CACpB,MAAO,6CACT,CAAC,EAEH,IAAMiB,EAAQ,MAAMhC,EAAQ4B,EAAK,KAAK,EAChC5F,EAAOgG,EAAQ5C,EAAU4C,CAAK,EAAIpD,EACxC,MAAMqC,EACJ,EAAQe,GACNb,EAAUS,EAAK,MAAM,GACrBP,EACEO,EAAK,OACLtB,EAAU,MAAMvE,EAASC,EAAM,MAAM,EAAG,CAAE,OAAM,CAAC,CACnD,CACJ,EACA,IAAM2D,EAASD,EAAQ,SAAY,CACjC,IAAMgC,EAAU,MAAM3F,EAASC,EAAM,MAAM,EACrC,CAAE,IAAKiG,EAAS,WAAY,MAAMC,EAAWR,EAASE,EAAK,OAAQ,CACvE,YACA,OACF,CAAC,EACKO,EAAOP,EAAK,MACdQ,GAAaH,EAASL,EAAK,MAAO,CAAE,OAAM,CAAC,EAC3CK,EAIJ,OAHIE,IAAST,GACX,MAAMtF,EAAUJ,EAAMmG,CAAI,EAErBE,CACT,CAAC,EACD,GAAI,CACFN,EAAKhB,EAAK,IAAK,CAAE,QAAS,MAAMpB,CAAO,CAAC,CAC1C,OAAS2C,EAAO,CACd,GAAIA,aAAiBC,GACnB,OAAOR,EAAKhB,EAAK,IAAK,CAAE,OAAQuB,EAAM,MAAO,CAAC,EAEhD,MAAMA,CACR,CACF,EAEME,EAAW,GAAkB,CACjC,GAAI,OAAOlD,GAAS,UAAY,CAACS,EAAYT,CAAI,EAC/C,MAAM,IAAIW,EACR,IACA,iDACF,EAEF,OAAOX,CACT,EAEMmD,EAAc,MAAO,EAAsB,IAAwB,CACvE,MAAMxB,EAAe,EAAI,EACzB,IAAMW,EAAO,MAAMC,EAASb,CAAG,EACzB1B,EAAOkD,EAAQE,EAASd,CAAI,EAAIA,EAAK,KAAO,IAAA,EAAS,EACrDe,EAAO,MAAM3C,EAAQ0C,EAASd,CAAI,EAAIA,EAAK,KAAO,IAAA,EAAS,EACjE,MAAMlC,EAAQ,SAAY,CACxB,IAAK,MAAMG,EAAW,EAAA,CAAG,SAASP,CAAI,EACpC,MAAM,IAAIW,EAAU,IAAK,yBAAyBX,GAAM,EAE1D,IAAM8B,EAASuB,EACXrC,EAAU,MAAMvE,EAASqD,EAAUuD,CAAI,EAAG,MAAM,EAAG,CAAE,MAAOA,CAAK,CAAC,EAClE,CAAC,EACC,CAAE,IAAKC,GAAY,MAAMV,EAAWW,GAAgBvD,CAAI,EAAG8B,EAAQ,CACvE,MAAO9B,CACT,CAAC,EACD,MAAMnD,EAAMkD,EAAQ,CAAE,UAAW,EAAK,CAAC,EACvCmB,EAAY,EACZ,MAAMpE,EAAUgD,EAAUE,CAAI,EAAGsD,CAAO,EACxC,IAAME,EAAU,MAAM3D,EAAK,EAC3B,MAAM/C,EAAUwC,EAAKmE,GAAeD,EAASvD,EAASD,CAAI,CAAC,CAAC,CAC9D,CAAC,EACDyC,EAAKhB,EAAK,IAAK,CAAE,OAAQ,MAAMlB,EAAW,CAAE,CAAC,CAC/C,EAEMmD,EAAc,MAClB,EACA,EACA,IACG,CACH,IAAMtB,EAAU,MAAM1B,EAAQ2C,CAAI,EAClC,GAAI,CAACjB,EACH,MAAM,IAAIzB,EAAU,IAAK,oBAAoB0C,GAAM,EAErD,MAAM1B,EAAe,EAAI,EACzB,IAAMW,EAAO,MAAMC,EAASb,CAAG,EACzB1B,EAAOkD,EAAQE,EAASd,CAAI,EAAIA,EAAK,KAAO,IAAA,EAAS,EAC3D,MAAMlC,EAAQ,SAAY,CACxB,GAAIJ,IAASoC,IAAY,MAAM7B,EAAW,EAAA,CAAG,SAASP,CAAI,EACxD,MAAM,IAAIW,EAAU,IAAK,yBAAyBX,GAAM,EAE1D,IAAMsD,EAAU,MAAM7G,EAASqD,EAAUsC,CAAO,EAAG,MAAM,EACzD,MAAMuB,GAAO7D,EAAUsC,CAAO,EAAGtC,EAAUE,CAAI,CAAC,EAChD,MAAMlD,EACJgD,EAAUE,CAAI,EACd4D,GAAqBN,EAASlB,EAASpC,CAAI,CAC7C,EACA,IAAMwD,EAAU,MAAM3D,EAAK,EAC3B,MAAM/C,EACJwC,EACAuE,GAAkBL,EAASvD,EAASmC,CAAO,EAAGnC,EAASD,CAAI,CAAC,CAC9D,CACF,CAAC,EACDyC,EAAKhB,EAAK,IAAK,CAAE,OAAQ,MAAMlB,EAAW,CAAE,CAAC,CAC/C,EAEMuD,EAAc,MAAO,EAAc,IAAwB,CAC/D,IAAM1B,EAAU,MAAM1B,EAAQV,CAAI,EAClC,GAAI,CAACoC,EACH,MAAM,IAAIzB,EAAU,IAAK,oBAAoBX,GAAM,EAErD,MAAM2B,EAAe,EAAI,EACzB,MAAMvB,EAAQ,SAAY,CACxB,MAAMnD,EAAG6C,EAAUsC,CAAO,CAAC,EAC3B,IAAMoB,EAAU,MAAM3D,EAAK,EAC3B,MAAM/C,EAAUwC,EAAKyE,GAAkBP,EAASvD,EAASmC,CAAO,CAAC,CAAC,CACpE,CAAC,EACDK,EAAKhB,EAAK,IAAK,CAAE,OAAQ,MAAMlB,EAAW,CAAE,CAAC,CAC/C,EAEMyD,EAAgB,MACpB,EACA,EACA,EACA,IACG,CACH,GAAI9I,IAAS,YAAcwG,EAAI,SAAW,MACxC,OAAOe,EAAKhB,EAAK,IAAK,CAAE,GAAI,MAAMwC,EAAO,IAAI,EAAI,KAAMA,EAAO,IAAK,CAAC,EAEtE,GAAI/I,IAAS,kBAAoBwG,EAAI,SAAW,OAAQ,CACtD,IAAMY,EAAO,MAAMC,EAASb,CAAG,EACzBwC,EAASd,EAASd,CAAI,EAAIA,EAAK,OAAS,IAAA,GAC9C,GAAI,OAAO4B,GAAW,UAAY,CAACC,GAAcD,CAAM,EACrD,MAAM,IAAIvD,EAAU,IAAK,qCAAqC,EAIhE,IAAMyB,EAAU,MAAM6B,EAAO,IAAI,CAAE,MAAO,EAAK,CAAC,EAChD,GAAI7B,EAAQ,SAAW,QAAUA,EAAQ,SAAW,MAAO,CACzD,IAAMvD,EAAQ,CAAE,GAAGuD,EAAS,KAAM6B,EAAO,IAAK,EAE9C,OADArD,EAAK,UAAW/B,CAAK,EACd4D,EAAKhB,EAAK,IAAK,CAAE,QAAS5C,CAAM,CAAC,CAC1C,CACA,OAAO4D,EAAKhB,EAAK,IAAK,CACpB,IAAKwC,EAAO,MAAM,GAAGC,IAASjF,GAAW,kBAAkB,CAC7D,CAAC,CACH,CACA,GAAI/D,IAAS,qBAAuBwG,EAAI,SAAW,MAAO,CACxD,IAAM0C,EAAQ,IAAI,IAAI1C,EAAI,KAAO,IAAK,kBAAkB,CAAC,CAAC,aACpD/E,EAAQyH,EAAM,IAAI,OAAO,EACzBC,EACJ1H,IAAU,MACT,MAAMsH,EAAO,SAASG,EAAM,IAAI,OAAO,GAAK,GAAIzH,CAAK,EACpD0H,GACFzD,EAAK,UAAW,CAAE,GAAI,MAAMqD,EAAO,IAAI,EAAI,KAAMA,EAAO,IAAK,CAAC,EAEhExC,EAAI,UAAU4C,EAAO,IAAM,IAAK,CAC9B,eAAgB,0BAClB,CAAC,EACD5C,EAAI,IACFtG,GACEkJ,EACI,CACE,OAAQ,UACR,MAAO,mBACP,QAAS,uDACT,MAAO,EACT,EACAD,EAAM,IAAI,OAAO,EACf,CACE,OAAQ,QACR,MAAO,oBACP,QAAS,0BACT,MAAO,EACT,EACA,CACE,OAAQ,QACR,MAAO,gCACP,QAAS,uCACT,MAAO,EACT,CACR,CACF,CAEF,KACA,IAAIlJ,IAAS,mBAAqBwG,EAAI,SAAW,OAAQ,CACvD,MAAMuC,EAAO,OAAO,EACpB,IAAMpF,EAAQ,MAAMoF,EAAO,IAAI,EAE/B,OADArD,EAAK,UAAW,CAAE,GAAG/B,EAAO,KAAMoF,EAAO,IAAK,CAAC,EACxCxB,EAAKhB,EAAK,IAAK,CAAE,GAAG5C,EAAO,KAAMoF,EAAO,IAAK,CAAC,CACvD,CACA,OAAOxB,EAAKhB,EAAK,IAAK,CAAE,MAAO,WAAY,CAAC,CAD5C,CAEF,EAiEA,MAAO,CAAE,aA/Da,EAAsB,IAAwB,CAClE,GAAI,CAAC6C,GAAe5C,CAAG,EACrB,OAAOe,EAAKhB,EAAK,IAAK,CAAE,MAAO,iCAAkC,CAAC,EAEpE,IAAMvG,EAAO,IAAI,IAAIwG,EAAI,KAAO,IAAK,kBAAkB,CAAC,CAAC,SACzD,GAAI,CACF,GAAIxG,IAAS,WAAawG,EAAI,SAAW,MACvC,OAAOF,EAAOE,EAAKD,CAAG,EAExB,GAAIvG,IAAS,YAAcA,EAAK,WAAW,WAAW,EACpD,OAAOqD,EACH,MAAMyF,EAAc9I,EAAMwG,EAAKD,EAAKlD,CAAO,EAC3CkE,EAAKhB,EAAK,IAAK,CAAE,MAAO,WAAY,CAAC,EAE3C,GAAIvG,IAAS,KAAOwG,EAAI,SAAW,MAAO,CACxC,IAAM0C,EAAQ,IAAI,IAAI1C,EAAI,KAAO,IAAK,kBAAkB,CAAC,CAAC,aACpDgB,EAAQ,MAAMhC,EAAQ0D,EAAM,IAAI,OAAO,CAAC,EACxCG,EAAOvD,EAAU,MAAMnB,EAAK,CAAC,EACnC,OAAO4C,EAAKhB,EAAK,IAAK,CACpB,OAAQiB,EACJ1B,EAAU,MAAMvE,EAASqD,EAAU4C,CAAK,EAAG,MAAM,EAAG,CAAE,OAAM,CAAC,EAC7D6B,EACJ,GAAI7B,GAAS,CAAE,QAAO,MAAK,EAC3B,SAAUT,GAAaC,EACvB,UAAWsC,EAAO,MAAMC,GAAeD,CAAI,EAAI,KAC/C,OAAQ,MAAMjE,EAAW,CAC3B,CAAC,CACH,CACA,GAAIrF,IAAS,KAAOwG,EAAI,SAAW,QACjC,OAAO,MAAMW,EAAMX,EAAKD,CAAG,EAE7B,GAAIvG,IAAS,WAAawG,EAAI,SAAW,OACvC,OAAO,MAAMyB,EAAYzB,EAAKD,CAAG,EAEnC,IAAMiD,EAAYxJ,EAAK,MAAM,qBAAqB,CAAC,GAAG,GACtD,GAAIwJ,GAAahD,EAAI,SAAW,QAC9B,OAAO,MAAMgC,EAAY,mBAAmBgB,CAAS,EAAGhD,EAAKD,CAAG,EAElE,GAAIiD,GAAahD,EAAI,SAAW,SAC9B,OAAO,MAAMoC,EAAY,mBAAmBY,CAAS,EAAGjD,CAAG,EAE7DgB,EAAKhB,EAAK,IAAK,CAAE,MAAO,WAAY,CAAC,CACvC,OAASuB,EAAO,CACd,GAAI2B,GAAc3B,CAAK,EACrB,OAAOP,EAAKhB,EAAK,IAAK,CAAE,MAAO,uBAAuBnC,GAAM,CAAC,EAE/D,GAAI0D,aAAiBrC,EACnB,OAAO8B,EAAKhB,EAAKuB,EAAM,OAAQ,CAAE,MAAOA,EAAM,OAAQ,CAAC,EAEzDP,EAAKhB,EAAK,IAAK,CACb,MAAOuB,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAC9D,CAAC,CACH,CACF,EAUiB,UARG,CAClBzB,EAAa,EACb,IAAK,IAAMzC,KAAUS,EACnBT,EAAO,IAAI,EAEbS,EAAQ,MAAM,CAChB,CAEuB,CACzB,CAKA,SAAS+E,GAAe,EAAsB,CAI5C,GAAI,CAHU,CAAC5C,EAAI,QAAQ,oBAAqBA,EAAI,QAAQ,IAAI,CAAC,CAC9D,KAAK,CAAC,CACN,OAAQ,GAAyB,OAAO3C,GAAS,QAC3C,CAAC,CAAC,MAAO,GAASG,EAAY,IAAIjE,GAAS8D,CAAI,CAAC,CAAC,EACxD,MAAO,GAET,IAAMmF,EAASxC,EAAI,QAAQ,OAC3B,GAAIwC,IAAW,IAAA,GACb,MAAO,GAET,GAAI,CACF,OAAOhF,EAAY,IAAI,IAAI,IAAIgF,CAAM,CAAC,CAAC,QAAQ,CACjD,MAAQ,CACN,MAAO,EACT,CACF,CAEA,SAASC,GAAc,EAAgB,CACrC,GAAI,CACF,IAAMjG,EAAM,IAAI,IAAIgG,CAAM,EAC1B,OAAOhG,EAAI,WAAa,SAAWgB,EAAY,IAAIhB,EAAI,QAAQ,CACjE,MAAQ,CACN,MAAO,EACT,CACF,CAEA,SAASjD,GAAS,EAAc,CAC9B,IAAMa,EAAQiD,EAAK,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,GAAK,GAC5C,OAAOjD,EAAM,WAAW,GAAG,EACvBA,EAAM,MAAM,EAAGA,EAAM,QAAQ,GAAG,EAAI,CAAC,EACpCA,EAAM,MAAM,GAAG,CAAC,CAAC,IAAM,EAC9B,CAGA,eAAe2I,GAAe,EAAwC,CACpE,GAAI,CACF,IAAMI,EAAS,KAAK,MAClB,MAAMpI,EAAS2C,EAAKoF,EAAM,aAAa,EAAG,MAAM,CAClD,EACA,OAAO,OAAO,KAAKK,EAAO,OAAS,CAAC,CAAC,CAAC,CAAC,IAAK,GAC1C7E,EAAK,QAAQ,YAAa,EAAE,CAC9B,CACF,MAAQ,CACN,OAAO,IACT,CACF,CAEA,IAAMW,EAAN,cAAwB,KAAM,CAEjB,OADX,YACE,EACA,EACA,CACA,MAAM/E,CAAO,EAHJ,KAAA,OAAAP,CAIX,CACF,EAEA,eAAekH,EAAS,EAAwC,CAC9D,IAAIuC,EAAO,EACLC,EAAmB,CAAC,EAC1B,UAAW,IAAMC,KAAStD,EAAK,CAE7B,GADA,GAAQsD,EAAM,OACVF,EAAO,IACT,MAAM,IAAInE,EAAU,IAAK,gBAAgB,EAE3CoE,EAAO,KAAKC,CAAK,CACnB,CACA,GAAI,CACF,OAAO,KAAK,MAAM,OAAO,OAAOD,CAAM,CAAC,CAAC,SAAS,MAAM,CAAC,CAC1D,MAAQ,CACN,MAAM,IAAIpE,EAAU,IAAK,cAAc,CACzC,CACF,CAEA,SAAS6B,GACP,EACuE,CACvE,GAAI,CAACY,EAASd,CAAI,GAAK,CAACc,EAASd,EAAK,MAAM,EAC1C,MAAO,GAET,IAAM2C,EAAQ3C,EAAK,MAenB,OAbE2C,IAAU,IAAA,IACV,EACE7B,EAAS6B,CAAK,GACd,OAAO,QAAQA,CAAK,CAAC,CAAC,OACnB,CAAC9C,EAAO+C,KACP,CAAC,QAAS,MAAM,CAAC,CAAC,SAAS/C,CAAK,GAChC,MAAM,QAAQ+C,CAAK,GACnBA,EAAM,MAAO,GAAS,OAAOlF,GAAS,QAAQ,CAClD,GAGK,GAEF,OAAO,QAAQsC,EAAK,MAAM,CAAC,CAAC,OAChC,CAACH,EAAOgD,KACP,CAAC,QAAS,QAAS,MAAM,CAAC,CAAC,SAAShD,CAAK,GACzCiB,EAAS+B,CAAI,GACb,OAAO,OAAOA,CAAI,CAAC,CAAC,MAAO,GAAU,OAAOrJ,GAAU,QAAQ,CAClE,CACF,CAEA,SAASsH,EAAS,EAAkD,CAClE,OAAO,OAAOtH,GAAU,YAAYA,GAAkB,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAEA,SAAS6I,GAAc,EAAgB,CACrC,OACE3B,aAAiB,OAAUA,EAAgC,OAAS,QAExE,CAEA,SAASP,EAAK,EAAqB,EAAgB,EAAe,CAChEhB,EAAI,UAAUpG,EAAQ,CAAE,eAAgB,kBAAmB,CAAC,EAC5DoG,EAAI,IAAI,KAAK,UAAUa,CAAI,CAAC,CAC9B,CC5kBA,MACM8C,EAAe,IAKrB,SAAgBC,GAAoB,CAClC,OACA,MAAM,QAAQ,IACd,QAAQ,WAAW,MACnB,OAAOnJ,EAAgBI,CAAG,EAC1B,cAA0B,uBAC1B,YAAwB,gEAQtB,CAAC,EAAkB,CACrB,IAAIgJ,EAEAC,EAAW,EAETC,kBAAU,IAAI,IAEdC,EAAU,SAAmC,CACjD,IAAM9I,EAAQL,EAAAoJ,cAAmB,MAAMlJ,EAAeE,CAAI,EAC1D,GAAI,CAACC,EACH,MAAO,CAAE,OAAQ,YAAa,EAEhC,GAAI,CACF,IAAM4B,EAAU,MAAMN,EAAa,CAAE,cAAa,QAAO,OAAM,CAAC,EAChE,GAAIM,IAAY,KACd,MAAO,CAAE,OAAQ,YAAa,EAEhC,GAAM,CAAE,SAAQ,YAAW,GAAGoH,GAAYpH,EACtCqD,EAAMgE,EAAO,SAAS,KAAK,EAC/B,GAAI/H,IAAc,IAAA,GAAW,CAC3B,IAAMC,EAASP,EAAcG,EAAW,CAAE,QAAO,WAAU,CAAC,EAC5D,EAAMI,GAAQ,OAAO,SAAS,KAAK,GAAK,GACxC,EAAW8D,GAAO9D,EAASA,EAAO,IAAM,IAAO,CACjD,KACE,GAAW8D,EAAM,IAA2B,EAE9C,MAAO,CAAE,OAAQA,EAAM,MAAQ,OAAQ,GAAG+D,CAAQ,CACpD,MAAQ,CAEN,IAAME,EAAOP,GAAQ,MACrB,OAAOO,GAAQA,EAAK,SAAW,aAC3B,CACE,GAAGA,EACH,OACEA,EAAK,SAAW,OAAS,KAAK,IAAI,EAAIN,EAClC,MACA,SACR,EACA,CAAE,OAAQ,SAAU,CAC1B,CACF,EAEA,MAAO,CACL,KAAM,IAAI,IAAIpH,CAAW,CAAC,CAAC,OAE3B,MAAM,IAAI,CAAE,QAAQ,IAAU,CAAC,EAAG,CAIhC,OAHI2H,GAAS,CAACR,GAAUA,EAAO,QAAU,KAAK,IAAI,KAChD,EAAS,CAAE,MAAO,MAAMG,EAAQ,EAAG,QAAS,KAAK,IAAI,EAAI,GAAO,GAE3DH,EAAO,KAChB,EAEA,MAAM,EAAU,CACd,IAAMtH,EAAM,KAAK,IAAI,EACrB,IAAK,GAAM,CAACa,EAAOkH,KAAYP,EACzBxH,EAAM+H,EAAUX,GAAcI,EAAQ,OAAO3G,CAAK,EAExD,IAAMA,EAAQL,EAAS,EAEvB,OADAgH,EAAQ,IAAI3G,EAAOb,CAAG,EACfU,EAAa,CAClB,cACA,WACA,QACA,OAAQ,qBACV,CAAC,CACH,EAEA,MAAM,SAAS,EAAO,EAAO,CAC3B,IAAMqH,EAAUP,EAAQ,IAAI3G,CAAK,EAOjC,OANIkH,IAAY,IAAA,IAAa,KAAK,IAAI,EAAIA,EAAUX,EAC3C,IAETI,EAAQ,OAAO3G,CAAK,EACpB,MAAMjC,EAAUD,EAAOD,CAAI,EAC3B,EAAS,IAAA,GACF,GACT,EAEA,MAAM,QAAS,CACb,MAAMK,EAAYL,CAAI,EACtB,EAAS,IAAA,EACX,CACF,CACF,CC3HA,SAASsJ,EAAmB,EAAc,EAAsB,CAC9D,GAAI,CAACtJ,EAAK,WAAW,GAAG,GAAK,CAACuJ,EAAWvJ,CAAI,EAC3C,IAAK,IAAMsD,IAAQ,CAAC,gBAAiB,eAAe,EAAG,CACrD,IAAM6E,EAASzF,EAAKoF,EAAMxE,CAAI,EAC9B,GAAKkG,EAAWrB,CAAM,EAGtB,IAAI,CACF,IAAMsB,EAAQC,EAAmB,CAC/B,KAAMvB,EACN,OAAQwB,EAAcxB,CAAM,CAC9B,CAAC,CAAC,GAAGnI,CAAI,CAAC,CAAC,GACX,GAAIyJ,EACF,OAAOA,CAEX,MAAQ,CAER,CACA,KADA,CAEF,CAEF,OAAOV,EAAQjB,EAAM9H,CAAI,CAC3B,CAGA,SAAS4J,EACP,EAC+D,CAC/D,GAAI,CACF,OAAO,KAAK,MAAMC,EAAanH,EAAKoF,EAAM,aAAa,EAAG,MAAM,CAAC,CACnE,MAAQ,CACN,MACF,CACF,CAIA,SAAgBgC,EAAe,EAAc,EAAsB,CACjE,GAAIlH,EACF,OAAO0G,EAAmBxB,EAAMlF,CAAG,EAErC,IAAMuF,EAASyB,EAAW9B,CAAI,EAC9B,GAAI,OAAOK,GAAQ,KAAQ,UAAYA,EAAO,MAAQ,GACpD,MAAU,MACR,8EACF,EAEF,OAAOmB,EAAmBxB,EAAMK,EAAO,GAAG,CAC5C,CAKA,SAAgB4B,GACd,EACA,EAA0C,CAAC,EACnC,CACR,GAAIC,EAAQ,IACV,OAAOV,EAAmBxB,EAAMkC,EAAQ,GAAG,EAE7C,IAAMC,EAASL,EAAW9B,CAAI,CAAC,EAAE,SAAS,OAI1C,OAFSpF,EADL,OAAOuH,GAAW,UAAYA,IAAW,GAC/BX,EAAmBxB,EAAMmC,CAAM,EAEjCtH,EAAQmH,EAAehC,EAAMkC,EAAQ,GAAG,CAAC,EAFL,QAAQ,CAG1D,CASA,eAAsBE,GACpB,EACsB,CACtB,IAAMC,EAAU1H,EAAmBuH,CAAO,EACpCI,EAASC,GAAc,EAAK,IAAQ,KAAKF,EAAQ,OAAOnF,EAAKD,CAAG,CAAC,EAMvE,OALA,MAAM,IAAI,SAAe,EAAM,IAAS,CACtCqF,EAAO,KAAK,QAASE,CAAI,EACzBF,EAAO,OAAOJ,EAAQ,MAAQ,EAAG,YAAarC,CAAI,CACpD,CAAC,EACDyC,EAAO,MAAM,EACN,CACL,KAAOA,EAAO,QAAQ,CAAC,CAAiB,KACxC,WACED,EAAQ,MAAM,EACP,IAAI,QAAS,GAASC,EAAO,UAAYzC,EAAK,CAAC,CAAC,EAE3D,CACF,C"}
|
package/dist/vite/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,i as t,n,o as r,t as i}from"../server-
|
|
1
|
+
import{a as e,i as t,n,o as r,t as i}from"../server-DeZ9Emfp.mjs";import{existsSync as a}from"node:fs";import{join as o}from"node:path";import{THEME_ATTRIBUTE as s,THEME_ENV as c,isThemeName as l}from"@slabui/theme-core";import{loadEnv as u}from"vite";function d(d={}){let f,p=e=>n(e,d);return[{name:`slabui:themes`,apply:`serve`,configureServer(n){let a=n.config.root,o=r({css:i(a,d.css),reference:d.reference,root:a,themes:p(a),account:t({root:a})});n.middlewares.use(e,(e,t)=>{o.handle(e,t)}),n.httpServer?.once(`close`,o.close)}},{name:`slabui:themes:html`,configResolved(e){f=e},transformIndexHtml(e){if(!f)return e;let t=d.theme??u(f.mode,f.envDir||f.root,`SLABUI_`)[c];if(!t||RegExp(`<html[^>]*\\s${s}=`).test(e))return e;if(!l(t))return f.logger.warn(`@slabui/theme-react: invalid theme name "${t}".`),e;try{let e=p(f.root);a(o(e,`${t}.css`))||f.logger.warn(`@slabui/theme-react: theme "${t}" not found in ${e}.`)}catch{}return e.replace(/<html(\s|>)/i,`<html ${s}="${t}"$1`)}}]}export{d as themes};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slabui/theme-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Live theme editor for SlabUI and its Vite and Next plugins",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"slabui",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"CHANGELOG.md"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@slabui/theme-core": "0.
|
|
60
|
+
"@slabui/theme-core": "0.5.0",
|
|
61
61
|
"get-tsconfig": "4.14.3",
|
|
62
62
|
"use-sync-external-store": "1.7.0"
|
|
63
63
|
},
|
package/dist/server-DfiIO3KF.mjs
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as t,watch as n}from"node:fs";import{createServer as r}from"node:http";import i,{basename as a,dirname as o,isAbsolute as s,join as c,relative as l,resolve as u,sep as ee}from"node:path";import{createPathsMatcher as d,parseTsconfig as f}from"get-tsconfig";import{mkdir as p,readFile as m,readdir as te,rename as ne,rm as h,writeFile as g}from"node:fs/promises";import{ThemeValidationError as re,defaultTheme as _,isThemeName as v,readTheme as y,writeTheme as b}from"@slabui/theme-core";import{addThemeImport as x,removeThemeImport as ie,removeTokens as ae,renameThemeImport as oe,renameThemeSelectors as se,themeFileHeader as ce}from"@slabui/theme-core/files";import{createHash as S,createPublicKey as C,randomBytes as w,verify as T}from"node:crypto";import{homedir as E,hostname as D}from"node:os";const le=[`spacing`,`font-size`],O=`/__slabui/theme`,k=/* @__PURE__ */ new Set([`localhost`,`127.0.0.1`,`[::1]`,`::1`]);function A({css:e,reference:t,root:r,themes:i=c(o(e),`themes`),account:s}){let u=/* @__PURE__ */ new Set,d,f,S,C,w=Promise.resolve(),T=()=>m(e,`utf8`),E=e=>c(i,`${e}.css`),D=t=>{let n=l(o(e),E(t)).split(ee).join(`/`);return n.startsWith(`.`)?n:`./${n}`},k=e=>{let t=w.then(e);return w=t.catch(()=>void 0),t},A=async()=>(await te(i).catch(()=>[])).filter(e=>e.endsWith(`.css`)).map(e=>e.slice(0,-4)).filter(v).sort(),N=async e=>{if(e!=null&&e!==``){if(typeof e!=`string`||!(await A()).includes(e))throw new P(404,`theme not found: ${String(e)}`);return e}},R=(e,t)=>{let n=`event: ${e}\ndata: ${JSON.stringify(t)}\n\n`;for(let e of u)e.write(n)},z=async()=>{R(`theme`,{tokens:y(await T().catch(()=>``))})},B=async()=>{R(`themes`,{themes:await A()})},V=()=>{if(!(f||u.size===0))try{f=n(i,(e,t)=>{(!t||t.endsWith(`.css`))&&(clearTimeout(C),C=setTimeout(()=>void B(),50))}),f.on(`error`,()=>{f?.close(),f=void 0})}catch{f=void 0}},H=()=>{if(V(),d)return;let t=a(e);d=n(o(e),(e,n)=>{n===t&&(clearTimeout(S),S=setTimeout(()=>void z(),50))})},U=()=>{clearTimeout(S),clearTimeout(C),d?.close(),f?.close(),d=void 0,f=void 0},W=(e,t)=>{t.writeHead(200,{"content-type":`text/event-stream`,"cache-control":`no-cache, no-transform`,connection:`keep-alive`}),t.write(`: connected
|
|
2
|
-
|
|
3
|
-
`),u.add(t),H(),e.on(`close`,()=>{u.delete(t),u.size===0&&U()})},G=async e=>{if(!s)return;let t=await s.get();if(t.status===`signed-out`)throw new P(401,`sign in to your SlabUI account to save`);if(e&&t.status!==`pro`)throw new P(403,`this needs SlabUI Pro`)},K=e=>le.some(t=>e.light?.[t]!==void 0||e.dark?.[t]!==void 0),q=(e,n)=>{let r=t??_;return Object.keys(e).some(t=>Object.keys(e[t]??{}).some(e=>n[t]?.[e]===void 0&&r[t]?.[e]===void 0))},J=async(n,r)=>{let i=await F(n);if(!fe(i))return L(r,400,{error:`expected { tokens: { theme, light, dark } }`});let a=await N(i.theme),o=a?E(a):e;await G(!!a||K(i.tokens)||q(i.tokens,y(await m(o,`utf8`),{theme:a})));let s=k(async()=>{let e=await m(o,`utf8`),{css:n,changes:r}=await b(e,i.tokens,{reference:t,theme:a}),s=i.unset?ae(n,i.unset,{theme:a}):n;return s!==e&&await g(o,s),r});try{L(r,200,{changes:await s})}catch(e){if(e instanceof re)return L(r,422,{issues:e.issues});throw e}},Y=e=>{if(typeof e!=`string`||!v(e))throw new P(400,`invalid theme name (use letters, numbers and -)`);return e},X=async(t,n)=>{await G(!0);let r=await F(t),a=Y(I(r)?r.name:void 0),o=await N(I(r)?r.from:void 0);await k(async()=>{if((await A()).includes(a))throw new P(409,`theme already exists: ${a}`);let t=o?y(await m(E(o),`utf8`),{theme:o}):{},{css:n}=await b(ce(a),t,{theme:a});await p(i,{recursive:!0}),V(),await g(E(a),n);let r=await T();await g(e,x(r,D(a)))}),L(n,201,{themes:await A()})},Z=async(t,n,r)=>{let i=await N(t);if(!i)throw new P(404,`theme not found: ${t}`);await G(!0);let a=await F(n),o=Y(I(a)?a.name:void 0);await k(async()=>{if(o!==i&&(await A()).includes(o))throw new P(409,`theme already exists: ${o}`);let t=await m(E(i),`utf8`);await ne(E(i),E(o)),await g(E(o),se(t,i,o));let n=await T();await g(e,oe(n,D(i),D(o)))}),L(r,200,{themes:await A()})},Q=async(t,n)=>{let r=await N(t);if(!r)throw new P(404,`theme not found: ${t}`);await G(!0),await k(async()=>{await h(E(r));let t=await T();await g(e,ie(t,D(r)))}),L(n,200,{themes:await A()})},$=async(e,t,n,r)=>{if(e===`/account`&&t.method===`GET`)return L(n,200,{...await r.get(),site:r.site});if(e===`/account/login`&&t.method===`POST`){let e=await F(t),i=I(e)?e.origin:void 0;if(typeof i!=`string`||!M(i))throw new P(400,`expected { origin } of a local page`);return L(n,200,{url:r.login(`${i}${O}/account/callback`)})}if(e===`/account/callback`&&t.method===`GET`){let e=new URL(t.url??`/`,`http://localhost`).searchParams,i=e.get(`token`),a=i!==null&&await r.complete(e.get(`state`)??``,i);a&&R(`account`,{...await r.get(),site:r.site}),n.writeHead(a?200:400,{"content-type":`text/html; charset=utf-8`}),n.end(ue(a?`Pronto! Você entrou na sua conta SlabUI. Pode fechar esta aba e voltar ao editor.`:e.get(`error`)?`Login cancelado. Pode fechar esta aba.`:`Este link de login expirou. Tente entrar de novo pelo editor.`))}else{if(e===`/account/logout`&&t.method===`POST`){await r.logout();let e=await r.get();return R(`account`,{...e,site:r.site}),L(n,200,{...e,site:r.site})}return L(n,404,{error:`not found`})}};return{handle:async(n,i)=>{if(!j(n))return L(i,403,{error:`only local requests are allowed`});let a=new URL(n.url??`/`,`http://localhost`).pathname;try{if(a===`/events`&&n.method===`GET`)return W(n,i);if(a===`/account`||a.startsWith(`/account/`))return s?await $(a,n,i,s):L(i,404,{error:`not found`});if(a===`/`&&n.method===`GET`){let e=new URL(n.url??`/`,`http://localhost`).searchParams,a=await N(e.get(`theme`)),o=y(await T());return L(i,200,{tokens:a?y(await m(E(a),`utf8`),{theme:a}):o,...a&&{theme:a,base:o},defaults:t??_,installed:r?await de(r):null,themes:await A()})}if(a===`/`&&n.method===`PATCH`)return await J(n,i);if(a===`/themes`&&n.method===`POST`)return await X(n,i);let e=a.match(/^\/themes\/([^/]+)$/)?.[1];if(e&&n.method===`PATCH`)return await Z(decodeURIComponent(e),n,i);if(e&&n.method===`DELETE`)return await Q(decodeURIComponent(e),i);L(i,404,{error:`not found`})}catch(t){if(pe(t))return L(i,404,{error:`CSS file not found: ${e}`});if(t instanceof P)return L(i,t.status,{error:t.message});L(i,500,{error:t instanceof Error?t.message:String(t)})}},close:()=>{U();for(let e of u)e.end();u.clear()}}}function j(e){if(![e.headers[`x-forwarded-host`],e.headers.host].flat().filter(e=>typeof e==`string`).every(e=>k.has(N(e))))return!1;let t=e.headers.origin;if(t===void 0)return!0;try{return k.has(new URL(t).hostname)}catch{return!1}}function M(e){try{let t=new URL(e);return t.protocol===`http:`&&k.has(t.hostname)}catch{return!1}}function ue(e){return`<!doctype html><meta charset="utf-8"><title>SlabUI</title><body style="font:16px system-ui;display:grid;place-items:center;height:100vh;margin:0;padding:0 1rem;text-align:center"><p>${e}</p></body>`}function N(e){let t=e.split(`,`)[0]?.trim()??``;return t.startsWith(`[`)?t.slice(0,t.indexOf(`]`)+1):t.split(`:`)[0]??``}async function de(e){try{let t=JSON.parse(await m(c(e,`slabui.json`),`utf8`));return Object.keys(t.items??{}).map(e=>e.replace(/^@[^/]+\//,``))}catch{return null}}var P=class extends Error{status;constructor(e,t){super(t),this.status=e}};async function F(e){let t=0,n=[];for await(let r of e){if(t+=r.length,t>1e5)throw new P(413,`body too large`);n.push(r)}try{return JSON.parse(Buffer.concat(n).toString(`utf8`))}catch{throw new P(400,`invalid JSON`)}}function fe(e){if(!I(e)||!I(e.tokens))return!1;let t=e.unset;return t!==void 0&&!(I(t)&&Object.entries(t).every(([e,t])=>[`light`,`dark`].includes(e)&&Array.isArray(t)&&t.every(e=>typeof e==`string`)))?!1:Object.entries(e.tokens).every(([e,t])=>[`theme`,`light`,`dark`].includes(e)&&I(t)&&Object.values(t).every(e=>typeof e==`string`))}function I(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function pe(e){return e instanceof Error&&e.code===`ENOENT`}function L(e,t,n){e.writeHead(t,{"content-type":`application/json`}),e.end(JSON.stringify(n))}function R(e=process.env,t=process.platform,n=E()){let r=t===`win32`?i.join(e.APPDATA??i.join(n,`AppData/Roaming`),`slabui`):i.join(e.XDG_CONFIG_HOME||i.join(n,`.config`),`slabui`);return i.join(r,`credentials.json`)}async function z(e=R()){try{let{token:t}=JSON.parse(await m(e,`utf8`));return typeof t==`string`&&t?t:void 0}catch{return}}async function B(e,t=R()){await p(i.dirname(t),{recursive:!0}),await g(t,`${JSON.stringify({token:e})}\n`,{mode:384})}async function V(e=R()){let t=await z(e)!==void 0;return await h(e,{force:!0}),t}function H(e){return S(`sha256`).update(e.trim()).digest(`hex`)}function U(e){return typeof e==`string`?C({key:Buffer.from(e,`base64`),format:`der`,type:`spki`}):e}function W(e,{token:t,publicKey:n,now:r=Date.now()}){if(typeof e!=`string`)return null;let[i,a,...o]=e.split(`.`);if(!i||!a||o.length>0)return null;try{if(!T(null,Buffer.from(i),U(n),Buffer.from(a,`base64url`)))return null;let e=JSON.parse(Buffer.from(i,`base64url`).toString(`utf8`)),o=r/1e3;return!Array.isArray(e.access)||e.sub!==H(t)||typeof e.exp!=`number`||e.exp<o||typeof e.iat!=`number`||e.iat>o+60?null:e}catch{return null}}async function G({registryUrl:e,token:t,fetch:n=globalThis.fetch,headers:r={}}){let i=`${e.replace(/\/+$/,``)}/whoami.json`,a=await n(i,{headers:{...r,authorization:`Bearer ${t.trim()}`},signal:AbortSignal.timeout(15e3)});if(a.status===401)return null;if(!a.ok)throw Error(`Error ${a.status} while fetching ${i}.`);let o=await a.json();return{...o,access:o.access??[]}}function K(){return w(24).toString(`base64url`)}function q({registryUrl:e,callback:t,state:n,client:r,host:i=D()}){let a=new URL(`/cli/authorize`,new URL(e).origin);return a.searchParams.set(`callback`,t),a.searchParams.set(`state`,n),a.searchParams.set(`client`,r),a.searchParams.set(`host`,i.replace(/\.local$/,``).slice(0,60)),a.toString()}const J=6e5;function Y({root:e,env:t=process.env,fetch:n=globalThis.fetch,file:r=R(t),registryUrl:i=`https://slabui.com/r`,publicKey:a=`MCowBQYDK2VwAyEACwMW1a+QMU+TLzvMDpFSulpcu3+K8WNHscFjRVNaNPY=`}={}){let o,s=0,c=/* @__PURE__ */ new Map,l=async()=>{let e=t.SLABUI_TOKEN||await z(r);if(!e)return{status:`signed-out`};try{let t=await G({registryUrl:i,token:e,fetch:n});if(t===null)return{status:`signed-out`};let{access:r,signature:o,...c}=t,l=r.includes(`pro`);if(a!==void 0){let t=W(o,{token:e,publicKey:a});l=t?.access.includes(`pro`)??!1,s=l&&t?t.exp*1e3:0}else s=l?1/0:0;return{status:l?`pro`:`free`,...c}}catch{let e=o?.state;return e&&e.status!==`signed-out`?{...e,status:e.status===`pro`&&Date.now()<s?`pro`:`offline`}:{status:`offline`}}};return{site:new URL(i).origin,async get(){return(!o||o.expires<Date.now())&&(o={state:await l(),expires:Date.now()+3e5}),o.state},login(e){let t=Date.now();for(let[e,n]of c)t-n>J&&c.delete(e);let n=K();return c.set(n,t),q({registryUrl:i,callback:e,state:n,client:`SlabUI theme editor`})},async complete(e,t){let n=c.get(e);return n===void 0||Date.now()-n>J?!1:(c.delete(e),await B(t,r),o=void 0,!0)},async logout(){await V(r),o=void 0}}}function X(t,n){if(!n.startsWith(`.`)&&!s(n))for(let r of[`tsconfig.json`,`jsconfig.json`]){let i=c(t,r);if(e(i)){try{let e=d({path:i,config:f(i)})?.(n)[0];if(e)return e}catch{}break}}return u(t,n)}function Z(e){try{return JSON.parse(t(c(e,`slabui.json`),`utf8`))}catch{return}}function Q(e,t){if(t)return X(e,t);let n=Z(e);if(typeof n?.css!=`string`||n.css===``)throw Error("@slabui/theme-react: set the `css` option or the `css` field in slabui.json.");return X(e,n.css)}function $(e,t={}){if(t.dir)return X(e,t.dir);let n=Z(e)?.aliases?.styles;return c(typeof n==`string`&&n!==``?X(e,n):o(Q(e,t.css)),`themes`)}async function me(e){let t=A(e),n=r((e,n)=>void t.handle(e,n));return await new Promise((t,r)=>{n.once(`error`,r),n.listen(e.port??0,`127.0.0.1`,t)}),n.unref(),{port:n.address().port,close:()=>(t.close(),new Promise(e=>n.close(()=>e())))}}export{O as a,Y as i,$ as n,A as o,me as r,Q as t};
|
|
4
|
-
//# sourceMappingURL=server-DfiIO3KF.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-DfiIO3KF.mjs","names":["hostname","path","PRO_TOKENS","THEME_PATH","LOCAL_HOSTS","createThemeHandler","join","dirname","css","clients","watcher","themesWatcher","timer","themesTimer","queue","read","readFile","themeFile","themes","name","importOf","relative","sep","enqueue","result","task","listThemes","readdir","file","isThemeName","themeOf","BodyError","emit","message","event","data","client","broadcast","readTheme","broadcastThemes","watchThemes","watch","filename","startWatching","basename","stopWatching","events","res","req","requireAccount","account","state","pro","proTokens","tokens","addsTokens","known","reference","defaultTheme","group","current","patch","body","readJson","isTokensBody","send","theme","written","writeTheme","next","removeTokens","writeFile","changes","error","ThemeValidationError","newName","createTheme","isObject","from","content","themeFileHeader","mkdir","globals","addThemeImport","renameTheme","rename","renameThemeSelectors","renameThemeImport","deleteTheme","rm","removeThemeImport","accountRoutes","source","origin","isLocalOrigin","query","token","done","callbackPage","isLocalRequest","base","root","installedItems","themePath","isMissingFile","host","hostname$1","url","value","config","status","size","chunks","chunk","unset","names","vars","credentialsFile","homedir","dir","platform","env","home","readSavedToken","saveToken","removeToken","existed","tokenHash","createHash","publicKeyOf","key","createPublicKey","verifyAccount","signed","payload","signature","rest","verify","publicKey","claims","seconds","now","fetchAccount","registryUrl","response","fetch","headers","newState","randomBytes","authorizeUrl","hostname","callback","STATE_TTL_MS","createAccountSource","cached","proUntil","pending","resolve","\"SLABUI_TOKEN\"","profile","access","last","created","resolveProjectFile","isAbsolute","existsSync","match","createPathsMatcher","parseTsconfig","readConfig","readFileSync","resolveCssPath","resolveThemesDir","options","styles","startThemeServer","handler","server","createServer","fail"],"sources":["../src/server/handler.ts","../../account/src/credentials.ts","../../account/src/signature.ts","../../account/src/index.ts","../src/server/account.ts","../src/server/index.ts"],"sourcesContent":["import { type FSWatcher, watch } from \"node:fs\"\nimport {\n mkdir,\n readdir,\n readFile,\n rename,\n rm,\n writeFile,\n} from \"node:fs/promises\"\nimport { basename, dirname, join, relative, sep } from \"node:path\"\n\nimport {\n defaultTheme,\n isThemeName,\n readTheme,\n ThemeValidationError,\n writeTheme,\n} from \"@slabui/theme-core\"\n\nimport {\n addThemeImport,\n removeThemeImport,\n removeTokens,\n renameThemeImport,\n renameThemeSelectors,\n themeFileHeader,\n type UnsetTokens,\n} from \"@slabui/theme-core/files\"\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\"\nimport type { ThemeTokens } from \"@slabui/theme-core\"\nimport type { AccountSource } from \"./account\"\n\n// Tokens que só a conta Pro grava (a seção Size do editor).\nconst PRO_TOKENS = [\"spacing\", \"font-size\"]\n\n// Caminho em que os plugins expõem o servidor, na origem do app.\nexport const THEME_PATH = \"/__slabui/theme\"\n\nconst MAX_BODY = 100_000\nconst LOCAL_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"])\n\nexport type ThemeHandlerOptions = {\n // Caminho absoluto do CSS global.\n css: string\n // Tema que define o tipo de cada token na validação.\n reference?: ThemeTokens\n // Raiz do projeto: os itens instalados vêm do `slabui.json` dela.\n root?: string\n // Pasta dos temas (`<nome>.css`). Padrão: `themes/` ao lado do CSS global.\n themes?: string\n // Conta SlabUI (os plugins passam `createAccountSource`). Com ela, salvar\n // exige login e vários temas e cores novas exigem Pro; sem ela, nada é\n // bloqueado e `/account` responde 404.\n account?: AccountSource\n}\n\nexport type ThemeHandler = {\n handle: (req: IncomingMessage, res: ServerResponse) => Promise<void>\n close: () => void\n}\n\n// Lê e grava o tema do CSS global por HTTP, com os caminhos relativos ao\n// ponto de montagem:\n// - `GET /`: `{ tokens, defaults, installed, themes }` (o tema do arquivo, o de\n// referência, os itens do `slabui.json` e os temas da pasta de temas)\n// - `GET /?theme=<nome>`: o mesmo, com `tokens` do arquivo do tema e `base`\n// (o CSS global, sobre o qual o tema é aplicado)\n// - `PATCH /` com `{ tokens, theme?, unset? }`: `{ changes }`, ou 422 com\n// `{ issues }` (`unset` tira variáveis do arquivo: num tema, voltam da base)\n// - `POST /themes` com `{ name, from? }`: cria o tema (vazio ou cópia de\n// `from`) e o `@import` no CSS global\n// - `PATCH /themes/<nome>` com `{ name }`: renomeia o arquivo, os seletores e\n// o `@import`\n// - `DELETE /themes/<nome>`: apaga o arquivo e o `@import`\n// - `GET /account`: a conta (`{ status, name?, image?, plan? }`)\n// - `POST /account/login` com `{ origin }`: `{ url }` da autorização no site,\n// que volta em `GET /account/callback?state&token`\n// - `POST /account/logout`: apaga o token gravado\n// - `GET /events`: SSE com `event: theme` (`{ tokens }`) a cada mudança no\n// CSS global e `event: themes` (`{ themes }`) a cada mudança na pasta de\n// temas, e `event: account` a cada login ou logout\nexport function createThemeHandler({\n css,\n reference,\n root,\n themes = join(dirname(css), \"themes\"),\n account,\n}: ThemeHandlerOptions): ThemeHandler {\n const clients = new Set<ServerResponse>()\n let watcher: FSWatcher | undefined\n let themesWatcher: FSWatcher | undefined\n let timer: NodeJS.Timeout | undefined\n let themesTimer: NodeJS.Timeout | undefined\n // Uma gravação de cada vez, para duas mudanças seguidas não se atropelarem.\n let queue: Promise<unknown> = Promise.resolve()\n\n const read = () => readFile(css, \"utf8\")\n const themeFile = (name: string) => join(themes, `${name}.css`)\n // `@import` do tema no CSS global: caminho relativo a ele.\n const importOf = (name: string) => {\n const path = relative(dirname(css), themeFile(name)).split(sep).join(\"/\")\n return path.startsWith(\".\") ? path : `./${path}`\n }\n\n // Uma alteração de arquivos de cada vez (gravar, criar, renomear, apagar).\n const enqueue = <T>(task: () => Promise<T>): Promise<T> => {\n const result = queue.then(task)\n // Uma tarefa que falha não trava as próximas.\n queue = result.catch(() => undefined)\n return result\n }\n\n // Temas da pasta: os `<nome>.css` com nome válido, em ordem.\n const listThemes = async () =>\n (await readdir(themes).catch(() => [] as string[]))\n .filter((file) => file.endsWith(\".css\"))\n .map((file) => file.slice(0, -\".css\".length))\n .filter(isThemeName)\n .sort()\n\n // Tema pedido (`?theme=` ou `theme` no corpo): precisa existir na pasta.\n const themeOf = async (name: unknown) => {\n if (name === undefined || name === null || name === \"\") {\n return undefined\n }\n if (typeof name !== \"string\" || !(await listThemes()).includes(name)) {\n throw new BodyError(404, `theme not found: ${String(name)}`)\n }\n return name\n }\n\n const emit = (event: string, data: unknown) => {\n const message = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`\n for (const client of clients) {\n client.write(message)\n }\n }\n\n const broadcast = async () => {\n emit(\"theme\", { tokens: readTheme(await read().catch(() => \"\")) })\n }\n\n const broadcastThemes = async () => {\n emit(\"themes\", { themes: await listThemes() })\n }\n\n // A pasta de temas pode não existir ainda: volta a tentar ao criar um tema.\n const watchThemes = () => {\n if (themesWatcher || clients.size === 0) {\n return\n }\n try {\n themesWatcher = watch(themes, (_, filename) => {\n if (filename && !filename.endsWith(\".css\")) {\n return\n }\n clearTimeout(themesTimer)\n themesTimer = setTimeout(() => void broadcastThemes(), 50)\n })\n // Pasta apagada com o servidor de pé: para de observar.\n themesWatcher.on(\"error\", () => {\n themesWatcher?.close()\n themesWatcher = undefined\n })\n } catch {\n themesWatcher = undefined\n }\n }\n\n // Observa a pasta, e não o arquivo: editores que gravam trocando o arquivo\n // derrubariam um watch direto nele.\n const startWatching = () => {\n watchThemes()\n if (watcher) {\n return\n }\n const name = basename(css)\n watcher = watch(dirname(css), (_, filename) => {\n if (filename !== name) {\n return\n }\n clearTimeout(timer)\n timer = setTimeout(() => void broadcast(), 50)\n })\n }\n\n const stopWatching = () => {\n clearTimeout(timer)\n clearTimeout(themesTimer)\n watcher?.close()\n themesWatcher?.close()\n watcher = undefined\n themesWatcher = undefined\n }\n\n const events = (req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n // `no-transform`: sem isso, a compressão do Next segura os eventos.\n \"cache-control\": \"no-cache, no-transform\",\n connection: \"keep-alive\",\n })\n res.write(\": connected\\n\\n\")\n clients.add(res)\n startWatching()\n req.on(\"close\", () => {\n clients.delete(res)\n if (clients.size === 0) {\n stopWatching()\n }\n })\n }\n\n // Salvar exige login; `pro` exige a assinatura. Também conferido no\n // editor, que mostra o motivo antes de a pessoa tentar.\n const requireAccount = async (pro: boolean) => {\n if (!account) {\n return\n }\n const state = await account.get()\n if (state.status === \"signed-out\") {\n throw new BodyError(401, \"sign in to your SlabUI account to save\")\n }\n if (pro && state.status !== \"pro\") {\n throw new BodyError(403, \"this needs SlabUI Pro\")\n }\n }\n\n // Tokens da seção Size do editor (espaçamento e tamanho do texto), recurso\n // Pro como as cores novas e os temas. A mesma lista do `pro` da seção em\n // `editor/controls.ts`.\n const proTokens = (tokens: ThemeTokens) =>\n PRO_TOKENS.some(\n (name) =>\n tokens.light?.[name] !== undefined || tokens.dark?.[name] !== undefined,\n )\n\n // Variáveis que ainda não existem no arquivo nem no tema de referência:\n // uma cor nova (recurso Pro).\n const addsTokens = (tokens: ThemeTokens, current: ThemeTokens) => {\n const known: ThemeTokens = reference ?? defaultTheme\n return (Object.keys(tokens) as (keyof ThemeTokens)[]).some((group) =>\n Object.keys(tokens[group] ?? {}).some(\n (name) =>\n current[group]?.[name] === undefined &&\n known[group]?.[name] === undefined,\n ),\n )\n }\n\n const patch = async (req: IncomingMessage, res: ServerResponse) => {\n const body = await readJson(req)\n if (!isTokensBody(body)) {\n return send(res, 400, {\n error: \"expected { tokens: { theme, light, dark } }\",\n })\n }\n const theme = await themeOf(body.theme)\n const file = theme ? themeFile(theme) : css\n await requireAccount(\n Boolean(theme) ||\n proTokens(body.tokens) ||\n addsTokens(\n body.tokens,\n readTheme(await readFile(file, \"utf8\"), { theme }),\n ),\n )\n const result = enqueue(async () => {\n const current = await readFile(file, \"utf8\")\n const { css: written, changes } = await writeTheme(current, body.tokens, {\n reference,\n theme,\n })\n const next = body.unset\n ? removeTokens(written, body.unset, { theme })\n : written\n if (next !== current) {\n await writeFile(file, next)\n }\n return changes\n })\n try {\n send(res, 200, { changes: await result })\n } catch (error) {\n if (error instanceof ThemeValidationError) {\n return send(res, 422, { issues: error.issues })\n }\n throw error\n }\n }\n\n const newName = (name: unknown) => {\n if (typeof name !== \"string\" || !isThemeName(name)) {\n throw new BodyError(\n 400,\n \"invalid theme name (use letters, numbers and -)\",\n )\n }\n return name\n }\n\n const createTheme = async (req: IncomingMessage, res: ServerResponse) => {\n await requireAccount(true)\n const body = await readJson(req)\n const name = newName(isObject(body) ? body.name : undefined)\n const from = await themeOf(isObject(body) ? body.from : undefined)\n await enqueue(async () => {\n if ((await listThemes()).includes(name)) {\n throw new BodyError(409, `theme already exists: ${name}`)\n }\n const tokens = from\n ? readTheme(await readFile(themeFile(from), \"utf8\"), { theme: from })\n : {}\n const { css: content } = await writeTheme(themeFileHeader(name), tokens, {\n theme: name,\n })\n await mkdir(themes, { recursive: true })\n watchThemes()\n await writeFile(themeFile(name), content)\n const globals = await read()\n await writeFile(css, addThemeImport(globals, importOf(name)))\n })\n send(res, 201, { themes: await listThemes() })\n }\n\n const renameTheme = async (\n from: string,\n req: IncomingMessage,\n res: ServerResponse,\n ) => {\n const current = await themeOf(from)\n if (!current) {\n throw new BodyError(404, `theme not found: ${from}`)\n }\n await requireAccount(true)\n const body = await readJson(req)\n const name = newName(isObject(body) ? body.name : undefined)\n await enqueue(async () => {\n if (name !== current && (await listThemes()).includes(name)) {\n throw new BodyError(409, `theme already exists: ${name}`)\n }\n const content = await readFile(themeFile(current), \"utf8\")\n await rename(themeFile(current), themeFile(name))\n await writeFile(\n themeFile(name),\n renameThemeSelectors(content, current, name),\n )\n const globals = await read()\n await writeFile(\n css,\n renameThemeImport(globals, importOf(current), importOf(name)),\n )\n })\n send(res, 200, { themes: await listThemes() })\n }\n\n const deleteTheme = async (name: string, res: ServerResponse) => {\n const current = await themeOf(name)\n if (!current) {\n throw new BodyError(404, `theme not found: ${name}`)\n }\n await requireAccount(true)\n await enqueue(async () => {\n await rm(themeFile(current))\n const globals = await read()\n await writeFile(css, removeThemeImport(globals, importOf(current)))\n })\n send(res, 200, { themes: await listThemes() })\n }\n\n const accountRoutes = async (\n path: string,\n req: IncomingMessage,\n res: ServerResponse,\n source: AccountSource,\n ) => {\n if (path === \"/account\" && req.method === \"GET\") {\n return send(res, 200, { ...(await source.get()), site: source.site })\n }\n if (path === \"/account/login\" && req.method === \"POST\") {\n const body = await readJson(req)\n const origin = isObject(body) ? body.origin : undefined\n if (typeof origin !== \"string\" || !isLocalOrigin(origin)) {\n throw new BodyError(400, \"expected { origin } of a local page\")\n }\n return send(res, 200, {\n url: source.login(`${origin}${THEME_PATH}/account/callback`),\n })\n }\n if (path === \"/account/callback\" && req.method === \"GET\") {\n const query = new URL(req.url ?? \"/\", \"http://localhost\").searchParams\n const token = query.get(\"token\")\n const done =\n token !== null &&\n (await source.complete(query.get(\"state\") ?? \"\", token))\n if (done) {\n emit(\"account\", { ...(await source.get()), site: source.site })\n }\n res.writeHead(done ? 200 : 400, {\n \"content-type\": \"text/html; charset=utf-8\",\n })\n res.end(\n callbackPage(\n done\n ? \"Pronto! Você entrou na sua conta SlabUI. Pode fechar esta aba e voltar ao editor.\"\n : query.get(\"error\")\n ? \"Login cancelado. Pode fechar esta aba.\"\n : \"Este link de login expirou. Tente entrar de novo pelo editor.\",\n ),\n )\n return\n }\n if (path === \"/account/logout\" && req.method === \"POST\") {\n await source.logout()\n const state = await source.get()\n emit(\"account\", { ...state, site: source.site })\n return send(res, 200, { ...state, site: source.site })\n }\n return send(res, 404, { error: \"not found\" })\n }\n\n const handle = async (req: IncomingMessage, res: ServerResponse) => {\n if (!isLocalRequest(req)) {\n return send(res, 403, { error: \"only local requests are allowed\" })\n }\n const path = new URL(req.url ?? \"/\", \"http://localhost\").pathname\n try {\n if (path === \"/events\" && req.method === \"GET\") {\n return events(req, res)\n }\n if (path === \"/account\" || path.startsWith(\"/account/\")) {\n return account\n ? await accountRoutes(path, req, res, account)\n : send(res, 404, { error: \"not found\" })\n }\n if (path === \"/\" && req.method === \"GET\") {\n const query = new URL(req.url ?? \"/\", \"http://localhost\").searchParams\n const theme = await themeOf(query.get(\"theme\"))\n const base = readTheme(await read())\n return send(res, 200, {\n tokens: theme\n ? readTheme(await readFile(themeFile(theme), \"utf8\"), { theme })\n : base,\n ...(theme && { theme, base }),\n defaults: reference ?? defaultTheme,\n installed: root ? await installedItems(root) : null,\n themes: await listThemes(),\n })\n }\n if (path === \"/\" && req.method === \"PATCH\") {\n return await patch(req, res)\n }\n if (path === \"/themes\" && req.method === \"POST\") {\n return await createTheme(req, res)\n }\n const themePath = path.match(/^\\/themes\\/([^/]+)$/)?.[1]\n if (themePath && req.method === \"PATCH\") {\n return await renameTheme(decodeURIComponent(themePath), req, res)\n }\n if (themePath && req.method === \"DELETE\") {\n return await deleteTheme(decodeURIComponent(themePath), res)\n }\n send(res, 404, { error: \"not found\" })\n } catch (error) {\n if (isMissingFile(error)) {\n return send(res, 404, { error: `CSS file not found: ${css}` })\n }\n if (error instanceof BodyError) {\n return send(res, error.status, { error: error.message })\n }\n send(res, 500, {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n const close = () => {\n stopWatching()\n for (const client of clients) {\n client.end()\n }\n clients.clear()\n }\n\n return { handle, close }\n}\n\n// Só aceita pedidos para um host local, vindos de uma página local. Impede que\n// um site externo use o navegador (ou um DNS apontando para 127.0.0.1) para\n// gravar no arquivo.\nfunction isLocalRequest(req: IncomingMessage) {\n const hosts = [req.headers[\"x-forwarded-host\"], req.headers.host]\n .flat()\n .filter((host): host is string => typeof host === \"string\")\n if (!hosts.every((host) => LOCAL_HOSTS.has(hostname(host)))) {\n return false\n }\n const origin = req.headers.origin\n if (origin === undefined) {\n return true\n }\n try {\n return LOCAL_HOSTS.has(new URL(origin).hostname)\n } catch {\n return false\n }\n}\n\nfunction isLocalOrigin(origin: string) {\n try {\n const url = new URL(origin)\n return url.protocol === \"http:\" && LOCAL_HOSTS.has(url.hostname)\n } catch {\n return false\n }\n}\n\nfunction callbackPage(message: string) {\n return `<!doctype html><meta charset=\"utf-8\"><title>SlabUI</title><body style=\"font:16px system-ui;display:grid;place-items:center;height:100vh;margin:0;padding:0 1rem;text-align:center\"><p>${message}</p></body>`\n}\n\nfunction hostname(host: string) {\n const value = host.split(\",\")[0]?.trim() ?? \"\"\n return value.startsWith(\"[\")\n ? value.slice(0, value.indexOf(\"]\") + 1)\n : (value.split(\":\")[0] ?? \"\")\n}\n\n// Nomes dos itens do `slabui.json` (sem o namespace), ou `null` sem o arquivo.\nasync function installedItems(root: string): Promise<string[] | null> {\n try {\n const config = JSON.parse(\n await readFile(join(root, \"slabui.json\"), \"utf8\"),\n ) as { items?: Record<string, unknown> }\n return Object.keys(config.items ?? {}).map((name) =>\n name.replace(/^@[^/]+\\//, \"\"),\n )\n } catch {\n return null\n }\n}\n\nclass BodyError extends Error {\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message)\n }\n}\n\nasync function readJson(req: IncomingMessage): Promise<unknown> {\n let size = 0\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n size += chunk.length\n if (size > MAX_BODY) {\n throw new BodyError(413, \"body too large\")\n }\n chunks.push(chunk)\n }\n try {\n return JSON.parse(Buffer.concat(chunks).toString(\"utf8\"))\n } catch {\n throw new BodyError(400, \"invalid JSON\")\n }\n}\n\nfunction isTokensBody(\n body: unknown,\n): body is { tokens: ThemeTokens; theme?: unknown; unset?: UnsetTokens } {\n if (!isObject(body) || !isObject(body.tokens)) {\n return false\n }\n const unset = body.unset\n if (\n unset !== undefined &&\n !(\n isObject(unset) &&\n Object.entries(unset).every(\n ([group, names]) =>\n [\"light\", \"dark\"].includes(group) &&\n Array.isArray(names) &&\n names.every((name) => typeof name === \"string\"),\n )\n )\n ) {\n return false\n }\n return Object.entries(body.tokens).every(\n ([group, vars]) =>\n [\"theme\", \"light\", \"dark\"].includes(group) &&\n isObject(vars) &&\n Object.values(vars).every((value) => typeof value === \"string\"),\n )\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction isMissingFile(error: unknown) {\n return (\n error instanceof Error && (error as NodeJS.ErrnoException).code === \"ENOENT\"\n )\n}\n\nfunction send(res: ServerResponse, status: number, body: unknown) {\n res.writeHead(status, { \"content-type\": \"application/json\" })\n res.end(JSON.stringify(body))\n}\n","import { mkdir, readFile, rm, writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport path from \"node:path\"\n\n// Token da conta SlabUI gravado pelo `slabui login` ou pelo \"Entrar\" do\n// editor de tema, fora do projeto para não ir parar no git. O CLI e o editor\n// usam o mesmo arquivo.\n\ntype Env = Record<string, string | undefined>\n\nexport function credentialsFile(\n env: Env = process.env,\n platform: NodeJS.Platform = process.platform,\n home = homedir(),\n) {\n const dir =\n platform === \"win32\"\n ? path.join(env.APPDATA ?? path.join(home, \"AppData/Roaming\"), \"slabui\")\n : path.join(env.XDG_CONFIG_HOME || path.join(home, \".config\"), \"slabui\")\n return path.join(dir, \"credentials.json\")\n}\n\nexport async function readSavedToken(\n file = credentialsFile(),\n): Promise<string | undefined> {\n try {\n const { token } = JSON.parse(await readFile(file, \"utf8\")) as {\n token?: unknown\n }\n return typeof token === \"string\" && token ? token : undefined\n } catch {\n return undefined\n }\n}\n\nexport async function saveToken(token: string, file = credentialsFile()) {\n await mkdir(path.dirname(file), { recursive: true })\n await writeFile(file, `${JSON.stringify({ token })}\\n`, { mode: 0o600 })\n}\n\n// `false` quando não havia token gravado.\nexport async function removeToken(file = credentialsFile()): Promise<boolean> {\n const existed = (await readSavedToken(file)) !== undefined\n await rm(file, { force: true })\n return existed\n}\n","import {\n createHash,\n createPrivateKey,\n createPublicKey,\n type KeyObject,\n sign,\n verify,\n} from \"node:crypto\"\n\n// Assinatura do `whoami.json`: o site assina com Ed25519 o que dá acesso\n// (`access`), o hash do token e o prazo, e o editor confere com a chave\n// pública. Uma resposta inventada, ou a de outro token, não vale.\n\n// Chave pública (SPKI, base64) do slabui.com. A privada fica só no site, em\n// SLABUI_ACCOUNT_SIGNING_KEY.\nexport const ACCOUNT_PUBLIC_KEY =\n \"MCowBQYDK2VwAyEACwMW1a+QMU+TLzvMDpFSulpcu3+K8WNHscFjRVNaNPY=\"\n\n// Validade de uma assinatura: o editor consulta a cada 5 minutos, e sem rede\n// mantém o plano só até ela vencer.\nexport const SIGNATURE_TTL_S = 60 * 60\n\nexport type AccountClaims = {\n access: string[]\n // sha256 do token, em hex.\n sub: string\n // Segundos desde 1970.\n iat: number\n exp: number\n}\n\nexport function tokenHash(token: string): string {\n return createHash(\"sha256\").update(token.trim()).digest(\"hex\")\n}\n\nfunction privateKeyOf(key: string | KeyObject) {\n return typeof key === \"string\"\n ? createPrivateKey({\n key: Buffer.from(key, \"base64\"),\n format: \"der\",\n type: \"pkcs8\",\n })\n : key\n}\n\nfunction publicKeyOf(key: string | KeyObject) {\n return typeof key === \"string\"\n ? createPublicKey({\n key: Buffer.from(key, \"base64\"),\n format: \"der\",\n type: \"spki\",\n })\n : key\n}\n\n// `<claims em base64url>.<assinatura em base64url>`.\nexport function signAccount({\n access,\n token,\n privateKey,\n now = Date.now(),\n}: {\n access: string[]\n token: string\n privateKey: string | KeyObject\n now?: number\n}): string {\n const iat = Math.floor(now / 1000)\n const claims: AccountClaims = {\n access,\n sub: tokenHash(token),\n iat,\n exp: iat + SIGNATURE_TTL_S,\n }\n const payload = Buffer.from(JSON.stringify(claims)).toString(\"base64url\")\n const signature = sign(null, Buffer.from(payload), privateKeyOf(privateKey))\n return `${payload}.${signature.toString(\"base64url\")}`\n}\n\n// As claims de uma assinatura válida, do token e no prazo; senão `null`.\nexport function verifyAccount(\n signed: unknown,\n {\n token,\n publicKey,\n now = Date.now(),\n }: { token: string; publicKey: string | KeyObject; now?: number },\n): AccountClaims | null {\n if (typeof signed !== \"string\") {\n return null\n }\n const [payload, signature, ...rest] = signed.split(\".\")\n if (!payload || !signature || rest.length > 0) {\n return null\n }\n try {\n const valid = verify(\n null,\n Buffer.from(payload),\n publicKeyOf(publicKey),\n Buffer.from(signature, \"base64url\"),\n )\n if (!valid) {\n return null\n }\n const claims = JSON.parse(\n Buffer.from(payload, \"base64url\").toString(\"utf8\"),\n ) as AccountClaims\n const seconds = now / 1000\n if (\n !Array.isArray(claims.access) ||\n claims.sub !== tokenHash(token) ||\n typeof claims.exp !== \"number\" ||\n claims.exp < seconds ||\n typeof claims.iat !== \"number\" ||\n claims.iat > seconds + 60\n ) {\n return null\n }\n return claims\n } catch {\n return null\n }\n}\n","import { randomBytes } from \"node:crypto\"\nimport { readFileSync } from \"node:fs\"\nimport { hostname } from \"node:os\"\nimport path from \"node:path\"\n\nexport {\n credentialsFile,\n readSavedToken,\n removeToken,\n saveToken,\n} from \"./credentials\"\nexport {\n ACCOUNT_PUBLIC_KEY,\n type AccountClaims,\n SIGNATURE_TTL_S,\n signAccount,\n tokenHash,\n verifyAccount,\n} from \"./signature\"\n\n// Conta SlabUI do CLI e do editor de tema (só Node). O token identifica a\n// conta no registry padrão; o `whoami.json` diz o plano.\n\nexport const DEFAULT_REGISTRY_URL = \"https://slabui.com/r\"\n\n// Variável com o token, antes do arquivo de credenciais.\nexport const TOKEN_ENV = \"SLABUI_TOKEN\"\n\n// Rota do registry que diz de quem é o token.\nexport const WHOAMI_PATH = \"whoami.json\"\n\nexport type Account = {\n name?: string\n image?: string\n // Nome do plano (\"Free\", \"Pro Yearly\"…).\n plan?: string\n // `[\"pro\"]` com assinatura ativa; `[]` no plano free.\n access: string[]\n // `access` assinado pelo site (ver `verifyAccount`).\n signature?: string\n}\n\n// URL do registry padrão: a do `slabui.json` da raiz (`registries[\"@slabui\"]`,\n// para desenvolver contra um registry local) ou a de produção.\nexport function defaultRegistryUrl(root?: string): string {\n if (root === undefined) {\n return DEFAULT_REGISTRY_URL\n }\n try {\n const config = JSON.parse(\n readFileSync(path.join(root, \"slabui.json\"), \"utf8\"),\n ) as { registries?: Record<string, unknown> }\n const entry = config.registries?.[\"@slabui\"]\n const url =\n typeof entry === \"string\"\n ? entry\n : (entry as { url?: unknown } | undefined)?.url\n return typeof url === \"string\" && url ? url : DEFAULT_REGISTRY_URL\n } catch {\n return DEFAULT_REGISTRY_URL\n }\n}\n\n// Confere o token no `whoami.json`: a conta, ou `null` se o token não vale.\n// Erros de rede sobem para quem chamou decidir.\nexport async function fetchAccount({\n registryUrl,\n token,\n fetch = globalThis.fetch,\n headers = {},\n}: {\n registryUrl: string\n token: string\n fetch?: typeof globalThis.fetch\n headers?: Record<string, string>\n}): Promise<Account | null> {\n const url = `${registryUrl.replace(/\\/+$/, \"\")}/${WHOAMI_PATH}`\n const response = await fetch(url, {\n headers: { ...headers, authorization: `Bearer ${token.trim()}` },\n signal: AbortSignal.timeout(15_000),\n })\n if (response.status === 401) {\n return null\n }\n if (!response.ok) {\n throw new Error(`Error ${response.status} while fetching ${url}.`)\n }\n const account = (await response.json()) as Account\n return { ...account, access: account.access ?? [] }\n}\n\n// Valor aleatório que volta no callback, para só aceitar o login pedido aqui.\nexport function newState() {\n return randomBytes(24).toString(\"base64url\")\n}\n\n// Página do site (na origem do registry) em que a pessoa entra e autoriza;\n// o token volta para o `callback` local com o `state`.\nexport function authorizeUrl({\n registryUrl,\n callback,\n state,\n client,\n host = hostname(),\n}: {\n registryUrl: string\n callback: string\n state: string\n client: string\n host?: string\n}) {\n const url = new URL(\"/cli/authorize\", new URL(registryUrl).origin)\n url.searchParams.set(\"callback\", callback)\n url.searchParams.set(\"state\", state)\n url.searchParams.set(\"client\", client)\n url.searchParams.set(\"host\", host.replace(/\\.local$/, \"\").slice(0, 60))\n return url.toString()\n}\n","import {\n ACCOUNT_PUBLIC_KEY,\n type Account,\n authorizeUrl,\n credentialsFile,\n DEFAULT_REGISTRY_URL,\n defaultRegistryUrl,\n fetchAccount,\n newState,\n readSavedToken,\n removeToken,\n saveToken,\n TOKEN_ENV,\n verifyAccount,\n} from \"@slabui/account\"\n\nimport type { KeyObject } from \"node:crypto\"\n\n// O build do pacote troca por `true` (tsdown.config.ts). Publicado, o editor\n// consulta sempre o slabui.com e só aceita o plano assinado por ele. Pelo src\n// do monorepo vale o registry do `slabui.json` (para um registry local) e a\n// assinatura só é conferida com SLABUI_ACCOUNT_PUBLIC_KEY.\ndeclare const __SLABUI_PUBLISHED__: boolean\nconst PUBLISHED =\n typeof __SLABUI_PUBLISHED__ !== \"undefined\" && __SLABUI_PUBLISHED__\n\n// Conta SlabUI no editor: deslogado, free ou pro. `offline` é quem tem token\n// mas o site não respondeu (vale como free até a próxima consulta).\nexport type AccountState =\n | { status: \"signed-out\" }\n | ({ status: \"free\" | \"pro\" | \"offline\" } & Omit<Account, \"access\">)\n\nexport type AccountSource = {\n // Origem do site da conta (links \"Gerenciar conta\" e \"Assinar Pro\").\n site: string\n get(): Promise<AccountState>\n // URL de autorização no site; o token volta em `callback` com o `state`.\n login(callback: string): string\n // Chamado pelo callback: `true` quando o `state` era o esperado.\n complete(state: string, token: string): Promise<boolean>\n logout(): Promise<void>\n}\n\nconst TTL_MS = 5 * 60 * 1000\nconst STATE_TTL_MS = 10 * 60 * 1000\n\n// Lê o token como o CLI (`SLABUI_TOKEN`, depois o arquivo do `slabui login`)\n// e consulta o `whoami.json` do registry padrão, com cache de 5 minutos.\n// Com `publicKey`, o Pro vale só com a assinatura do site para este token.\nexport function createAccountSource({\n root,\n env = process.env,\n fetch = globalThis.fetch,\n file = credentialsFile(env),\n registryUrl = PUBLISHED ? DEFAULT_REGISTRY_URL : defaultRegistryUrl(root),\n publicKey = PUBLISHED ? ACCOUNT_PUBLIC_KEY : env.SLABUI_ACCOUNT_PUBLIC_KEY,\n}: {\n root?: string\n env?: Record<string, string | undefined>\n fetch?: typeof globalThis.fetch\n file?: string\n registryUrl?: string\n publicKey?: string | KeyObject\n} = {}): AccountSource {\n let cached: { state: AccountState; expires: number } | undefined\n // Até quando o Pro vale sem rede: o prazo da última assinatura.\n let proUntil = 0\n // Logins pedidos por este servidor, até o callback chegar.\n const pending = new Map<string, number>()\n\n const resolve = async (): Promise<AccountState> => {\n const token = env[TOKEN_ENV] || (await readSavedToken(file))\n if (!token) {\n return { status: \"signed-out\" }\n }\n try {\n const account = await fetchAccount({ registryUrl, token, fetch })\n if (account === null) {\n return { status: \"signed-out\" }\n }\n const { access, signature, ...profile } = account\n let pro = access.includes(\"pro\")\n if (publicKey !== undefined) {\n const claims = verifyAccount(signature, { token, publicKey })\n pro = claims?.access.includes(\"pro\") ?? false\n proUntil = pro && claims ? claims.exp * 1000 : 0\n } else {\n proUntil = pro ? Number.POSITIVE_INFINITY : 0\n }\n return { status: pro ? \"pro\" : \"free\", ...profile }\n } catch {\n // Sem rede: mantém o Pro até a assinatura vencer, senão vale como free.\n const last = cached?.state\n return last && last.status !== \"signed-out\"\n ? {\n ...last,\n status:\n last.status === \"pro\" && Date.now() < proUntil\n ? \"pro\"\n : \"offline\",\n }\n : { status: \"offline\" }\n }\n }\n\n return {\n site: new URL(registryUrl).origin,\n\n async get() {\n if (!cached || cached.expires < Date.now()) {\n cached = { state: await resolve(), expires: Date.now() + TTL_MS }\n }\n return cached.state\n },\n\n login(callback) {\n const now = Date.now()\n for (const [state, created] of pending) {\n if (now - created > STATE_TTL_MS) pending.delete(state)\n }\n const state = newState()\n pending.set(state, now)\n return authorizeUrl({\n registryUrl,\n callback,\n state,\n client: \"SlabUI theme editor\",\n })\n },\n\n async complete(state, token) {\n const created = pending.get(state)\n if (created === undefined || Date.now() - created > STATE_TTL_MS) {\n return false\n }\n pending.delete(state)\n await saveToken(token, file)\n cached = undefined\n return true\n },\n\n async logout() {\n await removeToken(file)\n cached = undefined\n },\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { createServer } from \"node:http\"\nimport { dirname, isAbsolute, join, resolve } from \"node:path\"\n\nimport { createPathsMatcher, parseTsconfig } from \"get-tsconfig\"\n\nimport type { AddressInfo } from \"node:net\"\n\nimport { createThemeHandler, type ThemeHandlerOptions } from \"./handler\"\n\nexport {\n type AccountSource,\n type AccountState,\n createAccountSource,\n} from \"./account\"\nexport {\n createThemeHandler,\n THEME_PATH,\n type ThemeHandler,\n type ThemeHandlerOptions,\n} from \"./handler\"\n\n// Caminho de um arquivo do projeto: com alias dos `paths` do tsconfig\n// (`@/styles/globals.css`), como o CLI resolve; sem alias, relativo à raiz.\nfunction resolveProjectFile(root: string, file: string): string {\n if (!file.startsWith(\".\") && !isAbsolute(file)) {\n for (const name of [\"tsconfig.json\", \"jsconfig.json\"]) {\n const config = join(root, name)\n if (!existsSync(config)) {\n continue\n }\n try {\n const match = createPathsMatcher({\n path: config,\n config: parseTsconfig(config),\n })?.(file)[0]\n if (match) {\n return match\n }\n } catch {\n // tsconfig ilegível: segue como caminho relativo à raiz.\n }\n break\n }\n }\n return resolve(root, file)\n}\n\n// Campos do `slabui.json` usados aqui; `undefined` sem o arquivo.\nfunction readConfig(\n root: string,\n): { css?: unknown; aliases?: { styles?: unknown } } | undefined {\n try {\n return JSON.parse(readFileSync(join(root, \"slabui.json\"), \"utf8\"))\n } catch {\n return undefined\n }\n}\n\n// Caminho absoluto do CSS global: a opção `css` ou o campo `css` do\n// `slabui.json`, relativos à raiz do projeto ou com alias do tsconfig.\nexport function resolveCssPath(root: string, css?: string): string {\n if (css) {\n return resolveProjectFile(root, css)\n }\n const config = readConfig(root)\n if (typeof config?.css !== \"string\" || config.css === \"\") {\n throw new Error(\n \"@slabui/theme-react: set the `css` option or the `css` field in slabui.json.\",\n )\n }\n return resolveProjectFile(root, config.css)\n}\n\n// Pasta absoluta dos temas (`<nome>.css`): a opção `dir`; senão `themes/`\n// dentro do alias `styles` do `slabui.json` (`@/styles` → `src/styles/themes`\n// pelos `paths` do tsconfig); senão `themes/` ao lado do CSS global.\nexport function resolveThemesDir(\n root: string,\n options: { css?: string; dir?: string } = {},\n): string {\n if (options.dir) {\n return resolveProjectFile(root, options.dir)\n }\n const styles = readConfig(root)?.aliases?.styles\n if (typeof styles === \"string\" && styles !== \"\") {\n return join(resolveProjectFile(root, styles), \"themes\")\n }\n return join(dirname(resolveCssPath(root, options.css)), \"themes\")\n}\n\nexport type ThemeServer = {\n port: number\n close: () => Promise<void>\n}\n\n// Servidor próprio, só em 127.0.0.1, para frameworks sem servidor de\n// desenvolvimento extensível (o Next). Não segura o processo aberto.\nexport async function startThemeServer(\n options: ThemeHandlerOptions & { port?: number },\n): Promise<ThemeServer> {\n const handler = createThemeHandler(options)\n const server = createServer((req, res) => void handler.handle(req, res))\n await new Promise<void>((done, fail) => {\n server.once(\"error\", fail)\n server.listen(options.port ?? 0, \"127.0.0.1\", done)\n })\n server.unref()\n return {\n port: (server.address() as AddressInfo).port,\n close: () => {\n handler.close()\n return new Promise((done) => server.close(() => done()))\n },\n }\n}\n"],"mappings":"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"}
|