@neetru/cli 2.25.0 → 2.26.1

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.
@@ -0,0 +1,162 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { apiRequest } from '../lib/api-client.js';
4
+ import { requireToken, handleApiError } from '../lib/cli-read.js';
5
+ import { renderTable } from '../lib/render.js';
6
+ import { log } from '../utils/logger.js';
7
+ const ALL_EVENTS = [
8
+ 'subscription.activated',
9
+ 'subscription.cancelled',
10
+ 'subscription.payment_failed',
11
+ 'subscription.trial_ending',
12
+ 'usage.quota_exceeded',
13
+ 'account.suspended',
14
+ 'account.reactivated',
15
+ 'support.ticket_replied',
16
+ ];
17
+ export async function runWebhooksRegister(opts) {
18
+ const token = await requireToken(opts.json);
19
+ const spinner = opts.json ? null : ora({ text: 'Registrando webhook…', color: 'blue' }).start();
20
+ let res;
21
+ try {
22
+ res = await apiRequest('/api/cli/v1/webhooks', {
23
+ method: 'POST',
24
+ token,
25
+ body: {
26
+ productId: opts.productId,
27
+ url: opts.url,
28
+ events: opts.events,
29
+ secret: opts.secret,
30
+ },
31
+ });
32
+ spinner?.stop();
33
+ }
34
+ catch (error) {
35
+ spinner?.fail('Falha ao registrar webhook.');
36
+ handleApiError(error, opts.json);
37
+ }
38
+ if (opts.json) {
39
+ console.log(JSON.stringify(res));
40
+ return;
41
+ }
42
+ log.success('Webhook registrado com sucesso.');
43
+ console.log(` ${chalk.dim('ID:')} ${res.id}`);
44
+ console.log(` ${chalk.dim('URL:')} ${res.url}`);
45
+ console.log(` ${chalk.dim('Eventos:')} ${res.events.join(', ')}`);
46
+ console.log(` ${chalk.dim('Secret:')} ${res.hasSecret ? chalk.green('definido') : chalk.yellow('nenhum')}`);
47
+ console.log(` ${chalk.dim('Status:')} ${res.status}`);
48
+ }
49
+ export async function runWebhooksList(opts) {
50
+ const token = await requireToken(opts.json);
51
+ const spinner = opts.json ? null : ora({ text: 'Buscando webhooks…', color: 'blue' }).start();
52
+ let res;
53
+ try {
54
+ res = await apiRequest(`/api/cli/v1/webhooks?productId=${encodeURIComponent(opts.productId)}`, { token });
55
+ spinner?.stop();
56
+ }
57
+ catch (error) {
58
+ spinner?.fail('Falha ao listar webhooks.');
59
+ handleApiError(error, opts.json);
60
+ }
61
+ if (opts.json) {
62
+ console.log(JSON.stringify(res));
63
+ return;
64
+ }
65
+ if (res.count === 0) {
66
+ log.info('Nenhum webhook registrado para este produto.');
67
+ return;
68
+ }
69
+ log.heading(`Webhooks — ${res.productId} (${res.count})`);
70
+ renderTable([
71
+ { header: 'ID', value: (e) => e.id, maxWidth: 24 },
72
+ { header: 'URL', value: (e) => e.url, maxWidth: 48 },
73
+ { header: 'EVENTOS', value: (e) => e.events.join(', '), maxWidth: 36 },
74
+ { header: 'SECRET', value: (e) => (e.hasSecret ? chalk.green('sim') : chalk.yellow('não')) },
75
+ { header: 'STATUS', value: (e) => e.status === 'active' ? chalk.green(e.status) : chalk.red(e.status) },
76
+ { header: 'FALHAS', value: (e) => String(e.consecutiveFailures) },
77
+ ], res.endpoints);
78
+ }
79
+ export async function runWebhooksUnregister(opts) {
80
+ const token = await requireToken(opts.json);
81
+ const spinner = opts.json ? null : ora({ text: 'Removendo webhook…', color: 'yellow' }).start();
82
+ let res;
83
+ try {
84
+ res = await apiRequest(`/api/cli/v1/webhooks/${encodeURIComponent(opts.id)}?productId=${encodeURIComponent(opts.productId)}`, { method: 'DELETE', token });
85
+ spinner?.stop();
86
+ }
87
+ catch (error) {
88
+ spinner?.fail('Falha ao remover webhook.');
89
+ handleApiError(error, opts.json);
90
+ }
91
+ if (opts.json) {
92
+ console.log(JSON.stringify(res));
93
+ return;
94
+ }
95
+ log.success('Webhook removido.');
96
+ }
97
+ export async function runWebhooksTest(opts) {
98
+ const token = await requireToken(opts.json);
99
+ const spinner = opts.json ? null : ora({ text: 'Disparando evento de teste…', color: 'blue' }).start();
100
+ let res;
101
+ try {
102
+ res = await apiRequest(`/api/cli/v1/webhooks/${encodeURIComponent(opts.id)}/test?productId=${encodeURIComponent(opts.productId)}`, {
103
+ method: 'POST',
104
+ token,
105
+ body: opts.event ? { productId: opts.productId, event: opts.event } : { productId: opts.productId },
106
+ });
107
+ spinner?.stop();
108
+ }
109
+ catch (error) {
110
+ spinner?.fail('Falha ao disparar teste.');
111
+ handleApiError(error, opts.json);
112
+ }
113
+ if (opts.json) {
114
+ console.log(JSON.stringify(res));
115
+ return;
116
+ }
117
+ log.success(`Evento de teste "${res.eventType}" enfileirado (${res.eventId}).`);
118
+ console.log(` A entrega é assíncrona. Consulte ${chalk.cyan('neetru webhooks deliveries')} para ver o resultado.`);
119
+ }
120
+ export async function runWebhooksDeliveries(opts) {
121
+ const token = await requireToken(opts.json);
122
+ const params = new URLSearchParams({ productId: opts.productId });
123
+ if (opts.status)
124
+ params.set('status', opts.status);
125
+ if (opts.limit)
126
+ params.set('limit', String(opts.limit));
127
+ const spinner = opts.json ? null : ora({ text: 'Buscando entregas…', color: 'blue' }).start();
128
+ let res;
129
+ try {
130
+ res = await apiRequest(`/api/cli/v1/webhooks/${encodeURIComponent(opts.id)}/deliveries?${params.toString()}`, { token });
131
+ spinner?.stop();
132
+ }
133
+ catch (error) {
134
+ spinner?.fail('Falha ao listar entregas.');
135
+ handleApiError(error, opts.json);
136
+ }
137
+ if (opts.json) {
138
+ console.log(JSON.stringify(res));
139
+ return;
140
+ }
141
+ if (res.count === 0) {
142
+ log.info('Nenhuma entrega encontrada para este endpoint.');
143
+ return;
144
+ }
145
+ log.heading(`Entregas — ${res.endpointId} (${res.count})`);
146
+ renderTable([
147
+ { header: 'EVENTO', value: (d) => d.eventType ?? d.eventId.slice(0, 8), maxWidth: 28 },
148
+ { header: 'STATUS', value: (d) => {
149
+ switch (d.status) {
150
+ case 'delivered': return chalk.green(d.status);
151
+ case 'failed': return chalk.red(d.status);
152
+ case 'dead': return chalk.red.bold(d.status);
153
+ case 'delivering': return chalk.yellow(d.status);
154
+ default: return chalk.dim(d.status);
155
+ }
156
+ } },
157
+ { header: 'TENTATIVAS', value: (d) => `${d.attempts}${d.maxAttempts ? `/${d.maxAttempts}` : ''}` },
158
+ { header: 'CÓDIGO', value: (d) => d.lastStatusCode != null ? String(d.lastStatusCode) : '-' },
159
+ { header: 'ERRO', value: (d) => d.lastError ?? '-', maxWidth: 32 },
160
+ ], res.deliveries);
161
+ }
162
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/commands/webhooks.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -3,12 +3,15 @@ import { Command } from 'commander';
3
3
  import { log } from './utils/logger.js';
