@neetru/cli 2.10.0 → 2.11.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/CHANGELOG.md +63 -0
- package/README.md +11 -7
- package/dist/commands/agent-release.js +1 -1
- package/dist/commands/agent-release.js.map +1 -1
- package/dist/commands/autocomplete.d.ts +8 -1
- package/dist/commands/autocomplete.js +60 -18
- package/dist/commands/autocomplete.js.map +1 -1
- package/dist/commands/build.js +95 -14
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/config.d.ts +4 -1
- package/dist/commands/config.js +32 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/deploy.js +46 -10
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/doctor.js +3 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/logs.js +6 -4
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/promote.js +6 -6
- package/dist/commands/promote.js.map +1 -1
- package/dist/commands/publish.js +3 -2
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/status.js +2 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/validate.js +3 -2
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/whoami.js +3 -20
- package/dist/commands/whoami.js.map +1 -1
- package/dist/index.js +45 -17
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.d.ts +2 -0
- package/dist/lib/api-client.js +30 -2
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/auth.d.ts +10 -0
- package/dist/lib/auth.js +33 -1
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/cli-read.d.ts +3 -0
- package/dist/lib/cli-read.js +5 -2
- package/dist/lib/cli-read.js.map +1 -1
- package/dist/lib/telemetry.d.ts +45 -0
- package/dist/lib/telemetry.js +199 -0
- package/dist/lib/telemetry.js.map +1 -0
- package/dist/lib/token-resolver.d.ts +11 -0
- package/dist/lib/token-resolver.js +52 -0
- package/dist/lib/token-resolver.js.map +1 -0
- package/dist/lib/url-allowlist.d.ts +30 -0
- package/dist/lib/url-allowlist.js +90 -0
- package/dist/lib/url-allowlist.js.map +1 -0
- package/dist/utils/safe-exit.js +8 -15
- package/dist/utils/safe-exit.js.map +1 -1
- package/package.json +66 -66
package/dist/lib/auth.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* }
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
|
-
import { promises as fs, existsSync } from 'node:fs';
|
|
16
|
+
import { promises as fs, existsSync, readFileSync } from 'node:fs';
|
|
17
17
|
import { dirname, join } from 'node:path';
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
19
|
export function getAuthFilePath() {
|
|
@@ -102,6 +102,38 @@ export async function resolveSessionEmail() {
|
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* MASTER-HIGH-048 (2.11.0) — leitura sincrona APENAS do `auth.json`.
|
|
107
|
+
*
|
|
108
|
+
* Helper interno usado por `getActiveTokenSync()` (em `token-resolver.ts`).
|
|
109
|
+
* Mantido aqui pra preservar o limite de escopo `auth.ts` = `auth.json`-only.
|
|
110
|
+
*
|
|
111
|
+
* Retorna `null` em qualquer falha (arquivo ausente, JSON invalido, token
|
|
112
|
+
* vazio). Pra erro detalhado, usar `loadToken()` async.
|
|
113
|
+
*/
|
|
114
|
+
export function readAuthJsonSync() {
|
|
115
|
+
const authPath = getAuthFilePath();
|
|
116
|
+
if (!existsSync(authPath))
|
|
117
|
+
return null;
|
|
118
|
+
try {
|
|
119
|
+
const raw = readFileSync(authPath, 'utf8');
|
|
120
|
+
const parsed = JSON.parse(raw);
|
|
121
|
+
if (parsed &&
|
|
122
|
+
typeof parsed.token === 'string' &&
|
|
123
|
+
parsed.token.length > 0 &&
|
|
124
|
+
typeof parsed.email === 'string') {
|
|
125
|
+
return {
|
|
126
|
+
token: parsed.token,
|
|
127
|
+
email: parsed.email,
|
|
128
|
+
savedAt: parsed.savedAt ?? '',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// ignora — token ausente
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
105
137
|
export async function clearToken() {
|
|
106
138
|
const path = getAuthFilePath();
|
|
107
139
|
if (!existsSync(path))
|
package/dist/lib/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/cli-read.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve o Bearer token corrente. Retorna `null` se não houver sessão.
|
|
3
|
+
*
|
|
4
|
+
* MASTER-HIGH-048 (2.11.0): wrapper sobre `getActiveTokenSync()` —
|
|
5
|
+
* mantem `async` por compat com call-sites que esperam Promise.
|
|
3
6
|
*/
|
|
4
7
|
export declare function resolveToken(): Promise<string | null>;
|
|
5
8
|
/**
|
package/dist/lib/cli-read.js
CHANGED
|
@@ -9,13 +9,16 @@
|
|
|
9
9
|
* 1 — não autenticado / não encontrado (4xx semântico do usuário)
|
|
10
10
|
* 2 — token inválido/revogado, erro de rede, erro de servidor
|
|
11
11
|
*/
|
|
12
|
-
import { config } from './config.js';
|
|
13
12
|
import { loadToken, AuthFileMissingError, AuthFileInvalidError, } from './auth.js';
|
|
13
|
+
import { getActiveTokenSync } from './token-resolver.js';
|
|
14
14
|
import { CliApiError, CliNetworkError } from './api-client.js';
|
|
15
15
|
import { log } from '../utils/logger.js';
|
|
16
16
|
import { safeExit } from '../utils/safe-exit.js';
|
|
17
17
|
/**
|
|
18
18
|
* Resolve o Bearer token corrente. Retorna `null` se não houver sessão.
|
|
19
|
+
*
|
|
20
|
+
* MASTER-HIGH-048 (2.11.0): wrapper sobre `getActiveTokenSync()` —
|
|
21
|
+
* mantem `async` por compat com call-sites que esperam Promise.
|
|
19
22
|
*/
|
|
20
23
|
export async function resolveToken() {
|
|
21
24
|
try {
|
|
@@ -25,7 +28,7 @@ export async function resolveToken() {
|
|
|
25
28
|
catch (err) {
|
|
26
29
|
if (err instanceof AuthFileMissingError) {
|
|
27
30
|
// Fallback: Conf storage (legacy `neetru login --token <nrt_...>`).
|
|
28
|
-
return
|
|
31
|
+
return getActiveTokenSync();
|
|
29
32
|
}
|
|
30
33
|
if (err instanceof AuthFileInvalidError) {
|
|
31
34
|
log.error(err.message);
|
package/dist/lib/cli-read.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-read.js","sourceRoot":"","sources":["../../src/lib/cli-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"cli-read.js","sourceRoot":"","sources":["../../src/lib/cli-read.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface TelemetryEvent {
|
|
2
|
+
/** Nome do comando (ex: `db.apply`, `deploy`, `whoami`). */
|
|
3
|
+
command: string;
|
|
4
|
+
/** Duracao em ms (best-effort). */
|
|
5
|
+
durationMs: number;
|
|
6
|
+
/** Sucesso (exit code 0) ou falha. */
|
|
7
|
+
ok: boolean;
|
|
8
|
+
/** Exit code reportado (0..N). */
|
|
9
|
+
exitCode: number;
|
|
10
|
+
/** Carimbo ISO do evento. */
|
|
11
|
+
ts: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function isTelemetryEnabled(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Adiciona um evento ao buffer. Drop silencioso quando opt-in nao foi dado.
|
|
16
|
+
*
|
|
17
|
+
* Nao deve falhar — qualquer excecao eh engolida pra nao quebrar o comando.
|
|
18
|
+
*/
|
|
19
|
+
export declare function trackEvent(event: Omit<TelemetryEvent, 'ts'>): void;
|
|
20
|
+
/**
|
|
21
|
+
* Drena o buffer para o endpoint. Idempotente — chamadas subsequentes
|
|
22
|
+
* sao no-op apos primeira drenagem.
|
|
23
|
+
*
|
|
24
|
+
* **H-01 fix (Opus review 2026-05-27):** `keepalive: true` permite que a
|
|
25
|
+
* request POST sobreviva o `process.exit()` chamado por safeExit. Antes
|
|
26
|
+
* o request era abortado quando o event loop morria, e como safeExit
|
|
27
|
+
* tambem destruia o globalAgent (M-05), o fetch sequer chegava a sair.
|
|
28
|
+
*/
|
|
29
|
+
export declare function flushTelemetry(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Instala hooks de flush:
|
|
32
|
+
* - `beforeExit` — drena via event loop natural (sucesso, comandos que retornam)
|
|
33
|
+
* - `exit` — best-effort em paths que chamam `process.exit` direto (safeExit)
|
|
34
|
+
*
|
|
35
|
+
* **H-01 fix (Opus review 2026-05-27):** chamar no ENTRY-POINT do CLI
|
|
36
|
+
* (src/index.ts antes do parse), nao dentro de postAction (que nem dispara
|
|
37
|
+
* pra comandos que falham). Idempotente — subsequente eh no-op.
|
|
38
|
+
*/
|
|
39
|
+
export declare function installTelemetryFlushHook(): void;
|
|
40
|
+
export declare function markCommandStart(commandName: string): void;
|
|
41
|
+
export declare function markCommandEnd(exitCode: number): void;
|
|
42
|
+
/** Para tests — esvazia o estado de comando atual. */
|
|
43
|
+
export declare function _resetCurrentCommandForTests(): void;
|
|
44
|
+
/** Para tests — esvazia o buffer e o flag de flush. */
|
|
45
|
+
export declare function _resetTelemetryForTests(): void;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MASTER-HIGH-052 (2.11.0) — emitter de telemetry CLI.
|
|
3
|
+
*
|
|
4
|
+
* Pre-2.11 a flag `telemetry.enabled` existia no `config.ts` schema, era
|
|
5
|
+
* settable via `neetru config set telemetry.enabled true`, mas NENHUM
|
|
6
|
+
* call-site emitia evento. Comentario placeholder `// Sprint 11` ficou la
|
|
7
|
+
* por umas seis sprints. (HIGH-10 chefe-CLI HIGH-05.)
|
|
8
|
+
*
|
|
9
|
+
* Implementacao 2.11:
|
|
10
|
+
* - Buffer in-memory (max 50 eventos) drenado em processo `beforeExit`.
|
|
11
|
+
* - Opt-in default-OFF (`config.get('telemetry.enabled') === true`).
|
|
12
|
+
* - Anonimo: instalacao identificada por `installId` (UUID gerado uma
|
|
13
|
+
* vez em ~/.config/neetru-cli/install-id) — sem email, sem token,
|
|
14
|
+
* sem path absoluto, sem env vars, sem arg do usuario.
|
|
15
|
+
* - Endpoint: POST /api/sdk/v1/telemetry/log (Core ja tem rota viva).
|
|
16
|
+
* - Fire-and-forget: erro de rede NUNCA atrapalha o comando do usuario.
|
|
17
|
+
*
|
|
18
|
+
* Override pra dev/CI:
|
|
19
|
+
* - `NEETRU_TELEMETRY_DISABLED=1` desliga forcado.
|
|
20
|
+
* - `NEETRU_TELEMETRY_URL=...` redireciona (usado em vitest).
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { join, dirname } from 'node:path';
|
|
24
|
+
import { homedir } from 'node:os';
|
|
25
|
+
import { randomUUID } from 'node:crypto';
|
|
26
|
+
import { config } from './config.js';
|
|
27
|
+
import { CLI_VERSION } from '../version.js';
|
|
28
|
+
const MAX_BUFFER = 50;
|
|
29
|
+
const buffer = [];
|
|
30
|
+
let flushed = false;
|
|
31
|
+
let cachedInstallId = null;
|
|
32
|
+
function getInstallIdPath() {
|
|
33
|
+
return join(homedir(), '.config', 'neetru-cli', 'install-id');
|
|
34
|
+
}
|
|
35
|
+
function readOrCreateInstallId() {
|
|
36
|
+
if (cachedInstallId)
|
|
37
|
+
return cachedInstallId;
|
|
38
|
+
const filePath = getInstallIdPath();
|
|
39
|
+
if (existsSync(filePath)) {
|
|
40
|
+
try {
|
|
41
|
+
const raw = readFileSync(filePath, 'utf8').trim();
|
|
42
|
+
if (raw.length >= 8) {
|
|
43
|
+
cachedInstallId = raw;
|
|
44
|
+
return raw;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// re-cria
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const id = randomUUID();
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
54
|
+
writeFileSync(filePath, id, { encoding: 'utf8', mode: 0o600 });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// sem permissao — segue com installId em-memoria, sem persistencia
|
|
58
|
+
}
|
|
59
|
+
cachedInstallId = id;
|
|
60
|
+
return id;
|
|
61
|
+
}
|
|
62
|
+
function getEndpoint() {
|
|
63
|
+
if (process.env.NEETRU_TELEMETRY_URL)
|
|
64
|
+
return process.env.NEETRU_TELEMETRY_URL;
|
|
65
|
+
const base = config.get('neetruApiUrl') ??
|
|
66
|
+
'https://api.neetru.com';
|
|
67
|
+
return `${base.replace(/\/+$/, '')}/api/sdk/v1/telemetry/log`;
|
|
68
|
+
}
|
|
69
|
+
export function isTelemetryEnabled() {
|
|
70
|
+
if (process.env.NEETRU_TELEMETRY_DISABLED === '1')
|
|
71
|
+
return false;
|
|
72
|
+
const v = config.get('telemetry.enabled');
|
|
73
|
+
return v === true;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Adiciona um evento ao buffer. Drop silencioso quando opt-in nao foi dado.
|
|
77
|
+
*
|
|
78
|
+
* Nao deve falhar — qualquer excecao eh engolida pra nao quebrar o comando.
|
|
79
|
+
*/
|
|
80
|
+
export function trackEvent(event) {
|
|
81
|
+
try {
|
|
82
|
+
if (!isTelemetryEnabled())
|
|
83
|
+
return;
|
|
84
|
+
if (buffer.length >= MAX_BUFFER)
|
|
85
|
+
return; // soft cap, nao bloqueia comando
|
|
86
|
+
buffer.push({ ...event, ts: new Date().toISOString() });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// engole
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Drena o buffer para o endpoint. Idempotente — chamadas subsequentes
|
|
94
|
+
* sao no-op apos primeira drenagem.
|
|
95
|
+
*
|
|
96
|
+
* **H-01 fix (Opus review 2026-05-27):** `keepalive: true` permite que a
|
|
97
|
+
* request POST sobreviva o `process.exit()` chamado por safeExit. Antes
|
|
98
|
+
* o request era abortado quando o event loop morria, e como safeExit
|
|
99
|
+
* tambem destruia o globalAgent (M-05), o fetch sequer chegava a sair.
|
|
100
|
+
*/
|
|
101
|
+
export async function flushTelemetry() {
|
|
102
|
+
if (flushed)
|
|
103
|
+
return;
|
|
104
|
+
flushed = true;
|
|
105
|
+
if (!isTelemetryEnabled())
|
|
106
|
+
return;
|
|
107
|
+
if (buffer.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
const envelope = {
|
|
110
|
+
cliVersion: CLI_VERSION,
|
|
111
|
+
installId: readOrCreateInstallId(),
|
|
112
|
+
os: `${process.platform}-${process.arch}`,
|
|
113
|
+
events: buffer.splice(0, buffer.length),
|
|
114
|
+
};
|
|
115
|
+
const endpoint = getEndpoint();
|
|
116
|
+
// Fire-and-forget com timeout curto (1.5s). Erro de rede engolido.
|
|
117
|
+
const controller = new AbortController();
|
|
118
|
+
const tid = setTimeout(() => controller.abort(), 1500);
|
|
119
|
+
try {
|
|
120
|
+
await fetch(endpoint, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: { 'content-type': 'application/json' },
|
|
123
|
+
body: JSON.stringify(envelope),
|
|
124
|
+
signal: controller.signal,
|
|
125
|
+
// H-01 fix: garante que request sobrevive process.exit() do safeExit.
|
|
126
|
+
// Suportado em Node 18+ (undici) e em todos browsers/Edge runtimes.
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
128
|
+
// @ts-ignore — RequestInit em Node DOM types nao expoe `keepalive`
|
|
129
|
+
keepalive: true,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// engole — falha de telemetry nao afeta o usuario
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
clearTimeout(tid);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let exitHookInstalled = false;
|
|
140
|
+
/**
|
|
141
|
+
* Instala hooks de flush:
|
|
142
|
+
* - `beforeExit` — drena via event loop natural (sucesso, comandos que retornam)
|
|
143
|
+
* - `exit` — best-effort em paths que chamam `process.exit` direto (safeExit)
|
|
144
|
+
*
|
|
145
|
+
* **H-01 fix (Opus review 2026-05-27):** chamar no ENTRY-POINT do CLI
|
|
146
|
+
* (src/index.ts antes do parse), nao dentro de postAction (que nem dispara
|
|
147
|
+
* pra comandos que falham). Idempotente — subsequente eh no-op.
|
|
148
|
+
*/
|
|
149
|
+
export function installTelemetryFlushHook() {
|
|
150
|
+
if (exitHookInstalled)
|
|
151
|
+
return;
|
|
152
|
+
exitHookInstalled = true;
|
|
153
|
+
process.once('beforeExit', () => {
|
|
154
|
+
void flushTelemetry();
|
|
155
|
+
});
|
|
156
|
+
// exit hook adicional: capa comandos que chamam process.exit() direto
|
|
157
|
+
// (sem deixar event loop drenar). fetch keepalive:true permite que a
|
|
158
|
+
// request sobreviva. Note: process.on('exit') eh SINCRONO, void fetch
|
|
159
|
+
// nao bloqueia — best-effort.
|
|
160
|
+
process.once('exit', () => {
|
|
161
|
+
void flushTelemetry();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* **H-01 fix camada 2 (Opus review):** captura comandos que terminam via
|
|
166
|
+
* `safeExit(N != 0)` ou `process.exit(N)`. Sem isso, comandos que falham
|
|
167
|
+
* (a porcao mais importante pra debug UX) NUNCA emitem evento de
|
|
168
|
+
* telemetria.
|
|
169
|
+
*
|
|
170
|
+
* Chamar no `preAction` hook do Commander pra marcar inicio do comando.
|
|
171
|
+
* O `process.exit` hook captura o exit code real.
|
|
172
|
+
*/
|
|
173
|
+
let currentCommand = null;
|
|
174
|
+
export function markCommandStart(commandName) {
|
|
175
|
+
currentCommand = { name: commandName, start: Date.now() };
|
|
176
|
+
}
|
|
177
|
+
export function markCommandEnd(exitCode) {
|
|
178
|
+
if (!currentCommand)
|
|
179
|
+
return;
|
|
180
|
+
trackEvent({
|
|
181
|
+
command: currentCommand.name,
|
|
182
|
+
durationMs: Date.now() - currentCommand.start,
|
|
183
|
+
ok: exitCode === 0,
|
|
184
|
+
exitCode,
|
|
185
|
+
});
|
|
186
|
+
currentCommand = null;
|
|
187
|
+
}
|
|
188
|
+
/** Para tests — esvazia o estado de comando atual. */
|
|
189
|
+
export function _resetCurrentCommandForTests() {
|
|
190
|
+
currentCommand = null;
|
|
191
|
+
}
|
|
192
|
+
/** Para tests — esvazia o buffer e o flag de flush. */
|
|
193
|
+
export function _resetTelemetryForTests() {
|
|
194
|
+
buffer.length = 0;
|
|
195
|
+
flushed = false;
|
|
196
|
+
cachedInstallId = null;
|
|
197
|
+
exitHookInstalled = false;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/lib/telemetry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve o Bearer token corrente. Retorna `null` se nao houver sessao.
|
|
3
|
+
*
|
|
4
|
+
* Side effect: na primeira vez que o token vier do Conf (e nao do
|
|
5
|
+
* auth.json), emite um warning stderr (one-shot por processo) avisando da
|
|
6
|
+
* deprecacao da rota legacy. Comandos com `--json` ainda assim recebem o
|
|
7
|
+
* warning em stderr (machine-readable stdout fica limpo).
|
|
8
|
+
*/
|
|
9
|
+
export declare function getActiveTokenSync(): string | null;
|
|
10
|
+
/** Para tests — reseta o flag de warning one-shot. */
|
|
11
|
+
export declare function _resetLegacyWarningForTests(): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MASTER-HIGH-048 (2.11.0) — token resolver sincrono.
|
|
3
|
+
*
|
|
4
|
+
* Antes da 2.11, `api-client.ts` lia o token APENAS do `Conf` storage. Mas
|
|
5
|
+
* desde a Sprint 1 (2026-05-04) o storage canonico passou a ser
|
|
6
|
+
* `~/.config/neetru-cli/auth.json` (chmod 0600, fora do JSON do Conf).
|
|
7
|
+
* `login` gravava nos DOIS pra compat; comandos como `status`, `validate`,
|
|
8
|
+
* `db`, `deploy`, `vm`, `logs`, `doctor`, `publish` so conseguiam ler de
|
|
9
|
+
* Conf — entao copiar `auth.json` pra outra maquina deixava esses 8
|
|
10
|
+
* comandos "logado mas nao logado". Cause classica de bug de suporte.
|
|
11
|
+
*
|
|
12
|
+
* Solucao: resolver sincrono que prefere `auth.json` e usa Conf so como
|
|
13
|
+
* fallback. Quem chamava `config.get('neetruApiKey')` direto agora chama
|
|
14
|
+
* `getActiveTokenSync()`.
|
|
15
|
+
*
|
|
16
|
+
* Ordem:
|
|
17
|
+
* 1. `~/.config/neetru-cli/auth.json` (canonico)
|
|
18
|
+
* 2. Conf `neetruApiKey` (legacy — emite deprecation warning em 2.11; remove em 2.12.0)
|
|
19
|
+
* 3. null
|
|
20
|
+
*/
|
|
21
|
+
import { readAuthJsonSync } from './auth.js';
|
|
22
|
+
import { config } from './config.js';
|
|
23
|
+
let legacyWarningEmitted = false;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve o Bearer token corrente. Retorna `null` se nao houver sessao.
|
|
26
|
+
*
|
|
27
|
+
* Side effect: na primeira vez que o token vier do Conf (e nao do
|
|
28
|
+
* auth.json), emite um warning stderr (one-shot por processo) avisando da
|
|
29
|
+
* deprecacao da rota legacy. Comandos com `--json` ainda assim recebem o
|
|
30
|
+
* warning em stderr (machine-readable stdout fica limpo).
|
|
31
|
+
*/
|
|
32
|
+
export function getActiveTokenSync() {
|
|
33
|
+
const auth = readAuthJsonSync();
|
|
34
|
+
if (auth && auth.token)
|
|
35
|
+
return auth.token;
|
|
36
|
+
const legacy = config.get('neetruApiKey');
|
|
37
|
+
if (typeof legacy === 'string' && legacy.length > 0) {
|
|
38
|
+
if (!legacyWarningEmitted && !process.env.NEETRU_SUPPRESS_LEGACY_WARNINGS) {
|
|
39
|
+
legacyWarningEmitted = true;
|
|
40
|
+
// stderr — preserva pipe-friendly stdout.
|
|
41
|
+
process.stderr.write('⚠ Aviso: usando token do storage legacy (Conf). Execute `neetru logout && neetru login` pra migrar pra ~/.config/neetru-cli/auth.json.\n' +
|
|
42
|
+
' Storage legacy sera removido em @neetru/cli@2.12.\n');
|
|
43
|
+
}
|
|
44
|
+
return legacy;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/** Para tests — reseta o flag de warning one-shot. */
|
|
49
|
+
export function _resetLegacyWarningForTests() {
|
|
50
|
+
legacyWarningEmitted = false;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=token-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-resolver.js","sourceRoot":"","sources":["../../src/lib/token-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,IAAI,oBAAoB,GAAG,KAAK,CAAC;AAEjC;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,IAAI,GAAG,gBAAgB,EAAE,CAAC;IAChC,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC;IAE1C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,oBAAoB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,+BAA+B,EAAE,CAAC;YAC1E,oBAAoB,GAAG,IAAI,CAAC;YAC5B,0CAA0C;YAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,0IAA0I;gBACxI,uDAAuD,CAC1D,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,2BAA2B;IACzC,oBAAoB,GAAG,KAAK,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MASTER-HIGH-049 (2.11.0) — allowlist de hosts pra `neetruApiUrl`.
|
|
3
|
+
*
|
|
4
|
+
* Pre-2.11, `neetru config set neetruApiUrl http://evil.com` era aceito sem
|
|
5
|
+
* warning. Proxima chamada `neetru whoami` mandava o Bearer
|
|
6
|
+
* `nrt_<keyId>_<secret>` direto pra `evil.com` — vetor de exfiltracao do
|
|
7
|
+
* token CLI. (HIGH-10 / chefe-CLI HIGH-02.)
|
|
8
|
+
*
|
|
9
|
+
* Politica:
|
|
10
|
+
* - Hosts trustred: api.neetru.com, core.neetru.com, *.neetru.com,
|
|
11
|
+
* localhost (qualquer porta), 127.0.0.1 (qualquer porta).
|
|
12
|
+
* - Hosts NAO trusted: warning vermelho + bloqueio sem
|
|
13
|
+
* `--allow-untrusted-url` (ou env `NEETRU_ALLOW_UNTRUSTED_URL=1`).
|
|
14
|
+
*/
|
|
15
|
+
export interface AllowlistResult {
|
|
16
|
+
trusted: boolean;
|
|
17
|
+
host: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Classifica a URL alvo. Retorna `trusted=true` quando o host bate na
|
|
22
|
+
* allowlist; `trusted=false` (com `reason`) caso contrario.
|
|
23
|
+
*/
|
|
24
|
+
export declare function classifyApiUrl(rawUrl: string): AllowlistResult;
|
|
25
|
+
/**
|
|
26
|
+
* Override por env. Setar `NEETRU_ALLOW_UNTRUSTED_URL=1` pula a checagem
|
|
27
|
+
* tanto em `config set` quanto em runtime warnings — util pra dev em
|
|
28
|
+
* ambientes corporativos com proxy.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isOverrideEnabled(): boolean;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MASTER-HIGH-049 (2.11.0) — allowlist de hosts pra `neetruApiUrl`.
|
|
3
|
+
*
|
|
4
|
+
* Pre-2.11, `neetru config set neetruApiUrl http://evil.com` era aceito sem
|
|
5
|
+
* warning. Proxima chamada `neetru whoami` mandava o Bearer
|
|
6
|
+
* `nrt_<keyId>_<secret>` direto pra `evil.com` — vetor de exfiltracao do
|
|
7
|
+
* token CLI. (HIGH-10 / chefe-CLI HIGH-02.)
|
|
8
|
+
*
|
|
9
|
+
* Politica:
|
|
10
|
+
* - Hosts trustred: api.neetru.com, core.neetru.com, *.neetru.com,
|
|
11
|
+
* localhost (qualquer porta), 127.0.0.1 (qualquer porta).
|
|
12
|
+
* - Hosts NAO trusted: warning vermelho + bloqueio sem
|
|
13
|
+
* `--allow-untrusted-url` (ou env `NEETRU_ALLOW_UNTRUSTED_URL=1`).
|
|
14
|
+
*/
|
|
15
|
+
const TRUSTED_HOSTS_EXACT = new Set([
|
|
16
|
+
'api.neetru.com',
|
|
17
|
+
'core.neetru.com',
|
|
18
|
+
'neetru.com',
|
|
19
|
+
]);
|
|
20
|
+
const TRUSTED_HOST_PATTERNS = [
|
|
21
|
+
/\.neetru\.com$/i, // qualquer subdominio de neetru.com (auth.neetru.com etc)
|
|
22
|
+
/^localhost$/i, // dev
|
|
23
|
+
/^127\.0\.0\.1$/, // dev IPv4 loopback
|
|
24
|
+
/^\[::1\]$/, // dev IPv6 loopback (URL host com bracket)
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Classifica a URL alvo. Retorna `trusted=true` quando o host bate na
|
|
28
|
+
* allowlist; `trusted=false` (com `reason`) caso contrario.
|
|
29
|
+
*/
|
|
30
|
+
export function classifyApiUrl(rawUrl) {
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = new URL(rawUrl);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return {
|
|
37
|
+
trusted: false,
|
|
38
|
+
host: rawUrl,
|
|
39
|
+
reason: `URL invalida: ${err.message}`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
43
|
+
return {
|
|
44
|
+
trusted: false,
|
|
45
|
+
host: parsed.host,
|
|
46
|
+
reason: `Protocolo nao suportado: ${parsed.protocol}`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Hostname (sem porta).
|
|
50
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
51
|
+
const isLoopback = hostname === 'localhost' ||
|
|
52
|
+
hostname === '127.0.0.1' ||
|
|
53
|
+
hostname === '::1';
|
|
54
|
+
// Match contra allowlist (exact + pattern).
|
|
55
|
+
let hostnameAllowed = TRUSTED_HOSTS_EXACT.has(hostname);
|
|
56
|
+
if (!hostnameAllowed) {
|
|
57
|
+
for (const pattern of TRUSTED_HOST_PATTERNS) {
|
|
58
|
+
if (pattern.test(hostname)) {
|
|
59
|
+
hostnameAllowed = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!hostnameAllowed) {
|
|
65
|
+
return {
|
|
66
|
+
trusted: false,
|
|
67
|
+
host: parsed.host,
|
|
68
|
+
reason: 'Host fora da allowlist neetru.com / localhost',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Hostname permitido. Para hosts NAO-loopback, exige HTTPS (HTTP em
|
|
72
|
+
// host de producao expoe Bearer token a sniffing trivial).
|
|
73
|
+
if (!isLoopback && parsed.protocol === 'http:') {
|
|
74
|
+
return {
|
|
75
|
+
trusted: false,
|
|
76
|
+
host: parsed.host,
|
|
77
|
+
reason: `HTTP nao-cifrado para host nao-loopback (${hostname}). Use https://`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { trusted: true, host: parsed.host };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Override por env. Setar `NEETRU_ALLOW_UNTRUSTED_URL=1` pula a checagem
|
|
84
|
+
* tanto em `config set` quanto em runtime warnings — util pra dev em
|
|
85
|
+
* ambientes corporativos com proxy.
|
|
86
|
+
*/
|
|
87
|
+
export function isOverrideEnabled() {
|
|
88
|
+
return process.env.NEETRU_ALLOW_UNTRUSTED_URL === '1';
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=url-allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-allowlist.js","sourceRoot":"","sources":["../../src/lib/url-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,gBAAgB;IAChB,iBAAiB;IACjB,YAAY;CACb,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAA0B;IACnD,iBAAiB,EAAE,0DAA0D;IAC7E,cAAc,EAAE,MAAM;IACtB,gBAAgB,EAAE,oBAAoB;IACtC,WAAW,EAAE,2CAA2C;CACzD,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,iBAAkB,GAAa,CAAC,OAAO,EAAE;SAClD,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChE,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,4BAA4B,MAAM,CAAC,QAAQ,EAAE;SACtD,CAAC;IACJ,CAAC;IAED,wBAAwB;IACxB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IAE/C,MAAM,UAAU,GACd,QAAQ,KAAK,WAAW;QACxB,QAAQ,KAAK,WAAW;QACxB,QAAQ,KAAK,KAAK,CAAC;IAErB,4CAA4C;IAC5C,IAAI,eAAe,GAAG,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxD,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,KAAK,MAAM,OAAO,IAAI,qBAAqB,EAAE,CAAC;YAC5C,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC3B,eAAe,GAAG,IAAI,CAAC;gBACvB,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,+CAA+C;SACxD,CAAC;IACJ,CAAC;IAED,oEAAoE;IACpE,2DAA2D;IAC3D,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC/C,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,4CAA4C,QAAQ,iBAAiB;SAC9E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,GAAG,CAAC;AACxD,CAAC"}
|
package/dist/utils/safe-exit.js
CHANGED
|
@@ -37,21 +37,14 @@ function cleanupHandles() {
|
|
|
37
37
|
process.stdin.destroy();
|
|
38
38
|
}
|
|
39
39
|
catch { /* já fechado */ }
|
|
40
|
-
// 3.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
50
|
-
const https = require('node:https');
|
|
51
|
-
if (https?.globalAgent?.destroy)
|
|
52
|
-
https.globalAgent.destroy();
|
|
53
|
-
}
|
|
54
|
-
catch { /* best-effort */ }
|
|
40
|
+
// 3. M-05 fix (Opus review 2026-05-27): NÃO destruir globalAgent.
|
|
41
|
+
// Antes destruíamos http.globalAgent.destroy() + https.globalAgent.destroy()
|
|
42
|
+
// pra fechar keep-alive sockets — mas isso quebrava telemetria flush
|
|
43
|
+
// e qualquer retry em-flight. fetch() Node 20+ usa undici internamente
|
|
44
|
+
// e não depende do http.globalAgent legado. As assertions libuv
|
|
45
|
+
// UV_HANDLE_CLOSING em Windows resolvem com stdin.destroy() (acima)
|
|
46
|
+
// e o exit imediato — sockets http morrem com o processo de qualquer
|
|
47
|
+
// forma. Manter destroy aqui só prejudicava telemetria keepalive:true.
|
|
55
48
|
}
|
|
56
49
|
/**
|
|
57
50
|
* Sai com `code` após cleanup de handles libuv. NEVER returns.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-exit.js","sourceRoot":"","sources":["../../src/utils/safe-exit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,SAAS,cAAc;IACrB,gEAAgE;IAChE,IAAI,CAAC;QAAC,eAAe,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAEtD,6CAA6C;IAC7C,IAAI,CAAC;QAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAE3D,
|
|
1
|
+
{"version":3,"file":"safe-exit.js","sourceRoot":"","sources":["../../src/utils/safe-exit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,SAAS,cAAc;IACrB,gEAAgE;IAChE,IAAI,CAAC;QAAC,eAAe,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAEtD,6CAA6C;IAC7C,IAAI,CAAC;QAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAE3D,kEAAkE;IAClE,gFAAgF;IAChF,wEAAwE;IACxE,0EAA0E;IAC1E,mEAAmE;IACnE,uEAAuE;IACvE,wEAAwE;IACxE,0EAA0E;AAC5E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,cAAc,EAAE,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,cAAc,EAAE,CAAC;IACjB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC"}
|