@yakuzaa/jade 0.1.1 → 0.1.5

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 ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.js — Entry point do comando `jade`
4
+ *
5
+ * Comandos disponíveis:
6
+ * jade init <nome> → cria estrutura de projeto
7
+ * jade compilar <arquivo.jd> → compila + gera index.html + runtime.js
8
+ * jade servir [pasta] [porta] → servidor estático para testar no browser
9
+ * jade --version → exibe versão
10
+ * jade --help → exibe ajuda
11
+ */
12
+
13
+ import { readFileSync } from 'fs';
14
+ import { resolve, dirname } from 'path';
15
+ import { fileURLToPath } from 'url';
16
+
17
+ const __dir = dirname(fileURLToPath(import.meta.url));
18
+
19
+ function versao() {
20
+ try {
21
+ const pkg = JSON.parse(readFileSync(resolve(__dir, 'package.json'), 'utf-8'));
22
+ return pkg.version ?? '?';
23
+ } catch {
24
+ return '?';
25
+ }
26
+ }
27
+
28
+ const AJUDA = `
29
+ jade ${versao()} — JADE DSL em português
30
+
31
+ Comandos:
32
+ jade init <nome> Cria projeto JADE com estrutura completa
33
+ jade compilar <arquivo.jd> [-o] Compila e gera artefatos para o browser
34
+ jade servir [pasta] [porta] Inicia servidor local para testar no browser
35
+
36
+ Opções do compilar:
37
+ -o <prefixo> Prefixo de saída (padrão: dist/<nome>)
38
+ --so-wasm Gera apenas .wasm/.wat, sem HTML
39
+
40
+ Exemplos:
41
+ jade init meu-projeto
42
+ jade compilar src/app.jd
43
+ jade compilar src/app.jd -o dist/app
44
+ jade servir dist
45
+ jade servir dist 8080
46
+
47
+ Documentação: https://gabrielsymb.github.io/jade-language
48
+ `;
49
+
50
+ async function main() {
51
+ const args = process.argv.slice(2);
52
+
53
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
54
+ console.log(AJUDA);
55
+ process.exit(0);
56
+ }
57
+
58
+ if (args.includes('--version') || args.includes('-v')) {
59
+ console.log(`jade ${versao()}`);
60
+ process.exit(0);
61
+ }
62
+
63
+ const comando = args[0];
64
+
65
+ if (comando === 'init') {
66
+ const { init } = await import('./commands/init.js');
67
+ await init(args[1]);
68
+ return;
69
+ }
70
+
71
+ if (comando === 'compilar') {
72
+ const { compilar } = await import('./commands/compilar.js');
73
+ await compilar(args.slice(1));
74
+ return;
75
+ }
76
+
77
+ if (comando === 'servir') {
78
+ const { servir } = await import('./commands/servir.js');
79
+ const pasta = args[1] ?? 'dist';
80
+ const porta = parseInt(args[2] ?? '3000', 10);
81
+ await servir(pasta, porta);
82
+ return;
83
+ }
84
+
85
+ console.error(`\x1b[1;31merro\x1b[0m: comando desconhecido "${comando}".`);
86
+ console.error(`\x1b[2mdica: jade --help para ver os comandos disponíveis.\x1b[0m\n`);
87
+ process.exit(1);
88
+ }
89
+
90
+ main().catch((e) => {
91
+ console.error(`\x1b[1;31merro interno\x1b[0m: ${e?.message ?? e}`);
92
+ process.exit(2);
93
+ });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * commands/compilar.js — Compila e gera artefatos browser em um único passo
3
+ *
4
+ * Uso: jade compilar <arquivo.jd> [-o prefixo] [--so-wasm]
5
+ *
6
+ * jade compilar src/app.jd → dist/app.wasm + index.html + runtime.js
7
+ * jade compilar src/app.jd -o saida/app → saida/app.wasm + ...
8
+ * jade compilar src/app.jd --so-wasm → apenas .wasm/.wat (sem HTML)
9
+ *
10
+ * Roda jadec como subprocesso e, em caso de sucesso, chama gerarHTML_dist.
11
+ * Chamado por: jade/cli.js
12
+ */
13
+
14
+ import { spawn } from 'child_process';
15
+ import { resolve, basename, dirname, join } from 'path';
16
+ import { existsSync } from 'fs';
17
+ import { createRequire } from 'module';
18
+ import { fileURLToPath } from 'url';
19
+ import { gerarHTML_dist } from './html.js';
20
+
21
+ const require = createRequire(import.meta.url);
22
+
23
+ const azul = (s) => `\x1b[34m${s}\x1b[0m`;
24
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
25
+ const vermelho = (s) => `\x1b[1;31m${s}\x1b[0m`;
26
+
27
+ // ── Localiza o binário jadec ──────────────────────────────────────────────────
28
+
29
+ function localizarJadec() {
30
+ // 1. jadec no PATH (instalação global)
31
+ // 2. node_modules/.bin/jadec (instalação local)
32
+ // 3. monorepo (desenvolvimento)
33
+ const candidatos = [
34
+ // local project node_modules
35
+ join(process.cwd(), 'node_modules', '.bin', 'jadec'),
36
+ // monorepo
37
+ resolve(fileURLToPath(import.meta.url), '..', '..', '..', 'jade-compiler', 'dist', 'cli.js'),
38
+ ];
39
+
40
+ for (const c of candidatos) {
41
+ if (existsSync(c)) return c;
42
+ }
43
+
44
+ return 'jadec'; // assume no PATH
45
+ }
46
+
47
+ // ── Executa jadec como subprocesso ───────────────────────────────────────────
48
+
49
+ function rodarJadec(args) {
50
+ return new Promise((res, rej) => {
51
+ const jadec = localizarJadec();
52
+
53
+ // Se for um .js (caminho direto), roda com node
54
+ const cmd = jadec.endsWith('.js') ? 'node' : jadec;
55
+ const argv = jadec.endsWith('.js') ? [jadec, ...args] : args;
56
+
57
+ const proc = spawn(cmd, argv, { stdio: 'inherit' });
58
+ proc.on('close', code => code === 0 ? res() : rej(new Error(`jadec saiu com código ${code}`)));
59
+ proc.on('error', rej);
60
+ });
61
+ }
62
+
63
+ // ── Comando principal ─────────────────────────────────────────────────────────
64
+
65
+ export async function compilar(args) {
66
+ if (!args || args.length === 0) {
67
+ console.error(`\n${vermelho('erro')}: informe o arquivo a compilar.`);
68
+ console.error(` Uso: ${azul('jade compilar')} <arquivo.jd>\n`);
69
+ process.exit(1);
70
+ }
71
+
72
+ const arquivo = args.find(a => !a.startsWith('-'));
73
+ if (!arquivo) {
74
+ console.error(`\n${vermelho('erro')}: nenhum arquivo .jd encontrado nos argumentos.\n`);
75
+ process.exit(1);
76
+ }
77
+
78
+ const soWasm = args.includes('--so-wasm');
79
+
80
+ // Determina o prefixo de saída
81
+ const oIdx = args.indexOf('-o');
82
+ let prefixo;
83
+ if (oIdx !== -1 && args[oIdx + 1]) {
84
+ prefixo = resolve(args[oIdx + 1]);
85
+ } else {
86
+ // padrão: dist/<nome-do-arquivo>
87
+ const nome = basename(arquivo, '.jd');
88
+ prefixo = resolve(process.cwd(), 'dist', nome);
89
+ }
90
+
91
+ // Repassa todos os args para jadec (-o já incluído ou adicionado)
92
+ const jadecArgs = [...args];
93
+ if (oIdx === -1) {
94
+ jadecArgs.push('-o', prefixo);
95
+ }
96
+
97
+ // Executa jadec
98
+ await rodarJadec(jadecArgs);
99
+
100
+ // Geração de HTML (a menos que --so-wasm)
101
+ if (!soWasm) {
102
+ const nomeProjeto = basename(arquivo, '.jd');
103
+ await gerarHTML_dist({ prefixo, nome: nomeProjeto });
104
+ }
105
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * commands/html.js — Gera os artefatos HTML para o browser
3
+ *
4
+ * Dado um prefixo de saída (ex: dist/app), gera:
5
+ * dist/index.html — shell PWA com bootstrap que carrega WASM + UI
6
+ * dist/runtime.js — runtime JADE copiado do pacote instalado
7
+ * dist/manifest.json
8
+ * dist/sw.js
9
+ *
10
+ * Chamado por: commands/compilar.js após compilação bem-sucedida.
11
+ * Não depende de bundler no projeto do usuário.
12
+ */
13
+
14
+ import { readFileSync, writeFileSync, copyFileSync, existsSync, mkdirSync } from 'fs';
15
+ import { resolve, dirname, join, basename } from 'path';
16
+ import { createRequire } from 'module';
17
+
18
+ const require = createRequire(import.meta.url);
19
+
20
+ // ── Cores ─────────────────────────────────────────────────────────────────────
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`;
25
+ const amarelo = (s) => `\x1b[1;33m${s}\x1b[0m`;
26
+ const ok = () => verde('✓');
27
+ const aviso = () => amarelo('⚠');
28
+
29
+ // ── Localiza o browser.js do runtime instalado ────────────────────────────────
30
+
31
+ function localizarRuntime() {
32
+ // Tenta via require.resolve (funciona quando @yakuzaa/jade-runtime está instalado)
33
+ try {
34
+ const pkgPath = require.resolve('@yakuzaa/jade-runtime/package.json');
35
+ const pkgDir = dirname(pkgPath);
36
+ const browser = join(pkgDir, 'dist', 'browser.js');
37
+ if (existsSync(browser)) return browser;
38
+ } catch {
39
+ // não instalado via npm — tenta caminho relativo ao monorepo
40
+ }
41
+
42
+ // Fallback: monorepo local (desenvolvimento)
43
+ const mono = resolve(dirname(new URL(import.meta.url).pathname), '..', '..', 'jade-runtime', 'dist', 'browser.js');
44
+ if (existsSync(mono)) return mono;
45
+
46
+ return null;
47
+ }
48
+
49
+ // ── Bootstrap JS inline no index.html ────────────────────────────────────────
50
+
51
+ function bootstrap(wasmFile, uiFile) {
52
+ return `
53
+ import { JadeRuntime, UIEngine } from './runtime.js';
54
+
55
+ async function iniciar() {
56
+ const runtime = new JadeRuntime();
57
+ const ui = new UIEngine(runtime.getMemory());
58
+
59
+ // Carrega o módulo WASM compilado
60
+ const resposta = await fetch('./${wasmFile}');
61
+ await runtime.load(resposta);
62
+
63
+ // Carrega descritores de tela gerados pelo compilador
64
+ const telas = await fetch('./${uiFile}').then(r => r.json()).catch(() => []);
65
+
66
+ const container = document.getElementById('app');
67
+
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>';
73
+ }
74
+ }
75
+
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();
84
+ }
85
+
86
+ // ── HTML shell ────────────────────────────────────────────────────────────────
87
+
88
+ function gerarHTML(nome, wasmFile, uiFile, corTema = '#2563eb') {
89
+ return `<!DOCTYPE html>
90
+ <html lang="pt-BR">
91
+ <head>
92
+ <meta charset="UTF-8">
93
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
94
+ <meta name="theme-color" content="${corTema}">
95
+ <meta name="mobile-web-app-capable" content="yes">
96
+ <meta name="apple-mobile-web-app-capable" content="yes">
97
+ <title>${nome}</title>
98
+ <link rel="manifest" href="manifest.json">
99
+ <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
+ }
107
+ </style>
108
+ </head>
109
+ <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 });
124
+
125
+ if ('serviceWorker' in navigator) {
126
+ navigator.serviceWorker.register('./sw.js').catch(() => {});
127
+ }
128
+ </script>
129
+ </body>
130
+ </html>`;
131
+ }
132
+
133
+ // ── manifest.json ─────────────────────────────────────────────────────────────
134
+
135
+ function gerarManifest(nome) {
136
+ return JSON.stringify({
137
+ name: nome,
138
+ short_name: nome.slice(0, 12),
139
+ display: 'standalone',
140
+ start_url: '/',
141
+ scope: '/',
142
+ theme_color: '#2563eb',
143
+ background_color: '#ffffff',
144
+ icons: [
145
+ { src: 'icon-192.png', sizes: '192x192', type: 'image/png' },
146
+ { src: 'icon-512.png', sizes: '512x512', type: 'image/png' },
147
+ ],
148
+ }, null, 2);
149
+ }
150
+
151
+ // ── service worker ────────────────────────────────────────────────────────────
152
+
153
+ function gerarSW(nome, wasmFile) {
154
+ const cache = `jade-${nome.toLowerCase().replace(/\s+/g, '-')}-v1`;
155
+ return `const CACHE = '${cache}';
156
+ const ARQUIVOS = ['/', '/index.html', '/${wasmFile}', '/runtime.js', '/manifest.json'];
157
+
158
+ self.addEventListener('install', e => {
159
+ e.waitUntil(caches.open(CACHE).then(c => c.addAll(ARQUIVOS).catch(() => {})));
160
+ self.skipWaiting();
161
+ });
162
+
163
+ self.addEventListener('activate', e => {
164
+ e.waitUntil(
165
+ caches.keys().then(ks =>
166
+ Promise.all(ks.filter(k => k.startsWith('jade-') && k !== CACHE).map(k => caches.delete(k)))
167
+ )
168
+ );
169
+ self.clients.claim();
170
+ });
171
+
172
+ self.addEventListener('fetch', e => {
173
+ if (e.request.method !== 'GET') return;
174
+ e.respondWith(
175
+ caches.match(e.request).then(hit => hit ?? fetch(e.request).then(res => {
176
+ if (res.ok) caches.open(CACHE).then(c => c.put(e.request, res.clone()));
177
+ return res;
178
+ })).catch(() => new Response('<h1>Sem conexão</h1>', { headers: { 'Content-Type': 'text/html' } }))
179
+ );
180
+ });`;
181
+ }
182
+
183
+ // ── Comando principal ─────────────────────────────────────────────────────────
184
+
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`;
190
+
191
+ mkdirSync(distDir, { recursive: true });
192
+
193
+ console.log(`\n ${dim('gerando artefatos para o browser...')}`);
194
+
195
+ // 1. Copia runtime.js
196
+ const runtimeSrc = localizarRuntime();
197
+ if (runtimeSrc) {
198
+ copyFileSync(runtimeSrc, join(distDir, 'runtime.js'));
199
+ console.log(` ${ok()} runtime.js ${dim('(jade-runtime browser bundle)')}`);
200
+ } else {
201
+ console.warn(` ${aviso()} runtime.js não encontrado — execute 'npm run build:browser' no jade-runtime`);
202
+ }
203
+
204
+ // 2. index.html
205
+ const html = gerarHTML(nome, wasmFile, uiFile);
206
+ writeFileSync(join(distDir, 'index.html'), html, 'utf-8');
207
+ console.log(` ${ok()} index.html`);
208
+
209
+ // 3. manifest.json
210
+ writeFileSync(join(distDir, 'manifest.json'), gerarManifest(nome), 'utf-8');
211
+ console.log(` ${ok()} manifest.json`);
212
+
213
+ // 4. sw.js
214
+ writeFileSync(join(distDir, 'sw.js'), gerarSW(nome, wasmFile), 'utf-8');
215
+ console.log(` ${ok()} sw.js`);
216
+
217
+ console.log(`\n ${azul('→')} para abrir no browser: ${verde('jade servir ' + distDir)}\n`);
218
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * commands/init.js — Implementação do `jade init <nome-projeto>`
3
+ *
4
+ * Cria a estrutura completa de um projeto JADE com arquivos de exemplo funcionais.
5
+ * Análogo ao create-react-app, npm create svelte, npm create vue.
6
+ *
7
+ * Chamado por: jade/cli.js
8
+ * Templates: jade/templates/index.js
9
+ */
10
+
11
+ import { mkdirSync, writeFileSync, existsSync } from 'fs';
12
+ import { join, resolve } from 'path';
13
+ import * as T from '../templates/index.js';
14
+
15
+ // ── Utilitários de output ─────────────────────────────────────────────────────
16
+
17
+ const verde = (s) => `\x1b[1;32m${s}\x1b[0m`;
18
+ const azul = (s) => `\x1b[34m${s}\x1b[0m`;
19
+ const amarelo = (s) => `\x1b[1;33m${s}\x1b[0m`;
20
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
21
+ const negrito = (s) => `\x1b[1m${s}\x1b[0m`;
22
+ const ok = () => verde('✓');
23
+ const err = () => `\x1b[1;31m✗\x1b[0m`;
24
+
25
+ // ── Estrutura de pastas e arquivos ────────────────────────────────────────────
26
+
27
+ /**
28
+ * Cada entrada: [caminhRelativo, conteudo | null]
29
+ * null = pasta vazia (cria apenas o diretório)
30
+ */
31
+ function estrutura(nome) {
32
+ return [
33
+ // Módulos
34
+ ['src/modulos', null],
35
+ ['src/modulos/estoque.jd', T.moduloEstoque()],
36
+
37
+ // Serviços
38
+ ['src/servicos', null],
39
+ ['src/servicos/estoqueService.jd', T.estoqueService()],
40
+
41
+ // Entidades
42
+ ['src/entidades', null],
43
+ ['src/entidades/Produto.jd', T.Produto()],
44
+ ['src/entidades/Cliente.jd', T.Cliente()],
45
+
46
+ // Eventos
47
+ ['src/eventos', null],
48
+ ['src/eventos/ProdutoCriado.jd', T.ProdutoCriado()],
49
+
50
+ // UI
51
+ ['src/ui', null],
52
+ ['src/ui/telas', null],
53
+ ['src/ui/telas/ListaProdutos.jd', T.telaProdutos()],
54
+ ['src/ui/telas/Principal.jd', T.telaPrincipal()],
55
+ ['src/ui/componentes', null],
56
+
57
+ // Config
58
+ ['config', null],
59
+ ['config/jade.config.json', T.jadeConfig(nome)],
60
+ ['config/database.json', T.databaseConfig()],
61
+ ['config/deploy.json', T.deployConfig()],
62
+
63
+ // Dist (vazia, nunca commitar)
64
+ ['dist', null],
65
+
66
+ // Aux
67
+ ['docs', null],
68
+ ['tests', null],
69
+
70
+ // Raiz
71
+ ['package.json', T.packageJson(nome)],
72
+ ['.gitignore', T.gitignore()],
73
+ ['README.md', T.readme(nome)],
74
+ ];
75
+ }
76
+
77
+ // ── Funções auxiliares ────────────────────────────────────────────────────────
78
+
79
+ function criarArquivo(caminho, conteudo) {
80
+ writeFileSync(caminho, conteudo, 'utf-8');
81
+ }
82
+
83
+ function criarPasta(caminho) {
84
+ mkdirSync(caminho, { recursive: true });
85
+ }
86
+
87
+ function label(relativo) {
88
+ // Formata o caminho para exibição: pastas terminam com /
89
+ return relativo.includes('.') ? relativo : relativo + '/';
90
+ }
91
+
92
+ // ── Comando principal ─────────────────────────────────────────────────────────
93
+
94
+ export async function init(nome) {
95
+ // Validação do nome
96
+ if (!nome || typeof nome !== 'string' || nome.trim() === '') {
97
+ console.error(`\n${err()} Nome do projeto é obrigatório.\n`);
98
+ console.error(` Uso: ${azul('jade init')} ${amarelo('<nome-projeto>')}\n`);
99
+ process.exit(1);
100
+ }
101
+
102
+ nome = nome.trim();
103
+
104
+ // Verifica se o nome é válido (sem caracteres especiais problemáticos)
105
+ if (!/^[a-zA-Z0-9À-ÿ_-]+$/.test(nome)) {
106
+ console.error(`\n${err()} Nome inválido: ${amarelo(nome)}`);
107
+ console.error(` Use apenas letras, números, hífens e underscores.\n`);
108
+ process.exit(1);
109
+ }
110
+
111
+ const destino = resolve(process.cwd(), nome);
112
+
113
+ // Não sobrescrever projeto existente
114
+ if (existsSync(destino)) {
115
+ console.error(`\n${err()} A pasta ${amarelo(nome)} já existe.`);
116
+ console.error(` Escolha outro nome ou remova a pasta existente.\n`);
117
+ process.exit(1);
118
+ }
119
+
120
+ console.log(`\n${azul('JADE')} — criando projeto ${negrito(nome)}...\n`);
121
+
122
+ const itens = estrutura(nome);
123
+ const erros = [];
124
+
125
+ for (const [relativo, conteudo] of itens) {
126
+ const caminho = join(destino, relativo);
127
+ const exibir = label(relativo);
128
+
129
+ try {
130
+ if (conteudo === null) {
131
+ criarPasta(caminho);
132
+ } else {
133
+ // Garante que a pasta pai existe antes de criar o arquivo
134
+ criarPasta(join(caminho, '..'));
135
+ criarArquivo(caminho, conteudo);
136
+ }
137
+ console.log(` ${ok()} ${dim(exibir)}`);
138
+ } catch (e) {
139
+ erros.push({ relativo, erro: e.message });
140
+ console.error(` ${err()} ${exibir} — ${e.message}`);
141
+ }
142
+ }
143
+
144
+ if (erros.length > 0) {
145
+ console.error(`\n${err()} ${erros.length} erro(s) durante a criação. Verifique as permissões da pasta.\n`);
146
+ process.exit(1);
147
+ }
148
+
149
+ // ── Sucesso ──────────────────────────────────────────────────────────────
150
+
151
+ console.log(`\n${verde(`Projeto "${nome}" criado com sucesso!`)}\n`);
152
+ console.log(`${dim('Próximos passos:')}`);
153
+ console.log(` ${azul('cd')} ${nome}`);
154
+ console.log(` ${azul('npm install')}`);
155
+ console.log(` ${azul('jadec')} src/modulos/estoque.jd ${dim('--check')}`);
156
+ console.log(` ${dim('# abrir dist/index.html no navegador após compilar')}\n`);
157
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * commands/servir.js — Servidor estático local para apps JADE
3
+ *
4
+ * Uso: jade servir [pasta] [porta]
5
+ * jade servir → serve ./dist na porta 3000
6
+ * jade servir dist → serve ./dist na porta 3000
7
+ * jade servir dist 8080 → serve ./dist na porta 8080
8
+ *
9
+ * Usa apenas Node.js built-in (http, fs, path) — zero dependências extras.
10
+ * Necessário para PWA: service worker não funciona com file://
11
+ *
12
+ * Chamado por: jade/cli.js
13
+ */
14
+
15
+ import { createServer } from 'http';
16
+ import { readFileSync, existsSync, statSync } from 'fs';
17
+ import { join, extname, resolve } from 'path';
18
+
19
+ const MIME = {
20
+ '.html' : 'text/html; charset=utf-8',
21
+ '.js' : 'application/javascript; charset=utf-8',
22
+ '.mjs' : 'application/javascript; charset=utf-8',
23
+ '.wasm' : 'application/wasm',
24
+ '.json' : 'application/json; charset=utf-8',
25
+ '.css' : 'text/css; charset=utf-8',
26
+ '.png' : 'image/png',
27
+ '.jpg' : 'image/jpeg',
28
+ '.svg' : 'image/svg+xml',
29
+ '.ico' : 'image/x-icon',
30
+ '.txt' : 'text/plain; charset=utf-8',
31
+ };
32
+
33
+ const verde = (s) => `\x1b[1;32m${s}\x1b[0m`;
34
+ const azul = (s) => `\x1b[34m${s}\x1b[0m`;
35
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
36
+ const amarelo = (s) => `\x1b[1;33m${s}\x1b[0m`;
37
+ const vermelho = (s) => `\x1b[1;31m${s}\x1b[0m`;
38
+
39
+ export async function servir(pasta = 'dist', porta = 3000) {
40
+ const raiz = resolve(process.cwd(), pasta);
41
+
42
+ if (!existsSync(raiz)) {
43
+ console.error(`\n${vermelho('erro')}: pasta '${pasta}' não encontrada.`);
44
+ console.error(` ${dim('dica: compile primeiro com')} jadec app.jd -o dist/app\n`);
45
+ process.exit(1);
46
+ }
47
+
48
+ const server = createServer((req, res) => {
49
+ // Normaliza URL: remove query string e decodifica
50
+ let caminho = decodeURIComponent(req.url?.split('?')[0] ?? '/');
51
+
52
+ // Rota raiz → index.html
53
+ if (caminho === '/') caminho = '/index.html';
54
+
55
+ const arquivo = join(raiz, caminho);
56
+
57
+ // Proteção contra path traversal
58
+ if (!arquivo.startsWith(raiz)) {
59
+ res.writeHead(403);
60
+ res.end('Proibido');
61
+ return;
62
+ }
63
+
64
+ if (!existsSync(arquivo) || statSync(arquivo).isDirectory()) {
65
+ // SPA fallback → index.html
66
+ const index = join(raiz, 'index.html');
67
+ if (existsSync(index)) {
68
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
69
+ res.end(readFileSync(index));
70
+ } else {
71
+ res.writeHead(404);
72
+ res.end('404 — arquivo não encontrado');
73
+ }
74
+ return;
75
+ }
76
+
77
+ const ext = extname(arquivo).toLowerCase();
78
+ const tipo = MIME[ext] ?? 'application/octet-stream';
79
+ const dados = readFileSync(arquivo);
80
+
81
+ // Headers para PWA e WASM
82
+ res.writeHead(200, {
83
+ 'Content-Type': tipo,
84
+ 'Cross-Origin-Opener-Policy': 'same-origin',
85
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
86
+ });
87
+ res.end(dados);
88
+ });
89
+
90
+ server.listen(porta, '127.0.0.1', () => {
91
+ console.log(`\n${azul('JADE')} — servidor iniciado\n`);
92
+ console.log(` ${verde('→')} ${azul(`http://localhost:${porta}`)}`);
93
+ console.log(` ${dim(`servindo: ${raiz}`)}`);
94
+ console.log(`\n ${dim('Ctrl+C para encerrar')}\n`);
95
+ });
96
+
97
+ server.on('error', (e) => {
98
+ if (e.code === 'EADDRINUSE') {
99
+ console.error(`\n${vermelho('erro')}: porta ${porta} em uso.`);
100
+ console.error(` ${dim(`dica: tente outra porta → jade servir ${pasta} ${porta + 1}`)}\n`);
101
+ } else {
102
+ console.error(`\n${vermelho('erro')}: ${e.message}\n`);
103
+ }
104
+ process.exit(1);
105
+ });
106
+
107
+ // Não encerra — aguarda Ctrl+C
108
+ await new Promise(() => {});
109
+ }
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "@yakuzaa/jade",
3
- "version": "0.1.1",
4
- "description": "JADEDSL empresarial em português compilada para WebAssembly. Instala compilador + runtime.",
3
+ "version": "0.1.5",
4
+ "description": "Jade DSL linguagem empresarial em português compilada para WebAssembly. Instala compilador + runtime + CLI.",
5
5
  "type": "module",
6
+ "bin": {
7
+ "jade": "cli.js"
8
+ },
6
9
  "files": [
10
+ "cli.js",
11
+ "commands/",
12
+ "templates/",
7
13
  "postinstall.js",
8
14
  "README.md"
9
15
  ],
@@ -11,17 +17,20 @@
11
17
  "postinstall": "node postinstall.js"
12
18
  },
13
19
  "dependencies": {
14
- "@yakuzaa/jade-compiler": "0.1.1",
15
- "@yakuzaa/jade-runtime": "0.1.1"
20
+ "@yakuzaa/jade-compiler": "^0.1.6",
21
+ "@yakuzaa/jade-runtime": "^0.1.5"
16
22
  },
17
23
  "keywords": [
18
24
  "jade",
25
+ "jade-dsl",
19
26
  "dsl",
20
27
  "compiler",
21
28
  "runtime",
22
29
  "webassembly",
23
30
  "portuguese",
24
- "enterprise"
31
+ "enterprise",
32
+ "erp",
33
+ "mobile-first"
25
34
  ],
26
35
  "author": "yakuzaa",
27
36
  "license": "MIT",
@@ -0,0 +1,194 @@
1
+ /**
2
+ * templates/index.js — Conteúdo dos arquivos gerados pelo `jade init`
3
+ *
4
+ * Cada template é uma função que recebe o nome do projeto e retorna a string final.
5
+ * Mantido aqui para não poluir commands/init.js com strings longas.
6
+ */
7
+
8
+ // ── Entidades ────────────────────────────────────────────────────────────────
9
+
10
+ export const Produto = () => `\
11
+ entidade Produto
12
+ id: id
13
+ nome: texto
14
+ preco: moeda
15
+ estoque: numero
16
+ fim
17
+ `;
18
+
19
+ export const Cliente = () => `\
20
+ entidade Cliente
21
+ id: id
22
+ nome: texto
23
+ email: texto
24
+ fim
25
+ `;
26
+
27
+ // ── Eventos ──────────────────────────────────────────────────────────────────
28
+
29
+ export const ProdutoCriado = () => `\
30
+ evento ProdutoCriado
31
+ produtoId: id
32
+ nome: texto
33
+ fim
34
+ `;
35
+
36
+ // ── Serviços ─────────────────────────────────────────────────────────────────
37
+
38
+ export const estoqueService = () => `\
39
+ evento ProdutoCriado
40
+ produtoId: id
41
+ nome: texto
42
+ fim
43
+
44
+ servico estoqueService
45
+ funcao calcularValor(preco: decimal, quantidade: decimal) -> decimal
46
+ retornar preco * quantidade
47
+ fim
48
+
49
+ funcao temEstoque(quantidade: numero) -> booleano
50
+ se quantidade > 0
51
+ retornar verdadeiro
52
+ senao
53
+ retornar falso
54
+ fim
55
+ fim
56
+
57
+ escutar ProdutoCriado
58
+ fim
59
+ fim
60
+ `;
61
+
62
+ // ── Módulos ──────────────────────────────────────────────────────────────────
63
+
64
+ export const moduloEstoque = () => `\
65
+ modulo estoque
66
+ importar entidades.Produto
67
+ importar entidades.Cliente
68
+ importar eventos.ProdutoCriado
69
+ importar servicos.estoqueService
70
+ fim
71
+ `;
72
+
73
+ // ── UI / Telas ────────────────────────────────────────────────────────────────
74
+
75
+ export const telaProdutos = () => `\
76
+ entidade Produto
77
+ id: id
78
+ nome: texto
79
+ preco: moeda
80
+ estoque: numero
81
+ fim
82
+
83
+ tela ListaProdutos "Lista de Produtos"
84
+ tabela ListaProdutos
85
+ entidade: Produto
86
+ filtravel: verdadeiro
87
+ fim
88
+ fim
89
+ `;
90
+
91
+ export const telaPrincipal = () => `\
92
+ tela Principal "Página Inicial"
93
+ cartao BemVindo
94
+ fim
95
+ fim
96
+ `;
97
+
98
+ // ── Configurações ─────────────────────────────────────────────────────────────
99
+
100
+ export const jadeConfig = (nome) => JSON.stringify({
101
+ projeto: nome,
102
+ versao: '1.0.0',
103
+ idioma: 'pt-BR',
104
+ alvo: 'browser',
105
+ entrada: 'src/modulos/',
106
+ saida: 'dist/',
107
+ }, null, 2) + '\n';
108
+
109
+ export const databaseConfig = () => JSON.stringify({
110
+ provedor: 'local',
111
+ banco: 'jade-db',
112
+ versao: 1,
113
+ tabelas: ['Produto', 'Cliente'],
114
+ }, null, 2) + '\n';
115
+
116
+ export const deployConfig = () => JSON.stringify({
117
+ alvo: 'browser',
118
+ baseUrl: '/',
119
+ pwa: true,
120
+ offlineFirst: true,
121
+ }, null, 2) + '\n';
122
+
123
+ // ── package.json do projeto gerado ────────────────────────────────────────────
124
+
125
+ export const packageJson = (nome) => JSON.stringify({
126
+ name: nome,
127
+ version: '1.0.0',
128
+ description: `Projeto JADE — ${nome}`,
129
+ type: 'module',
130
+ scripts: {
131
+ compilar: 'jadec src/modulos/ -o dist/',
132
+ compilarEAssistir: 'jadec src/modulos/ -o dist/ --watch',
133
+ verificar: 'jadec src/modulos/ --check',
134
+ },
135
+ dependencies: {
136
+ '@yakuzaa/jade': 'latest',
137
+ '@yakuzaa/jade-compiler': 'latest',
138
+ '@yakuzaa/jade-runtime': 'latest',
139
+ },
140
+ }, null, 2) + '\n';
141
+
142
+ // ── .gitignore ────────────────────────────────────────────────────────────────
143
+
144
+ export const gitignore = () => `\
145
+ # Gerado pelo compilador JADE — nunca commitar
146
+ dist/
147
+
148
+ # Dependências
149
+ node_modules/
150
+
151
+ # Logs
152
+ *.log
153
+ `;
154
+
155
+ // ── README ────────────────────────────────────────────────────────────────────
156
+
157
+ export const readme = (nome) => `\
158
+ # ${nome}
159
+
160
+ Projeto criado com **JADE** — DSL empresarial em português.
161
+
162
+ ## Primeiros passos
163
+
164
+ \`\`\`bash
165
+ # Instalar dependências
166
+ npm install
167
+
168
+ # Compilar o projeto
169
+ npm run compilar
170
+
171
+ # Abrir no navegador
172
+ dist/index.html
173
+ \`\`\`
174
+
175
+ ## Estrutura
176
+
177
+ \`\`\`
178
+ src/
179
+ modulos/ → agrupamentos de funcionalidades
180
+ servicos/ → lógica de negócio
181
+ entidades/ → estruturas de dados
182
+ eventos/ → eventos de domínio
183
+ ui/telas/ → interfaces declarativas
184
+ config/
185
+ jade.config.json → configuração do compilador
186
+ database.json → configuração do banco de dados
187
+ deploy.json → configuração de deploy
188
+ dist/ → gerado pelo compilador (não commitar)
189
+ \`\`\`
190
+
191
+ ## Documentação
192
+
193
+ Acesse a documentação completa em: https://gabrielsymb.github.io/jade-language
194
+ `;