@neetru/cli 2.28.7 → 2.28.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/CHANGELOG.md +44 -0
- package/README.md +23 -0
- package/dist/commands/admin/oidc-login.js +6 -3
- package/dist/commands/admin/oidc-login.js.map +1 -1
- package/dist/commands/admin/sdk-credential.js +11 -6
- package/dist/commands/admin/sdk-credential.js.map +1 -1
- package/dist/commands/agent-release.js +14 -27
- package/dist/commands/agent-release.js.map +1 -1
- package/dist/commands/ai-models.js +9 -4
- package/dist/commands/ai-models.js.map +1 -1
- package/dist/commands/archive.js +9 -2
- package/dist/commands/archive.js.map +1 -1
- package/dist/commands/auth.js +17 -2
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/autocomplete.d.ts +1 -1
- package/dist/commands/autocomplete.js +2 -0
- package/dist/commands/autocomplete.js.map +1 -1
- package/dist/commands/chat-menu.js +20 -6
- package/dist/commands/chat-menu.js.map +1 -1
- package/dist/commands/chat.d.ts +14 -0
- package/dist/commands/chat.js +32 -4
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/config.js +7 -0
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/db-move.d.ts +204 -0
- package/dist/commands/db-move.js +890 -0
- package/dist/commands/db-move.js.map +1 -0
- package/dist/commands/db.d.ts +14 -3
- package/dist/commands/db.js +102 -67
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.js +13 -1
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev-up.d.ts +5 -0
- package/dist/commands/dev-up.js +29 -4
- package/dist/commands/dev-up.js.map +1 -1
- package/dist/commands/domains.js +48 -36
- package/dist/commands/domains.js.map +1 -1
- package/dist/commands/init.d.ts +0 -8
- package/dist/commands/init.js +14 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/logs.js +18 -21
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/mobile.d.ts +26 -0
- package/dist/commands/mobile.js +344 -0
- package/dist/commands/mobile.js.map +1 -0
- package/dist/commands/ops.d.ts +3 -0
- package/dist/commands/ops.js +21 -0
- package/dist/commands/ops.js.map +1 -1
- package/dist/commands/promote.js +13 -32
- package/dist/commands/promote.js.map +1 -1
- package/dist/commands/schema.d.ts +4 -0
- package/dist/commands/schema.js +25 -16
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/smoke.d.ts +123 -0
- package/dist/commands/smoke.js +529 -0
- package/dist/commands/smoke.js.map +1 -0
- package/dist/commands/vm-memory.d.ts +53 -0
- package/dist/commands/vm-memory.js +307 -0
- package/dist/commands/vm-memory.js.map +1 -0
- package/dist/commands/webhooks.d.ts +2 -0
- package/dist/commands/webhooks.js +7 -0
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/index.js +351 -15
- package/dist/index.js.map +1 -1
- package/dist/lib/mobile-local/index.d.ts +7 -0
- package/dist/lib/mobile-local/index.js +7 -0
- package/dist/lib/mobile-local/index.js.map +1 -0
- package/dist/lib/mobile-local/mobile-json.d.ts +57 -0
- package/dist/lib/mobile-local/mobile-json.js +169 -0
- package/dist/lib/mobile-local/mobile-json.js.map +1 -0
- package/dist/lib/product-config.d.ts +5 -0
- package/dist/lib/product-config.js +37 -0
- package/dist/lib/product-config.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `neetru smoke` — percorre as páginas do produto LOCAL (antes do deploy) e
|
|
3
|
+
* confirma que estão de pé, que o login carrega e que o banco responde — mais
|
|
4
|
+
* um smoke básico de produção opcional.
|
|
5
|
+
*
|
|
6
|
+
* MOTIVAÇÃO (pedido do owner): devs não testavam o SaaS localmente antes de
|
|
7
|
+
* mandar pra prod. `neetru smoke` é a rede de segurança leve pra rodar contra o
|
|
8
|
+
* `next dev`/`neetru dev up` local (`--base-url`) antes de `neetru build` +
|
|
9
|
+
* `neetru deploy` — pega página quebrada, login que não renderiza, ou banco
|
|
10
|
+
* fora do ar SEM subir um browser.
|
|
11
|
+
*
|
|
12
|
+
* ABORDAGEM LEVE (decisão explícita): NADA de Playwright/headless browser. O
|
|
13
|
+
* CLI é publicado no npm e não pode ganhar uma dependência de ~300MB de browser.
|
|
14
|
+
* Fazemos só HTTP GET (fetch nativo) + inspeção do HTML/JSON retornado. Captura
|
|
15
|
+
* de erro de console (que exigiria um browser real) fica DEFERIDA pra um v2
|
|
16
|
+
* documentado, atrás de um flag opt-in que instale o headless sob demanda —
|
|
17
|
+
* ver `docs`/CHANGELOG.
|
|
18
|
+
*
|
|
19
|
+
* CHECKS (v1):
|
|
20
|
+
* 1. Descoberta de rotas + alcance — varre o App Router (`app/**\/page.*`) e o
|
|
21
|
+
* Pages Router legado (`pages/**\/*` exceto `_app`/`_document`/`api`),
|
|
22
|
+
* deriva as URLs (route groups `(...)` removidos; segmentos dinâmicos
|
|
23
|
+
* `[param]`/`[...slug]` PULADOS com aviso — não falham) e faz GET em cada
|
|
24
|
+
* uma. PASS se 200–399 e sem marcador de erro no corpo; FAIL se >=400 ou
|
|
25
|
+
* marcador de erro.
|
|
26
|
+
* 2. Login carrega — acha a rota de login (`/login` ou que contenha "login";
|
|
27
|
+
* senão tenta `/login`), confere que renderiza (status ok) e, idealmente,
|
|
28
|
+
* que tem uma "afford" de login (botão Neetru/OIDC). Sem botão = aviso;
|
|
29
|
+
* não-200 = falha.
|
|
30
|
+
* 3. Banco / health — GET no `--health-path` (default `/api/health`). 200 com
|
|
31
|
+
* corpo saudável = PASS; 404 = AVISO (produto não expõe health path — DB
|
|
32
|
+
* não verificável por aqui); qualquer outro não-200 = FALHA.
|
|
33
|
+
* 4. Smoke de prod (só com `--prod`) — GET em `<prod>/` e `<prod><health>`,
|
|
34
|
+
* espera 200 em cada.
|
|
35
|
+
*
|
|
36
|
+
* SAÍDA: humana (✓/✗/⚠ por check + tally) ou `--json` (uma linha, disciplina do
|
|
37
|
+
* `neetru build --json`). Exit 1 se QUALQUER falha dura; avisos não falham.
|
|
38
|
+
*
|
|
39
|
+
* TESTABILIDADE: a descoberta de rotas e as classificações são funções puras; o
|
|
40
|
+
* I/O de rede aceita um `fetchImpl` injetável (default `globalThis.fetch`) e a
|
|
41
|
+
* descoberta aceita um diretório — dá pra testar sem servidor vivo.
|
|
42
|
+
*/
|
|
43
|
+
import * as fs from 'node:fs';
|
|
44
|
+
import * as path from 'node:path';
|
|
45
|
+
import chalk from 'chalk';
|
|
46
|
+
import { log } from '../utils/logger.js';
|
|
47
|
+
import { safeExit } from '../utils/safe-exit.js';
|
|
48
|
+
function scopedLog(json) {
|
|
49
|
+
if (!json)
|
|
50
|
+
return log;
|
|
51
|
+
const noop = (_msg) => { };
|
|
52
|
+
return { info: noop, warn: noop, dim: noop, success: noop, heading: noop, error: log.error };
|
|
53
|
+
}
|
|
54
|
+
const APP_DIRS = ['app', 'src/app'];
|
|
55
|
+
const PAGES_DIRS = ['pages', 'src/pages'];
|
|
56
|
+
const PAGE_EXT_RE = /\.(tsx|ts|jsx|js)$/;
|
|
57
|
+
const DYNAMIC_SKIP_REASON = 'segmento dinâmico ([param]/[...slug]) — sem valor de amostra trivial, pulado (não falha)';
|
|
58
|
+
/**
|
|
59
|
+
* Deriva a URL de um arquivo `page.*` do App Router a partir do caminho relativo
|
|
60
|
+
* ao diretório `app/`. Route groups `(...)` e slots paralelos `@slot` são
|
|
61
|
+
* removidos; pastas privadas `_x` desligam o roteamento (retorna null); qualquer
|
|
62
|
+
* segmento `[...]` marca a rota como dinâmica.
|
|
63
|
+
*
|
|
64
|
+
* page.tsx → { route: '/', dynamic: false }
|
|
65
|
+
* dashboard/page.tsx → { route: '/dashboard', dynamic: false }
|
|
66
|
+
* (marketing)/about/page.tsx → { route: '/about', dynamic: false }
|
|
67
|
+
* blog/[slug]/page.tsx → { route: '/blog/[slug]', dynamic: true }
|
|
68
|
+
* api/health/route.ts → null (não é page.*)
|
|
69
|
+
*/
|
|
70
|
+
export function deriveAppRoutePath(relPath) {
|
|
71
|
+
const parts = relPath.split(/[\\/]/).filter(Boolean);
|
|
72
|
+
const file = parts[parts.length - 1];
|
|
73
|
+
if (!file || !/^page\.(tsx|ts|jsx|js)$/.test(file))
|
|
74
|
+
return null;
|
|
75
|
+
const dirs = parts.slice(0, -1);
|
|
76
|
+
const segs = [];
|
|
77
|
+
let dynamic = false;
|
|
78
|
+
for (const seg of dirs) {
|
|
79
|
+
if (seg.startsWith('(') && seg.endsWith(')'))
|
|
80
|
+
continue; // route group / intercepting
|
|
81
|
+
if (seg.startsWith('@'))
|
|
82
|
+
continue; // parallel route slot — transparente pra URL
|
|
83
|
+
if (seg.startsWith('_'))
|
|
84
|
+
return null; // pasta privada — não roteável
|
|
85
|
+
if (seg.includes('['))
|
|
86
|
+
dynamic = true;
|
|
87
|
+
segs.push(seg);
|
|
88
|
+
}
|
|
89
|
+
return { route: joinRoute(segs), dynamic };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Deriva a URL de um arquivo do Pages Router legado a partir do caminho relativo
|
|
93
|
+
* ao diretório `pages/`. Exclui `_app`/`_document`/`_error` e tudo sob `api/`.
|
|
94
|
+
*
|
|
95
|
+
* index.tsx → { route: '/', dynamic: false }
|
|
96
|
+
* about.tsx → { route: '/about', dynamic: false }
|
|
97
|
+
* dashboard/index.tsx → { route: '/dashboard', dynamic: false }
|
|
98
|
+
* blog/[slug].tsx → { route: '/blog/[slug]', dynamic: true }
|
|
99
|
+
* api/health.ts → null
|
|
100
|
+
* _app.tsx → null
|
|
101
|
+
*/
|
|
102
|
+
export function derivePagesRoutePath(relPath) {
|
|
103
|
+
const parts = relPath.split(/[\\/]/).filter(Boolean);
|
|
104
|
+
if (parts.length === 0)
|
|
105
|
+
return null;
|
|
106
|
+
if (parts.includes('api'))
|
|
107
|
+
return null; // rotas de API não são páginas
|
|
108
|
+
const file = parts[parts.length - 1];
|
|
109
|
+
const m = PAGE_EXT_RE.exec(file);
|
|
110
|
+
if (!m)
|
|
111
|
+
return null;
|
|
112
|
+
const base = file.slice(0, m.index);
|
|
113
|
+
if (base === '_app' || base === '_document' || base === '_error')
|
|
114
|
+
return null;
|
|
115
|
+
const dirs = parts.slice(0, -1);
|
|
116
|
+
const segs = [];
|
|
117
|
+
let dynamic = false;
|
|
118
|
+
for (const seg of dirs) {
|
|
119
|
+
if (seg.includes('['))
|
|
120
|
+
dynamic = true;
|
|
121
|
+
segs.push(seg);
|
|
122
|
+
}
|
|
123
|
+
if (base !== 'index') {
|
|
124
|
+
if (base.includes('['))
|
|
125
|
+
dynamic = true;
|
|
126
|
+
segs.push(base);
|
|
127
|
+
}
|
|
128
|
+
return { route: joinRoute(segs), dynamic };
|
|
129
|
+
}
|
|
130
|
+
function joinRoute(segs) {
|
|
131
|
+
if (segs.length === 0)
|
|
132
|
+
return '/';
|
|
133
|
+
return '/' + segs.join('/');
|
|
134
|
+
}
|
|
135
|
+
function isDir(p) {
|
|
136
|
+
try {
|
|
137
|
+
return fs.statSync(p).isDirectory();
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Coleta recursivamente todos os arquivos sob `root` (pula node_modules/.next/.git). */
|
|
144
|
+
function walkFiles(root) {
|
|
145
|
+
const out = [];
|
|
146
|
+
const stack = [root];
|
|
147
|
+
while (stack.length > 0) {
|
|
148
|
+
const dir = stack.pop();
|
|
149
|
+
let entries;
|
|
150
|
+
try {
|
|
151
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
for (const e of entries) {
|
|
157
|
+
if (e.name === 'node_modules' || e.name === '.next' || e.name === '.git')
|
|
158
|
+
continue;
|
|
159
|
+
const full = path.join(dir, e.name);
|
|
160
|
+
if (e.isDirectory())
|
|
161
|
+
stack.push(full);
|
|
162
|
+
else if (e.isFile())
|
|
163
|
+
out.push(full);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Descobre as rotas de um produto Next.js num diretório. Varre App Router
|
|
170
|
+
* (`app/`, `src/app/`) e Pages Router legado (`pages/`, `src/pages/`) e mescla,
|
|
171
|
+
* deduplicando. Rotas dinâmicas viram `skipped` (aviso). Função pura de I/O de
|
|
172
|
+
* disco — dá pra apontar pra uma fixture no teste.
|
|
173
|
+
*/
|
|
174
|
+
export function discoverRoutes(cwd) {
|
|
175
|
+
const routeSet = new Set();
|
|
176
|
+
const skippedMap = new Map();
|
|
177
|
+
let router = null;
|
|
178
|
+
let scannedDir = null;
|
|
179
|
+
const collect = (dirs, derive, label) => {
|
|
180
|
+
for (const rel of dirs) {
|
|
181
|
+
const abs = path.join(cwd, rel);
|
|
182
|
+
if (!isDir(abs))
|
|
183
|
+
continue;
|
|
184
|
+
if (router === null)
|
|
185
|
+
router = label;
|
|
186
|
+
if (scannedDir === null)
|
|
187
|
+
scannedDir = rel;
|
|
188
|
+
for (const file of walkFiles(abs)) {
|
|
189
|
+
const relToRoot = path.relative(abs, file);
|
|
190
|
+
const derived = derive(relToRoot);
|
|
191
|
+
if (!derived)
|
|
192
|
+
continue;
|
|
193
|
+
if (derived.dynamic) {
|
|
194
|
+
skippedMap.set(derived.route, DYNAMIC_SKIP_REASON);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
routeSet.add(derived.route);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
collect(APP_DIRS, deriveAppRoutePath, 'app');
|
|
202
|
+
collect(PAGES_DIRS, derivePagesRoutePath, 'pages');
|
|
203
|
+
const skipped = Array.from(skippedMap.entries())
|
|
204
|
+
.map(([route, reason]) => ({ route, reason }))
|
|
205
|
+
.sort((a, b) => a.route.localeCompare(b.route));
|
|
206
|
+
return { routes: Array.from(routeSet).sort(), skipped, router, scannedDir };
|
|
207
|
+
}
|
|
208
|
+
/** Um GET com timeout via AbortController. NUNCA lança — devolve HttpProbe. */
|
|
209
|
+
async function httpGet(fetchImpl, url, timeoutMs) {
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
212
|
+
try {
|
|
213
|
+
const res = await fetchImpl(url, {
|
|
214
|
+
method: 'GET',
|
|
215
|
+
// Não seguir redirect: um 3xx conta como alcance OK (2xx–3xx) e evita
|
|
216
|
+
// pendurar num IdP externo. undici devolve o status real no modo manual.
|
|
217
|
+
redirect: 'manual',
|
|
218
|
+
signal: controller.signal,
|
|
219
|
+
headers: { accept: 'text/html,application/json;q=0.9,*/*;q=0.8' },
|
|
220
|
+
});
|
|
221
|
+
const bodyText = await res.text().catch(() => '');
|
|
222
|
+
return { httpStatus: res.status, bodyText };
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
const aborted = controller.signal.aborted;
|
|
226
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
227
|
+
return { networkError: aborted ? `timeout após ${timeoutMs}ms` : msg };
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
clearTimeout(timer);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// ── Marcadores de erro no corpo (Next.js) ────────────────────────────────────
|
|
234
|
+
const ERROR_MARKERS = [
|
|
235
|
+
'application error',
|
|
236
|
+
'internal server error',
|
|
237
|
+
'__next_error__',
|
|
238
|
+
'this page could not be found',
|
|
239
|
+
];
|
|
240
|
+
/** Retorna o marcador de erro encontrado no corpo, ou null. */
|
|
241
|
+
export function bodyHasErrorMarker(bodyText) {
|
|
242
|
+
if (!bodyText)
|
|
243
|
+
return null;
|
|
244
|
+
const lower = bodyText.toLowerCase();
|
|
245
|
+
for (const m of ERROR_MARKERS) {
|
|
246
|
+
if (lower.includes(m))
|
|
247
|
+
return m;
|
|
248
|
+
}
|
|
249
|
+
// Título 500 (ex: `<title>500: ...</title>`).
|
|
250
|
+
if (/<title>\s*500\b/i.test(bodyText))
|
|
251
|
+
return '500 (title)';
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
// ── Heurísticas de corpo /health (espelham o contrato do deploy.ts) ──────────
|
|
255
|
+
const HEALTHY_STATUS = new Set(['ok', 'healthy', 'pass', 'passing', 'up', 'ready', 'live']);
|
|
256
|
+
const DEGRADED_STATUS = new Set(['degraded', 'down', 'unhealthy', 'fail', 'error', 'fatal']);
|
|
257
|
+
function parseJsonSafe(text) {
|
|
258
|
+
if (!text)
|
|
259
|
+
return undefined;
|
|
260
|
+
try {
|
|
261
|
+
return JSON.parse(text);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function pickBodyStatus(body) {
|
|
268
|
+
if (!body || typeof body !== 'object')
|
|
269
|
+
return undefined;
|
|
270
|
+
const b = body;
|
|
271
|
+
const candidate = b.status ?? b.state ?? b.health;
|
|
272
|
+
return typeof candidate === 'string' ? candidate.toLowerCase() : undefined;
|
|
273
|
+
}
|
|
274
|
+
function pickDbStatus(body) {
|
|
275
|
+
if (!body || typeof body !== 'object')
|
|
276
|
+
return undefined;
|
|
277
|
+
const db = body.db;
|
|
278
|
+
if (typeof db === 'string')
|
|
279
|
+
return db.toLowerCase();
|
|
280
|
+
if (db && typeof db === 'object' && typeof db.status === 'string') {
|
|
281
|
+
return String(db.status).toLowerCase();
|
|
282
|
+
}
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
function bodyLooksHealthy(body) {
|
|
286
|
+
if (!body || typeof body !== 'object')
|
|
287
|
+
return false;
|
|
288
|
+
const b = body;
|
|
289
|
+
if (b.ok === true || b.healthy === true)
|
|
290
|
+
return true;
|
|
291
|
+
const status = pickBodyStatus(body);
|
|
292
|
+
if (status && HEALTHY_STATUS.has(status))
|
|
293
|
+
return true;
|
|
294
|
+
const db = pickDbStatus(body);
|
|
295
|
+
if (db && HEALTHY_STATUS.has(db))
|
|
296
|
+
return true;
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
function bodyLooksDegraded(body) {
|
|
300
|
+
if (!body || typeof body !== 'object')
|
|
301
|
+
return false;
|
|
302
|
+
const b = body;
|
|
303
|
+
if (b.ok === false || b.healthy === false)
|
|
304
|
+
return true;
|
|
305
|
+
const status = pickBodyStatus(body);
|
|
306
|
+
if (status && DEGRADED_STATUS.has(status))
|
|
307
|
+
return true;
|
|
308
|
+
const db = pickDbStatus(body);
|
|
309
|
+
if (db && DEGRADED_STATUS.has(db))
|
|
310
|
+
return true;
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
function snippet(text) {
|
|
314
|
+
return (text ?? '').replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
315
|
+
}
|
|
316
|
+
/** Alcance de rota: 200–399 e sem marcador de erro = pass; >=400/marcador/rede = fail. */
|
|
317
|
+
export function classifyReachability(p) {
|
|
318
|
+
if (p.networkError)
|
|
319
|
+
return { result: 'fail', status: 'erro_rede', reason: p.networkError };
|
|
320
|
+
const s = p.httpStatus ?? 0;
|
|
321
|
+
if (s < 200)
|
|
322
|
+
return { result: 'fail', status: String(s), reason: `HTTP ${s} inesperado` };
|
|
323
|
+
if (s >= 400)
|
|
324
|
+
return { result: 'fail', status: String(s), reason: `HTTP ${s}` };
|
|
325
|
+
const marker = bodyHasErrorMarker(p.bodyText);
|
|
326
|
+
if (marker) {
|
|
327
|
+
return { result: 'fail', status: String(s), reason: `HTTP ${s} mas o corpo tem marcador de erro ("${marker}")` };
|
|
328
|
+
}
|
|
329
|
+
return { result: 'pass', status: String(s), reason: `HTTP ${s}` };
|
|
330
|
+
}
|
|
331
|
+
/** Health/DB: 200 saudável = pass; 200 degradado = fail; 404 = warn; resto = fail. */
|
|
332
|
+
export function classifyHealth(p) {
|
|
333
|
+
if (p.networkError)
|
|
334
|
+
return { result: 'fail', status: 'erro_rede', reason: p.networkError };
|
|
335
|
+
const s = p.httpStatus ?? 0;
|
|
336
|
+
if (s === 404) {
|
|
337
|
+
return { result: 'warn', status: '404', reason: 'produto não expõe health path — DB não verificável por aqui' };
|
|
338
|
+
}
|
|
339
|
+
if (s === 200) {
|
|
340
|
+
const body = parseJsonSafe(p.bodyText);
|
|
341
|
+
if (bodyLooksDegraded(body)) {
|
|
342
|
+
return { result: 'fail', status: '200', reason: `health respondeu 200 mas o corpo indica degradado/down: ${snippet(p.bodyText)}` };
|
|
343
|
+
}
|
|
344
|
+
if (bodyLooksHealthy(body)) {
|
|
345
|
+
return { result: 'pass', status: '200', reason: 'health ok (corpo indica saudável — DB responde)' };
|
|
346
|
+
}
|
|
347
|
+
return { result: 'pass', status: '200', reason: 'HTTP 200 (endpoint respondeu; corpo de health não reconhecido)' };
|
|
348
|
+
}
|
|
349
|
+
return { result: 'fail', status: String(s), reason: `HTTP ${s}` };
|
|
350
|
+
}
|
|
351
|
+
const LOGIN_AFFORDANCE = [
|
|
352
|
+
'entrar com neetru',
|
|
353
|
+
'neetrologinbutton',
|
|
354
|
+
'neetru-login',
|
|
355
|
+
'data-neetru-login',
|
|
356
|
+
'/authorize',
|
|
357
|
+
'auth.neetru.com',
|
|
358
|
+
'oidc',
|
|
359
|
+
'sign in',
|
|
360
|
+
'entrar',
|
|
361
|
+
'login',
|
|
362
|
+
];
|
|
363
|
+
/** Login: não-200/marcador/rede = fail; 2xx–3xx com afford = pass; sem afford = warn. */
|
|
364
|
+
export function classifyLogin(p) {
|
|
365
|
+
if (p.networkError)
|
|
366
|
+
return { result: 'fail', status: 'erro_rede', reason: p.networkError };
|
|
367
|
+
const s = p.httpStatus ?? 0;
|
|
368
|
+
if (s < 200 || s >= 400)
|
|
369
|
+
return { result: 'fail', status: String(s), reason: `HTTP ${s}` };
|
|
370
|
+
const marker = bodyHasErrorMarker(p.bodyText);
|
|
371
|
+
if (marker) {
|
|
372
|
+
return { result: 'fail', status: String(s), reason: `HTTP ${s} mas o corpo tem marcador de erro ("${marker}")` };
|
|
373
|
+
}
|
|
374
|
+
const lower = (p.bodyText ?? '').toLowerCase();
|
|
375
|
+
const hasAffordance = LOGIN_AFFORDANCE.some((a) => lower.includes(a));
|
|
376
|
+
if (hasAffordance) {
|
|
377
|
+
return { result: 'pass', status: String(s), reason: `HTTP ${s} — página de login carrega e tem afford de login` };
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
result: 'warn',
|
|
381
|
+
status: String(s),
|
|
382
|
+
reason: `HTTP ${s} — página carrega mas não detectei um botão/afford de login (Neetru/OIDC)`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/** Smoke de prod: espera exatamente 200. */
|
|
386
|
+
export function classifyExpect200(p) {
|
|
387
|
+
if (p.networkError)
|
|
388
|
+
return { result: 'fail', status: 'erro_rede', reason: p.networkError };
|
|
389
|
+
const s = p.httpStatus ?? 0;
|
|
390
|
+
if (s === 200)
|
|
391
|
+
return { result: 'pass', status: '200', reason: 'HTTP 200' };
|
|
392
|
+
return { result: 'fail', status: String(s), reason: `esperava 200, veio HTTP ${s}` };
|
|
393
|
+
}
|
|
394
|
+
// ── Helpers de path/URL ──────────────────────────────────────────────────────
|
|
395
|
+
/** Garante barra inicial e remove barra final (exceto na raiz). */
|
|
396
|
+
export function normalizePath(p) {
|
|
397
|
+
let s = p.trim();
|
|
398
|
+
if (!s.startsWith('/'))
|
|
399
|
+
s = '/' + s;
|
|
400
|
+
if (s.length > 1)
|
|
401
|
+
s = s.replace(/\/+$/, '');
|
|
402
|
+
return s;
|
|
403
|
+
}
|
|
404
|
+
function trimTrailingSlash(u) {
|
|
405
|
+
return u.replace(/\/+$/, '');
|
|
406
|
+
}
|
|
407
|
+
function icon(r) {
|
|
408
|
+
if (r === 'pass')
|
|
409
|
+
return chalk.green('✓');
|
|
410
|
+
if (r === 'fail')
|
|
411
|
+
return chalk.red('✗');
|
|
412
|
+
return chalk.yellow('⚠');
|
|
413
|
+
}
|
|
414
|
+
// ── Orquestração ─────────────────────────────────────────────────────────────
|
|
415
|
+
/**
|
|
416
|
+
* Executa o smoke. Retorna o `SmokeResult` (útil pra teste). Em falha dura chama
|
|
417
|
+
* `safeExit(1)` DEPOIS de imprimir (JSON em --json, humano caso contrário);
|
|
418
|
+
* avisos não falham.
|
|
419
|
+
*/
|
|
420
|
+
export async function runSmoke(opts = {}) {
|
|
421
|
+
const started = Date.now();
|
|
422
|
+
const json = !!opts.json;
|
|
423
|
+
const lg = scopedLog(json);
|
|
424
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
425
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
426
|
+
const baseUrl = trimTrailingSlash(opts.baseUrl ?? 'http://localhost:3000');
|
|
427
|
+
const healthPath = normalizePath(opts.healthPath ?? '/api/health');
|
|
428
|
+
const timeout = opts.timeout && opts.timeout > 0 ? opts.timeout : 10000;
|
|
429
|
+
const prod = opts.prod ? trimTrailingSlash(opts.prod) : null;
|
|
430
|
+
if (!json)
|
|
431
|
+
log.banner();
|
|
432
|
+
lg.heading('neetru smoke');
|
|
433
|
+
lg.dim(` base=${baseUrl} health=${healthPath} timeout=${timeout}ms${prod ? ` prod=${prod}` : ''}`);
|
|
434
|
+
const checks = [];
|
|
435
|
+
// ── 1. Descoberta de rotas ──────────────────────────────────────────────
|
|
436
|
+
let routes;
|
|
437
|
+
if (opts.routes && opts.routes.trim()) {
|
|
438
|
+
routes = Array.from(new Set(opts.routes
|
|
439
|
+
.split(',')
|
|
440
|
+
.map((r) => r.trim())
|
|
441
|
+
.filter(Boolean)
|
|
442
|
+
.map((r) => normalizePath(r))));
|
|
443
|
+
lg.info(`Rotas explícitas (--routes): ${routes.length}`);
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
const disc = discoverRoutes(cwd);
|
|
447
|
+
routes = disc.routes;
|
|
448
|
+
for (const sk of disc.skipped) {
|
|
449
|
+
checks.push({ name: 'rota', target: sk.route, status: 'pulado', result: 'warn', reason: sk.reason });
|
|
450
|
+
}
|
|
451
|
+
if (routes.length === 0) {
|
|
452
|
+
// Fallback: ao menos confirma que o servidor responde na raiz.
|
|
453
|
+
routes = ['/'];
|
|
454
|
+
const reason = disc.skipped.length
|
|
455
|
+
? 'só rotas dinâmicas descobertas — vou checar só "/" como smoke mínimo'
|
|
456
|
+
: 'nenhuma rota descoberta em app/ ou pages/ — checando só "/" (rode no diretório do produto ou use --routes)';
|
|
457
|
+
checks.push({ name: 'descoberta', target: cwd, status: 'nenhuma', result: 'warn', reason });
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
lg.info(`Rotas descobertas (${disc.router ?? '?'}): ${routes.length}` +
|
|
461
|
+
(disc.skipped.length ? ` (+${disc.skipped.length} dinâmica(s) pulada(s))` : ''));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// ── 1b. Alcance de cada rota ────────────────────────────────────────────
|
|
465
|
+
for (const route of routes) {
|
|
466
|
+
const probe = await httpGet(fetchImpl, baseUrl + route, timeout);
|
|
467
|
+
const v = classifyReachability(probe);
|
|
468
|
+
checks.push({ name: 'rota', target: route, status: v.status, result: v.result, reason: v.reason });
|
|
469
|
+
}
|
|
470
|
+
// ── 2. Login carrega ────────────────────────────────────────────────────
|
|
471
|
+
const loginRoute = routes.find((r) => r === '/login' || r.toLowerCase().includes('login')) ?? '/login';
|
|
472
|
+
{
|
|
473
|
+
const probe = await httpGet(fetchImpl, baseUrl + loginRoute, timeout);
|
|
474
|
+
const v = classifyLogin(probe);
|
|
475
|
+
checks.push({ name: 'login', target: loginRoute, status: v.status, result: v.result, reason: v.reason });
|
|
476
|
+
}
|
|
477
|
+
// ── 3. Banco / health ───────────────────────────────────────────────────
|
|
478
|
+
{
|
|
479
|
+
const probe = await httpGet(fetchImpl, baseUrl + healthPath, timeout);
|
|
480
|
+
const v = classifyHealth(probe);
|
|
481
|
+
checks.push({ name: 'health', target: healthPath, status: v.status, result: v.result, reason: v.reason });
|
|
482
|
+
}
|
|
483
|
+
// ── 4. Smoke de prod (opcional) ─────────────────────────────────────────
|
|
484
|
+
if (prod) {
|
|
485
|
+
const rootProbe = await httpGet(fetchImpl, prod + '/', timeout);
|
|
486
|
+
const rv = classifyExpect200(rootProbe);
|
|
487
|
+
checks.push({ name: 'prod /', target: prod + '/', status: rv.status, result: rv.result, reason: rv.reason });
|
|
488
|
+
const healthProbe = await httpGet(fetchImpl, prod + healthPath, timeout);
|
|
489
|
+
const hv = classifyExpect200(healthProbe);
|
|
490
|
+
checks.push({ name: 'prod health', target: prod + healthPath, status: hv.status, result: hv.result, reason: hv.reason });
|
|
491
|
+
}
|
|
492
|
+
// ── Tally ────────────────────────────────────────────────────────────────
|
|
493
|
+
const passed = checks.filter((c) => c.result === 'pass').length;
|
|
494
|
+
const failed = checks.filter((c) => c.result === 'fail').length;
|
|
495
|
+
const warned = checks.filter((c) => c.result === 'warn').length;
|
|
496
|
+
const summary = { passed, failed, warned };
|
|
497
|
+
const result = {
|
|
498
|
+
ok: failed === 0,
|
|
499
|
+
baseUrl,
|
|
500
|
+
prod,
|
|
501
|
+
checks,
|
|
502
|
+
summary,
|
|
503
|
+
durationMs: Date.now() - started,
|
|
504
|
+
};
|
|
505
|
+
// ── Saída humana ───────────────────────────────────────────────────────
|
|
506
|
+
if (!json) {
|
|
507
|
+
log.heading('Resultado');
|
|
508
|
+
for (const c of checks) {
|
|
509
|
+
console.log(` ${icon(c.result)} ${chalk.bold(c.name)} ${chalk.dim(c.target)} — ${c.reason}`);
|
|
510
|
+
}
|
|
511
|
+
log.separator();
|
|
512
|
+
console.log(` ${chalk.green(passed + ' ok')} · ${chalk.yellow(warned + ' aviso(s)')} · ${chalk.red(failed + ' falha(s)')}`);
|
|
513
|
+
if (failed > 0) {
|
|
514
|
+
log.error(`smoke FALHOU: ${failed} verificação(ões) com falha — corrija antes de deployar.`);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
log.success('smoke passou (avisos não bloqueiam).');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
// --json: UMA linha no stdout (sucesso e falha).
|
|
522
|
+
console.log(JSON.stringify(result));
|
|
523
|
+
}
|
|
524
|
+
// Exit 1 só em falha dura; avisos não falham.
|
|
525
|
+
if (failed > 0)
|
|
526
|
+
safeExit(1);
|
|
527
|
+
return result;
|
|
528
|
+
}
|
|
529
|
+
//# sourceMappingURL=smoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.js","sourceRoot":"","sources":["../../src/commands/smoke.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface MemoryAllocationEntry {
|
|
2
|
+
slug: string;
|
|
3
|
+
memoryMaxMb: number;
|
|
4
|
+
memoryCurrentMb: number | null;
|
|
5
|
+
headroomMb: number | null;
|
|
6
|
+
memoryHighMb?: number | null;
|
|
7
|
+
maxOldSpaceMb?: number | null;
|
|
8
|
+
}
|
|
9
|
+
export interface VmMemoryStatusOptions {
|
|
10
|
+
server?: string;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `neetru vm memory status [serverId]` (alias `list`) — lista os inquilinos da
|
|
15
|
+
* VM com teto desejado, uso vivo reportado pelo heartbeat e folga, mais o
|
|
16
|
+
* rodapé de soma/reserva/livre/RAM total.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runVmMemoryStatus(serverIdArg: string | undefined, opts: VmMemoryStatusOptions): Promise<void>;
|
|
19
|
+
export interface VmMemorySetOptions {
|
|
20
|
+
/** `--memory-max <MB>` (alias `--max`) — teto de cgroup systemd MemoryMax. */
|
|
21
|
+
memoryMax?: string;
|
|
22
|
+
max?: string;
|
|
23
|
+
/** `--max-old-space <MB>` — NODE_OPTIONS --max-old-space-size (heap V8). */
|
|
24
|
+
maxOldSpace?: string;
|
|
25
|
+
/** `--high <MB>` — cgroup MemoryHigh (throttle suave antes do OOM). */
|
|
26
|
+
high?: string;
|
|
27
|
+
/** `--restart` — força restart do app (default: só reinicia se max-old-space muda). */
|
|
28
|
+
restart?: boolean;
|
|
29
|
+
/** `--confirm-shrink` — reconhece que reduzir abaixo do uso vivo pode OOM o alvo. */
|
|
30
|
+
confirmShrink?: boolean;
|
|
31
|
+
/** `--server <id>` — VM alvo (default: resolve a VM compartilhada). */
|
|
32
|
+
server?: string;
|
|
33
|
+
/** `--reason <texto>` — motivo (paper-trail no audit do Core). */
|
|
34
|
+
reason?: string;
|
|
35
|
+
/** `--mfa <code>` (alias `--mfa-token`) — TOTP fresco pra step-up MFA. */
|
|
36
|
+
mfa?: string;
|
|
37
|
+
mfaToken?: string;
|
|
38
|
+
yes?: boolean;
|
|
39
|
+
json?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `neetru vm memory set <slug> --memory-max <MB> [--max-old-space <MB>] [--high <MB>]`
|
|
43
|
+
*
|
|
44
|
+
* Fluxo (design §3.7):
|
|
45
|
+
* 1. GET da alocação atual (pra projeção + aviso de encolhimento).
|
|
46
|
+
* 2. Deriva o teto efetivo (--memory-max, ou o teto atual do slug se só
|
|
47
|
+
* mudando --max-old-space).
|
|
48
|
+
* 3. Mostra a PROJEÇÃO (Σ tetos + reserva vs RAM×0.90) e AVISA se estoura ou
|
|
49
|
+
* se encolhe abaixo do uso vivo.
|
|
50
|
+
* 4. Confirma (inquirer) + step-up MFA.
|
|
51
|
+
* 5. POST → setProductMemoryLimit (o Core re-valida fail-closed + audita).
|
|
52
|
+
*/
|
|
53
|
+
export declare function runVmMemorySet(slug: string | undefined, opts: VmMemorySetOptions): Promise<void>;
|