4
4
  import { CLI_VERSION } from './version.js';
5
5
  import { installTelemetryFlushHook, markCommandStart, markCommandEnd, } from './lib/telemetry.js';
6
- import { safeExit } from './utils/safe-exit.js';
7
- // safeExit() em src/utils/safe-exit.ts já faz cleanupHandles() que destrói
8
- // stdin ANTES do process.exit(). O handler 'exit' abaixo causava double-destroy
9
- // (safeExit → cleanupHandles → stdin.destroy() → process.exit → handler exit
10
- // → stdin.destroy() de novo), disparando UV_HANDLE_CLOSING no Windows.
11
- // Removido em bug_818cf6d / bug_6362578e fix definitivo (2026-07-15).
6
+ import { safeExit, isSafeExitHalt } from './utils/safe-exit.js';
7
+ // UV_HANDLE_CLOSING no Windows (bug_818cf6d): o double-destroy de stdin (handler
8
+ // 'exit' redundante) foi removido em 2026-07-15 (ddd479f), MAS isso não bastou
9
+ // — a causa dominante é o `uv_async_t` do undici (fetch) ainda vivo quando o
10
+ // `process.exit()` roda SÍNCRONO. Fix real (2026-07-16, re-confirmado por repro
11
+ // empírico 6/6): `safeExit()` em produção DEFERE o exit um macrotask (~100ms) e
12
+ // lança `SafeExitHalt`; os traps globais abaixo engolem o sinal — e deferem
13
+ // também qualquer erro não-tratado, que senão dispararia o mesmo crash pelo
14
+ // exit síncrono default do Node.
12
15
  // H-01 fix (Opus review 2026-05-27): instala flush hook NO ENTRY-POINT,
