@yakuzaa/jade 0.1.5 → 0.1.6
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/commands/compilar.js +29 -3
- package/commands/html.js +374 -74
- package/package.json +3 -3
package/commands/compilar.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { spawn } from 'child_process';
|
|
15
15
|
import { resolve, basename, dirname, join } from 'path';
|
|
16
|
-
import { existsSync } from 'fs';
|
|
16
|
+
import { existsSync, readFileSync } from 'fs';
|
|
17
17
|
import { createRequire } from 'module';
|
|
18
18
|
import { fileURLToPath } from 'url';
|
|
19
19
|
import { gerarHTML_dist } from './html.js';
|
|
@@ -99,7 +99,33 @@ export async function compilar(args) {
|
|
|
99
99
|
|
|
100
100
|
// Geração de HTML (a menos que --so-wasm)
|
|
101
101
|
if (!soWasm) {
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
// Lê jade.config.json se existir (tema, nome do projeto)
|
|
103
|
+
let tema = {};
|
|
104
|
+
let nomeProjeto = basename(arquivo, '.jd');
|
|
105
|
+
const configCandidatos = [
|
|
106
|
+
join(process.cwd(), 'config', 'jade.config.json'),
|
|
107
|
+
join(process.cwd(), 'jade.config.json'),
|
|
108
|
+
];
|
|
109
|
+
for (const cfg of configCandidatos) {
|
|
110
|
+
if (existsSync(cfg)) {
|
|
111
|
+
try {
|
|
112
|
+
const c = JSON.parse(readFileSync(cfg, 'utf-8'));
|
|
113
|
+
if (c.nome) nomeProjeto = c.nome;
|
|
114
|
+
if (c.tema) tema = c.tema;
|
|
115
|
+
} catch { /* config inválido — ignora */ }
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Seeds: procura seeds.json na raiz do projeto
|
|
121
|
+
const seedsOrigem = (() => {
|
|
122
|
+
const candidatos = [
|
|
123
|
+
join(process.cwd(), 'seeds.json'),
|
|
124
|
+
join(process.cwd(), 'seeds', 'seeds.json'),
|
|
125
|
+
];
|
|
126
|
+
return candidatos.find(p => existsSync(p)) ?? null;
|
|
127
|
+
})();
|
|
128
|
+
|
|
129
|
+
await gerarHTML_dist({ prefixo, nome: nomeProjeto, tema, seedsOrigem });
|
|
104
130
|
}
|
|
105
131
|
}
|
package/commands/html.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Não depende de bundler no projeto do usuário.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { writeFileSync, copyFileSync, existsSync, mkdirSync } from 'fs';
|
|
15
15
|
import { resolve, dirname, join, basename } from 'path';
|
|
16
16
|
import { createRequire } from 'module';
|
|
17
17
|
|
|
@@ -19,25 +19,22 @@ const require = createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// ── Cores ─────────────────────────────────────────────────────────────────────
|
|
21
21
|
|
|
22
|
-
const verde
|
|
23
|
-
const azul
|
|
24
|
-
const dim
|
|
22
|
+
const verde = (s) => `\x1b[1;32m${s}\x1b[0m`;
|
|
23
|
+
const azul = (s) => `\x1b[34m${s}\x1b[0m`;
|
|
24
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
25
25
|
const amarelo = (s) => `\x1b[1;33m${s}\x1b[0m`;
|
|
26
|
-
const ok
|
|
27
|
-
const aviso
|
|
26
|
+
const ok = () => verde('✓');
|
|
27
|
+
const aviso = () => amarelo('⚠');
|
|
28
28
|
|
|
29
29
|
// ── Localiza o browser.js do runtime instalado ────────────────────────────────
|
|
30
30
|
|
|
31
31
|
function localizarRuntime() {
|
|
32
|
-
// Tenta via require.resolve (funciona quando @yakuzaa/jade-runtime está instalado)
|
|
33
32
|
try {
|
|
34
33
|
const pkgPath = require.resolve('@yakuzaa/jade-runtime/package.json');
|
|
35
34
|
const pkgDir = dirname(pkgPath);
|
|
36
35
|
const browser = join(pkgDir, 'dist', 'browser.js');
|
|
37
36
|
if (existsSync(browser)) return browser;
|
|
38
|
-
} catch {
|
|
39
|
-
// não instalado via npm — tenta caminho relativo ao monorepo
|
|
40
|
-
}
|
|
37
|
+
} catch { /* não instalado via npm */ }
|
|
41
38
|
|
|
42
39
|
// Fallback: monorepo local (desenvolvimento)
|
|
43
40
|
const mono = resolve(dirname(new URL(import.meta.url).pathname), '..', '..', 'jade-runtime', 'dist', 'browser.js');
|
|
@@ -46,85 +43,376 @@ function localizarRuntime() {
|
|
|
46
43
|
return null;
|
|
47
44
|
}
|
|
48
45
|
|
|
46
|
+
// ── CSS custom properties (tema em português) ─────────────────────────────────
|
|
47
|
+
|
|
48
|
+
function gerarVariaveisCSS(tema = {}) {
|
|
49
|
+
const t = {
|
|
50
|
+
corPrimaria: tema.corPrimaria ?? '#2563eb',
|
|
51
|
+
corSecundaria: tema.corSecundaria ?? '#7c3aed',
|
|
52
|
+
corTexto: tema.corTexto ?? '#0f172a',
|
|
53
|
+
corTextoMuted: tema.corTextoMuted ?? '#64748b',
|
|
54
|
+
corFundo: tema.corFundo ?? '#f8fafc',
|
|
55
|
+
corFundoCard: tema.corFundoCard ?? '#ffffff',
|
|
56
|
+
corFundoNav: tema.corFundoNav ?? '#1e293b',
|
|
57
|
+
corBorda: tema.corBorda ?? '#e2e8f0',
|
|
58
|
+
corDestaque: tema.corDestaque ?? '#dbeafe',
|
|
59
|
+
corSucesso: tema.corSucesso ?? '#16a34a',
|
|
60
|
+
corErro: tema.corErro ?? '#dc2626',
|
|
61
|
+
corAviso: tema.corAviso ?? '#d97706',
|
|
62
|
+
raio: tema.raio ?? '8px',
|
|
63
|
+
fonte: tema.fonte ?? "system-ui, -apple-system, 'Segoe UI', sans-serif",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return `
|
|
67
|
+
--jade-cor-primaria: ${t.corPrimaria};
|
|
68
|
+
--jade-cor-secundaria: ${t.corSecundaria};
|
|
69
|
+
--jade-cor-texto: ${t.corTexto};
|
|
70
|
+
--jade-cor-texto-muted: ${t.corTextoMuted};
|
|
71
|
+
--jade-cor-fundo: ${t.corFundo};
|
|
72
|
+
--jade-cor-fundo-card: ${t.corFundoCard};
|
|
73
|
+
--jade-cor-fundo-nav: ${t.corFundoNav};
|
|
74
|
+
--jade-cor-borda: ${t.corBorda};
|
|
75
|
+
--jade-cor-destaque: ${t.corDestaque};
|
|
76
|
+
--jade-cor-sucesso: ${t.corSucesso};
|
|
77
|
+
--jade-cor-erro: ${t.corErro};
|
|
78
|
+
--jade-cor-aviso: ${t.corAviso};
|
|
79
|
+
--jade-raio: ${t.raio};
|
|
80
|
+
--jade-fonte: ${t.fonte};`.trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── CSS do shell (layout, nav, conteúdo) ──────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
function gerarCSS(tema = {}) {
|
|
86
|
+
return `
|
|
87
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
88
|
+
|
|
89
|
+
:root {
|
|
90
|
+
${gerarVariaveisCSS(tema)}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
body {
|
|
94
|
+
font-family: var(--jade-fonte);
|
|
95
|
+
background: var(--jade-cor-fundo);
|
|
96
|
+
color: var(--jade-cor-texto);
|
|
97
|
+
min-height: 100dvh;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Layout principal */
|
|
101
|
+
#jade-app {
|
|
102
|
+
display: flex;
|
|
103
|
+
min-height: 100dvh;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Nav lateral */
|
|
107
|
+
#jade-nav {
|
|
108
|
+
width: 240px;
|
|
109
|
+
min-height: 100dvh;
|
|
110
|
+
background: var(--jade-cor-fundo-nav);
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
flex-shrink: 0;
|
|
114
|
+
position: sticky;
|
|
115
|
+
top: 0;
|
|
116
|
+
height: 100dvh;
|
|
117
|
+
overflow-y: auto;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#jade-nav-header {
|
|
121
|
+
padding: 20px 16px 12px;
|
|
122
|
+
border-bottom: 1px solid rgba(255,255,255,0.08);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#jade-nav-titulo {
|
|
126
|
+
font-size: 0.875rem;
|
|
127
|
+
font-weight: 700;
|
|
128
|
+
color: #fff;
|
|
129
|
+
letter-spacing: 0.04em;
|
|
130
|
+
text-transform: uppercase;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#jade-nav-versao {
|
|
134
|
+
font-size: 0.7rem;
|
|
135
|
+
color: rgba(255,255,255,0.35);
|
|
136
|
+
margin-top: 2px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#jade-nav-lista {
|
|
140
|
+
flex: 1;
|
|
141
|
+
padding: 8px 8px;
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-direction: column;
|
|
144
|
+
gap: 2px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.jade-nav-item {
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
gap: 10px;
|
|
151
|
+
width: 100%;
|
|
152
|
+
padding: 9px 12px;
|
|
153
|
+
border: none;
|
|
154
|
+
border-radius: var(--jade-raio);
|
|
155
|
+
background: transparent;
|
|
156
|
+
color: rgba(255,255,255,0.6);
|
|
157
|
+
font-size: 0.875rem;
|
|
158
|
+
font-family: var(--jade-fonte);
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
text-align: left;
|
|
161
|
+
transition: background 0.15s, color 0.15s;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.jade-nav-item:hover {
|
|
165
|
+
background: rgba(255,255,255,0.07);
|
|
166
|
+
color: rgba(255,255,255,0.9);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.jade-nav-ativo {
|
|
170
|
+
background: var(--jade-cor-primaria) !important;
|
|
171
|
+
color: #fff !important;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.jade-nav-icone { font-size: 1rem; }
|
|
175
|
+
|
|
176
|
+
/* Área de conteúdo */
|
|
177
|
+
#jade-conteudo {
|
|
178
|
+
flex: 1;
|
|
179
|
+
min-width: 0;
|
|
180
|
+
padding: 24px;
|
|
181
|
+
overflow-y: auto;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Carregando */
|
|
185
|
+
#jade-carregando {
|
|
186
|
+
display: flex;
|
|
187
|
+
align-items: center;
|
|
188
|
+
justify-content: center;
|
|
189
|
+
min-height: 100dvh;
|
|
190
|
+
color: var(--jade-cor-texto-muted);
|
|
191
|
+
font-size: 0.9rem;
|
|
192
|
+
gap: 10px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.jade-spinner {
|
|
196
|
+
width: 20px; height: 20px;
|
|
197
|
+
border: 2px solid var(--jade-cor-borda);
|
|
198
|
+
border-top-color: var(--jade-cor-primaria);
|
|
199
|
+
border-radius: 50%;
|
|
200
|
+
animation: jade-giro 0.7s linear infinite;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@keyframes jade-giro { to { transform: rotate(360deg); } }
|
|
204
|
+
|
|
205
|
+
/* Mobile: nav vira barra inferior */
|
|
206
|
+
@media (max-width: 640px) {
|
|
207
|
+
#jade-app { flex-direction: column-reverse; }
|
|
208
|
+
|
|
209
|
+
#jade-nav {
|
|
210
|
+
width: 100%;
|
|
211
|
+
min-height: auto;
|
|
212
|
+
height: auto;
|
|
213
|
+
position: sticky;
|
|
214
|
+
bottom: 0;
|
|
215
|
+
top: auto;
|
|
216
|
+
border-top: 1px solid rgba(255,255,255,0.1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#jade-nav-header { display: none; }
|
|
220
|
+
|
|
221
|
+
#jade-nav-lista {
|
|
222
|
+
flex-direction: row;
|
|
223
|
+
overflow-x: auto;
|
|
224
|
+
padding: 6px 8px;
|
|
225
|
+
gap: 4px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.jade-nav-item {
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
gap: 2px;
|
|
231
|
+
padding: 6px 12px;
|
|
232
|
+
font-size: 0.7rem;
|
|
233
|
+
white-space: nowrap;
|
|
234
|
+
flex-shrink: 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.jade-nav-icone { font-size: 1.2rem; }
|
|
238
|
+
|
|
239
|
+
#jade-conteudo { padding: 16px; }
|
|
240
|
+
}
|
|
241
|
+
`.trim();
|
|
242
|
+
}
|
|
243
|
+
|
|
49
244
|
// ── Bootstrap JS inline no index.html ────────────────────────────────────────
|
|
50
245
|
|
|
51
|
-
function
|
|
246
|
+
function gerarBootstrap(uiArquivo, wasmArquivo, nomeApp) {
|
|
52
247
|
return `
|
|
53
|
-
|
|
248
|
+
import { JadeRuntime, UIEngine, LocalDatastore } from './runtime.js';
|
|
249
|
+
|
|
250
|
+
const NOME_APP = ${JSON.stringify(nomeApp)};
|
|
251
|
+
const WASM_FILE = ${JSON.stringify('./' + wasmArquivo)};
|
|
252
|
+
const UI_FILE = ${JSON.stringify('./' + uiArquivo)};
|
|
253
|
+
const SEEDS_FILE = './seeds.json';
|
|
254
|
+
|
|
255
|
+
function icone(nome) {
|
|
256
|
+
const n = (nome || '').toLowerCase();
|
|
257
|
+
if (/produto|estoque|item|mercadoria/.test(n)) return '📦';
|
|
258
|
+
if (/cliente|pessoa|contato|fornecedor/.test(n)) return '👥';
|
|
259
|
+
if (/pedido|venda|ordem|compra/.test(n)) return '🛒';
|
|
260
|
+
if (/fiscal|nota|nfe|imposto|tributo/.test(n)) return '🧾';
|
|
261
|
+
if (/relatorio|relat|estatistica/.test(n)) return '📊';
|
|
262
|
+
if (/config|configurac|preferencia/.test(n)) return '⚙️';
|
|
263
|
+
if (/dashboard|painel|resumo|inicio/.test(n)) return '🏠';
|
|
264
|
+
if (/caixa|pagamento|financ|receber|pagar/.test(n)) return '💰';
|
|
265
|
+
if (/usuario|user|acesso|perfil/.test(n)) return '👤';
|
|
266
|
+
if (/moviment|lancament|transac/.test(n)) return '↕️';
|
|
267
|
+
return '📋';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function coletarEntidades(telas) {
|
|
271
|
+
const nomes = new Set();
|
|
272
|
+
for (const tela of telas) {
|
|
273
|
+
for (const el of tela.elementos || []) {
|
|
274
|
+
for (const prop of el.propriedades || []) {
|
|
275
|
+
if (prop.chave === 'entidade' && prop.valor) nomes.add(String(prop.valor));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return [...nomes];
|
|
280
|
+
}
|
|
54
281
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
282
|
+
async function mudarTela(idx, telas, db, ui) {
|
|
283
|
+
document.querySelectorAll('.jade-nav-item').forEach((el, i) => {
|
|
284
|
+
el.classList.toggle('jade-nav-ativo', i === idx);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const tela = telas[idx];
|
|
288
|
+
const container = document.getElementById('jade-conteudo');
|
|
289
|
+
container.innerHTML = '';
|
|
290
|
+
|
|
291
|
+
// Carrega dados do banco local para cada entidade referenciada nesta tela
|
|
292
|
+
const dadosMap = {};
|
|
293
|
+
for (const el of tela.elementos || []) {
|
|
294
|
+
for (const prop of el.propriedades || []) {
|
|
295
|
+
if (prop.chave === 'entidade' && prop.valor && !dadosMap[prop.valor]) {
|
|
296
|
+
dadosMap[prop.valor] = await db.find(String(prop.valor)).catch(() => []);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
58
300
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
await runtime.load(resposta);
|
|
301
|
+
ui.renderizarTela(tela, container, dadosMap);
|
|
302
|
+
}
|
|
62
303
|
|
|
63
|
-
|
|
64
|
-
|
|
304
|
+
async function iniciar() {
|
|
305
|
+
// 1. Carrega descritores de tela compilados (.jade-ui.json)
|
|
306
|
+
const telas = await fetch(UI_FILE).then(r => r.json()).catch(() => []);
|
|
65
307
|
|
|
66
|
-
|
|
308
|
+
// 2. Inicializa banco local com as entidades declaradas nas telas
|
|
309
|
+
const entidades = coletarEntidades(telas);
|
|
310
|
+
const db = new LocalDatastore(NOME_APP, entidades);
|
|
311
|
+
await db.init();
|
|
67
312
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
313
|
+
// 3. Carrega seeds.json na primeira execução (tabelas vazias)
|
|
314
|
+
try {
|
|
315
|
+
const seeds = await fetch(SEEDS_FILE).then(r => { if (!r.ok) throw 0; return r.json(); });
|
|
316
|
+
for (const [entidade, registros] of Object.entries(seeds)) {
|
|
317
|
+
const existentes = await db.find(entidade).catch(() => []);
|
|
318
|
+
if (existentes.length === 0) {
|
|
319
|
+
for (const reg of registros) {
|
|
320
|
+
await db.insert(entidade, reg).catch(() => {});
|
|
321
|
+
}
|
|
73
322
|
}
|
|
74
323
|
}
|
|
324
|
+
} catch { /* sem seeds ou já populado */ }
|
|
75
325
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
326
|
+
// 4. Carrega WASM (lógica de negócio compilada)
|
|
327
|
+
const runtime = new JadeRuntime();
|
|
328
|
+
try {
|
|
329
|
+
const resp = await fetch(WASM_FILE);
|
|
330
|
+
if (resp.ok) await runtime.load(resp);
|
|
331
|
+
} catch { /* WASM ausente se o arquivo só tem telas */ }
|
|
332
|
+
|
|
333
|
+
const ui = new UIEngine(runtime.getMemory());
|
|
334
|
+
|
|
335
|
+
// 5. Remove tela de carregamento
|
|
336
|
+
document.getElementById('jade-carregando')?.remove();
|
|
337
|
+
document.getElementById('jade-app').style.display = '';
|
|
338
|
+
|
|
339
|
+
if (telas.length === 0) {
|
|
340
|
+
document.getElementById('jade-conteudo').innerHTML =
|
|
341
|
+
'<p style="color:var(--jade-cor-texto-muted);padding:2rem">Nenhuma tela declarada.</p>';
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// 6. Constrói nav a partir dos descritores
|
|
346
|
+
const nav = document.getElementById('jade-nav-lista');
|
|
347
|
+
telas.forEach((tela, i) => {
|
|
348
|
+
const btn = document.createElement('button');
|
|
349
|
+
btn.className = 'jade-nav-item' + (i === 0 ? ' jade-nav-ativo' : '');
|
|
350
|
+
btn.dataset.idx = String(i);
|
|
351
|
+
btn.innerHTML =
|
|
352
|
+
'<span class="jade-nav-icone">' + icone(tela.nome) + '</span>' +
|
|
353
|
+
'<span>' + (tela.titulo || tela.nome) + '</span>';
|
|
354
|
+
btn.addEventListener('click', () => mudarTela(i, telas, db, ui));
|
|
355
|
+
nav.appendChild(btn);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// 7. Renderiza primeira tela
|
|
359
|
+
await mudarTela(0, telas, db, ui);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
iniciar().catch(e => {
|
|
363
|
+
document.getElementById('jade-carregando')?.remove();
|
|
364
|
+
const app = document.getElementById('jade-app');
|
|
365
|
+
app.style.display = '';
|
|
366
|
+
app.innerHTML =
|
|
367
|
+
'<p style="padding:2rem;color:var(--jade-cor-erro)">' +
|
|
368
|
+
'<strong>Erro ao iniciar:</strong> ' + e.message + '</p>';
|
|
369
|
+
console.error('[JADE]', e);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if ('serviceWorker' in navigator) {
|
|
373
|
+
navigator.serviceWorker.register('./sw.js').catch(() => {});
|
|
374
|
+
}
|
|
375
|
+
`.trim();
|
|
84
376
|
}
|
|
85
377
|
|
|
86
378
|
// ── HTML shell ────────────────────────────────────────────────────────────────
|
|
87
379
|
|
|
88
|
-
function gerarHTML(nome, wasmFile, uiFile,
|
|
380
|
+
function gerarHTML(nome, wasmFile, uiFile, tema = {}) {
|
|
381
|
+
const corPrimaria = tema.corPrimaria ?? '#2563eb';
|
|
382
|
+
|
|
89
383
|
return `<!DOCTYPE html>
|
|
90
384
|
<html lang="pt-BR">
|
|
91
385
|
<head>
|
|
92
386
|
<meta charset="UTF-8">
|
|
93
387
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
|
94
|
-
<meta name="theme-color" content="${
|
|
388
|
+
<meta name="theme-color" content="${corPrimaria}">
|
|
95
389
|
<meta name="mobile-web-app-capable" content="yes">
|
|
96
390
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
97
391
|
<title>${nome}</title>
|
|
98
392
|
<link rel="manifest" href="manifest.json">
|
|
99
393
|
<style>
|
|
100
|
-
|
|
101
|
-
body { font-family: system-ui, -apple-system, sans-serif; background: #f9fafb; }
|
|
102
|
-
#app { min-height: 100dvh; }
|
|
103
|
-
#jade-carregando {
|
|
104
|
-
display: flex; align-items: center; justify-content: center;
|
|
105
|
-
min-height: 100dvh; font-family: sans-serif; color: #6b7280;
|
|
106
|
-
}
|
|
394
|
+
${gerarCSS(tema)}
|
|
107
395
|
</style>
|
|
108
396
|
</head>
|
|
109
397
|
<body>
|
|
110
|
-
<div id="jade-carregando">
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
398
|
+
<div id="jade-carregando">
|
|
399
|
+
<div class="jade-spinner"></div>
|
|
400
|
+
Carregando...
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<div id="jade-app" style="display:none">
|
|
404
|
+
<nav id="jade-nav">
|
|
405
|
+
<div id="jade-nav-header">
|
|
406
|
+
<div id="jade-nav-titulo">${nome}</div>
|
|
407
|
+
<div id="jade-nav-versao">feito com Jade DSL</div>
|
|
408
|
+
</div>
|
|
409
|
+
<div id="jade-nav-lista"></div>
|
|
410
|
+
</nav>
|
|
411
|
+
<main id="jade-conteudo"></main>
|
|
412
|
+
</div>
|
|
124
413
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
414
|
+
<script type="module">
|
|
415
|
+
${gerarBootstrap(uiFile, wasmFile, nome)}
|
|
128
416
|
</script>
|
|
129
417
|
</body>
|
|
130
418
|
</html>`;
|
|
@@ -132,15 +420,15 @@ function gerarHTML(nome, wasmFile, uiFile, corTema = '#2563eb') {
|
|
|
132
420
|
|
|
133
421
|
// ── manifest.json ─────────────────────────────────────────────────────────────
|
|
134
422
|
|
|
135
|
-
function gerarManifest(nome) {
|
|
423
|
+
function gerarManifest(nome, tema = {}) {
|
|
136
424
|
return JSON.stringify({
|
|
137
425
|
name: nome,
|
|
138
426
|
short_name: nome.slice(0, 12),
|
|
139
427
|
display: 'standalone',
|
|
140
428
|
start_url: '/',
|
|
141
429
|
scope: '/',
|
|
142
|
-
theme_color: '#2563eb',
|
|
143
|
-
background_color: '#
|
|
430
|
+
theme_color: tema.corPrimaria ?? '#2563eb',
|
|
431
|
+
background_color: tema.corFundo ?? '#f8fafc',
|
|
144
432
|
icons: [
|
|
145
433
|
{ src: 'icon-192.png', sizes: '192x192', type: 'image/png' },
|
|
146
434
|
{ src: 'icon-512.png', sizes: '512x512', type: 'image/png' },
|
|
@@ -150,10 +438,10 @@ function gerarManifest(nome) {
|
|
|
150
438
|
|
|
151
439
|
// ── service worker ────────────────────────────────────────────────────────────
|
|
152
440
|
|
|
153
|
-
function gerarSW(nome, wasmFile) {
|
|
441
|
+
function gerarSW(nome, wasmFile, uiFile) {
|
|
154
442
|
const cache = `jade-${nome.toLowerCase().replace(/\s+/g, '-')}-v1`;
|
|
155
443
|
return `const CACHE = '${cache}';
|
|
156
|
-
const ARQUIVOS = ['/', '/index.html', '/${wasmFile}', '/runtime.js', '/manifest.json'];
|
|
444
|
+
const ARQUIVOS = ['/', '/index.html', '/${wasmFile}', '/${uiFile}', '/runtime.js', '/manifest.json'];
|
|
157
445
|
|
|
158
446
|
self.addEventListener('install', e => {
|
|
159
447
|
e.waitUntil(caches.open(CACHE).then(c => c.addAll(ARQUIVOS).catch(() => {})));
|
|
@@ -182,11 +470,18 @@ self.addEventListener('fetch', e => {
|
|
|
182
470
|
|
|
183
471
|
// ── Comando principal ─────────────────────────────────────────────────────────
|
|
184
472
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
473
|
+
/**
|
|
474
|
+
* @param {object} opts
|
|
475
|
+
* @param {string} opts.prefixo - caminho de saída sem extensão (ex: dist/app)
|
|
476
|
+
* @param {string} opts.nome - nome do app (usado no título e manifest)
|
|
477
|
+
* @param {object} [opts.tema] - objeto de tema do jade.config.json
|
|
478
|
+
* @param {string} [opts.seedsOrigem] - caminho absoluto do seeds.json a copiar para dist/
|
|
479
|
+
*/
|
|
480
|
+
export async function gerarHTML_dist({ prefixo, nome, tema = {}, seedsOrigem = null }) {
|
|
481
|
+
const distDir = dirname(resolve(prefixo));
|
|
482
|
+
const baseName = basename(prefixo);
|
|
483
|
+
const wasmFile = `${baseName}.wasm`;
|
|
484
|
+
const uiFile = `${baseName}.jade-ui.json`;
|
|
190
485
|
|
|
191
486
|
mkdirSync(distDir, { recursive: true });
|
|
192
487
|
|
|
@@ -202,17 +497,22 @@ export async function gerarHTML_dist({ prefixo, nome }) {
|
|
|
202
497
|
}
|
|
203
498
|
|
|
204
499
|
// 2. index.html
|
|
205
|
-
|
|
206
|
-
writeFileSync(join(distDir, 'index.html'), html, 'utf-8');
|
|
500
|
+
writeFileSync(join(distDir, 'index.html'), gerarHTML(nome, wasmFile, uiFile, tema), 'utf-8');
|
|
207
501
|
console.log(` ${ok()} index.html`);
|
|
208
502
|
|
|
209
503
|
// 3. manifest.json
|
|
210
|
-
writeFileSync(join(distDir, 'manifest.json'), gerarManifest(nome), 'utf-8');
|
|
504
|
+
writeFileSync(join(distDir, 'manifest.json'), gerarManifest(nome, tema), 'utf-8');
|
|
211
505
|
console.log(` ${ok()} manifest.json`);
|
|
212
506
|
|
|
213
507
|
// 4. sw.js
|
|
214
|
-
writeFileSync(join(distDir, 'sw.js'), gerarSW(nome, wasmFile), 'utf-8');
|
|
508
|
+
writeFileSync(join(distDir, 'sw.js'), gerarSW(nome, wasmFile, uiFile), 'utf-8');
|
|
215
509
|
console.log(` ${ok()} sw.js`);
|
|
216
510
|
|
|
511
|
+
// 5. Copia seeds.json se existir no projeto
|
|
512
|
+
if (seedsOrigem && existsSync(seedsOrigem)) {
|
|
513
|
+
copyFileSync(seedsOrigem, join(distDir, 'seeds.json'));
|
|
514
|
+
console.log(` ${ok()} seeds.json ${dim('(carga inicial de dados)')}`);
|
|
515
|
+
}
|
|
516
|
+
|
|
217
517
|
console.log(`\n ${azul('→')} para abrir no browser: ${verde('jade servir ' + distDir)}\n`);
|
|
218
518
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yakuzaa/jade",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Jade DSL — linguagem empresarial em português compilada para WebAssembly. Instala compilador + runtime + CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"postinstall": "node postinstall.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@yakuzaa/jade-compiler": "^0.1.
|
|
21
|
-
"@yakuzaa/jade-runtime": "^0.1.
|
|
20
|
+
"@yakuzaa/jade-compiler": "^0.1.8",
|
|
21
|
+
"@yakuzaa/jade-runtime": "^0.1.6"
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|
|
24
24
|
"jade",
|