@yakuzaa/jade 0.1.4 → 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.
@@ -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
- const nomeProjeto = basename(arquivo, '.jd');
103
- await gerarHTML_dist({ prefixo, nome: nomeProjeto });
102
+ // 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 { readFileSync, writeFileSync, copyFileSync, existsSync, mkdirSync } from 'fs';
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 = (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`;
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 = () => verde('✓');
27
- const aviso = () => amarelo('⚠');
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 bootstrap(wasmFile, uiFile) {
246
+ function gerarBootstrap(uiArquivo, wasmArquivo, nomeApp) {
52
247
  return `
53
- import { JadeRuntime, UIEngine } from './runtime.js';
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
- async function iniciar() {
56
- const runtime = new JadeRuntime();
57
- const ui = new UIEngine(runtime.getMemory());
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
- // Carrega o módulo WASM compilado
60
- const resposta = await fetch('./${wasmFile}');
61
- await runtime.load(resposta);
301
+ ui.renderizarTela(tela, container, dadosMap);
302
+ }
62
303
 
63
- // Carrega descritores de tela gerados pelo compilador
64
- const telas = await fetch('./${uiFile}').then(r => r.json()).catch(() => []);
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
- const container = document.getElementById('app');
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
- if (telas.length > 0) {
69
- // renderizarTela: lê o descriptor do compilador e decide O COMO automaticamente
70
- ui.renderizarTela(telas[0], container);
71
- } else {
72
- container.innerHTML = '<p style="font-family:sans-serif;padding:2rem">App JADE carregado. Nenhuma tela declarada.</p>';
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
- iniciar().catch(e => {
77
- document.getElementById('app').innerHTML =
78
- \`<p style="font-family:sans-serif;color:#dc2626;padding:2rem">
79
- <strong>Erro ao iniciar:</strong> \${e.message}
80
- </p>\`;
81
- console.error('[JADE]', e);
82
- });
83
- `.trim();
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, corTema = '#2563eb') {
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="${corTema}">
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
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
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">Carregando...</div>
111
- <div id="app" style="display:none"></div>
112
- <script type="module">
113
- ${bootstrap(wasmFile, uiFile)}
114
-
115
- // Remove tela de carregamento quando o app montar
116
- const obs = new MutationObserver(() => {
117
- if (document.getElementById('app').children.length > 0) {
118
- document.getElementById('jade-carregando').remove();
119
- document.getElementById('app').style.display = '';
120
- obs.disconnect();
121
- }
122
- });
123
- obs.observe(document.getElementById('app'), { childList: true });
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
- if ('serviceWorker' in navigator) {
126
- navigator.serviceWorker.register('./sw.js').catch(() => {});
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: '#ffffff',
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
- export async function gerarHTML_dist({ prefixo, nome }) {
186
- const distDir = dirname(resolve(prefixo));
187
- const baseName = basename(prefixo);
188
- const wasmFile = `${baseName}.wasm`;
189
- const uiFile = `${baseName}.jade-ui.json`;
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
- const html = gerarHTML(nome, wasmFile, uiFile);
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.4",
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.6",
21
- "@yakuzaa/jade-runtime": "^0.1.4"
20
+ "@yakuzaa/jade-compiler": "^0.1.8",
21
+ "@yakuzaa/jade-runtime": "^0.1.6"
22
22
  },
23
23
  "keywords": [
24
24
  "jade",