13
16
  // antes de qualquer parse. Garante que beforeExit + exit hooks estao armados
14
17
  // pra TODOS os caminhos — sucesso, falha via safeExit, ou exit direto.
@@ -16,6 +19,26 @@ import { safeExit } from './utils/safe-exit.js';
16
19
  // pulava safeExit(N != 0) — feature CLI telemetry capturava ~ε% do
17
20
  // que se propunha a medir.
18
21
  installTelemetryFlushHook();
22
+ // ── Traps globais de saída (bug_818cf6d / UV_HANDLE_CLOSING) ──────────────
23
+ // `safeExit()` lança `SafeExitHalt` em produção pra parar o caller sem
24
+ // `process.exit()` síncrono (que crasharia o undici no Windows). Aqui só
25
+ // engolimos o sinal — o `setTimeout` agendado dentro do `safeExit` faz a saída
26
+ // real. Qualquer OUTRO erro não-tratado é logado (com stack) e sai com exit
27
+ // DEFERIDO pelo mesmo motivo. `log.error` vai em try/catch pra nunca re-entrar.
28
+ function handleFatalSignal(err) {
29
+ if (isSafeExitHalt(err))
30
+ return; // saída já agendada dentro do safeExit
31
+ try {
32
+ log.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
33
+ }
34
+ catch {
35
+ /* logger indisponível — segue direto pro exit deferido */
36
+ }
37
+ process.exitCode = 1;
38
+ setTimeout(() => process.exit(1), 100);
39
+ }
40
+ process.on('uncaughtException', handleFatalSignal);
41
+ process.on('unhandledRejection', handleFatalSignal);
19
42
  const program = new Command();
20
43
  program
21
44
  .name('neetru')
@@ -271,12 +294,11 @@ program
271
294
  // ── neetru dev ────────────────────────────────────────────────────────
272
295
  // `neetru dev` (sem subcomando) = banco local Docker + watch de schema (M1
273
296
  // GAP-M1-4) — comportamento legado preservado (action + options intactos).
274
- // `neetru dev up` = emulador local completo (Firebase Emulators + Core),
275
- // estilo `firebase emulators` (ENG_REVERSA_CLI F-EMU/P0). Subcomando aditivo:
276
- // a action de `dev` só dispara quando nenhum subcomando casa.
297
+ // `neetru dev up` = ambiente dev-local (banco + agente) via Docker Compose.
298
+ // Subcomando aditivo: a action de `dev` só dispara quando nenhum subcomando casa.
277
299
  const devCmd = program
