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 +43 -5
- package/package.json +1 -1
- package/server/server.js +32 -2
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
<
|
|
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
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.
|
|
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,
|