@slabui/theme-react 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/LICENSE +59 -0
- package/README.md +12 -2
- package/dist/editor/index.d.mts +7 -0
- package/dist/editor/index.d.mts.map +1 -0
- package/dist/editor/index.mjs +143 -0
- package/dist/editor/index.mjs.map +1 -0
- package/dist/next/index.d.mts +13 -0
- package/dist/next/index.d.mts.map +1 -0
- package/dist/next/index.mjs +2 -0
- package/dist/next/index.mjs.map +1 -0
- package/dist/server-CVSf3fOz.mjs +4 -0
- package/dist/server-CVSf3fOz.mjs.map +1 -0
- package/dist/vite/index.d.mts +10 -0
- package/dist/vite/index.d.mts.map +1 -0
- package/dist/vite/index.mjs +2 -0
- package/dist/vite/index.mjs.map +1 -0
- package/package.json +74 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ThemeTokens } from "@slabui/theme-core";
|
|
2
|
+
import { NextConfig } from "next";
|
|
3
|
+
type NextConfigFunction = (phase: string, context: {
|
|
4
|
+
defaultConfig: NextConfig;
|
|
5
|
+
}) => NextConfig | Promise<NextConfig>;
|
|
6
|
+
export type WithThemeOptions = {
|
|
7
|
+
css?: string;
|
|
8
|
+
dir?: string;
|
|
9
|
+
reference?: ThemeTokens;
|
|
10
|
+
};
|
|
11
|
+
export declare function withTheme(nextConfig?: NextConfig | NextConfigFunction, options?: WithThemeOptions): NextConfigFunction;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/next/index.ts"],"mappings":";;KAeK,sBACH,eACA;EAAW,eAAe;MACvB,aAAa,QAAQ;YAId;EAEV;EAIA;EACA,YAAY;;wBASE,UACd,aAAY,aAAa,oBACzB,UAAS,mBACR"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as e,i as t,n,r,t as i}from"../server-CVSf3fOz.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
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["THEME_PATH","createAccountSource","resolveCssPath","servers","withTheme","config","nextConfig","phase","context","css","options","port","startThemeServer","resolveThemesDir","server","rewrite","withRewrite","rewrites"],"sources":["../../src/next/index.ts"],"sourcesContent":["import type { ThemeTokens } from \"@slabui/theme-core\"\nimport type { NextConfig } from \"next\"\n\nimport {\n createAccountSource,\n resolveCssPath,\n resolveThemesDir,\n startThemeServer,\n THEME_PATH,\n} from \"../server\"\n\n// O mesmo valor de `PHASE_DEVELOPMENT_SERVER` (`next/constants`), sem\n// carregar o Next em tempo de execução.\nconst DEV_PHASE = \"phase-development-server\"\n\ntype NextConfigFunction = (\n phase: string,\n context: { defaultConfig: NextConfig },\n) => NextConfig | Promise<NextConfig>\n\ntype Rewrites = Awaited<ReturnType<NonNullable<NextConfig[\"rewrites\"]>>>\n\nexport type WithThemeOptions = {\n // CSS global, relativo à raiz do projeto. Padrão: o `css` do `slabui.json`.\n css?: string\n // Pasta dos temas (`<nome>.css`), relativa à raiz ou com alias do tsconfig.\n // Padrão: `themes/` no alias `styles` do `slabui.json` ou, sem ele, ao lado\n // do CSS global.\n dir?: string\n reference?: ThemeTokens\n}\n\n// Servidores já iniciados neste processo, por arquivo: o Next pode carregar a\n// config mais de uma vez.\nconst servers = new Map<string, Promise<number>>()\n\n// Em `next dev`, sobe o servidor do tema em 127.0.0.1 e o expõe na origem do\n// app, em `/__slabui/theme`, por um rewrite. Fora do dev, a config fica igual.\nexport function withTheme(\n nextConfig: NextConfig | NextConfigFunction = {},\n options: WithThemeOptions = {},\n): NextConfigFunction {\n return async (phase, context) => {\n const config =\n typeof nextConfig === \"function\"\n ? await nextConfig(phase, context)\n : nextConfig\n if (phase !== DEV_PHASE) {\n return config\n }\n\n const css = resolveCssPath(process.cwd(), options.css)\n let port = servers.get(css)\n if (!port) {\n port = startThemeServer({\n css,\n reference: options.reference,\n root: process.cwd(),\n themes: resolveThemesDir(process.cwd(), options),\n account: createAccountSource({ root: process.cwd() }),\n }).then((server) => server.port)\n servers.set(css, port)\n }\n const rewrite = {\n source: `${THEME_PATH}/:path*`,\n destination: `http://127.0.0.1:${await port}/:path*`,\n }\n\n return {\n ...config,\n rewrites: async () => withRewrite(await config.rewrites?.(), rewrite),\n }\n }\n}\n\n// O rewrite do tema vai antes dos arquivos e das rotas do app.\nfunction withRewrite(\n rewrites: Rewrites | undefined,\n rewrite: { source: string; destination: string },\n): Rewrites {\n if (rewrites === undefined) {\n return { beforeFiles: [rewrite] }\n }\n if (Array.isArray(rewrites)) {\n return { beforeFiles: [rewrite], afterFiles: rewrites }\n }\n return {\n ...rewrites,\n beforeFiles: [rewrite, ...(rewrites.beforeFiles ?? [])],\n }\n}\n"],"mappings":"A,6DAaA,MAqBMG,kBAAU,IAAI,IAIpB,SAAgBC,EACd,EAA8C,CAAC,EAC/C,EAA4B,CAAC,EACT,CACpB,OAAO,MAAO,EAAO,IAAY,CAC/B,IAAMC,EACJ,OAAOC,GAAe,WAClB,MAAMA,EAAWC,EAAOC,CAAO,EAC/BF,EACN,GAAIC,IAAU,2BACZ,OAAOF,EAGT,IAAMI,EAAMP,EAAe,QAAQ,IAAI,EAAGQ,EAAQ,GAAG,EACjDC,EAAOR,EAAQ,IAAIM,CAAG,EACrBE,IACH,EAAOC,EAAiB,CACtB,MACA,UAAWF,EAAQ,UACnB,KAAM,QAAQ,IAAI,EAClB,OAAQG,EAAiB,QAAQ,IAAI,EAAGH,CAAO,EAC/C,QAAST,EAAoB,CAAE,KAAM,QAAQ,IAAI,CAAE,CAAC,CACtD,CAAC,CAAC,CAAC,KAAM,GAAWa,EAAO,IAAI,EAC/BX,EAAQ,IAAIM,EAAKE,CAAI,GAEvB,IAAMI,EAAU,CACd,OAAQ,GAAGf,EAAW,SACtB,YAAa,oBAAoB,MAAMW,EAAK,QAC9C,EAEA,MAAO,CACL,GAAGN,EACH,SAAU,SAAYW,EAAY,MAAMX,EAAO,WAAW,EAAGU,CAAO,CACtE,CACF,CACF,CAGA,SAASC,EACP,EACA,EACU,CAOV,OANIC,IAAa,IAAA,GACR,CAAE,YAAa,CAACF,CAAO,CAAE,EAE9B,MAAM,QAAQE,CAAQ,EACjB,CAAE,YAAa,CAACF,CAAO,EAAG,WAAYE,CAAS,EAEjD,CACL,GAAGA,EACH,YAAa,CAACF,EAAS,GAAIE,EAAS,aAAe,CAAC,CAAE,CACxD,CACF,C"}
|
|
@@ -0,0 +1,4 @@
|
|
|
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 ee,resolve as l,sep as te}from"node:path";import{createPathsMatcher as u,parseTsconfig as d}from"get-tsconfig";import{mkdir as f,readFile as p,readdir as ne,rename as re,rm as m,writeFile as h}from"node:fs/promises";import{ThemeValidationError as ie,defaultTheme as g,isThemeName as _,readTheme as v,writeTheme as y}from"@slabui/theme-core";import{addThemeImport as b,removeThemeImport as ae,removeTokens as oe,renameThemeImport as se,renameThemeSelectors as x,themeFileHeader as S}from"@slabui/theme-core/files";import{randomBytes as C}from"node:crypto";import{homedir as w,hostname as T}from"node:os";const E=`/__slabui/theme`,D=/* @__PURE__ */ new Set([`localhost`,`127.0.0.1`,`[::1]`,`::1`]);function O({css:e,reference:t,root:r,themes:i=c(o(e),`themes`),account:s}){let l=/* @__PURE__ */ new Set,u,d,C,w,T=Promise.resolve(),D=()=>p(e,`utf8`),O=e=>c(i,`${e}.css`),M=t=>{let n=ee(o(e),O(t)).split(te).join(`/`);return n.startsWith(`.`)?n:`./${n}`},R=e=>{let t=T.then(e);return T=t.catch(()=>void 0),t},z=async()=>(await ne(i).catch(()=>[])).filter(e=>e.endsWith(`.css`)).map(e=>e.slice(0,-4)).filter(_).sort(),B=async e=>{if(e!=null&&e!==``){if(typeof e!=`string`||!(await z()).includes(e))throw new N(404,`theme not found: ${String(e)}`);return e}},V=(e,t)=>{let n=`event: ${e}\ndata: ${JSON.stringify(t)}\n\n`;for(let e of l)e.write(n)},H=async()=>{V(`theme`,{tokens:v(await D().catch(()=>``))})},U=async()=>{V(`themes`,{themes:await z()})},W=()=>{if(!(d||l.size===0))try{d=n(i,(e,t)=>{(!t||t.endsWith(`.css`))&&(clearTimeout(w),w=setTimeout(()=>void U(),50))}),d.on(`error`,()=>{d?.close(),d=void 0})}catch{d=void 0}},G=()=>{if(W(),u)return;let t=a(e);u=n(o(e),(e,n)=>{n===t&&(clearTimeout(C),C=setTimeout(()=>void H(),50))})},K=()=>{clearTimeout(C),clearTimeout(w),u?.close(),d?.close(),u=void 0,d=void 0},q=(e,t)=>{t.writeHead(200,{"content-type":`text/event-stream`,"cache-control":`no-cache, no-transform`,connection:`keep-alive`}),t.write(`: connected
|
|
2
|
+
|
|
3
|
+
`),l.add(t),G(),e.on(`close`,()=>{l.delete(t),l.size===0&&K()})},J=async e=>{if(!s)return;let t=await s.get();if(t.status===`signed-out`)throw new N(401,`sign in to your SlabUI account to save`);if(e&&t.status!==`pro`)throw new N(403,`this needs SlabUI Pro`)},Y=(e,n)=>{let r=t??g;return Object.keys(e).some(t=>Object.keys(e[t]??{}).some(e=>n[t]?.[e]===void 0&&r[t]?.[e]===void 0))},X=async(n,r)=>{let i=await P(n);if(!F(i))return L(r,400,{error:`expected { tokens: { theme, light, dark } }`});let a=await B(i.theme),o=a?O(a):e;await J(!!a||Y(i.tokens,v(await p(o,`utf8`),{theme:a})));let s=R(async()=>{let e=await p(o,`utf8`),{css:n,changes:r}=await y(e,i.tokens,{reference:t,theme:a}),s=i.unset?oe(n,i.unset,{theme:a}):n;return s!==e&&await h(o,s),r});try{L(r,200,{changes:await s})}catch(e){if(e instanceof ie)return L(r,422,{issues:e.issues});throw e}},Z=e=>{if(typeof e!=`string`||!_(e))throw new N(400,`invalid theme name (use letters, numbers and -)`);return e},Q=async(t,n)=>{await J(!0);let r=await P(t),a=Z(I(r)?r.name:void 0),o=await B(I(r)?r.from:void 0);await R(async()=>{if((await z()).includes(a))throw new N(409,`theme already exists: ${a}`);let t=o?v(await p(O(o),`utf8`),{theme:o}):{},{css:n}=await y(S(a),t,{theme:a});await f(i,{recursive:!0}),W(),await h(O(a),n);let r=await D();await h(e,b(r,M(a)))}),L(n,201,{themes:await z()})},$=async(t,n,r)=>{let i=await B(t);if(!i)throw new N(404,`theme not found: ${t}`);await J(!0);let a=await P(n),o=Z(I(a)?a.name:void 0);await R(async()=>{if(o!==i&&(await z()).includes(o))throw new N(409,`theme already exists: ${o}`);let t=await p(O(i),`utf8`);await re(O(i),O(o)),await h(O(o),x(t,i,o));let n=await D();await h(e,se(n,M(i),M(o)))}),L(r,200,{themes:await z()})},ue=async(t,n)=>{let r=await B(t);if(!r)throw new N(404,`theme not found: ${t}`);await J(!0),await R(async()=>{await m(O(r));let t=await D();await h(e,ae(t,M(r)))}),L(n,200,{themes:await z()})},de=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 P(t),i=I(e)?e.origin:void 0;if(typeof i!=`string`||!A(i))throw new N(400,`expected { origin } of a local page`);return L(n,200,{url:r.login(`${i}${E}/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&&V(`account`,{...await r.get(),site:r.site}),n.writeHead(a?200:400,{"content-type":`text/html; charset=utf-8`}),n.end(j(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 V(`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(!k(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 q(n,i);if(a===`/account`||a.startsWith(`/account/`))return s?await de(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 B(e.get(`theme`)),o=v(await D());return L(i,200,{tokens:a?v(await p(O(a),`utf8`),{theme:a}):o,...a&&{theme:a,base:o},defaults:t??g,installed:r?await ce(r):null,themes:await z()})}if(a===`/`&&n.method===`PATCH`)return await X(n,i);if(a===`/themes`&&n.method===`POST`)return await Q(n,i);let e=a.match(/^\/themes\/([^/]+)$/)?.[1];if(e&&n.method===`PATCH`)return await $(decodeURIComponent(e),n,i);if(e&&n.method===`DELETE`)return await ue(decodeURIComponent(e),i);L(i,404,{error:`not found`})}catch(t){if(le(t))return L(i,404,{error:`CSS file not found: ${e}`});if(t instanceof N)return L(i,t.status,{error:t.message});L(i,500,{error:t instanceof Error?t.message:String(t)})}},close:()=>{K();for(let e of l)e.end();l.clear()}}}function k(e){if(![e.headers[`x-forwarded-host`],e.headers.host].flat().filter(e=>typeof e==`string`).every(e=>D.has(M(e))))return!1;let t=e.headers.origin;if(t===void 0)return!0;try{return D.has(new URL(t).hostname)}catch{return!1}}function A(e){try{let t=new URL(e);return t.protocol===`http:`&&D.has(t.hostname)}catch{return!1}}function j(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 M(e){let t=e.split(`,`)[0]?.trim()??``;return t.startsWith(`[`)?t.slice(0,t.indexOf(`]`)+1):t.split(`:`)[0]??``}async function ce(e){try{let t=JSON.parse(await p(c(e,`slabui.json`),`utf8`));return Object.keys(t.items??{}).map(e=>e.replace(/^@[^/]+\//,``))}catch{return null}}var N=class extends Error{status;constructor(e,t){super(t),this.status=e}};async function P(e){let t=0,n=[];for await(let r of e){if(t+=r.length,t>1e5)throw new N(413,`body too large`);n.push(r)}try{return JSON.parse(Buffer.concat(n).toString(`utf8`))}catch{throw new N(400,`invalid JSON`)}}function F(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 le(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=w()){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 p(e,`utf8`));return typeof t==`string`&&t?t:void 0}catch{return}}async function B(e,t=R()){await f(i.dirname(t),{recursive:!0}),await h(t,`${JSON.stringify({token:e})}\n`,{mode:384})}async function V(e=R()){let t=await z(e)!==void 0;return await m(e,{force:!0}),t}const H=`https://slabui.com/r`;function U(e){if(e===void 0)return H;try{let n=JSON.parse(t(i.join(e,`slabui.json`),`utf8`)).registries?.[`@slabui`],r=typeof n==`string`?n:n?.url;return typeof r==`string`&&r?r:H}catch{return H}}async function W({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 G(){return C(24).toString(`base64url`)}function K({registryUrl:e,callback:t,state:n,client:r,host:i=T()}){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 q=6e5;function J({root:e,env:t=process.env,fetch:n=globalThis.fetch,file:r=R(t),registryUrl:i=U(e)}={}){let a,o=/* @__PURE__ */ new Map,s=async()=>{let e=t.SLABUI_TOKEN||await z(r);if(!e)return{status:`signed-out`};try{let t=await W({registryUrl:i,token:e,fetch:n});if(t===null)return{status:`signed-out`};let{access:r,...a}=t;return{status:r.includes(`pro`)?`pro`:`free`,...a}}catch{let e=a?.state;return e&&e.status!==`signed-out`?{...e,status:e.status===`pro`?`pro`:`offline`}:{status:`offline`}}};return{site:new URL(i).origin,async get(){return(!a||a.expires<Date.now())&&(a={state:await s(),expires:Date.now()+3e5}),a.state},login(e){let t=Date.now();for(let[e,n]of o)t-n>q&&o.delete(e);let n=G();return o.set(n,t),K({registryUrl:i,callback:e,state:n,client:`SlabUI theme editor`})},async complete(e,t){let n=o.get(e);return n===void 0||Date.now()-n>q?!1:(o.delete(e),await B(t,r),a=void 0,!0)},async logout(){await V(r),a=void 0}}}function Y(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=u({path:i,config:d(i)})?.(n)[0];if(e)return e}catch{}break}}return l(t,n)}function X(e){try{return JSON.parse(t(c(e,`slabui.json`),`utf8`))}catch{return}}function Z(e,t){if(t)return Y(e,t);let n=X(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 Y(e,n.css)}function Q(e,t={}){if(t.dir)return Y(e,t.dir);let n=X(e)?.aliases?.styles;return c(typeof n==`string`&&n!==``?Y(e,n):o(Z(e,t.css)),`themes`)}async function $(e){let t=O(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{E as a,J as i,Q as n,O as o,$ as r,Z as t};
|
|
4
|
+
//# sourceMappingURL=server-CVSf3fOz.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-CVSf3fOz.mjs","names":["hostname","path","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","addsTokens","known","reference","defaultTheme","tokens","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","DEFAULT_REGISTRY_URL","defaultRegistryUrl","entry","readFileSync","fetchAccount","registryUrl","response","fetch","headers","newState","randomBytes","authorizeUrl","hostname","callback","STATE_TTL_MS","createAccountSource","cached","pending","resolve","\"SLABUI_TOKEN\"","profile","access","last","now","created","resolveProjectFile","isAbsolute","existsSync","match","createPathsMatcher","parseTsconfig","readConfig","resolveCssPath","resolveThemesDir","options","styles","startThemeServer","handler","server","createServer","fail"],"sources":["../src/server/handler.ts","../../account/src/credentials.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// 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 // 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 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 { 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\"\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}\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 type Account,\n authorizeUrl,\n credentialsFile,\n defaultRegistryUrl,\n fetchAccount,\n newState,\n readSavedToken,\n removeToken,\n saveToken,\n TOKEN_ENV,\n} from \"@slabui/account\"\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.\nexport function createAccountSource({\n root,\n env = process.env,\n fetch = globalThis.fetch,\n file = credentialsFile(env),\n registryUrl = defaultRegistryUrl(root),\n}: {\n root?: string\n env?: Record<string, string | undefined>\n fetch?: typeof globalThis.fetch\n file?: string\n registryUrl?: string\n} = {}): AccountSource {\n let cached: { state: AccountState; expires: number } | undefined\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, ...profile } = account\n return { status: access.includes(\"pro\") ? \"pro\" : \"free\", ...profile }\n } catch {\n // Sem rede: mantém o último plano conhecido, senão vale como free.\n const last = cached?.state\n return last && last.status !== \"signed-out\"\n ? { ...last, status: last.status === \"pro\" ? \"pro\" : \"offline\" }\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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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ThemeTokens } from "@slabui/theme-core";
|
|
2
|
+
import { Plugin } from "vite";
|
|
3
|
+
export type ThemesPluginOptions = {
|
|
4
|
+
css?: string;
|
|
5
|
+
reference?: ThemeTokens;
|
|
6
|
+
dir?: string;
|
|
7
|
+
theme?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function themes(options?: ThemesPluginOptions): Plugin[];
|
|
10
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/index.ts"],"mappings":";;YAiBY;EAEV;EACA,YAAY;EAIZ;EAGA;;wBAMc,OAAO,UAAS,sBAA2B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as e,i as t,n,o as r,t as i}from"../server-CVSf3fOz.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
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["THEME_PATH","createAccountSource","createThemeHandler","resolveCssPath","themes","config","themesDir","resolveThemesDir","root","options","dev","handler","req","res","resolved","page","theme","loadEnv","THEME_ENV","THEME_ATTRIBUTE","isThemeName","dir","existsSync","join"],"sources":["../../src/vite/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\"\nimport { join } from \"node:path\"\n\nimport { isThemeName, THEME_ATTRIBUTE, THEME_ENV } from \"@slabui/theme-core\"\n\nimport { loadEnv, type Plugin, type ResolvedConfig } from \"vite\"\n\nimport type { ThemeTokens } from \"@slabui/theme-core\"\n\nimport {\n createAccountSource,\n createThemeHandler,\n resolveCssPath,\n resolveThemesDir,\n THEME_PATH,\n} from \"../server\"\n\nexport type ThemesPluginOptions = {\n // CSS global, relativo à raiz do Vite. Padrão: o `css` do `slabui.json`.\n css?: string\n reference?: ThemeTokens\n // Pasta dos temas (`<nome>.css`), relativa à raiz ou com alias do tsconfig.\n // Padrão: `themes/` no alias `styles` do `slabui.json` ou, sem ele, ao lado\n // do CSS global.\n dir?: string\n // Tema aplicado no `<html>`. Padrão: a variável `SLABUI_THEME` (ambiente ou\n // arquivos `.env`).\n theme?: string\n}\n\n// Monta o servidor do tema no servidor de desenvolvimento do Vite, em\n// `/__slabui/theme`, e aplica o tema escolhido no `<html>` do `index.html`\n// (no dev e no build).\nexport function themes(options: ThemesPluginOptions = {}): Plugin[] {\n let config: ResolvedConfig | undefined\n\n const themesDir = (root: string) => resolveThemesDir(root, options)\n\n const server: Plugin = {\n name: \"slabui:themes\",\n apply: \"serve\",\n configureServer(dev) {\n const root = dev.config.root\n const handler = createThemeHandler({\n css: resolveCssPath(root, options.css),\n reference: options.reference,\n root,\n themes: themesDir(root),\n account: createAccountSource({ root }),\n })\n dev.middlewares.use(THEME_PATH, (req, res) => {\n void handler.handle(req, res)\n })\n dev.httpServer?.once(\"close\", handler.close)\n },\n }\n\n const html: Plugin = {\n name: \"slabui:themes:html\",\n configResolved(resolved) {\n config = resolved\n },\n transformIndexHtml(page) {\n if (!config) {\n return page\n }\n const theme =\n options.theme ??\n loadEnv(config.mode, config.envDir || config.root, \"SLABUI_\")[THEME_ENV]\n // O atributo escrito no `index.html` vale mais que a variável.\n if (!theme || new RegExp(`<html[^>]*\\\\s${THEME_ATTRIBUTE}=`).test(page)) {\n return page\n }\n if (!isThemeName(theme)) {\n config.logger.warn(\n `@slabui/theme-react: invalid theme name \"${theme}\".`,\n )\n return page\n }\n try {\n const dir = themesDir(config.root)\n if (!existsSync(join(dir, `${theme}.css`))) {\n config.logger.warn(\n `@slabui/theme-react: theme \"${theme}\" not found in ${dir}.`,\n )\n }\n } catch {\n // Sem o CSS global configurado, não dá para conferir a pasta.\n }\n return page.replace(\n /<html(\\s|>)/i,\n `<html ${THEME_ATTRIBUTE}=\"${theme}\"$1`,\n )\n },\n }\n\n return [server, html]\n}\n"],"mappings":"A,4PAiCA,SAAgBI,EAAO,EAA+B,CAAC,EAAa,CAClE,IAAIC,EAEEC,EAAa,GAAiBC,EAAiBC,EAAMC,CAAO,EA4DlE,MAAO,CAAC,CAzDN,KAAM,gBACN,MAAO,QACP,gBAAgB,EAAK,CACnB,IAAMD,EAAOE,EAAI,OAAO,KAClBC,EAAUT,EAAmB,CACjC,IAAKC,EAAeK,EAAMC,EAAQ,GAAG,EACrC,UAAWA,EAAQ,UACnB,OACA,OAAQH,EAAUE,CAAI,EACtB,QAASP,EAAoB,CAAE,MAAK,CAAC,CACvC,CAAC,EACDS,EAAI,YAAY,IAAIV,GAAa,EAAK,IAAQ,CAC5CW,EAAa,OAAOC,EAAKC,CAAG,CAC9B,CAAC,EACDH,EAAI,YAAY,KAAK,QAASC,EAAQ,KAAK,CAC7C,CA0CW,EAAG,CAtCd,KAAM,qBACN,eAAe,EAAU,CACvB,EAASG,CACX,EACA,mBAAmB,EAAM,CACvB,GAAI,CAACT,EACH,OAAOU,EAET,IAAMC,EACJP,EAAQ,OACRQ,EAAQZ,EAAO,KAAMA,EAAO,QAAUA,EAAO,KAAM,SAAS,CAAC,CAACa,GAEhE,GAAI,CAACF,GAAa,OAAO,gBAAgBG,EAAgB,EAAE,CAAC,CAAC,KAAKJ,CAAI,EACpE,OAAOA,EAET,GAAI,CAACK,EAAYJ,CAAK,EAIpB,OAHAX,EAAO,OAAO,KACZ,4CAA4CW,EAAM,GACpD,EACOD,EAET,GAAI,CACF,IAAMM,EAAMf,EAAUD,EAAO,IAAI,EAC5BiB,EAAWC,EAAKF,EAAK,GAAGL,EAAM,KAAK,CAAC,GACvCX,EAAO,OAAO,KACZ,+BAA+BW,EAAM,iBAAiBK,EAAI,EAC5D,CAEJ,MAAQ,CAER,CACA,OAAON,EAAK,QACV,eACA,SAASI,EAAgB,IAAIH,EAAM,IACrC,CACF,CAGiB,CAAC,CACtB,C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slabui/theme-react",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Live theme editor for SlabUI and its Vite and Next plugins",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"slabui",
|
|
7
|
+
"theme",
|
|
8
|
+
"theme-editor",
|
|
9
|
+
"theming",
|
|
10
|
+
"css-variables",
|
|
11
|
+
"design-tokens",
|
|
12
|
+
"tailwindcss",
|
|
13
|
+
"react",
|
|
14
|
+
"vite",
|
|
15
|
+
"vite-plugin",
|
|
16
|
+
"nextjs"
|
|
17
|
+
],
|
|
18
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
19
|
+
"author": "André Felipe Schulle <andrefelipeschulle@icloud.com>",
|
|
20
|
+
"homepage": "https://slabui.com/docs/themes/introduction",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/slabui/ui.git",
|
|
24
|
+
"directory": "packages/theme-react"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/slabui/ui/issues"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"exports": {
|
|
31
|
+
"./editor": {
|
|
32
|
+
"types": "./dist/editor/index.d.mts",
|
|
33
|
+
"import": "./dist/editor/index.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./next": {
|
|
36
|
+
"types": "./dist/next/index.d.mts",
|
|
37
|
+
"import": "./dist/next/index.mjs"
|
|
38
|
+
},
|
|
39
|
+
"./vite": {
|
|
40
|
+
"types": "./dist/vite/index.d.mts",
|
|
41
|
+
"import": "./dist/vite/index.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"sideEffects": false,
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"CHANGELOG.md"
|
|
50
|
+
],
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@slabui/theme-core": "0.0.1",
|
|
53
|
+
"get-tsconfig": "4.14.3"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"next": ">=15",
|
|
57
|
+
"react": ">=19",
|
|
58
|
+
"vite": ">=6"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"next": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"react": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"vite": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"engines": {
|
|
72
|
+
"node": ">=22.12.0"
|
|
73
|
+
},
|
|
74
|
+
"publishConfig": {
|
|
75
|
+
"access": "public"
|
|
76
|
+
}
|
|
6
77
|
}
|