278
300
  .command('dev')
279
- .description('Ambiente de dev local — banco Docker (default) ou emulador completo (`dev up`)')
301
+ .description('Ambiente de dev local — banco Docker (default) ou ambiente completo (`dev up`)')
280
302
  .option('--db <name>', 'banco alvo do .neetru/db.json (default: único registrado)')
281
303
  .option('--schema <path>', 'caminho do arquivo de schema (default: db/schema.ts)')
282
304
  .action(async (opts) => {
@@ -285,22 +307,12 @@ const devCmd = program
285
307
  });
286
308
  devCmd
287
309
  .command('up')
288
- .description('Subir emulador local completo (Firebase Emulators + Core Next.js) — 100% offline')
289
- .option('--only <emus>', 'emuladores a subir (default: contexto dev ou firestore,auth,functions)')
290
- .option('--core-port <port>', 'porta do Core (default 9003)')
291
- .option('--core-path <path>', 'diretório do Core (default: cwd; precisa ter firebase.json)')
292
- .option('--skip-core', 'só os emuladores, sem subir o Core')
293
- .option('--import <dir>', 'importa estado dos emuladores deste diretório (seed portátil)')
294
- .option('--export-on-exit <dir>', 'exporta o estado dos emuladores ao sair (snapshot)')
310
+ .description('Sobe o ambiente dev-local (banco + agente) via Docker Compose')
311
+ .option('--core-port <port>', 'porta do app SaaS local (default 3000)')
295
312
  .action(async (opts) => {
296
313
  const { runDevUp } = await import('./commands/dev-up.js');
297
314
  await runDevUp({
298
- only: opts.only,
299
315
  corePort: opts.corePort ? Number(opts.corePort) : undefined,
300
- corePath: opts.corePath,
301
- skipCore: Boolean(opts.skipCore),
302
- importDir: opts.import,
303
- exportOnExit: opts.exportOnExit,
304
316
  });
305
317
  });
306
318
  devCmd
@@ -401,7 +413,7 @@ program
401
413
  .option('--target <target>', 'workspace | cloud-run | vm (interativo se omitido)')
402
414
  .option('--server <id>', 'id do server (target=vm)')
403
415
  .option('--domain <domain>', 'domínio público')
404
- .option('--port <port>', 'porta interna do app (obrigatório com --target=vm + --stack=node em modo --non-interactive)', (v) => Number.parseInt(v, 10))
416
+ .option('--port <port>', 'porta interna do app (target=cloud-run apenas; em target=vm é ignorado — porta é gerenciada pelo Core)', (v) => Number.parseInt(v, 10))
405
417
  .option('--artifact <file>', 'caminho do tarball já buildado')
406
418
  .option('--artifact-url <url>', 'URL pública/signed do artifact (GCS, S3)')
407
419
  .option('--artifact-sha256 <sha>', 'sha256 do artifact (par com --artifact-url)')
@@ -1409,6 +1421,7 @@ program
1409
1421
  .option('--channel <channel>', 'filtrar por channel (stdout | stderr | app)')
1410
1422
  .option('--correlation-id <id>', 'filtrar por correlationId (UUID)')
1411
1423
  .option('--json', 'saída em JSON (NDJSON linha a linha, útil para pipes)')
1424
+ .option('--local [path]', 'ler NDJSON local do emulador (dev up) em vez do Core; default .neetru/dev/logs.ndjson')
1412
1425
  .action(async (opts) => {
1413
1426
  const { runLogs } = await import('./commands/logs.js');
1414
1427
  await runLogs({
@@ -1422,6 +1435,7 @@ program
1422
1435
  channel: opts.channel,
1423
1436
  correlationId: opts.correlationId,
1424
1437
  json: !!opts.json,
1438
+ local: opts.local,
1425
1439
  });
1426
1440
  });
1427
1441
  // ── neetru vm ─────────────────────────────────────────────────────────
@@ -2361,6 +2375,79 @@ docsCmd
2361
2375
  json: !!opts.json,
