@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 +93 -0
- package/commands/compilar.js +105 -0
- package/commands/html.js +218 -0
- package/commands/init.js +157 -0
- package/commands/servir.js +109 -0
- package/package.json +14 -5
- package/templates/index.js +194 -0
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
|
+
}
|
package/commands/html.js
ADDED
|
@@ -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
|
+
}
|
package/commands/init.js
ADDED
|
@@ -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.
|
|
4
|
-
"description": "
|
|
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.
|
|
15
|
-
"@yakuzaa/jade-runtime": "0.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
|
+
`;
|