@neetru/cli 2.31.0 → 2.32.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.
- package/CHANGELOG.md +99 -2
- package/README.md +2 -0
- package/dist/cli-kit/choices.d.ts +33 -0
- package/dist/cli-kit/choices.js +32 -0
- package/dist/cli-kit/command-context.d.ts +98 -0
- package/dist/cli-kit/command-context.js +108 -0
- package/dist/cli-kit/errors.d.ts +79 -0
- package/dist/cli-kit/errors.js +94 -0
- package/dist/cli-kit/index.d.ts +8 -0
- package/dist/cli-kit/index.js +8 -0
- package/dist/cli-kit/suggest.d.ts +74 -0
- package/dist/cli-kit/suggest.js +207 -0
- package/dist/commands/admin/oidc-login.js +1 -1
- package/dist/commands/admin/sdk-credential.js +1 -1
- package/dist/commands/autocomplete.d.ts +1 -1
- package/dist/commands/autocomplete.js +1 -0
- package/dist/commands/bug.d.ts +59 -0
- package/dist/commands/bug.js +118 -6
- package/dist/commands/build.d.ts +24 -1
- package/dist/commands/build.js +195 -73
- package/dist/commands/chat.d.ts +94 -1
- package/dist/commands/chat.js +258 -26
- package/dist/commands/customers.js +1 -1
- package/dist/commands/db-move.js +3 -3
- package/dist/commands/db.js +20 -33
- package/dist/commands/deploy.d.ts +91 -2
- package/dist/commands/deploy.js +423 -78
- package/dist/commands/deployments.js +11 -6
- package/dist/commands/dev-context.js +15 -5
- package/dist/commands/docs-produto.d.ts +25 -0
- package/dist/commands/docs-produto.js +70 -8
- package/dist/commands/doctor.js +36 -1
- package/dist/commands/domains.js +85 -4
- package/dist/commands/env.d.ts +9 -3
- package/dist/commands/env.js +23 -3
- package/dist/commands/infra-read.js +2 -2
- package/dist/commands/init.js +8 -1
- package/dist/commands/login.js +35 -8
- package/dist/commands/logs.js +12 -4
- package/dist/commands/mobile.js +33 -9
- package/dist/commands/new.d.ts +29 -1
- package/dist/commands/new.js +92 -4
- package/dist/commands/open.js +16 -6
- package/dist/commands/ops.js +1 -1
- package/dist/commands/products-db.d.ts +1 -0
- package/dist/commands/products-db.js +41 -8
- package/dist/commands/products.d.ts +17 -3
- package/dist/commands/products.js +86 -18
- package/dist/commands/promote.js +30 -5
- package/dist/commands/secrets.d.ts +91 -0
- package/dist/commands/secrets.js +471 -0
- package/dist/commands/servers.js +8 -5
- package/dist/commands/status.js +2 -2
- package/dist/commands/support.js +5 -3
- package/dist/commands/tenants.js +28 -16
- package/dist/commands/upgrade.d.ts +9 -0
- package/dist/commands/upgrade.js +50 -1
- package/dist/commands/vm-memory.js +1 -0
- package/dist/commands/webhooks.d.ts +2 -0
- package/dist/commands/webhooks.js +9 -3
- package/dist/commands/workspaces.js +42 -11
- package/dist/index.js +70 -14
- package/dist/lib/api-client.d.ts +14 -0
- package/dist/lib/api-client.js +59 -6
- package/dist/lib/api-error.d.ts +14 -0
- package/dist/lib/api-error.js +107 -46
- package/dist/lib/build-targets.d.ts +127 -0
- package/dist/lib/build-targets.js +348 -0
- package/dist/lib/cli-error.d.ts +13 -0
- package/dist/lib/cli-error.js +24 -0
- package/dist/lib/cli-write.d.ts +43 -9
- package/dist/lib/cli-write.js +154 -45
- package/dist/lib/config-schema.d.ts +8 -8
- package/dist/lib/db-local/db-json.d.ts +1 -1
- package/dist/lib/db-local/db-json.js +13 -5
- package/dist/lib/db-local/env.d.ts +15 -3
- package/dist/lib/db-local/env.js +37 -10
- package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
- package/dist/lib/db-pipeline/build-deps.js +23 -3
- package/dist/lib/db-pipeline/types.d.ts +6 -2
- package/dist/lib/db-pipeline/types.js +0 -18
- package/dist/lib/deploy-target-contract.d.ts +152 -0
- package/dist/lib/deploy-target-contract.js +411 -0
- package/dist/lib/env-file-do-build.d.ts +19 -0
- package/dist/lib/env-file-do-build.js +44 -0
- package/dist/lib/envs.d.ts +108 -0
- package/dist/lib/envs.js +127 -0
- package/dist/lib/fs-seguro.d.ts +63 -0
- package/dist/lib/fs-seguro.js +108 -0
- package/dist/lib/inquirer-sob-demanda.d.ts +14 -0
- package/dist/lib/inquirer-sob-demanda.js +18 -0
- package/dist/lib/mobile-local/mobile-json.js +10 -4
- package/dist/lib/pickers.d.ts +44 -7
- package/dist/lib/pickers.js +65 -7
- package/dist/lib/product-docs/sync.d.ts +6 -0
- package/dist/lib/product-docs/sync.js +10 -1
- package/dist/lib/public-secret-scan.d.ts +44 -3
- package/dist/lib/public-secret-scan.js +361 -36
- package/dist/lib/render.d.ts +16 -0
- package/dist/lib/render.js +34 -4
- package/dist/lib/segredo-produto.d.ts +124 -0
- package/dist/lib/segredo-produto.js +336 -0
- package/dist/lib/texto-utf8.d.ts +96 -0
- package/dist/lib/texto-utf8.js +196 -0
- package/package.json +2 -1
- package/dist/cli-kit/format.js.map +0 -1
- package/dist/cli-kit/glyphs.js.map +0 -1
- package/dist/cli-kit/index.js.map +0 -1
- package/dist/cli-kit/palette.js.map +0 -1
- package/dist/commands/add.js.map +0 -1
- package/dist/commands/admin/iam-check.js.map +0 -1
- package/dist/commands/admin/oidc-login.js.map +0 -1
- package/dist/commands/admin/sdk-credential.js.map +0 -1
- package/dist/commands/agent-release.js.map +0 -1
- package/dist/commands/agent-write.js.map +0 -1
- package/dist/commands/ai-models.js.map +0 -1
- package/dist/commands/ai.js.map +0 -1
- package/dist/commands/api-catalog.js.map +0 -1
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/artifact-registry.js.map +0 -1
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/autocomplete.js.map +0 -1
- package/dist/commands/billing.js.map +0 -1
- package/dist/commands/bootstrap.js.map +0 -1
- package/dist/commands/bug.js.map +0 -1
- package/dist/commands/build.js.map +0 -1
- package/dist/commands/chat-menu.js.map +0 -1
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/cloud-run.js.map +0 -1
- package/dist/commands/code.js.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/customers.js.map +0 -1
- package/dist/commands/db-move.js.map +0 -1
- package/dist/commands/db.js.map +0 -1
- package/dist/commands/deploy.js.map +0 -1
- package/dist/commands/deployments.js.map +0 -1
- package/dist/commands/dev-context.js.map +0 -1
- package/dist/commands/dev-up.js.map +0 -1
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/docs-produto.js.map +0 -1
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/domains.js.map +0 -1
- package/dist/commands/dr.js.map +0 -1
- package/dist/commands/env.js.map +0 -1
- package/dist/commands/hosting-write.js.map +0 -1
- package/dist/commands/infra-read.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/mobile.js.map +0 -1
- package/dist/commands/mocks.js.map +0 -1
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/open.js.map +0 -1
- package/dist/commands/ops.js.map +0 -1
- package/dist/commands/products-db.js.map +0 -1
- package/dist/commands/products.js.map +0 -1
- package/dist/commands/promote.js.map +0 -1
- package/dist/commands/publish.js.map +0 -1
- package/dist/commands/schema.js.map +0 -1
- package/dist/commands/servers.js.map +0 -1
- package/dist/commands/smoke.js.map +0 -1
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/support.js.map +0 -1
- package/dist/commands/surface-status.js.map +0 -1
- package/dist/commands/tenants.js.map +0 -1
- package/dist/commands/ui.js.map +0 -1
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/vm-memory.js.map +0 -1
- package/dist/commands/vm.js.map +0 -1
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/commands/whoami.js.map +0 -1
- package/dist/commands/workspaces.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/agent-artifact-url.js.map +0 -1
- package/dist/lib/agent-commands.js.map +0 -1
- package/dist/lib/ai/auth-discovery.js.map +0 -1
- package/dist/lib/ai/context.js.map +0 -1
- package/dist/lib/ai/orchestrator.js.map +0 -1
- package/dist/lib/api-client.js.map +0 -1
- package/dist/lib/api-error.js.map +0 -1
- package/dist/lib/auth.js.map +0 -1
- package/dist/lib/cli-read.js.map +0 -1
- package/dist/lib/cli-write.js.map +0 -1
- package/dist/lib/code-agent-budget.js.map +0 -1
- package/dist/lib/code-agent-files.js.map +0 -1
- package/dist/lib/code-agent-gateway.js.map +0 -1
- package/dist/lib/code-agent-git-tools.js.map +0 -1
- package/dist/lib/code-agent-protocol.js.map +0 -1
- package/dist/lib/code-agent-session.js.map +0 -1
- package/dist/lib/code-agent-settings.js.map +0 -1
- package/dist/lib/code-agent-skills.js.map +0 -1
- package/dist/lib/code-agent-subagents.js.map +0 -1
- package/dist/lib/code-agent-tools.js.map +0 -1
- package/dist/lib/code-agent-tui.js.map +0 -1
- package/dist/lib/code-agent-types.js.map +0 -1
- package/dist/lib/code-agent-ui.js.map +0 -1
- package/dist/lib/code-agent-web-tools.js.map +0 -1
- package/dist/lib/code-agent-write-tools.js.map +0 -1
- package/dist/lib/compose-generator.js.map +0 -1
- package/dist/lib/config-schema.js.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/db-local/db-json.js.map +0 -1
- package/dist/lib/db-local/env.js.map +0 -1
- package/dist/lib/db-local/fingerprint.js.map +0 -1
- package/dist/lib/db-local/index.js.map +0 -1
- package/dist/lib/db-pipeline/build-deps.js.map +0 -1
- package/dist/lib/db-pipeline/errors.js.map +0 -1
- package/dist/lib/db-pipeline/index.js.map +0 -1
- package/dist/lib/db-pipeline/pipeline.js.map +0 -1
- package/dist/lib/db-pipeline/rehearse.js.map +0 -1
- package/dist/lib/db-pipeline/types.js.map +0 -1
- package/dist/lib/dev-local-context.js.map +0 -1
- package/dist/lib/iam-preflight.js.map +0 -1
- package/dist/lib/mobile-local/index.js.map +0 -1
- package/dist/lib/mobile-local/mobile-json.js.map +0 -1
- package/dist/lib/pickers.js.map +0 -1
- package/dist/lib/product-config.js.map +0 -1
- package/dist/lib/product-docs/check.js.map +0 -1
- package/dist/lib/product-docs/config.js.map +0 -1
- package/dist/lib/product-docs/dates.js.map +0 -1
- package/dist/lib/product-docs/frontmatter.js.map +0 -1
- package/dist/lib/product-docs/git.js.map +0 -1
- package/dist/lib/product-docs/markdown.js.map +0 -1
- package/dist/lib/product-docs/ops.js.map +0 -1
- package/dist/lib/product-docs/structure.js.map +0 -1
- package/dist/lib/product-docs/sync.js.map +0 -1
- package/dist/lib/product-docs/taxonomy.js.map +0 -1
- package/dist/lib/product-docs/templates.js.map +0 -1
- package/dist/lib/public-secret-scan.js.map +0 -1
- package/dist/lib/render.js.map +0 -1
- package/dist/lib/schema-cache.js.map +0 -1
- package/dist/lib/sse.js.map +0 -1
- package/dist/lib/telemetry.js.map +0 -1
- package/dist/lib/token-resolver.js.map +0 -1
- package/dist/lib/url-allowlist.js.map +0 -1
- package/dist/lib/version-check.js.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/logo.js.map +0 -1
- package/dist/utils/safe-exit.js.map +0 -1
- package/dist/utils/spinner-registry.js.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -13,6 +13,7 @@ import chalk from 'chalk';
|
|
|
13
13
|
import { apiRequest, CliApiError, CliNetworkError } from '../lib/api-client.js';
|
|
14
14
|
import { requireToken, handleApiError, confirmDestructive, printWriteResult, resolveStepUp, } from '../lib/cli-write.js';
|
|
15
15
|
import { pickProductId, pickServer, pickTenant, promptChoice, promptText } from '../lib/pickers.js';
|
|
16
|
+
import { DEFAULT_TENANT_ENVIRONMENT_ID, ESCOLHAS_AMBIENTE_TENANT, resolverAmbienteTenant, } from '../lib/envs.js';
|
|
16
17
|
import { log } from '../utils/logger.js';
|
|
17
18
|
import { safeExit } from '../utils/safe-exit.js';
|
|
18
19
|
/** Monta o texto do console de deploy (estilo GCP): um bloco por step. */
|
|
@@ -39,12 +40,16 @@ export async function runDeploymentsCreate(opts) {
|
|
|
39
40
|
message: 'Tenant alvo:',
|
|
40
41
|
});
|
|
41
42
|
const version = opts.version ?? (await promptText('Versao:', { json: opts.json }));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
// bug_7dcb68e2666a43f69ef362d2cbff94ed: POST /api/cli/v1/deployments valida
|
|
44
|
+
// environmentId contra ['dev-local','staging','prod'] por igualdade ESTRITA.
|
|
45
|
+
// Oferecer 'dev' no prompt (e usa-lo como padrao) garantia 400
|
|
46
|
+
// validation_failed. Alfabeto unico em ../lib/envs.js.
|
|
47
|
+
const env = opts.env
|
|
48
|
+
? resolverAmbienteTenant(opts.env, opts.json)
|
|
49
|
+
: (await promptChoice('Ambiente:', [...ESCOLHAS_AMBIENTE_TENANT], {
|
|
50
|
+
default: DEFAULT_TENANT_ENVIRONMENT_ID,
|
|
51
|
+
json: opts.json,
|
|
52
|
+
}));
|
|
48
53
|
const server = await pickServer(token, {
|
|
49
54
|
fromFlag: opts.server,
|
|
50
55
|
status: 'online',
|
|
@@ -7,12 +7,22 @@ import { listProductDatabases, listProducts, listWorkspaces, resolveProductIdent
|
|
|
7
7
|
import { requireToken, handleApiError } from '../lib/cli-read.js';
|
|
8
8
|
import { log } from '../utils/logger.js';
|
|
9
9
|
import { safeExit } from '../utils/safe-exit.js';
|
|
10
|
-
|
|
10
|
+
import { invalidChoiceError } from '../cli-kit/choices.js';
|
|
11
|
+
import { exitWithCliError } from '../lib/cli-error.js';
|
|
12
|
+
/** Ambientes aceitos por `neetru dev sync --env` — validação e mensagem saem daqui. */
|
|
13
|
+
const SYNC_ENVS = ['dev', 'dev-local', 'dev-core'];
|
|
14
|
+
function normalizeEnv(value, json) {
|
|
11
15
|
const raw = (value ?? 'dev').trim().toLowerCase();
|
|
12
|
-
if (raw
|
|
16
|
+
if (SYNC_ENVS.includes(raw))
|
|
13
17
|
return raw;
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
// bug_89d2497e: a lista da mensagem é a MESMA que validou (`SYNC_ENVS`).
|
|
19
|
+
return exitWithCliError(invalidChoiceError({
|
|
20
|
+
flag: '--env',
|
|
21
|
+
value,
|
|
22
|
+
valid: SYNC_ENVS,
|
|
23
|
+
subject: 'Ambiente do dev-local',
|
|
24
|
+
code: 'invalid_env',
|
|
25
|
+
}), json);
|
|
16
26
|
}
|
|
17
27
|
function envMatches(rowEnv, wanted) {
|
|
18
28
|
const env = (rowEnv ?? '').toLowerCase();
|
|
@@ -79,7 +89,7 @@ async function resolveProductForSync(token, opts) {
|
|
|
79
89
|
}
|
|
80
90
|
async function syncFromCore(cwd, opts) {
|
|
81
91
|
const token = await requireToken(opts.json);
|
|
82
|
-
const env = normalizeEnv(opts.env);
|
|
92
|
+
const env = normalizeEnv(opts.env, opts.json);
|
|
83
93
|
const current = resolveDevContext(cwd);
|
|
84
94
|
if (current?.mode === 'prototype' && !opts.product && !opts.productId) {
|
|
85
95
|
log.error('Este projeto está marcado como protótipo local. Use `neetru dev use --product <slug>` para ligar ao Core.');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CheckResult } from '../lib/product-docs/check.js';
|
|
2
2
|
import { type InitDocsResult } from '../lib/product-docs/ops.js';
|
|
3
3
|
import { type SyncResult } from '../lib/product-docs/sync.js';
|
|
4
|
+
import { NeetruCliError } from '../cli-kit/errors.js';
|
|
4
5
|
export interface DocsInitOptions {
|
|
5
6
|
comApi?: boolean;
|
|
6
7
|
migrar?: boolean;
|
|
@@ -37,6 +38,30 @@ export interface DocsSyncOptions {
|
|
|
37
38
|
offline?: boolean;
|
|
38
39
|
json?: boolean;
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* As falhas de envio como UM erro tipado com filhos (P105 / bug_89d2497e,
|
|
43
|
+
* etapa 3 aplicada ao `docs sync`). Com docs que deram certo (enviados ou já
|
|
44
|
+
* iguais no Core) a falha se reporta como PARCIAL — no texto ("Falha parcial:
|
|
45
|
+
* 1 de 3 itens falharam") e no JSON (`partial`, `succeeded`, `failed`,
|
|
46
|
+
* `children`) — e sai com o código do filho mais grave. `null` sem falhas.
|
|
47
|
+
*/
|
|
48
|
+
export declare function erroDeSyncComFalhas(r: SyncResult): NeetruCliError | null;
|
|
49
|
+
/** O desfecho do `docs sync`, o MESMO para a saída humana e o `--json`. */
|
|
50
|
+
export interface DesfechoDoSync {
|
|
51
|
+
ok: boolean;
|
|
52
|
+
exit: number;
|
|
53
|
+
/** As falhas de envio como erro tipado, ou `null` sem falhas. */
|
|
54
|
+
erro: NeetruCliError | null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Uma regra só para os dois modos (revisão do #87, BAIXO 2). Qualquer doc que
|
|
58
|
+
* falhou no envio faz o comando falhar com o código do filho mais grave —
|
|
59
|
+
* MESMO que o lote termine num status informativo (`core-sem-suporte` no meio
|
|
60
|
+
* do lote: o doc 1 tomou 500 e o doc 2 tomou 405). Antes a saída humana
|
|
61
|
+
* escondia a falha e saía 0 enquanto o `--json` saía 4. Sem falhas de envio,
|
|
62
|
+
* vale a regra de sempre: status de `SYNC_FAILURE` sai 1, o resto sai 0.
|
|
63
|
+
*/
|
|
64
|
+
export declare function desfechoDoSync(r: SyncResult): DesfechoDoSync;
|
|
40
65
|
export declare function runDocsSync(opts: DocsSyncOptions): Promise<void>;
|
|
41
66
|
export interface DeployDocsPrecheck {
|
|
42
67
|
ran: boolean;
|
|
@@ -19,6 +19,10 @@ import { checkProductDocs } from '../lib/product-docs/check.js';
|
|
|
19
19
|
import { resolveTaxonomy } from '../lib/product-docs/taxonomy.js';
|
|
20
20
|
import { createProductDoc, DocsNewError, initProductDocs, migrateProductDocs, } from '../lib/product-docs/ops.js';
|
|
21
21
|
import { syncProductDocs } from '../lib/product-docs/sync.js';
|
|
22
|
+
import { NeetruCliError } from '../cli-kit/errors.js';
|
|
23
|
+
import { CliApiError, CliNetworkError } from '../lib/api-client.js';
|
|
24
|
+
import { cliErrorFromApiError } from '../lib/api-error.js';
|
|
25
|
+
import { exitWithCliError } from '../lib/cli-error.js';
|
|
22
26
|
function fail(json, code, message) {
|
|
23
27
|
if (json)
|
|
24
28
|
console.log(JSON.stringify({ ok: false, error: code, message }));
|
|
@@ -207,8 +211,7 @@ function printSync(r) {
|
|
|
207
211
|
log.warn(`Não enviado (erro no check: ${b.codes.join(', ')}): ${b.file}`);
|
|
208
212
|
for (const x of r.recusados)
|
|
209
213
|
log.warn(`Não pode subir: ${x.file} — ${x.motivo}.`);
|
|
210
|
-
|
|
211
|
-
log.error(`Falha em ${f.slug}: ${f.message}`);
|
|
214
|
+
// As falhas de envio saem no erro tipado (`erroDeSyncComFalhas`), uma vez só.
|
|
212
215
|
if (r.orfaos.length) {
|
|
213
216
|
log.warn(`${r.orfaos.length} doc(s) existem no Core mas não no repositório (nada foi apagado): ${r.orfaos.join(', ')}`);
|
|
214
217
|
log.dim(' A remoção de docs de produto ainda não existe no Core — eles continuam lá até o Core oferecer isso.');
|
|
@@ -216,6 +219,52 @@ function printSync(r) {
|
|
|
216
219
|
}
|
|
217
220
|
/** Status de sync que contam como falha do comando `neetru docs sync`. */
|
|
218
221
|
const SYNC_FAILURE = ['parcial', 'sem-login', 'limite-excedido', 'produto-nao-encontrado', 'falha'];
|
|
222
|
+
/**
|
|
223
|
+
* Código de saída de um doc que o Core recusou: a MESMA tabela do
|
|
224
|
+
* `handleApiError` (401→2, 403→3, outro 4xx→1, 5xx e sem resposta→4), lida do
|
|
225
|
+
* próprio `cliErrorFromApiError` em vez de copiada.
|
|
226
|
+
*/
|
|
227
|
+
function exitDaFalha(httpStatus) {
|
|
228
|
+
const erro = httpStatus > 0 ? new CliApiError(httpStatus, 'docs_sync', '') : new CliNetworkError('');
|
|
229
|
+
return cliErrorFromApiError(erro).exit;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* As falhas de envio como UM erro tipado com filhos (P105 / bug_89d2497e,
|
|
233
|
+
* etapa 3 aplicada ao `docs sync`). Com docs que deram certo (enviados ou já
|
|
234
|
+
* iguais no Core) a falha se reporta como PARCIAL — no texto ("Falha parcial:
|
|
235
|
+
* 1 de 3 itens falharam") e no JSON (`partial`, `succeeded`, `failed`,
|
|
236
|
+
* `children`) — e sai com o código do filho mais grave. `null` sem falhas.
|
|
237
|
+
*/
|
|
238
|
+
export function erroDeSyncComFalhas(r) {
|
|
239
|
+
if (r.falhas.length === 0)
|
|
240
|
+
return null;
|
|
241
|
+
const children = r.falhas.map((f) => new NeetruCliError(f.message, {
|
|
242
|
+
code: f.httpStatus > 0 ? `http_${f.httpStatus}` : 'sem_resposta',
|
|
243
|
+
exit: exitDaFalha(f.httpStatus),
|
|
244
|
+
details: { slug: f.slug, httpStatus: f.httpStatus },
|
|
245
|
+
human: `Falha em ${f.slug}: ${f.message}`,
|
|
246
|
+
}));
|
|
247
|
+
return new NeetruCliError(r.message, {
|
|
248
|
+
code: r.status === 'parcial' ? 'docs_sync_parcial' : 'docs_sync_falha',
|
|
249
|
+
children,
|
|
250
|
+
succeeded: r.enviados.length + r.inalterados.length,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Uma regra só para os dois modos (revisão do #87, BAIXO 2). Qualquer doc que
|
|
255
|
+
* falhou no envio faz o comando falhar com o código do filho mais grave —
|
|
256
|
+
* MESMO que o lote termine num status informativo (`core-sem-suporte` no meio
|
|
257
|
+
* do lote: o doc 1 tomou 500 e o doc 2 tomou 405). Antes a saída humana
|
|
258
|
+
* escondia a falha e saía 0 enquanto o `--json` saía 4. Sem falhas de envio,
|
|
259
|
+
* vale a regra de sempre: status de `SYNC_FAILURE` sai 1, o resto sai 0.
|
|
260
|
+
*/
|
|
261
|
+
export function desfechoDoSync(r) {
|
|
262
|
+
const erro = erroDeSyncComFalhas(r);
|
|
263
|
+
if (erro)
|
|
264
|
+
return { ok: false, exit: erro.exit, erro };
|
|
265
|
+
const failed = SYNC_FAILURE.includes(r.status);
|
|
266
|
+
return { ok: !failed, exit: failed ? 1 : 0, erro: null };
|
|
267
|
+
}
|
|
219
268
|
export async function runDocsSync(opts) {
|
|
220
269
|
const repo = await loadRepoOrExit(opts.json);
|
|
221
270
|
const tax = await resolveTaxonomy({ offline: opts.offline });
|
|
@@ -226,22 +275,35 @@ export async function runDocsSync(opts) {
|
|
|
226
275
|
? undefined
|
|
227
276
|
: (ms, motivo) => log.dim(` ${motivo === 'ritmo' ? 'Respeitando o limite de envios do Core' : 'O Core pediu para esperar'}: aguardando ${Math.ceil(ms / 1000)} s…`),
|
|
228
277
|
});
|
|
229
|
-
const
|
|
278
|
+
const desfecho = desfechoDoSync(r);
|
|
230
279
|
if (opts.json) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
280
|
+
// O resultado inteiro de antes e, com falhas de envio, o agregado do erro
|
|
281
|
+
// tipado (`error`, `partial`, `succeeded`, `failed`, `children`).
|
|
282
|
+
let agregado = {};
|
|
283
|
+
if (desfecho.erro) {
|
|
284
|
+
const { ok: _ok, ...resto } = desfecho.erro.toJSON();
|
|
285
|
+
agregado = resto;
|
|
286
|
+
}
|
|
287
|
+
console.log(JSON.stringify({ ok: desfecho.ok, ...r, ...agregado }));
|
|
288
|
+
if (desfecho.exit !== 0)
|
|
289
|
+
safeExit(desfecho.exit);
|
|
234
290
|
return;
|
|
235
291
|
}
|
|
236
292
|
log.heading(`Publicar docs de ${repo.slug} no Core${r.dryRun ? ' (simulação)' : ''}`);
|
|
237
293
|
printSync(r);
|
|
294
|
+
if (desfecho.erro) {
|
|
295
|
+
// O cabeçalho é a mensagem do status (inclusive o "Core sem suporte"); as
|
|
296
|
+
// falhas de envio vêm logo abaixo, uma por doc — nunca somem.
|
|
297
|
+
exitWithCliError(desfecho.erro, false);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
238
300
|
if (r.status === 'core-sem-suporte' || r.status === 'desligado' || r.status === 'nada-a-enviar') {
|
|
239
301
|
log.info(r.message);
|
|
240
302
|
return;
|
|
241
303
|
}
|
|
242
|
-
if (
|
|
304
|
+
if (desfecho.exit !== 0) {
|
|
243
305
|
log.error(r.message);
|
|
244
|
-
safeExit(
|
|
306
|
+
safeExit(desfecho.exit);
|
|
245
307
|
}
|
|
246
308
|
log.success(r.message);
|
|
247
309
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -25,7 +25,13 @@ import { log } from '../utils/logger.js';
|
|
|
25
25
|
// Fix do bug onde 2.0.0 ficava hardcoded depois de bumps.
|
|
26
26
|
import { CLI_VERSION } from '../version.js';
|
|
27
27
|
import { safeExit } from '../utils/safe-exit.js';
|
|
28
|
+
import { RUNTIME_ENVIRONMENT_IDS, TENANT_ENVIRONMENT_IDS } from '../lib/envs.js';
|
|
28
29
|
const NPM_REGISTRY = 'https://registry.npmjs.org/@neetru/cli';
|
|
30
|
+
/**
|
|
31
|
+
* `NEETRU_ENV` que o doctor ainda aceita sem estar em alfabeto nenhum — legado
|
|
32
|
+
* declarado (ver `checkNeetruEnv`), candidato a deprecação.
|
|
33
|
+
*/
|
|
34
|
+
const NEETRU_ENV_LEGADOS = ['beta'];
|
|
29
35
|
/* ── Checks individuais — exportados pra teste ────────────────────── */
|
|
30
36
|
export async function checkToken() {
|
|
31
37
|
const t = getActiveTokenSync();
|
|
@@ -92,7 +98,36 @@ export async function checkConfigSchema(cwd) {
|
|
|
92
98
|
export async function checkNeetruEnv(cwd) {
|
|
93
99
|
// bug: 'production' não é valor canônico do SDK/Core — o correto é 'prod'.
|
|
94
100
|
// 'production' causava doctor OK mas runtime falhava ao comparar NEETRU_ENV.
|
|
95
|
-
|
|
101
|
+
// 2026-10-12: a lista rejeitava `dev-local`, que o Core INJETA.
|
|
102
|
+
//
|
|
103
|
+
// O Core grava `NEETRU_ENV = environmentId` no deploy
|
|
104
|
+
// (`_vm-deploy-readiness.ts:9` e `:76`), e `environmentId` só pode ser
|
|
105
|
+
// `dev-local`, `staging` ou `prod` (`ENVIRONMENT_IDS`, schemas/envs.ts:39).
|
|
106
|
+
// Com `dev-local` fora desta lista, o doctor reprovava com exit 1 uma
|
|
107
|
+
// configuração que o próprio Core tinha acabado de escrever — e em CI isso
|
|
108
|
+
// quebra o build sobre uma config correta.
|
|
109
|
+
//
|
|
110
|
+
// `staging` entra aqui pelo mesmo motivo (o Core o produz), mas com uma
|
|
111
|
+
// ressalva REGISTRADA e não resolvida: o tipo `NeetruEnv` do SDK
|
|
112
|
+
// (`src/types.ts:873`) lista `dev | workspace | prod | dev-local` e NÃO
|
|
113
|
+
// inclui `staging`. Procurei o guard de runtime (`resolveNeetruEnv`) e ele
|
|
114
|
+
// não existe no `dist` publicado do SDK — só é citado na doc e no código de
|
|
115
|
+
// deploy do Core. Então NÃO consegui confirmar se `staging` quebra em
|
|
116
|
+
// runtime. O doctor para de reprovar (o Core produz esse valor), e a dúvida
|
|
117
|
+
// fica anotada em vez de virar silêncio nos dois sentidos.
|
|
118
|
+
//
|
|
119
|
+
// `workspace` e `beta` ficam por compatibilidade: estavam aceitos antes e
|
|
120
|
+
// remover valor de uma lista de validação quebra quem já configurou. `beta`
|
|
121
|
+
// não aparece em nenhum alfabeto atual — candidato a deprecação declarada,
|
|
122
|
+
// não a remoção silenciosa.
|
|
123
|
+
//
|
|
124
|
+
// P105: a lista era escrita à mão. Agora é a UNIÃO das duas fontes — o
|
|
125
|
+
// `NEETRU_ENV` do SDK (`RUNTIME_ENVIRONMENT_IDS`) e o `environmentId` que o
|
|
126
|
+
// Core injeta (`TENANT_ENVIRONMENT_IDS`) — mais o legado declarado abaixo.
|
|
127
|
+
// Mesmo conjunto de antes; só a ordem do texto "esperado …" mudou.
|
|
128
|
+
const valid = [
|
|
129
|
+
...new Set([...RUNTIME_ENVIRONMENT_IDS, ...TENANT_ENVIRONMENT_IDS, ...NEETRU_ENV_LEGADOS]),
|
|
130
|
+
];
|
|
96
131
|
// Procura .env.local pra ver NEETRU_ENV.
|
|
97
132
|
try {
|
|
98
133
|
const raw = await fs.readFile(path.join(cwd, '.env.local'), 'utf8');
|
package/dist/commands/domains.js
CHANGED
|
@@ -53,6 +53,56 @@ function outcomeColor(outcome) {
|
|
|
53
53
|
default: return chalk.dim(outcome);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Texto plain PT-BR do motivo de um `outcome: 'skipped'` (bug_6e9fe576).
|
|
58
|
+
* `stage_sem_subdominio`/`subdomain_cloud_run_unsupported` são esperados
|
|
59
|
+
* (dev-local e produto Cloud Run nunca têm domínio por VM); os outros dois
|
|
60
|
+
* exigem ação de quem pediu o reconcile. `undefined` cobre o Core anterior
|
|
61
|
+
* ao #677, que nunca manda `reason` — nesse caso dizemos isso explicitamente
|
|
62
|
+
* em vez de deixar a célula vazia (o ponto do bug original era exatamente
|
|
63
|
+
* "pulei e não disse por quê").
|
|
64
|
+
*/
|
|
65
|
+
function skipReasonText(reason) {
|
|
66
|
+
switch (reason) {
|
|
67
|
+
case 'stage_sem_subdominio':
|
|
68
|
+
return 'ambiente local, sem domínio público (esperado)';
|
|
69
|
+
case 'subdomain_cloud_run_unsupported':
|
|
70
|
+
return 'produto Cloud Run, não usa domínio por VM (esperado)';
|
|
71
|
+
case 'subdomain_deploy_config_missing':
|
|
72
|
+
return 'ambiente sem configuração de deploy — corrija o ambiente';
|
|
73
|
+
case 'subdomain_hard_cap_reached':
|
|
74
|
+
return 'limite de subdomínios atingido — requer ação do operador';
|
|
75
|
+
default:
|
|
76
|
+
return 'motivo não informado por este Core (atualize o Core para ver detalhes)';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Célula da coluna "Motivo/erro" — reason em pulados, err em erros, vazio nos demais. */
|
|
80
|
+
function reconcileNote(r) {
|
|
81
|
+
if (r.outcome === 'skipped')
|
|
82
|
+
return skipReasonText(r.reason);
|
|
83
|
+
if (r.outcome === 'error')
|
|
84
|
+
return r.err ?? '';
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
/** Pulados que exigem ação de quem pediu o reconcile (os outros dois são esperados). */
|
|
88
|
+
const SKIP_EXIGE_ACAO = new Set([
|
|
89
|
+
'subdomain_deploy_config_missing',
|
|
90
|
+
'subdomain_hard_cap_reached',
|
|
91
|
+
]);
|
|
92
|
+
/**
|
|
93
|
+
* A execução teve erro? Olha a contagem `errors` ALÉM do `ok` do Core: o Core
|
|
94
|
+
* anterior ao #677 manda `ok: true` FIXO mesmo com erro (é o próprio
|
|
95
|
+
* bug_6e9fe576), e a CLI publicada conversa com ele até o deploy. Decidir só
|
|
96
|
+
* pelo `ok` reabria a mentira: "Erros: 1" seguido de "tudo já existe" com
|
|
97
|
+
* exit 0 (revisão do cli#80).
|
|
98
|
+
*/
|
|
99
|
+
function reconcileFalhou(resp) {
|
|
100
|
+
return resp.ok === false || resp.errors > 0;
|
|
101
|
+
}
|
|
102
|
+
/** Ambientes pulados por um motivo que exige ação (só o Core #677+ informa o motivo). */
|
|
103
|
+
function pendentesDeAcao(resp) {
|
|
104
|
+
return resp.details.filter((r) => r.outcome === 'skipped' && r.reason !== undefined && SKIP_EXIGE_ACAO.has(r.reason));
|
|
105
|
+
}
|
|
56
106
|
export async function runDomainsList(opts) {
|
|
57
107
|
if (!opts.product) {
|
|
58
108
|
const msg = '--product <slug> é obrigatório.';
|
|
@@ -95,7 +145,7 @@ export async function runDomainsList(opts) {
|
|
|
95
145
|
{ header: 'Cert', value: (r) => certStatusColor(r.certStatus), maxWidth: 18 },
|
|
96
146
|
{ header: 'Alvo', value: (r) => r.targetKind ?? '—', maxWidth: 14 },
|
|
97
147
|
{ header: 'Porta', value: (r) => r.upstreamPort != null ? String(r.upstreamPort) : '—', maxWidth: 8 },
|
|
98
|
-
{ header: 'VM', value: (r) => r.serverId ?? '—',
|
|
148
|
+
{ header: 'VM', value: (r) => r.serverId ?? '—', identificador: true },
|
|
99
149
|
{ header: 'Atualizado', value: (r) => fmtTimestamp(r.updatedAt), maxWidth: 20 },
|
|
100
150
|
], resp.subdomains);
|
|
101
151
|
}
|
|
@@ -127,8 +177,15 @@ export async function runDomainsReconcile(opts) {
|
|
|
127
177
|
spinner?.fail('Falha ao reconciliar domínios.');
|
|
128
178
|
handleApiError(err, opts.json);
|
|
129
179
|
}
|
|
180
|
+
// bug_6e9fe576: `ok:false` é o Core sinalizando que a execução teve erro
|
|
181
|
+
// real (não é exceção de rede/HTTP — a resposta é 200, o resumo que reporta
|
|
182
|
+
// a falha). Trata igual ao resto da CLI que agrega um resultado em lote
|
|
183
|
+
// (`doctor`/`smoke`): --json imprime o corpo e sai já com o código certo,
|
|
184
|
+
// sem cair nos prints humanos abaixo.
|
|
130
185
|
if (opts.json) {
|
|
131
186
|
console.log(JSON.stringify(resp));
|
|
187
|
+
if (reconcileFalhou(resp) || pendentesDeAcao(resp).length > 0)
|
|
188
|
+
safeExit(1);
|
|
132
189
|
return;
|
|
133
190
|
}
|
|
134
191
|
log.heading(`Reconcile — "${chalk.bold(resp.productSlug)}"`);
|
|
@@ -147,15 +204,39 @@ export async function runDomainsReconcile(opts) {
|
|
|
147
204
|
{ header: 'Resultado', value: (r) => outcomeColor(r.outcome), maxWidth: 12 },
|
|
148
205
|
{ header: 'FQDN', value: (r) => r.fqdn || '—', maxWidth: 44 },
|
|
149
206
|
{ header: 'Status', value: (r) => subdomainStatusColor(r.status), maxWidth: 18 },
|
|
150
|
-
{ header: '
|
|
207
|
+
{ header: 'Motivo/erro', value: reconcileNote, maxWidth: 46 },
|
|
151
208
|
], resp.details);
|
|
152
209
|
}
|
|
210
|
+
// bug_6e9fe576: "Todos os domínios obrigatórios já existem." era impresso
|
|
211
|
+
// mesmo com `skipped > 0` — dois ambientes target=workspace, nada criado,
|
|
212
|
+
// nada existente, e a CLI dizia que tudo estava ok. Agora a frase de
|
|
213
|
+
// sucesso-sem-criação só sai quando NADA foi pulado; com pulados, aponta
|
|
214
|
+
// pra tabela acima (que já tem o motivo, com ou sem `reason` do Core).
|
|
215
|
+
const falhou = reconcileFalhou(resp);
|
|
216
|
+
const exigeAcao = pendentesDeAcao(resp);
|
|
153
217
|
if (resp.created > 0) {
|
|
154
218
|
log.success(`${resp.created} subdomínio(s) criado(s). Execute \`neetru domains list --product ${opts.product}\` para ver o status.`);
|
|
155
219
|
}
|
|
156
|
-
|
|
157
|
-
log.
|
|
220
|
+
if (falhou) {
|
|
221
|
+
log.error('Reconcile encontrou erro(s). Veja os detalhes acima.');
|
|
158
222
|
}
|
|
223
|
+
if (exigeAcao.length > 0) {
|
|
224
|
+
const quais = exigeAcao.map((r) => r.envType).join(', ');
|
|
225
|
+
log.warn(`${exigeAcao.length} ambiente(s) precisam de ação (${quais}) — o domínio não foi garantido. Veja o motivo na tabela acima.`);
|
|
226
|
+
}
|
|
227
|
+
if (resp.created === 0 && !falhou && exigeAcao.length === 0) {
|
|
228
|
+
if (resp.skipped > 0) {
|
|
229
|
+
log.warn(`Nenhum domínio criado — ${resp.skipped} ambiente(s) pulado(s). Veja o motivo na tabela acima.`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
log.success('Todos os domínios obrigatórios já existem.');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Exit 1 quando houve erro (pela contagem local, não só pelo `ok` do Core) ou
|
|
236
|
+
// quando um ambiente ficou sem domínio por um motivo que exige ação — como o
|
|
237
|
+
// doctor/smoke, que derivam o exit do resultado que a própria CLI conta.
|
|
238
|
+
if (falhou || exigeAcao.length > 0)
|
|
239
|
+
safeExit(1);
|
|
159
240
|
}
|
|
160
241
|
export async function runDomainsRetry(opts) {
|
|
161
242
|
if (!opts.product) {
|
package/dist/commands/env.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { type RuntimeEnvironmentId } from '../lib/envs.js';
|
|
2
|
+
/**
|
|
3
|
+
* O `NEETRU_ENV` do SDK sai da tabela `VOCABULARIOS_DE_AMBIENTE` (`lib/envs.ts`,
|
|
4
|
+
* P105) — era `['dev','workspace','prod']` escrito à mão — mais o apelido
|
|
5
|
+
* `production`, aceito na entrada e gravado como `prod`.
|
|
6
|
+
*/
|
|
7
|
+
declare const APELIDO_PRODUCTION = "production";
|
|
8
|
+
export type EnvTarget = RuntimeEnvironmentId | typeof APELIDO_PRODUCTION;
|
|
9
|
+
type StoredEnvTarget = RuntimeEnvironmentId;
|
|
4
10
|
export interface EnvSwitchOptions {
|
|
5
11
|
target: string;
|
|
6
12
|
cwd?: string;
|
package/dist/commands/env.js
CHANGED
|
@@ -8,7 +8,15 @@ import * as fs from 'node:fs/promises';
|
|
|
8
8
|
import * as fsSync from 'node:fs';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
10
|
import { log } from '../utils/logger.js';
|
|
11
|
-
|
|
11
|
+
import { problemaDeCodificacao, recusaDoEnvSwitch } from '../lib/texto-utf8.js';
|
|
12
|
+
import { RUNTIME_ENVIRONMENT_IDS } from '../lib/envs.js';
|
|
13
|
+
/**
|
|
14
|
+
* O `NEETRU_ENV` do SDK sai da tabela `VOCABULARIOS_DE_AMBIENTE` (`lib/envs.ts`,
|
|
15
|
+
* P105) — era `['dev','workspace','prod']` escrito à mão — mais o apelido
|
|
16
|
+
* `production`, aceito na entrada e gravado como `prod`.
|
|
17
|
+
*/
|
|
18
|
+
const APELIDO_PRODUCTION = 'production';
|
|
19
|
+
const SUPPORTED = [...RUNTIME_ENVIRONMENT_IDS, APELIDO_PRODUCTION];
|
|
12
20
|
/**
|
|
13
21
|
* Normaliza um target de ambiente (aceita o alias `production` → `prod`) ou
|
|
14
22
|
* `null` se não reconhecido. Exportada (2026-07-22) pra `neetru build
|
|
@@ -18,7 +26,7 @@ const SUPPORTED = ['dev', 'workspace', 'prod', 'production'];
|
|
|
18
26
|
export function normalizeTarget(t) {
|
|
19
27
|
if (!SUPPORTED.includes(t))
|
|
20
28
|
return null;
|
|
21
|
-
return t ===
|
|
29
|
+
return t === APELIDO_PRODUCTION ? 'prod' : t;
|
|
22
30
|
}
|
|
23
31
|
/**
|
|
24
32
|
* Substitui ou adiciona `NEETRU_ENV=<value>` em `content` (texto do .env).
|
|
@@ -60,7 +68,19 @@ export async function runEnvSwitch(opts) {
|
|
|
60
68
|
const filePath = path.join(cwd, file);
|
|
61
69
|
let content = '';
|
|
62
70
|
if (fsSync.existsSync(filePath)) {
|
|
63
|
-
|
|
71
|
+
// Rodada final 2 do cli#81 (BAIXA): `readFile(…, 'utf-8')` trocava cada
|
|
72
|
+
// byte que não é UTF-8 por U+FFFD, e a regravação abaixo levava a troca ao
|
|
73
|
+
// disco — um ANSI com `Café` terminava `Caf\uFFFD`, com exit 0 e "✓".
|
|
74
|
+
// Agora lê em bytes e recusa ANTES de gravar o que a regravação estragaria.
|
|
75
|
+
const bruto = await fs.readFile(filePath);
|
|
76
|
+
const problema = problemaDeCodificacao(bruto);
|
|
77
|
+
if (problema === 'utf16' || problema === 'nao-utf8') {
|
|
78
|
+
log.error(recusaDoEnvSwitch(file, problema).message);
|
|
79
|
+
safeExit(1);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// A marca BOM fica no texto e volta ao disco como estava.
|
|
83
|
+
content = bruto.toString('utf8');
|
|
64
84
|
}
|
|
65
85
|
const updated = applyEnvSwitch(content, storedTarget);
|
|
66
86
|
await fs.writeFile(filePath, updated, 'utf-8');
|
|
@@ -68,7 +68,7 @@ export async function runHostingList(opts) {
|
|
|
68
68
|
}
|
|
69
69
|
renderTable([
|
|
70
70
|
{ header: 'HOST', value: (d) => d.host ?? '—', maxWidth: 32 },
|
|
71
|
-
{ header: 'TENANT', value: (d) => d.tenantId ?? '—',
|
|
71
|
+
{ header: 'TENANT', value: (d) => d.tenantId ?? '—', identificador: true },
|
|
72
72
|
{ header: 'STATUS', value: (d) => d.status ?? '—' },
|
|
73
73
|
{ header: 'CERT', value: (d) => d.certStatus ?? '—' },
|
|
74
74
|
{ header: 'TARGET', value: (d) => d.targetService ?? '—', maxWidth: 24 },
|
|
@@ -98,7 +98,7 @@ export async function runBuildsList(opts) {
|
|
|
98
98
|
}
|
|
99
99
|
log.heading(`Cloud Builds (${res.builds.length})`);
|
|
100
100
|
renderTable([
|
|
101
|
-
{ header: 'ID', value: (b) => b.id,
|
|
101
|
+
{ header: 'ID', value: (b) => b.id, identificador: true },
|
|
102
102
|
{ header: 'STATUS', value: (b) => b.status },
|
|
103
103
|
{ header: 'REGIÃO', value: (b) => b.region ?? 'global' },
|
|
104
104
|
{ header: 'SOURCE', value: (b) => b.source ?? '—', maxWidth: 28 },
|
package/dist/commands/init.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
-
|
|
4
|
+
// `inquirer` NÃO é importado aqui: só o caminho interativo de `resolveTarget`
|
|
5
|
+
// pergunta, e ele o carrega na hora (P112, 2026-10-18). O import a frio do
|
|
6
|
+
// inquirer custava ~0,35 s sozinho e ~1,1 s com a suíte rodando — pago por
|
|
7
|
+
// todo `neetru init --yes` (CI e scripts) e pelo 1º teste do init.test.ts,
|
|
8
|
+
// que estourava o timeout de 10 s com a máquina carregada.
|
|
5
9
|
import { log } from '../utils/logger.js';
|
|
10
|
+
import { carregarInquirer } from '../lib/inquirer-sob-demanda.js';
|
|
6
11
|
import { safeExit } from '../utils/safe-exit.js';
|
|
7
12
|
/**
|
|
8
13
|
* Inspeciona um diretório pra decidir se é projeto existente, vazio, ou
|
|
@@ -81,6 +86,8 @@ async function resolveTarget(name, cwd, opts) {
|
|
|
81
86
|
return { dir: subDir, mode: 'new', force: false };
|
|
82
87
|
}
|
|
83
88
|
// ── Interactive ──────────────────────────────────────────────────────
|
|
89
|
+
// Só aqui há pergunta: o inquirer é carregado agora (ver o topo do arquivo).
|
|
90
|
+
const inquirer = await carregarInquirer();
|
|
84
91
|
console.log();
|
|
85
92
|
log.info(`Onde você quer inicializar o projeto "${name}"?`);
|
|
86
93
|
console.log();
|
package/dist/commands/login.js
CHANGED
|
@@ -21,6 +21,7 @@ import { spawn } from 'node:child_process';
|
|
|
21
21
|
import { config } from '../lib/config.js';
|
|
22
22
|
import { saveToken } from '../lib/auth.js';
|
|
23
23
|
import { apiRequest, CliApiError, CliNetworkError } from '../lib/api-client.js';
|
|
24
|
+
import { discardMfaSessionOfOtherUid } from '../lib/cli-write.js';
|
|
24
25
|
import { log } from '../utils/logger.js';
|
|
25
26
|
import { safeExit } from '../utils/safe-exit.js';
|
|
26
27
|
const TOKEN_PATTERN = /^nrt_[a-z0-9]{8,16}_[A-Za-z0-9_\-=]{20,64}$/;
|
|
@@ -51,28 +52,52 @@ function openBrowser(url) {
|
|
|
51
52
|
catch {
|
|
52
53
|
return; // URL inválida → não abre (caller já imprimiu pra copiar/colar)
|
|
53
54
|
}
|
|
55
|
+
// bug_d414f21c (medium): sem binário de abrir browser (ex.: WSL sem
|
|
56
|
+
// `xdg-open`), `spawn(...)` emite 'error' (ENOENT) ASSINCRONAMENTE — sem
|
|
57
|
+
// listener, o Node trata como exceção não capturada e derruba o processo,
|
|
58
|
+
// abortando o polling do device-code antes de começar. Registra o handler
|
|
59
|
+
// e avisa o usuário (ele já tem a URL impressa pra copiar/colar); nunca
|
|
60
|
+
// deixa esse erro propagar.
|
|
61
|
+
const warnFallback = () => {
|
|
62
|
+
log.warn('Não consegui abrir o navegador; abra a URL acima manualmente.');
|
|
63
|
+
};
|
|
54
64
|
try {
|
|
65
|
+
let child;
|
|
55
66
|
if (process.platform === 'win32') {
|
|
56
|
-
spawn('rundll32', ['url.dll,FileProtocolHandler', url], {
|
|
67
|
+
child = spawn('rundll32', ['url.dll,FileProtocolHandler', url], {
|
|
57
68
|
detached: true,
|
|
58
69
|
stdio: 'ignore',
|
|
59
70
|
windowsHide: true,
|
|
60
|
-
})
|
|
61
|
-
return;
|
|
71
|
+
});
|
|
62
72
|
}
|
|
63
|
-
if (process.platform === 'darwin') {
|
|
64
|
-
spawn('open', [url], { detached: true, stdio: 'ignore' })
|
|
65
|
-
|
|
73
|
+
else if (process.platform === 'darwin') {
|
|
74
|
+
child = spawn('open', [url], { detached: true, stdio: 'ignore' });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
child = spawn('xdg-open', [url], { detached: true, stdio: 'ignore' });
|
|
66
78
|
}
|
|
67
|
-
|
|
79
|
+
child.on('error', warnFallback);
|
|
80
|
+
child.unref();
|
|
68
81
|
}
|
|
69
82
|
catch {
|
|
70
|
-
|
|
83
|
+
warnFallback();
|
|
71
84
|
}
|
|
72
85
|
}
|
|
73
86
|
function sleep(ms) {
|
|
74
87
|
return new Promise((res) => setTimeout(res, ms));
|
|
75
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* A8 (2026-10-13): o login trocava o Bearer mas mantinha o `mfa-session.json`
|
|
91
|
+
* da conta anterior. O Core só aceita sessão MFA do MESMO uid do Bearer, então
|
|
92
|
+
* por até 1h todo comando destrutivo da conta nova tomava 401. Sessão de outro
|
|
93
|
+
* uid sai junto com a troca de conta; a da mesma conta fica.
|
|
94
|
+
*/
|
|
95
|
+
async function discardForeignMfaSession(uid, json) {
|
|
96
|
+
if (await discardMfaSessionOfOtherUid(uid)) {
|
|
97
|
+
if (!json)
|
|
98
|
+
log.dim('Sessão MFA de 1h da conta anterior descartada (rode `neetru auth mfa-session` se precisar).');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
76
101
|
async function pollForToken(deviceCode, initialIntervalSec, expiresAtMs, json) {
|
|
77
102
|
let intervalMs = initialIntervalSec * 1000;
|
|
78
103
|
const spinner = json
|
|
@@ -143,6 +168,7 @@ export async function runLogin(opts = {}) {
|
|
|
143
168
|
const path = await saveToken(token, me.email);
|
|
144
169
|
// Compat: alguns comandos legados ainda leem do Conf.
|
|
145
170
|
config.set('neetruApiKey', token);
|
|
171
|
+
await discardForeignMfaSession(me.uid, json);
|
|
146
172
|
if (json) {
|
|
147
173
|
emitJson({ ok: true, email: me.email, role: me.role, path });
|
|
148
174
|
}
|
|
@@ -213,6 +239,7 @@ export async function runLogin(opts = {}) {
|
|
|
213
239
|
const path = await saveToken(accessToken, me.email);
|
|
214
240
|
// Compat: legacy commands podem usar Conf — mantemos o token lá também.
|
|
215
241
|
config.set('neetruApiKey', accessToken);
|
|
242
|
+
await discardForeignMfaSession(me.uid, json);
|
|
216
243
|
if (json) {
|
|
217
244
|
emitJson({ ok: true, email: me.email, role: me.role, path });
|
|
218
245
|
}
|
package/dist/commands/logs.js
CHANGED
|
@@ -10,9 +10,8 @@
|
|
|
10
10
|
import { existsSync, readFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import chalk from 'chalk';
|
|
13
|
-
import { apiRequest, CliNetworkError } from '../lib/api-client.js';
|
|
13
|
+
import { apiRequest, CliNetworkError, resolveTrustedApiUrl } from '../lib/api-client.js';
|
|
14
14
|
import { handleApiError } from '../lib/api-error.js';
|
|
15
|
-
import { config } from '../lib/config.js';
|
|
16
15
|
import { getActiveTokenSync } from '../lib/token-resolver.js';
|
|
17
16
|
import { log } from '../utils/logger.js';
|
|
18
17
|
import { safeExit } from '../utils/safe-exit.js';
|
|
@@ -239,7 +238,6 @@ async function fetchLogs(clientId, params) {
|
|
|
239
238
|
async function runSseFollow(clientId, opts, jsonMode) {
|
|
240
239
|
// MASTER-HIGH-048 (2.11.0): SSE stream usa o mesmo resolver canonico.
|
|
241
240
|
const apiKey = getActiveTokenSync() ?? undefined;
|
|
242
|
-
const apiBase = config.get('apiBase') ?? 'https://api.neetru.com';
|
|
243
241
|
const qs = new URLSearchParams({ clientId });
|
|
244
242
|
if (opts.level)
|
|
245
243
|
qs.set('level', opts.level);
|
|
@@ -249,7 +247,17 @@ async function runSseFollow(clientId, opts, jsonMode) {
|
|
|
249
247
|
qs.set('channel', opts.channel);
|
|
250
248
|
if (opts.correlationId)
|
|
251
249
|
qs.set('correlationId', opts.correlationId);
|
|
252
|
-
|
|
250
|
+
// C7 (2026-10-13): a base e a guarda são as MESMAS do `apiRequest` (que fez o
|
|
251
|
+
// primeiro request deste comando). Antes o stream lia `config.get('apiBase')`
|
|
252
|
+
// — chave fora do schema — e mandava o Bearer pra qualquer host gravado ali,
|
|
253
|
+
// além de ignorar o `neetruApiUrl` de staging/local.
|
|
254
|
+
let streamUrl;
|
|
255
|
+
try {
|
|
256
|
+
streamUrl = resolveTrustedApiUrl(`/api/cli/v1/logs/stream?${qs.toString()}`);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
handleApiError(error, jsonMode);
|
|
260
|
+
}
|
|
253
261
|
let attempts = 0;
|
|
254
262
|
const MAX_RECONNECTS = 5;
|
|
255
263
|
let lastEventId;
|