2362
2376
  });
2363
2377
  });
2378
+ // ── neetru webhooks ──────────────────────────────────────────────────
2379
+ // Produtos SaaS registram endpoints de webhook pra receber eventos do Core.
2380
+ const webhooksCmd = program
2381
+ .command('webhooks')
2382
+ .description('Gerenciar webhooks de produto (register/list/unregister/test)');
2383
+ webhooksCmd
2384
+ .command('register')
2385
+ .description('Registrar endpoint de webhook para receber eventos do Core')
2386
+ .requiredOption('--product-id <id>', 'ID do produto')
2387
+ .requiredOption('--url <url>', 'URL do endpoint que receberá os eventos')
2388
+ .requiredOption('--events <csv>', 'eventos separados por vírgula')
2389
+ .option('--secret <secret>', 'segredo HMAC para assinar payloads (gerado automaticamente se omitido)')
2390
+ .option('--json', 'saída em JSON')
2391
+ .action(async (opts) => {
2392
+ const { runWebhooksRegister } = await import('./commands/webhooks.js');
2393
+ await runWebhooksRegister({
2394
+ productId: opts.productId,
2395
+ url: opts.url,
2396
+ events: opts.events.split(',').map((s) => s.trim()),
2397
+ secret: opts.secret,
2398
+ json: !!opts.json,
2399
+ });
2400
+ });
2401
+ webhooksCmd
2402
+ .command('list')
2403
+ .description('Listar endpoints de webhook registrados para um produto')
2404
+ .requiredOption('--product-id <id>', 'ID do produto')
2405
+ .option('--json', 'saída em JSON')
2406
+ .action(async (opts) => {
2407
+ const { runWebhooksList } = await import('./commands/webhooks.js');
2408
+ await runWebhooksList({ productId: opts.productId, json: !!opts.json });
2409
+ });
2410
+ webhooksCmd
2411
+ .command('unregister <id>')
2412
+ .description('Remover endpoint de webhook')
2413
+ .requiredOption('--product-id <id>', 'ID do produto')
2414
+ .option('--json', 'saída em JSON')
2415
+ .action(async (id, opts) => {
2416
+ const { runWebhooksUnregister } = await import('./commands/webhooks.js');
2417
+ await runWebhooksUnregister({ id, productId: opts.productId, json: !!opts.json });
2418
+ });
2419
+ webhooksCmd
2420
+ .command('test <id>')
2421
+ .description('Disparar evento de teste para um endpoint')
2422
+ .requiredOption('--product-id <id>', 'ID do produto')
2423
+ .option('--event <event>', 'evento específico a testar (default: primeiro inscrito)')
2424
+ .option('--json', 'saída em JSON')
2425
+ .action(async (id, opts) => {
2426
+ const { runWebhooksTest } = await import('./commands/webhooks.js');
2427
+ await runWebhooksTest({
2428
+ id,
2429
+ productId: opts.productId,
2430
+ event: opts.event,
2431
+ json: !!opts.json,
2432
+ });
2433
+ });
2434
+ webhooksCmd
2435
+ .command('deliveries <id>')
2436
+ .description('Histórico de entregas de um endpoint')
2437
+ .requiredOption('--product-id <id>', 'ID do produto')
2438
+ .option('--status <status>', 'filtrar por status: pending | delivering | delivered | failed | dead')
2439
+ .option('--limit <n>', 'máximo de eventos (1-100)', '20')
2440
+ .option('--json', 'saída em JSON')
2441
+ .action(async (id, opts) => {
2442
+ const { runWebhooksDeliveries } = await import('./commands/webhooks.js');
2443
+ await runWebhooksDeliveries({
2444
+ id,
2445
+ productId: opts.productId,
2446
+ status: opts.status,
2447
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
2448
+ json: !!opts.json,
2449
+ });
2450
+ });
2364
2451
  // ── neetru domains ──────────────────────────────────────────────────
2365
2452
  // Devs/IAs gerem os domínios de ambiente dos seus produtos (lista/reconcile/retry).
2366
2453
  const domainsCmd = program