@yakuzaa/jade 0.1.6 → 0.1.8

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/cli.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * Comandos disponíveis:
6
6
  * jade init <nome> → cria estrutura de projeto
7
7
  * jade compilar <arquivo.jd> → compila + gera index.html + runtime.js
8
+ * jade formatar [arquivo.jd] → formata arquivo(s) .jd (sem arg = todos)
8
9
  * jade servir [pasta] [porta] → servidor estático para testar no browser
9
10
  * jade --version → exibe versão
10
11
  * jade --help → exibe ajuda
@@ -31,6 +32,7 @@ jade ${versao()} — JADE DSL em português
31
32
  Comandos:
32
33
  jade init <nome> Cria projeto JADE com estrutura completa
33
34
  jade compilar <arquivo.jd> [-o] Compila e gera artefatos para o browser
35
+ jade formatar [arquivo.jd] Formata arquivos .jd (sem arg = todo o projeto)
34
36
  jade servir [pasta] [porta] Inicia servidor local para testar no browser
35
37
 
36
38
  Opções do compilar:
@@ -41,6 +43,8 @@ Exemplos:
41
43
  jade init meu-projeto
42
44
  jade compilar src/app.jd
43
45
  jade compilar src/app.jd -o dist/app
46
+ jade formatar src/estoque.jd
47
+ jade formatar
44
48
  jade servir dist
45
49
  jade servir dist 8080
46
50
 
@@ -74,6 +78,12 @@ async function main() {
74
78
  return;
75
79
  }
76
80
 
