nex-app 0.1.0

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/ui/index.html ADDED
@@ -0,0 +1,113 @@
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Create NEX App</title>
7
+ <link rel="stylesheet" href="assets/styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <header>
12
+ <h1>🚀 Create NEX App</h1>
13
+ <p>Crie seu projeto NEX em minutos</p>
14
+ </header>
15
+
16
+ <form id="installer-form">
17
+ <!-- Step 1: Template -->
18
+ <div class="step active" data-step="1">
19
+ <h2>Template</h2>
20
+ <div class="template-grid">
21
+ <label class="template-card">
22
+ <input type="radio" name="template" value="blank" checked>
23
+ <div class="card-content">
24
+ <h3>Blank</h3>
25
+ <p>Projeto vazio para começar do zero</p>
26
+ </div>
27
+ </label>
28
+ <label class="template-card">
29
+ <input type="radio" name="template" value="full-stack">
30
+ <div class="card-content">
31
+ <h3>Full Stack</h3>
32
+ <p>Frontend + Backend completo</p>
33
+ </div>
34
+ </label>
35
+ <label class="template-card">
36
+ <input type="radio" name="template" value="api-only">
37
+ <div class="card-content">
38
+ <h3>API Only</h3>
39
+ <p>Apenas backend/API</p>
40
+ </div>
41
+ </label>
42
+ </div>
43
+ </div>
44
+
45
+ <!-- Step 2: Agentes -->
46
+ <div class="step" data-step="2">
47
+ <h2>Agentes Iniciais</h2>
48
+ <p class="subtitle">Selecione os agentes que deseja instalar (opcional)</p>
49
+
50
+ <div class="agent-search">
51
+ <input
52
+ type="text"
53
+ id="agent-search"
54
+ placeholder="Buscar agentes..."
55
+ autocomplete="off"
56
+ >
57
+ </div>
58
+
59
+ <div id="agent-results" class="agent-results"></div>
60
+ <div id="selected-agents" class="selected-agents">
61
+ <h3>Agentes Selecionados</h3>
62
+ <div id="selected-list" class="selected-list">
63
+ <p class="empty">Nenhum agente selecionado</p>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- Step 3: Cursor -->
69
+ <div class="step" data-step="3">
70
+ <h2>Integração Cursor</h2>
71
+ <label class="checkbox-label">
72
+ <input type="checkbox" name="cursorIntegration" checked>
73
+ <span>Configurar integração automática com Cursor IDE</span>
74
+ </label>
75
+ <small class="hint">Cria arquivos de configuração para integração com Cursor</small>
76
+ </div>
77
+
78
+ <!-- Step 4: Package Manager -->
79
+ <div class="step" data-step="4">
80
+ <h2>Package Manager</h2>
81
+ <select name="packageManager" id="packageManager">
82
+ <option value="npm">npm</option>
83
+ <option value="pnpm">pnpm</option>
84
+ <option value="yarn">yarn</option>
85
+ </select>
86
+ </div>
87
+
88
+ <div class="actions">
89
+ <button type="button" id="prev-btn" class="btn btn-secondary" disabled>Anterior</button>
90
+ <button type="button" id="next-btn" class="btn btn-primary">Próximo</button>
91
+ <button type="submit" id="submit-btn" class="btn btn-primary hidden">Criar Projeto</button>
92
+ </div>
93
+ </form>
94
+
95
+ <div id="progress" class="progress hidden">
96
+ <div class="spinner"></div>
97
+ <p>Gerando projeto...</p>
98
+ <small id="progress-message"></small>
99
+ </div>
100
+
101
+ <div id="success" class="success hidden">
102
+ <h2>✅ Projeto criado com sucesso!</h2>
103
+ <div id="success-message"></div>
104
+ <div class="next-steps">
105
+ <h3>Próximos passos:</h3>
106
+ <pre id="next-steps-commands"></pre>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <script src="assets/app.js"></script>
112
+ </body>
113
+ </html>
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Abrir navegador no sistema operacional
3
+ */
4
+
5
+ export async function openBrowser(url) {
6
+ const platform = process.platform
7
+
8
+ // Tentar usar pacote 'open' primeiro
9
+ try {
10
+ const { default: open } = await import('open')
11
+ await open(url)
12
+ return
13
+ } catch (error) {
14
+ // Fallback para comandos nativos
15
+ }
16
+
17
+ // Fallback: comandos nativos
18
+ const { exec } = await import('child_process')
19
+ const { promisify } = await import('util')
20
+ const execAsync = promisify(exec)
21
+
22
+ let command
23
+
24
+ switch (platform) {
25
+ case 'win32':
26
+ command = `start "" "${url}"`
27
+ break
28
+ case 'darwin':
29
+ command = `open "${url}"`
30
+ break
31
+ default:
32
+ command = `xdg-open "${url}"`
33
+ }
34
+
35
+ try {
36
+ await execAsync(command)
37
+ } catch (error) {
38
+ throw new Error(`Não foi possível abrir o navegador. Acesse manualmente: ${url}`)
39
+ }
40
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Detecção de ambiente (headless, CI, display, etc)
3
+ */
4
+
5
+ import { execSync } from 'child_process'
6
+
7
+ export function detectEnvironment() {
8
+ const isCI = Boolean(
9
+ process.env.CI ||
10
+ process.env.CONTINUOUS_INTEGRATION ||
11
+ process.env.BUILD_NUMBER ||
12
+ process.env.RUN_ID ||
13
+ process.env.GITHUB_ACTIONS ||
14
+ process.env.GITLAB_CI ||
15
+ process.env.CIRCLECI
16
+ )
17
+
18
+ const isHeadless = Boolean(
19
+ !process.stdout.isTTY ||
20
+ process.env.TERM === 'dumb' ||
21
+ process.env.NODE_ENV === 'test'
22
+ )
23
+
24
+ // Detectar se tem display (Linux/Mac)
25
+ let hasDisplay = true
26
+ if (process.platform !== 'win32') {
27
+ try {
28
+ // Tentar verificar se X11 está disponível
29
+ execSync('xset q', { stdio: 'ignore' })
30
+ } catch {
31
+ hasDisplay = false
32
+ }
33
+ }
34
+
35
+ return {
36
+ isCI,
37
+ isHeadless,
38
+ hasDisplay,
39
+ platform: process.platform,
40
+ nodeVersion: process.version
41
+ }
42
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Encontrar porta livre para o servidor
3
+ */
4
+
5
+ import { createServer } from 'net'
6
+
7
+ export async function findFreePort(startPort = 3000, maxAttempts = 10) {
8
+ return new Promise((resolve, reject) => {
9
+ let attempts = 0
10
+
11
+ const tryPort = (port) => {
12
+ if (attempts >= maxAttempts) {
13
+ reject(new Error(`Não foi possível encontrar porta livre após ${maxAttempts} tentativas`))
14
+ return
15
+ }
16
+
17
+ attempts++
18
+ const server = createServer()
19
+
20
+ server.listen(port, '127.0.0.1', () => {
21
+ const actualPort = server.address().port
22
+ server.close(() => resolve(actualPort))
23
+ })
24
+
25
+ server.on('error', (err) => {
26
+ if (err.code === 'EADDRINUSE') {
27
+ // Porta ocupada, tentar próxima
28
+ tryPort(port + 1)
29
+ } else {
30
+ reject(err)
31
+ }
32
+ })
33
+ }
34
+
35
+ tryPort(startPort)
36
+ })
37
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Validações de inputs
3
+ */
4
+
5
+ export function validateProjectName(name) {
6
+ if (!name || name.trim().length === 0) {
7
+ return { valid: false, error: 'Nome do projeto é obrigatório' }
8
+ }
9
+
10
+ if (!/^[a-z0-9-]+$/.test(name)) {
11
+ return {
12
+ valid: false,
13
+ error: 'Nome inválido. Use apenas letras minúsculas, números e hífens'
14
+ }
15
+ }
16
+
17
+ if (name.length > 50) {
18
+ return { valid: false, error: 'Nome muito longo (máximo 50 caracteres)' }
19
+ }
20
+
21
+ return { valid: true }
22
+ }
23
+
24
+ export function validateTemplate(template) {
25
+ const validTemplates = ['blank', 'full-stack', 'api-only']
26
+ if (!validTemplates.includes(template)) {
27
+ return {
28
+ valid: false,
29
+ error: `Template inválido. Use: ${validTemplates.join(', ')}`
30
+ }
31
+ }
32
+ return { valid: true }
33
+ }
34
+
35
+ export function validatePackageManager(pm) {
36
+ const validPMs = ['npm', 'pnpm', 'yarn']
37
+ if (!validPMs.includes(pm)) {
38
+ return {
39
+ valid: false,
40
+ error: `Package manager inválido. Use: ${validPMs.join(', ')}`
41
+ }
42
+ }
43
+ return { valid: true }
44
+ }