aiplang 2.11.10 → 2.11.11

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/bin/aiplang.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs')
5
5
  const path = require('path')
6
6
  const http = require('http')
7
7
 
8
- const VERSION = '2.11.10'
8
+ const VERSION = '2.11.11'
9
9
  const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
10
10
  const cmd = process.argv[2]
11
11
  const args = process.argv.slice(3)
@@ -686,7 +686,7 @@ function generateTypes(app, srcFile) {
686
686
  }
687
687
 
688
688
  lines.push(`// ── aiplang version ──────────────────────────────────────────`)
689
- lines.push(`export const AIPLANG_VERSION = '2.11.10'`)
689
+ lines.push(`export const AIPLANG_VERSION = '2.11.11'`)
690
690
  lines.push(``)
691
691
  return lines.join('\n')
692
692
  }
@@ -936,7 +936,18 @@ console.error(`\n ✗ Unknown command: ${cmd}\n Run aiplang --help\n`)
936
936
  process.exit(1)
937
937
 
938
938
  function parsePages(src) {
939
- return src.split(/\n---\n/).map(s=>parsePage(s.trim())).filter(Boolean)
939
+ // Extrair variáveis globais ~var name = value
940
+ const gVars = {}
941
+ src.split('\n').forEach(line => {
942
+ const vm = line.trim().match(/^~var\s+(\w+)\s*=\s*(.+)$/)
943
+ if (vm) gVars[vm[1].trim()] = vm[2].trim().replace(/^["']|["']$/g,'')
944
+ })
945
+ // Expandir $var em todo o src
946
+ function expand(s) {
947
+ if (!Object.keys(gVars).length) return s
948
+ return s.replace(/\$(\w+)/g, (m,k) => gVars[k]!==undefined ? gVars[k] : m)
949
+ }
950
+ return src.split(/\n---\n/).map(s=>parsePage(expand(s.trim()))).filter(Boolean)
940
951
  }
941
952
 
942
953
  function parsePage(src) {
@@ -944,6 +955,7 @@ function parsePage(src) {
944
955
  if(!lines.length) return null
945
956
  const p={id:'page',theme:'dark',route:'/',customTheme:null,themeVars:null,state:{},queries:[],blocks:[]}
946
957
  for(const line of lines) {
958
+ if(line.startsWith('~var ')) continue // já processado globalmente
947
959
  if(line.startsWith('%')) {
948
960
  const pts=line.slice(1).trim().split(/\s+/)
949
961
  p.id=pts[0]||'page'; p.route=pts[2]||'/'
@@ -1206,6 +1218,18 @@ function applyMods(html, b) {
1206
1218
 
1207
1219
  function renderPage(page, allPages) {
1208
1220
  const needsJS=page.queries.length>0||page.blocks.some(b=>['table','list','form','if','btn','select','faq'].includes(b.kind))
1221
+ // Expandir variáveis $var no conteúdo da página
1222
+ const _vars = (page.appVars||{})
1223
+ function expandVars(s) {
1224
+ if (!s || typeof s !== 'string' || !Object.keys(_vars).length) return s
1225
+ return s.replace(/\$(\w+)/g, (m, k) => _vars[k] !== undefined ? _vars[k] : m)
1226
+ }
1227
+ // Pré-processar rawLine de cada bloco
1228
+ page.blocks.forEach(b => {
1229
+ if (b.rawLine) b.rawLine = expandVars(b.rawLine)
1230
+ if (b.title) b.title = expandVars(b.title)
1231
+ if (b.sub) b.sub = expandVars(b.sub)
1232
+ })
1209
1233
  const body=page.blocks.map(b=>{try{return applyMods(renderBlock(b,page),b)}catch(e){console.error('[aiplang] Block render error:',b.kind,e.message);return ''}}).join('')
1210
1234
 
1211
1235
  const tableBlocks = page.blocks.filter(b => b.kind === 'table' && b.binding && b.cols && b.cols.length)
@@ -1260,8 +1284,12 @@ function renderPage(page, allPages) {
1260
1284
  <meta name="description" content="${esc((()=>{const h=page.blocks.find(b=>b.kind==='hero');if(!h)return '';const m=h.rawLine&&h.rawLine.match(/\{([^}]+)\}/);if(!m)return '';const p=m[1].split('>');const s=p[0].split('|')[1];return s?s.trim():''})())}">
1261
1285
  <meta property="og:title" content="${_title}">
1262
1286
  <meta property="og:type" content="website">
1287
+ <link rel="preconnect" href="https://fonts.googleapis.com">
1288
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1263
1289
  <meta property="og:type" content="website">
1264
- <style>${css(page.theme)}${customVars}${themeVarCSS}</style>
1290
+ <link rel="preconnect" href="https://fonts.googleapis.com">
1291
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1292
+ <style>${minCSS(css(page.theme)+customVars+themeVarCSS)}</style>
1265
1293
  </head>
1266
1294
  <body>
1267
1295
  ${body}${hydrate}
@@ -1862,6 +1890,16 @@ function genThemeVarCSS(t) {
1862
1890
  }
1863
1891
 
1864
1892
 
1893
+ function minCSS(s) {
1894
+ if(!s) return ''
1895
+ return s
1896
+ .replace(/\/\*[\s\S]*?\*\//g,'')
1897
+ .replace(/\s*([{};:,>~+|])\s*/g,'$1')
1898
+ .replace(/\s+/g,' ')
1899
+ .replace(/;}/g,'}')
1900
+ .trim()
1901
+ }
1902
+
1865
1903
  function css(theme) {
1866
1904
  // ════════════════════════════════════════════════════════════════
1867
1905
  // GEIST DESIGN SYSTEM — Vercel's design language for aiplang
@@ -1869,7 +1907,7 @@ function css(theme) {
1869
1907
  // Fonts: Geist Sans + Geist Mono (Google Fonts)
1870
1908
  // ════════════════════════════════════════════════════════════════
1871
1909
  const base = `
1872
- @import url('https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700;800;900&family=Geist+Mono:wght@400;500;700&display=swap');
1910
+ @import url('https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700;800;900&family=Geist+Mono:wght@400;500;700&display=swap&display=swap');
1873
1911
 
1874
1912
  :root {
1875
1913
  --geist-font:'Geist','Geist Sans',-apple-system,BlinkMacSystemFont,system-ui,sans-serif;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiplang",
3
- "version": "2.11.10",
3
+ "version": "2.11.11",
4
4
  "description": "AI-first web language. One .aip file = complete app. Frontend + backend + database + auth.",
5
5
  "keywords": [
6
6
  "aiplang",
package/server/server.js CHANGED
@@ -1806,6 +1806,25 @@ class AiplangServer {
1806
1806
  if (this._requestLogger) this._requestLogger(req, Date.now() - _start)
1807
1807
  return
1808
1808
  }
1809
+ // Servir arquivos estaticos /public/*
1810
+ const _sext = req.path && req.path.match(/\.([a-zA-Z0-9]+)$/)
1811
+ if (_sext && req.method === 'GET') {
1812
+ const _appDir = server && server._appDir ? server._appDir : process.cwd()
1813
+ const _cands = [
1814
+ require('path').join(_appDir,'public',req.path),
1815
+ require('path').join(process.cwd(),'public',req.path),
1816
+ require('path').join(_appDir,req.path.slice(1)),
1817
+ ]
1818
+ for (const _fp of _cands) {
1819
+ if (require('fs').existsSync(_fp)) {
1820
+ const _mime = getMimeType(_fp)
1821
+ const _buf = require('fs').readFileSync(_fp)
1822
+ res.setHeader('Cache-Control','public,max-age=86400')
1823
+ res.writeHead(200,{'Content-Type':_mime,'Content-Length':_buf.length})
1824
+ return res.end(_buf)
1825
+ }
1826
+ }
1827
+ }
1809
1828
  const _404b='{"error":"Not found"}'; res.writeHead(404,{'Content-Type':'application/json','Content-Length':18}); res.end(_404b)
1810
1829
  }
1811
1830
 
@@ -2137,6 +2156,17 @@ function getMime(filename) {
2137
2156
  return types[ext] || 'application/octet-stream'
2138
2157
  }
2139
2158
 
2159
+ // Cache-Control helper
2160
+ function _cacheHeaders(res, type, maxAge=0) {
2161
+ if (type && (type.startsWith('image/') || type.startsWith('font/') || type.endsWith('javascript') || type.endsWith('css'))) {
2162
+ res.setHeader('Cache-Control', `public, max-age=${maxAge||86400}, immutable`)
2163
+ } else if (type && type.includes('html')) {
2164
+ res.setHeader('Cache-Control', 'no-cache')
2165
+ } else {
2166
+ res.setHeader('Cache-Control', 'public, max-age=3600')
2167
+ }
2168
+ }
2169
+
2140
2170
  // ═══════════════════════════════════════════════════════════════════
2141
2171
  // PLUGIN SYSTEM — ~plugin ./my-plugin.js | ~use rate-limit max=100
2142
2172
  // ═══════════════════════════════════════════════════════════════════
@@ -2387,7 +2417,7 @@ async function startServer(aipFile, port = 3000) {
2387
2417
  // Static assets
2388
2418
  srv.addRoute('GET', '/aiplang-hydrate.js', (req, res) => {
2389
2419
  const p = path.join(__dirname, '..', 'runtime', 'aiplang-hydrate.js')
2390
- if (fs.existsSync(p)) { res.writeHead(200,{'Content-Type':'application/javascript'}); res.end(fs.readFileSync(p)) }
2420
+ if (fs.existsSync(p)) { res.setHeader('Cache-Control','public,max-age=86400'); res.writeHead(200,{'Content-Type':'application/javascript'}); res.end(fs.readFileSync(p)) }
2391
2421
  else { res.writeHead(404); res.end('// not found') }
2392
2422
  })
2393
2423
 
@@ -2432,7 +2462,7 @@ async function startServer(aipFile, port = 3000) {
2432
2462
  })
2433
2463
 
2434
2464
  srv.addRoute('GET', '/health', (req, res) => res.json(200, {
2435
- status:'ok', version:'2.11.10',
2465
+ status:'ok', version:'2.11.11',
2436
2466
  models: app.models.map(m=>m.name),
2437
2467
  routes: app.apis.length, pages: app.pages.length,
2438
2468
  admin: app.admin?.prefix || null,