81
+ if (comando === 'formatar') {
82
+ const { formatar } = await import('./commands/formatar.js');
83
+ await formatar(args.slice(1));
84
+ return;
85
+ }
86
+
77
87
  if (comando === 'servir') {
78
88
  const { servir } = await import('./commands/servir.js');
79
89
  const pasta = args[1] ?? 'dist';
@@ -0,0 +1,123 @@
1
+ /**
2
+ * commands/formatar.js — Formata arquivos .jd usando o formatter do compilador
3
+ *
4
+ * Uso:
5
+ * jade formatar <arquivo.jd> → formata e sobrescreve um arquivo
6
+ * jade formatar → formata todos os .jd do projeto recursivamente
7
+ *
8
+ * Delega para: jadec --format-write
9
+ * Chamado por: jade/cli.js
10
+ */
11
+
12
+ import { spawn } from 'child_process';
13
+ import { resolve, join, relative } from 'path';
14
+ import { existsSync, readdirSync, statSync } from 'fs';
15
+ import { fileURLToPath } from 'url';
16
+
17
+ const verde = (s) => `\x1b[1;32m${s}\x1b[0m`;
18
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
19
+ const vermelho = (s) => `\x1b[1;31m${s}\x1b[0m`;
20
+ const azul = (s) => `\x1b[34m${s}\x1b[0m`;
21
+
22
+ // ── Localiza jadec ────────────────────────────────────────────────────────────
23
+
24
+ function localizarJadec() {
25
+ const candidatos = [
26
+ join(process.cwd(), 'node_modules', '.bin', 'jadec'),
27
+ resolve(fileURLToPath(import.meta.url), '..', '..', '..', 'jade-compiler', 'dist', 'cli.js'),
28
+ ];
29
+ for (const c of candidatos) {
30
+ if (existsSync(c)) return c;
31
+ }
32
+ return 'jadec';
33
+ }
34
+
35
+ // ── Coleta todos os .jd recursivamente ───────────────────────────────────────
36
+
37
+ function coletarArquivos(dir, arquivos = []) {
38
+ const IGNORAR = ['node_modules', 'dist', '.git', '.cache'];
39
+ for (const entry of readdirSync(dir)) {
40
+ if (IGNORAR.includes(entry)) continue;
41
+ const caminho = join(dir, entry);
42
+ const stat = statSync(caminho);
43
+ if (stat.isDirectory()) {
44
+ coletarArquivos(caminho, arquivos);
45
+ } else if (entry.endsWith('.jd')) {
46
+ arquivos.push(caminho);
47
+ }
48
+ }
49
+ return arquivos;
50
+ }
51
+
52
+ // ── Roda jadec --format-write em um arquivo ──────────────────────────────────
53
+
54
+ function formatarArquivo(arquivo) {
55
+ return new Promise((res, rej) => {
56
+ const jadec = localizarJadec();
57
+ const cmd = jadec.endsWith('.js') ? 'node' : jadec;
58
+ const argv = jadec.endsWith('.js') ? [jadec, arquivo, '--format-write'] : [arquivo, '--format-write'];
59
+
60
+ const proc = spawn(cmd, argv, { stdio: ['inherit', 'pipe', 'pipe'] });
61
+
62
+ let stdout = '';
63
+ let stderr = '';
64
+ proc.stdout?.on('data', d => stdout += d);
65
+ proc.stderr?.on('data', d => stderr += d);
66
+
67
+ proc.on('close', code => {
68
+ if (code === 0) res({ ok: true, stdout, stderr });
69
+ else rej(new Error(stderr || `jadec saiu com código ${code}`));
70
+ });
71
+ proc.on('error', rej);
72
+ });
73
+ }
74
+
75
+ // ── Comando principal ─────────────────────────────────────────────────────────
76
+
77
+ export async function formatar(args) {
78
+ const arquivoArg = args?.find(a => !a.startsWith('-'));
79
+
80
+ let arquivos;
81
+
82
+ if (arquivoArg) {
83
+ // Arquivo específico
84
+ const caminho = resolve(arquivoArg);
85
+ if (!existsSync(caminho)) {
86
+ console.error(`\n${vermelho('erro')}: arquivo '${arquivoArg}' não encontrado.\n`);
87
+ process.exit(1);
88
+ }
89
+ arquivos = [caminho];
90
+ } else {
91
+ // Todos os .jd do projeto
92
+ arquivos = coletarArquivos(process.cwd());
93
+ if (arquivos.length === 0) {
94
+ console.log(dim('nenhum arquivo .jd encontrado.'));
95
+ return;
96
+ }
97
+ }
98
+
99
+ console.log(dim(`\nformatando ${arquivos.length} arquivo${arquivos.length > 1 ? 's' : ''}...\n`));
100
+
101
+ let ok = 0;
102
+ let erros = 0;
103
+
104
+ for (const arquivo of arquivos) {
105
+ const rel = relative(process.cwd(), arquivo);
106
+ try {
107
+ await formatarArquivo(arquivo);
108
+ console.log(` ${verde('ok')} ${azul(rel)}`);
109
+ ok++;
110
+ } catch (e) {
111
+ console.error(` ${vermelho('erro')} ${azul(rel)}: ${e.message}`);
112
+ erros++;
113
+ }
114
+ }
115
+
116
+ console.log('');
117
+ if (erros === 0) {
118
+ console.log(`${verde('formatação concluída')} — ${ok} arquivo${ok > 1 ? 's' : ''} formatado${ok > 1 ? 's' : ''}.`);
119
+ } else {
120
+ console.log(`${ok} ok, ${vermelho(`${erros} com erro${erros > 1 ? 's' : ''}`)}.`);
121
+ process.exit(1);
122
+ }
123
+ }
package/commands/html.js CHANGED
@@ -171,7 +171,66 @@ function gerarCSS(tema = {}) {
171
171
  color: #fff !important;
172
172
  }
173
173
 
174
- .jade-nav-icone { font-size: 1rem; }
174
+ .jade-nav-icone { display: flex; align-items: center; }
175
+
176
+ /* Toolbar */
177
+ .jade-toolbar {
178
+ display: flex;
179
+ flex-wrap: wrap;
180
+ gap: 8px;
181
+ margin-bottom: 16px;
182
+ }
183
+
184
+ /* Busca */
185
+ .jade-busca-wrapper { margin-bottom: 16px; }
186
+ .jade-busca-form { display: flex; gap: 0; }
187
+ .jade-busca-input {
188
+ flex: 1;
189
+ min-height: 44px;
190
+ padding: 10px 14px;
191
+ border: 1.5px solid var(--jade-cor-borda);
192
+ border-right: none;
193
+ border-radius: var(--jade-raio) 0 0 var(--jade-raio);
194
+ font-size: 1rem;
195
+ background: #fff;
196
+ }
197
+ .jade-busca-btn {
198
+ min-height: 44px;
199
+ padding: 0 14px;
200
+ border: 1.5px solid var(--jade-cor-primaria);
201
+ background: var(--jade-cor-primaria);
202
+ color: #fff;
203
+ border-radius: 0 var(--jade-raio) var(--jade-raio) 0;
204
+ cursor: pointer;
205
+ display: flex;
206
+ align-items: center;
207
+ }
208
+
209
+ /* Divisor */
210
+ .jade-divisor { border: none; border-top: 1px solid var(--jade-cor-borda); margin: 20px 0 12px; }
211
+ .jade-divisor-rotulo {
212
+ position: relative;
213
+ text-align: center;
214
+ margin: 20px 0 12px;
215
+ }
216
+ .jade-divisor-rotulo::before {
217
+ content: '';
218
+ position: absolute;
219
+ top: 50%;
220
+ left: 0; right: 0;
221
+ border-top: 1px solid var(--jade-cor-borda);
222
+ }
223
+ .jade-divisor-rotulo::after {
224
+ content: attr(data-rotulo);
225
+ position: relative;
226
+ background: var(--jade-cor-fundo);
227
+ padding: 0 12px;
228
+ font-size: 0.8125rem;
229
+ font-weight: 600;
230
+ color: var(--jade-cor-texto-muted);
231
+ text-transform: uppercase;
232
+ letter-spacing: 0.05em;
233
+ }
175
234
 
176
235
  /* Área de conteúdo */
177
236
  #jade-conteudo {
@@ -245,26 +304,40 @@ function gerarCSS(tema = {}) {
245
304
 
246
305
  function gerarBootstrap(uiArquivo, wasmArquivo, nomeApp) {
247
306
  return `
248
- import { JadeRuntime, UIEngine, LocalDatastore } from './runtime.js';
307
+ import { JadeRuntime, UIEngine, LocalDatastore, criarElementoIcone } from './runtime.js';
249
308
 
250
309
  const NOME_APP = ${JSON.stringify(nomeApp)};
251
310
  const WASM_FILE = ${JSON.stringify('./' + wasmArquivo)};
252
311
  const UI_FILE = ${JSON.stringify('./' + uiArquivo)};
253
312
  const SEEDS_FILE = './seeds.json';
254
313
 
255
- function icone(nome) {
314
+ // Mapeia nome da tela para ícone do catálogo JADE (nomes em português)
315
+ function nomeIcone(nome) {
256
316
  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 '📋';
317
+ if (/produto|estoque|item|mercadoria/.test(n)) return 'caixa';
318
+ if (/cliente|pessoa|contato|fornecedor/.test(n)) return 'usuarios';
319
+ if (/pedido|venda|ordem|compra/.test(n)) return 'carrinho';
320
+ if (/fiscal|nota|nfe|imposto|tributo/.test(n)) return 'relatorio';
321
+ if (/relatorio|relat|estatistica/.test(n)) return 'grafico';
322
+ if (/config|configurac|preferencia/.test(n)) return 'configuracoes';
323
+ if (/dashboard|painel|resumo|inicio/.test(n)) return 'casa';
324
+ if (/caixa|pagamento|financ|receber|pagar/.test(n)) return 'dinheiro';
325
+ if (/usuario|user|acesso|perfil/.test(n)) return 'usuario';
326
+ if (/moviment|lancament|transac/.test(n)) return 'atualizar';
327
+ return 'tabela_icone';
328
+ }
329
+
330
+ // Telas que não entram no nav (shells de login/formulário/navegação)
331
+ function ehTelaDeNav(tela) {
332
+ const tipos = (tela.elementos || []).map(e => e.tipo);
333
+ if (tipos.length === 0) return false;
334
+ // Shell pura de gaveta — é o menu, não uma tela navegável
335
+ if (tipos.every(t => t === 'gaveta')) return false;
336
+ // Tela de login
337
+ if (tipos.includes('login')) return false;
338
+ // Formulários de criação (somente formulario + botao)
339
+ if (tipos.every(t => t === 'formulario' || t === 'botao')) return false;
340
+ return true;
268
341
  }
269
342
 
270
343
  function coletarEntidades(telas) {
@@ -279,16 +352,16 @@ function coletarEntidades(telas) {
279
352
  return [...nomes];
280
353
  }
281
354
 
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
- });
355
+ async function mudarTela(nome, telas, db, ui, navItems) {
356
+ const idx = telas.findIndex(t => t.nome === nome);
357
+ if (idx < 0) return;
358
+
359
+ navItems.forEach((btn, i) => btn.classList.toggle('jade-nav-ativo', i === idx));
286
360
 
287
361
  const tela = telas[idx];
288
362
  const container = document.getElementById('jade-conteudo');
289
363
  container.innerHTML = '';
290
364
 
291
- // Carrega dados do banco local para cada entidade referenciada nesta tela
292
365
  const dadosMap = {};
293
366
  for (const el of tela.elementos || []) {
294
367
  for (const prop of el.propriedades || []) {
@@ -302,37 +375,30 @@ async function mudarTela(idx, telas, db, ui) {
302
375
  }
303
376
 
304
377
  async function iniciar() {
305
- // 1. Carrega descritores de tela compilados (.jade-ui.json)
306
378
  const telas = await fetch(UI_FILE).then(r => r.json()).catch(() => []);
307
379
 
308
- // 2. Inicializa banco local com as entidades declaradas nas telas
309
380
  const entidades = coletarEntidades(telas);
310
381
  const db = new LocalDatastore(NOME_APP, entidades);
311
382
  await db.init();
312
383
 
313
- // 3. Carrega seeds.json na primeira execução (tabelas vazias)
314
384
  try {
315
385
  const seeds = await fetch(SEEDS_FILE).then(r => { if (!r.ok) throw 0; return r.json(); });
316
386
  for (const [entidade, registros] of Object.entries(seeds)) {
317
387
  const existentes = await db.find(entidade).catch(() => []);
318
388
  if (existentes.length === 0) {
319
- for (const reg of registros) {
320
- await db.insert(entidade, reg).catch(() => {});
321
- }
389
+ for (const reg of registros) await db.insert(entidade, reg).catch(() => {});
322
390
  }
323
391
  }
324
392
  } catch { /* sem seeds ou já populado */ }
325
393
 
326
- // 4. Carrega WASM (lógica de negócio compilada)
327
394
  const runtime = new JadeRuntime();
328
395
  try {
329
396
  const resp = await fetch(WASM_FILE);
330
397
  if (resp.ok) await runtime.load(resp);
331
- } catch { /* WASM ausente se o arquivo só tem telas */ }
398
+ } catch { /* WASM ausente */ }
332
399
 
333
400
  const ui = new UIEngine(runtime.getMemory());
334
401
 
335
- // 5. Remove tela de carregamento
336
402
  document.getElementById('jade-carregando')?.remove();
337
403
  document.getElementById('jade-app').style.display = '';
338
404
 
@@ -342,21 +408,51 @@ async function iniciar() {
342
408
  return;
343
409
  }
344
410
 
345
- // 6. Constrói nav a partir dos descritores
411
+ // Constrói nav apenas com telas navegáveis
346
412
  const nav = document.getElementById('jade-nav-lista');
347
- telas.forEach((tela, i) => {
413
+ const telasNav = telas.filter(ehTelaDeNav);
414
+ const navItems = [];
415
+
416
+ telasNav.forEach((tela, i) => {
348
417
  const btn = document.createElement('button');
349
418
  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));
419
+ btn.dataset.tela = tela.nome;
420
+
421
+ const svgIcone = criarElementoIcone(nomeIcone(tela.nome), 18);
422
+ const spanIcone = document.createElement('span');
423
+ spanIcone.className = 'jade-nav-icone';
424
+ if (svgIcone) spanIcone.appendChild(svgIcone);
425
+ btn.appendChild(spanIcone);
426
+
427
+ const spanLabel = document.createElement('span');
428
+ spanLabel.textContent = tela.titulo || tela.nome;
429
+ btn.appendChild(spanLabel);
430
+
431
+ btn.addEventListener('click', () => mudarTela(tela.nome, telas, db, ui, navItems));
355
432
  nav.appendChild(btn);
433
+ navItems.push(btn);
434
+ });
435
+
436
+ // Handler: jade:navegar — gaveta e navegar disparam este evento
437
+ window.addEventListener('jade:navegar', (e) => {
438
+ const nomeTela = e.detail?.tela;
439
+ if (nomeTela) mudarTela(nomeTela, telas, db, ui, navItems);
356
440
  });
357
441
 
358
- // 7. Renderiza primeira tela
359
- await mudarTela(0, telas, db, ui);
442
+ // Handler: jade:acao dispara jade:acao:concluido após processar
443
+ // (evita spinner eterno em botões sem implementação WASM real)
444
+ window.addEventListener('jade:acao', (e) => {
445
+ const acao = e.detail?.acao;
446
+ // Navegar via router.navegar() é tratado pelo runtime interno —
447
+ // aqui garantimos que o botão sai do estado de carregamento
448
+ setTimeout(() => {
449
+ window.dispatchEvent(new CustomEvent('jade:acao:concluido', { detail: { acao } }));
450
+ }, 300);
451
+ });
452
+
453
+ // Renderiza primeira tela navegável
454
+ const primeiraNome = telasNav[0]?.nome ?? telas[0]?.nome;
455
+ if (primeiraNome) await mudarTela(primeiraNome, telas, db, ui, navItems);
360
456
  }
361
457
 
362
458
  iniciar().catch(e => {
@@ -461,7 +557,10 @@ self.addEventListener('fetch', e => {
461
557
  if (e.request.method !== 'GET') return;
462
558
  e.respondWith(
463
559
  caches.match(e.request).then(hit => hit ?? fetch(e.request).then(res => {
464
- if (res.ok) caches.open(CACHE).then(c => c.put(e.request, res.clone()));
560
+ if (res.ok) {
561
+ const clone = res.clone();
562
+ caches.open(CACHE).then(c => c.put(e.request, clone));
563
+ }
465
564
  return res;
466
565
  })).catch(() => new Response('<h1>Sem conexão</h1>', { headers: { 'Content-Type': 'text/html' } }))
467
566
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yakuzaa/jade",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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.8",
21
- "@yakuzaa/jade-runtime": "^0.1.6"
20
+ "@yakuzaa/jade-compiler": "^0.1.11",
21
+ "@yakuzaa/jade-runtime": "^0.1.8"
22
22
  },
23
23
  "keywords": [
24
24
  "jade",