@neetru/cli 2.31.0 → 2.32.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 +91 -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 +114 -2
- 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 +17 -24
- package/dist/commands/deploy.d.ts +91 -2
- package/dist/commands/deploy.js +414 -69
- 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 +7 -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 +84 -16
- 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 +6 -4
- 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/mobile-local/mobile-json.js +10 -4
- package/dist/lib/pickers.d.ts +44 -7
- package/dist/lib/pickers.js +60 -4
- 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
package/dist/commands/mobile.js
CHANGED
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
* GET /api/cli/v1/products/{slug}/mobile-apps — role admin|operator
|
|
12
12
|
* DELETE /api/cli/v1/products/{slug}/mobile-apps/{appId} — role admin ONLY + step-up MFA
|
|
13
13
|
*
|
|
14
|
-
* `redirectUri` PRECISA ser `https://` (Universal Link/App Link, RFC 8252)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* `redirectUri` PRECISA ser `https://` (Universal Link/App Link, RFC 8252)
|
|
15
|
+
* ou `http://localhost`/`http://127.0.0.1` (loopback de dev) — mesma regra
|
|
16
|
+
* do Core (`validateRedirectUris`). O Core rejeita custom scheme
|
|
17
|
+
* (`meuapp://`) com 400. Isso é decisão de segurança do owner, não
|
|
18
|
+
* limitação temporária — a CLI valida o mesmo localmente pra falhar rápido
|
|
19
|
+
* com mensagem clara, mas NUNCA oferece custom scheme como alternativa.
|
|
19
20
|
*
|
|
20
21
|
* O `id` retornado pelo Core é `<slug>__<platform>` — o app é PKCE público
|
|
21
22
|
* (Authorization Code + PKCE, sem client secret).
|
|
@@ -42,6 +43,28 @@ const MOBILE_JSON_REL = '.neetru/mobile.json';
|
|
|
42
43
|
function jsonError(message, error = 'error') {
|
|
43
44
|
console.log(JSON.stringify({ ok: false, error, message }));
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* bug_afcf958a (low): espelha EXATAMENTE a regra de `redirectUri` do Core
|
|
48
|
+
* (`_oidc-clients-core.ts` → `validateRedirectUris`, usada também pelo
|
|
49
|
+
* registro de apps mobile em `_product-mobile-apps-core.ts`): aceita
|
|
50
|
+
* `https://` ou `http://` com host `localhost`/`127.0.0.1` (loopback de
|
|
51
|
+
* dev, RFC 8252). NUNCA custom scheme (`meuapp://`) — isso o Core rejeita
|
|
52
|
+
* com 400, decisão de segurança do owner, e a CLI não pode ser MAIS
|
|
53
|
+
* permissiva que o servidor que vai validar de novo.
|
|
54
|
+
*/
|
|
55
|
+
function isAcceptableMobileRedirectUri(uri) {
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = new URL(uri);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (parsed.protocol === 'https:')
|
|
64
|
+
return true;
|
|
65
|
+
return (parsed.protocol === 'http:' &&
|
|
66
|
+
(parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1'));
|
|
67
|
+
}
|
|
45
68
|
function mobileStatusColor(status) {
|
|
46
69
|
switch (status) {
|
|
47
70
|
case 'active': return chalk.green(status);
|
|
@@ -82,8 +105,9 @@ export async function runMobileInit(opts = {}) {
|
|
|
82
105
|
safeExit(1);
|
|
83
106
|
return;
|
|
84
107
|
}
|
|
85
|
-
if (!redirectUri
|
|
86
|
-
const msg = `--redirect-uri precisa ser https:// (Universal Link/App Link)
|
|
108
|
+
if (!isAcceptableMobileRedirectUri(redirectUri)) {
|
|
109
|
+
const msg = `--redirect-uri precisa ser https:// (Universal Link/App Link) ou http://localhost ` +
|
|
110
|
+
`(ou http://127.0.0.1) para dev — mesma regra do Core. ` +
|
|
87
111
|
`Custom scheme (ex: meuapp://) NÃO é aceito — decisão de segurança do owner (RFC 8252), ` +
|
|
88
112
|
`não é limitação temporária. Recebido: "${redirectUri}"`;
|
|
89
113
|
if (opts.json) {
|
|
@@ -269,7 +293,7 @@ export async function runMobileList(opts = {}) {
|
|
|
269
293
|
return;
|
|
270
294
|
}
|
|
271
295
|
renderTable([
|
|
272
|
-
{ header: 'ID', value: (r) => r.id,
|
|
296
|
+
{ header: 'ID', value: (r) => r.id, identificador: true },
|
|
273
297
|
{ header: 'Platform', value: (r) => r.platform, maxWidth: 10 },
|
|
274
298
|
{
|
|
275
299
|
header: 'Bundle/Package',
|
|
@@ -277,7 +301,7 @@ export async function runMobileList(opts = {}) {
|
|
|
277
301
|
maxWidth: 30,
|
|
278
302
|
},
|
|
279
303
|
{ header: 'Status', value: (r) => mobileStatusColor(r.status), maxWidth: 14 },
|
|
280
|
-
{ header: 'OIDC Client ID', value: (r) => r.oidcClientId,
|
|
304
|
+
{ header: 'OIDC Client ID', value: (r) => r.oidcClientId, identificador: true },
|
|
281
305
|
], resp.apps);
|
|
282
306
|
}
|
|
283
307
|
/**
|
package/dist/commands/new.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export interface NewMacroOptions {
|
|
2
2
|
productSlug?: string;
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* bug_7dcb68e2666a43f69ef362d2cbff94ed: era `'dev' | 'staging' | 'prod'`.
|
|
5
|
+
* `'dev'` não existe no enum do Core — ver `../lib/envs.ts`.
|
|
6
|
+
*/
|
|
7
|
+
env?: string;
|
|
4
8
|
tier?: 'dev' | 'standard' | 'enterprise';
|
|
5
9
|
customer?: string;
|
|
6
10
|
/** Pula scaffold local (útil quando o repo já existe). */
|
|
@@ -28,4 +32,28 @@ export interface NewMacroOptions {
|
|
|
28
32
|
semDocs?: boolean;
|
|
29
33
|
json?: boolean;
|
|
30
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Decisão PURA de qual `customerId` a macro deve usar.
|
|
37
|
+
*
|
|
38
|
+
* bug_7dcb68e2666a43f69ef362d2cbff94ed — antes a macro assumia `'self'` em
|
|
39
|
+
* silêncio. `'self'` é um sentinel que o Core só aceita para produto
|
|
40
|
+
* **Neetru-owned** (`resolveDefaultCustomer`); para qualquer outro produto ele
|
|
41
|
+
* recusa a criação do workspace. Como a macro cria o produto no passo [1/5] e
|
|
42
|
+
* o workspace no [2/5], o resultado era um produto órfão no Core e um erro
|
|
43
|
+
* incompreensível no meio da macro.
|
|
44
|
+
*
|
|
45
|
+
* Agora a macro exige `--customer` explícito quando o produto não é
|
|
46
|
+
* Neetru-owned — e diz isso, em vez de deixar o Core recusar depois.
|
|
47
|
+
*/
|
|
48
|
+
export declare function decidirCustomerDaMacro(entrada: {
|
|
49
|
+
fromFlag?: string;
|
|
50
|
+
produtoEhNeetru: boolean;
|
|
51
|
+
slug: string;
|
|
52
|
+
}): {
|
|
53
|
+
ok: true;
|
|
54
|
+
customerId: string;
|
|
55
|
+
} | {
|
|
56
|
+
ok: false;
|
|
57
|
+
mensagem: string;
|
|
58
|
+
};
|
|
31
59
|
export declare function runNewMacro(name: string, opts?: NewMacroOptions): Promise<void>;
|
package/dist/commands/new.js
CHANGED
|
@@ -27,6 +27,8 @@ import { runInit } from './init.js';
|
|
|
27
27
|
import { runDbInit } from './db.js';
|
|
28
28
|
import { requireToken } from '../lib/cli-read.js';
|
|
29
29
|
import { config } from '../lib/config.js';
|
|
30
|
+
import { isNeetruOwnedProduct, isNeetruOwnedSlug } from '../lib/pickers.js';
|
|
31
|
+
import { DEFAULT_TENANT_ENVIRONMENT_ID, resolverAmbienteTenant, } from '../lib/envs.js';
|
|
30
32
|
import { safeExit } from '../utils/safe-exit.js';
|
|
31
33
|
/**
|
|
32
34
|
* Captura erro de um passo do macro como mensagem human-readable.
|
|
@@ -140,15 +142,65 @@ function buildDashboardUrl(productId) {
|
|
|
140
142
|
const baseUrl = String(config.get('neetruCoreUrl') ?? 'https://core.neetru.com').replace(/\/+$/, '');
|
|
141
143
|
return `${baseUrl}/products/${encodeURIComponent(productId)}`;
|
|
142
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Decisão PURA de qual `customerId` a macro deve usar.
|
|
147
|
+
*
|
|
148
|
+
* bug_7dcb68e2666a43f69ef362d2cbff94ed — antes a macro assumia `'self'` em
|
|
149
|
+
* silêncio. `'self'` é um sentinel que o Core só aceita para produto
|
|
150
|
+
* **Neetru-owned** (`resolveDefaultCustomer`); para qualquer outro produto ele
|
|
151
|
+
* recusa a criação do workspace. Como a macro cria o produto no passo [1/5] e
|
|
152
|
+
* o workspace no [2/5], o resultado era um produto órfão no Core e um erro
|
|
153
|
+
* incompreensível no meio da macro.
|
|
154
|
+
*
|
|
155
|
+
* Agora a macro exige `--customer` explícito quando o produto não é
|
|
156
|
+
* Neetru-owned — e diz isso, em vez de deixar o Core recusar depois.
|
|
157
|
+
*/
|
|
158
|
+
export function decidirCustomerDaMacro(entrada) {
|
|
159
|
+
const informado = entrada.fromFlag?.trim();
|
|
160
|
+
if (informado)
|
|
161
|
+
return { ok: true, customerId: informado };
|
|
162
|
+
// Produto da própria Neetru → o Core injeta o customer interno sozinho.
|
|
163
|
+
if (entrada.produtoEhNeetru)
|
|
164
|
+
return { ok: true, customerId: 'self' };
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
mensagem: `O produto "${entrada.slug}" não é Neetru-owned — o Core exige um customer explícito.\n\n` +
|
|
168
|
+
' Customer = a entidade comercial (PJ) que vai operar o ambiente.\n' +
|
|
169
|
+
' Sem ele o Core recusa a criação do workspace, e o `neetru new` não\n' +
|
|
170
|
+
' tem como adivinhar qual é.\n\n' +
|
|
171
|
+
' Rode de novo informando o customer:\n' +
|
|
172
|
+
` neetru new "${entrada.slug}" --customer <customerId>\n\n` +
|
|
173
|
+
' Para descobrir ou criar um customerId:\n' +
|
|
174
|
+
' neetru customers list\n' +
|
|
175
|
+
' neetru customers create --name "..." --email "..."\n' +
|
|
176
|
+
' • Painel: https://core.neetru.com/customers\n',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Descobre se o produto alvo é Neetru-owned ANTES de criar qualquer coisa.
|
|
181
|
+
* Se o slug já existe no Core, pergunta ao Core; se ainda não existe, só o
|
|
182
|
+
* espelho de slugs conhecidos pode responder.
|
|
183
|
+
*/
|
|
184
|
+
async function produtoDoSlugEhNeetru(token, slug) {
|
|
185
|
+
if (isNeetruOwnedSlug(slug))
|
|
186
|
+
return true;
|
|
187
|
+
const existente = await lookupProductBySlug(token, slug);
|
|
188
|
+
if (!existente)
|
|
189
|
+
return false;
|
|
190
|
+
return isNeetruOwnedProduct(token, existente.id);
|
|
191
|
+
}
|
|
143
192
|
export async function runNewMacro(name, opts = {}) {
|
|
144
193
|
if (!name || !name.trim()) {
|
|
145
194
|
log.error('Uso: neetru new <name>');
|
|
146
195
|
safeExit(1);
|
|
147
196
|
}
|
|
148
197
|
let slug = (opts.productSlug ?? deriveSlug(name)) || deriveSlug(name);
|
|
149
|
-
|
|
198
|
+
// bug_7dcb68e2666a43f69ef362d2cbff94ed: o padrão era 'dev', token que o Core
|
|
199
|
+
// não conhece — a macro morria no passo [2/5] com 400 validation_failed.
|
|
200
|
+
const env = opts.env
|
|
201
|
+
? resolverAmbienteTenant(opts.env, opts.json)
|
|
202
|
+
: DEFAULT_TENANT_ENVIRONMENT_ID;
|
|
150
203
|
const tier = opts.tier ?? 'standard';
|
|
151
|
-
const customer = opts.customer ?? 'self';
|
|
152
204
|
if (!opts.json) {
|
|
153
205
|
log.banner();
|
|
154
206
|
log.heading(`neetru new ${name}`);
|
|
@@ -158,6 +210,28 @@ export async function runNewMacro(name, opts = {}) {
|
|
|
158
210
|
const summary = [];
|
|
159
211
|
// ─── Autenticar (obrigatório para lookup de conflito e criação) ──────────
|
|
160
212
|
const token = await requireToken(opts.json);
|
|
213
|
+
// ─── Customer — resolvido ANTES do passo [1/5] ───────────────────────────
|
|
214
|
+
// Falhar aqui evita o pior cenário do bug: produto criado no Core e macro
|
|
215
|
+
// abortada no passo seguinte, deixando lixo que o dev precisa limpar à mão.
|
|
216
|
+
const decisaoCustomer = decidirCustomerDaMacro({
|
|
217
|
+
fromFlag: opts.customer,
|
|
218
|
+
produtoEhNeetru: await produtoDoSlugEhNeetru(token, slug),
|
|
219
|
+
slug,
|
|
220
|
+
});
|
|
221
|
+
if (!decisaoCustomer.ok) {
|
|
222
|
+
if (opts.json) {
|
|
223
|
+
console.log(JSON.stringify({
|
|
224
|
+
ok: false,
|
|
225
|
+
error: 'customer_required',
|
|
226
|
+
message: decisaoCustomer.mensagem,
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
log.error(decisaoCustomer.mensagem);
|
|
231
|
+
}
|
|
232
|
+
safeExit(1);
|
|
233
|
+
}
|
|
234
|
+
const customer = decisaoCustomer.customerId;
|
|
161
235
|
// ─── Step 1 — Criar produto (com tratamento de conflito de slug) ─────────
|
|
162
236
|
let product;
|
|
163
237
|
let isLinked = false;
|
|
@@ -184,14 +258,23 @@ export async function runNewMacro(name, opts = {}) {
|
|
|
184
258
|
console.log();
|
|
185
259
|
log.success(`[1/5] Vinculado ao produto existente: ${resolution.product.id} (${resolution.product.slug ?? slug})`);
|
|
186
260
|
}
|
|
261
|
+
// Produto VINCULADO, não criado — nada aqui veio do servidor. Antes
|
|
262
|
+
// este objeto chutava `planTemplate: 'free'` e
|
|
263
|
+
// `environments: ['dev','prod']`: a mesma mentira do bug
|
|
264
|
+
// bug_db50b21f84e8400f9c3f6286b69c353b, agora inventada localmente
|
|
265
|
+
// sobre um produto preexistente que pode ter qualquer configuração.
|
|
266
|
+
// `null` + não-confirmado é o que de fato sabemos.
|
|
187
267
|
product = {
|
|
188
268
|
ok: true,
|
|
189
269
|
productId: resolution.product.id,
|
|
190
270
|
slug: resolution.product.slug ?? slug,
|
|
191
271
|
name: resolution.product.name ?? name,
|
|
192
272
|
ownerTeam: 'neetru-eng',
|
|
193
|
-
planTemplate:
|
|
194
|
-
environments:
|
|
273
|
+
planTemplate: null,
|
|
274
|
+
environments: null,
|
|
275
|
+
environmentsRequested: [],
|
|
276
|
+
environmentsConfirmed: false,
|
|
277
|
+
planTemplateConfirmed: false,
|
|
195
278
|
createdAt: null,
|
|
196
279
|
dashboardUrl: buildDashboardUrl(resolution.product.id),
|
|
197
280
|
};
|
|
@@ -284,6 +367,11 @@ export async function runNewMacro(name, opts = {}) {
|
|
|
284
367
|
// Use `--open` para abrir explicitamente. Útil em dev local; indesejável em CI.
|
|
285
368
|
const dashboardUrl = buildWorkspaceDashboardUrl(workspaceId);
|
|
286
369
|
if (opts.open) {
|
|
370
|
+
// bug_d414f21c: este `try/catch` dá falso conforto. A falha de `spawn` por
|
|
371
|
+
// binário ausente (WSL/container sem `xdg-open`) chega como evento 'error'
|
|
372
|
+
// ASSÍNCRONO, depois que `openBrowser` já retornou — catch nenhum aqui pega.
|
|
373
|
+
// Quem segura isso é o listener dentro de `safeOpenUrl` (open.ts); sem ele o
|
|
374
|
+
// processo morria neste passo [4/5] e o passo [5/5] (`runDbInit`) nunca rodava.
|
|
287
375
|
try {
|
|
288
376
|
openBrowser(dashboardUrl);
|
|
289
377
|
if (!opts.json)
|
package/dist/commands/open.js
CHANGED
|
@@ -35,20 +35,30 @@ export function safeOpenUrl(url) {
|
|
|
35
35
|
log.warn(`URL inválida (protocolo não permitido): ${url}`);
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
+
const avisarFallback = () => {
|
|
39
|
+
log.warn(`Não consegui abrir browser. Acesse manualmente: ${url}`);
|
|
40
|
+
};
|
|
38
41
|
try {
|
|
39
42
|
if (process.platform === 'win32') {
|
|
40
43
|
// rundll32 url.dll,FileProtocolHandler não passa pelo shell — sem injeção.
|
|
44
|
+
// `spawnSync` falha de forma SÍNCRONA, então o `catch` abaixo já cobre.
|
|
41
45
|
spawnSync('rundll32', ['url.dll,FileProtocolHandler', url], { shell: false });
|
|
42
46
|
return;
|
|
43
47
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
// bug_d414f21c: `spawn` com binário ausente (WSL/container sem `xdg-open`)
|
|
49
|
+
// emite 'error' (ENOENT) de forma ASSÍNCRONA. Sem listener o Node promove
|
|
50
|
+
// isso a exceção não capturada e DERRUBA o processo — o `try/catch` daqui
|
|
51
|
+
// não pega nada, porque a falha chega depois do `return`. O 59c13b8 colocou
|
|
52
|
+
// o listener no `openBrowser` do login e deixou este helper (usado por
|
|
53
|
+
// `neetru open`, `docs open`, `workspaces open` e `new --open`) sem ele.
|
|
54
|
+
const filho = process.platform === 'darwin'
|
|
55
|
+
? spawn('open', [url], { detached: true, stdio: 'ignore', shell: false })
|
|
56
|
+
: spawn('xdg-open', [url], { detached: true, stdio: 'ignore', shell: false });
|
|
57
|
+
filho.on('error', avisarFallback);
|
|
58
|
+
filho.unref();
|
|
49
59
|
}
|
|
50
60
|
catch (_e) {
|
|
51
|
-
|
|
61
|
+
avisarFallback();
|
|
52
62
|
}
|
|
53
63
|
}
|
|
54
64
|
/** @deprecated Use safeOpenUrl — mantido para compatibilidade interna. */
|
package/dist/commands/ops.js
CHANGED
|
@@ -145,7 +145,7 @@ export async function runOpsSmokeObservability(opts) {
|
|
|
145
145
|
export async function runOpsRegToken(serverId, opts) {
|
|
146
146
|
const token = await requireToken(opts.json);
|
|
147
147
|
// H5 (auditoria cli/v1 2026-09-29): o token dá credencial de agente pra
|
|
148
|
-
// QUALQUER server — o Core exige step-up MFA (
|
|
148
|
+
// QUALQUER server — o Core exige step-up MFA (--mfa-token → sessão 1h → prompt).
|
|
149
149
|
const { headers } = await resolveStepUp({ mfaToken: opts.mfaToken, json: opts.json });
|
|
150
150
|
const spinner = opts.json ? null : ora({ text: `Gerando registration token para ${serverId}…`, color: 'blue' }).start();
|
|
151
151
|
let res;
|
|
@@ -34,6 +34,7 @@ export declare function runDbRotate(id: string | undefined, opts: {
|
|
|
34
34
|
export declare function runDbDelete(id: string | undefined, opts: {
|
|
35
35
|
json?: boolean;
|
|
36
36
|
force?: boolean;
|
|
37
|
+
mfaToken?: string;
|
|
37
38
|
}): Promise<void>;
|
|
38
39
|
export declare function runDbEngines(opts: {
|
|
39
40
|
json?: boolean;
|
|
@@ -23,6 +23,12 @@ import { pickDatabase, pickProductId, pickServer, promptChoice, promptText, } fr
|
|
|
23
23
|
import { log } from '../utils/logger.js';
|
|
24
24
|
// bug_863ae5: pre-flight IAM check antes de operações de provisionamento.
|
|
25
25
|
import { assertIamReady } from '../lib/iam-preflight.js';
|
|
26
|
+
// QUINTA cópia do defeito do `--env` (bug_7dcb68e2): este comando oferecia e
|
|
27
|
+
// usava `'dev'` por padrão, token que o Core recusa. A fonte única do alfabeto
|
|
28
|
+
// de BANCO é `db-local/env.js` — NÃO `lib/envs.js`, que é o alfabeto de
|
|
29
|
+
// tenant/workspace/deployment e usa `'prod'` onde o banco usa `'production'`.
|
|
30
|
+
import { normalizeEnv, erroAmbienteBancoInvalido, NEETRU_ENVS, } from '../lib/db-local/env.js';
|
|
31
|
+
import { exitWithCliError } from '../lib/cli-error.js';
|
|
26
32
|
function colorStatus(s) {
|
|
27
33
|
if (s === 'active')
|
|
28
34
|
return chalk.green(s);
|
|
@@ -62,8 +68,8 @@ export async function runDbList(opts) {
|
|
|
62
68
|
}
|
|
63
69
|
log.heading(`Bancos de dados (${res.count})`);
|
|
64
70
|
renderTable([
|
|
65
|
-
{ header: 'ID', value: (r) => r.id,
|
|
66
|
-
{ header: 'PRODUTO', value: (r) => r.productId,
|
|
71
|
+
{ header: 'ID', value: (r) => r.id, identificador: true },
|
|
72
|
+
{ header: 'PRODUTO', value: (r) => r.productId, identificador: true },
|
|
67
73
|
{ header: 'LABEL', value: (r) => r.label, maxWidth: 22 },
|
|
68
74
|
{ header: 'ENGINE', value: (r) => r.engine, maxWidth: 20 },
|
|
69
75
|
{ header: 'ENV', value: (r) => r.environment },
|
|
@@ -74,6 +80,7 @@ export async function runDbCreate(opts) {
|
|
|
74
80
|
const token = await requireToken(opts.json);
|
|
75
81
|
const productId = await pickProductId(token, {
|
|
76
82
|
fromFlag: opts.productId,
|
|
83
|
+
inputs: ['--product-id'],
|
|
77
84
|
json: opts.json,
|
|
78
85
|
message: 'Produto do banco:',
|
|
79
86
|
});
|
|
@@ -89,15 +96,27 @@ export async function runDbCreate(opts) {
|
|
|
89
96
|
{ name: 'vm-mysql-cluster', value: 'vm-mysql-cluster' },
|
|
90
97
|
{ name: 'nosql-vm (MongoDB)', value: 'nosql-vm' },
|
|
91
98
|
], { default: 'firestore-instance', json: opts.json }));
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
// bug_7dcb68e2 (5ª cópia): o prompt oferecia `'dev'` E o usava como padrão.
|
|
100
|
+
// `_createProductDatabaseCore` (neetru-core:337) valida por igualdade estrita
|
|
101
|
+
// contra ['dev-local','staging','production'] — quem aceitava o padrão
|
|
102
|
+
// sugerido pela própria ferramenta tomava erro. `normalizeEnv` colapsa os
|
|
103
|
+
// apelidos (dev→dev-local, prod→production) e avisa no stderr, em vez de
|
|
104
|
+
// reescrever em silêncio.
|
|
105
|
+
let environment;
|
|
106
|
+
try {
|
|
107
|
+
environment = opts.env
|
|
108
|
+
? normalizeEnv(opts.env)
|
|
109
|
+
: (await promptChoice('Ambiente:',
|
|
110
|
+
// bug_89d2497e: o que a CLI OFERECE sai da mesma lista que valida.
|
|
111
|
+
NEETRU_ENVS.map((v) => ({ name: v === 'dev-local' ? 'dev-local (padrão)' : v, value: v })), { default: 'dev-local', json: opts.json }));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return exitWithCliError(erroAmbienteBancoInvalido(opts.env), opts.json);
|
|
115
|
+
}
|
|
98
116
|
const serverId = opts.serverId ??
|
|
99
117
|
(engine.startsWith('vm-')
|
|
100
118
|
? await pickServer(token, {
|
|
119
|
+
inputs: ['--server-id'],
|
|
101
120
|
json: opts.json,
|
|
102
121
|
status: 'online',
|
|
103
122
|
capacity: true,
|
|
@@ -294,11 +313,25 @@ export async function runDbDelete(id, opts) {
|
|
|
294
313
|
json: opts.json,
|
|
295
314
|
message: 'Banco para arquivar:',
|
|
296
315
|
});
|
|
316
|
+
// bug_757b9746 (2026-10-11): `/delete` exige step-up MFA no Core
|
|
317
|
+
// (`requireCliStepUpMfa`), mas este comando chamava `apiRequest` SEM header
|
|
318
|
+
// nenhum — nem `X-Neetru-MFA-Token`, nem `X-Neetru-MFA-Session`. A sessão de
|
|
319
|
+
// 1h ERA gravada e ERA carregada; `admin database delete` é que nunca pedia
|
|
320
|
+
// por ela. O Core devolvia 401 mesmo com sessão ativa, e o hint do
|
|
321
|
+
// `handleApiError` mandava passar `--mfa-token` — flag que o subcomando nem
|
|
322
|
+
// declarava. Resultado: impossível arquivar banco pela CLI, e o anti-replay
|
|
323
|
+
// do Core recusava o mesmo TOTP nas tentativas seguintes.
|
|
324
|
+
// Mesmo hotfix que `runDbRotate` levou em 2026-08-16, pelo mesmo motivo.
|
|
325
|
+
const { headers: mfaHeaders } = await resolveStepUp({
|
|
326
|
+
mfaToken: opts.mfaToken,
|
|
327
|
+
json: opts.json,
|
|
328
|
+
});
|
|
297
329
|
try {
|
|
298
330
|
const res = await apiRequest(`/api/cli/v1/db/${encodeURIComponent(databaseId)}/delete`, {
|
|
299
331
|
token,
|
|
300
332
|
method: 'POST',
|
|
301
333
|
body: { force: !!opts.force },
|
|
334
|
+
headers: mfaHeaders,
|
|
302
335
|
});
|
|
303
336
|
if (opts.json) {
|
|
304
337
|
console.log(JSON.stringify(res));
|
|
@@ -39,7 +39,13 @@ export interface ProductsCreateOptions {
|
|
|
39
39
|
description?: string;
|
|
40
40
|
ownerTeam?: string;
|
|
41
41
|
planTemplate?: string;
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* CSV de envs (ex: "dev,prod").
|
|
44
|
+
*
|
|
45
|
+
* ATENÇÃO (bug bug_db50b21f84e8400f9c3f6286b69c353b): hoje o Core NÃO honra
|
|
46
|
+
* esse campo — ver `AMBIENTES_AUTO_PROVISIONADOS` abaixo. Continua aceito pra
|
|
47
|
+
* não quebrar script existente, mas a saída marca que o pedido foi ignorado.
|
|
48
|
+
*/
|
|
43
49
|
env?: string;
|
|
44
50
|
/** Pula prompts — exige pelo menos `name`. */
|
|
45
51
|
yes?: boolean;
|
|
@@ -51,8 +57,16 @@ export interface ProductCreatedResult {
|
|
|
51
57
|
slug: string;
|
|
52
58
|
name: string;
|
|
53
59
|
ownerTeam: string;
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
/** Plano-template CONFIRMADO pelo servidor. `null` quando ele não devolveu. */
|
|
61
|
+
planTemplate: ProductPlanTemplate | null;
|
|
62
|
+
/** Ambientes CONFIRMADOS pelo servidor. `null` quando ele não devolveu. */
|
|
63
|
+
environments: string[] | null;
|
|
64
|
+
/** O que o usuário pediu (`--env` ou default). Pedido ≠ gravado. */
|
|
65
|
+
environmentsRequested: string[];
|
|
66
|
+
/** `true` só quando a resposta do servidor trouxe `environments`. */
|
|
67
|
+
environmentsConfirmed: boolean;
|
|
68
|
+
/** `true` só quando a resposta do servidor trouxe `planTemplate`. */
|
|
69
|
+
planTemplateConfirmed: boolean;
|
|
56
70
|
createdAt: string | number | null;
|
|
57
71
|
dashboardUrl: string;
|
|
58
72
|
}
|
|
@@ -17,6 +17,7 @@ import { requireToken, handleApiError } from '../lib/cli-read.js';
|
|
|
17
17
|
import { printWriteResult } from '../lib/cli-write.js';
|
|
18
18
|
import { renderTable } from '../lib/render.js';
|
|
19
19
|
import { pickProductSlug } from '../lib/pickers.js';
|
|
20
|
+
import { TENANT_ENVIRONMENT_IDS, VOCABULARIOS_DE_AMBIENTE } from '../lib/envs.js';
|
|
20
21
|
import { log } from '../utils/logger.js';
|
|
21
22
|
import { safeExit } from '../utils/safe-exit.js';
|
|
22
23
|
// ─── Erros tipados ─────────────────────────────────────────────────────────
|
|
@@ -82,7 +83,7 @@ export async function runProductsList(opts) {
|
|
|
82
83
|
}
|
|
83
84
|
log.heading(`Produtos (${res.count})`);
|
|
84
85
|
renderTable([
|
|
85
|
-
{ header: 'ID', value: (r) => r.id,
|
|
86
|
+
{ header: 'ID', value: (r) => r.id, identificador: true },
|
|
86
87
|
{ header: 'NOME', value: (r) => r.name ?? '—', maxWidth: 26 },
|
|
87
88
|
{ header: 'SLUG', value: (r) => r.slug ?? '—', maxWidth: 22 },
|
|
88
89
|
{ header: 'STATUS', value: (r) => colorStatus(r.status) },
|
|
@@ -101,6 +102,7 @@ async function runProductsPublishToggle(slug, published, opts) {
|
|
|
101
102
|
const token = await requireToken(opts.json);
|
|
102
103
|
const productSlug = await pickProductSlug(token, {
|
|
103
104
|
fromFlag: slug,
|
|
105
|
+
inputs: ['<slug>'],
|
|
104
106
|
json: opts.json,
|
|
105
107
|
message: published ? 'Produto a publicar:' : 'Produto a despublicar:',
|
|
106
108
|
});
|
|
@@ -128,6 +130,25 @@ export async function runProductsUnpublish(slug, opts) {
|
|
|
128
130
|
return runProductsPublishToggle(slug, false, opts);
|
|
129
131
|
}
|
|
130
132
|
const VALID_PLAN_TEMPLATES = ['free', 'starter', 'pro'];
|
|
133
|
+
/**
|
|
134
|
+
* Ambientes que o Core cria sozinho ao criar o produto.
|
|
135
|
+
*
|
|
136
|
+
* Não é escolha da CLI: `_products-core.ts` (Core) chama `_ensureEnvironmentCore`
|
|
137
|
+
* pra 'staging' e 'prod' logo depois de gravar o doc do produto, e deixa
|
|
138
|
+
* 'dev-local' de fora de propósito (é local, não existe no servidor).
|
|
139
|
+
* Serve só pra EXPLICAR por que o `--env` do usuário não valeu — não é promessa
|
|
140
|
+
* de que os dois ambientes existem, porque lá a criação é best-effort.
|
|
141
|
+
*
|
|
142
|
+
* P105: derivado do alfabeto do Core (todos menos o local) em vez de escrito à mão.
|
|
143
|
+
*/
|
|
144
|
+
const AMBIENTES_AUTO_PROVISIONADOS = TENANT_ENVIRONMENT_IDS.filter((e) => e !== 'dev-local');
|
|
145
|
+
/**
|
|
146
|
+
* O `environments` que a CLI manda quando o `--env` não vem. O Core IGNORA o
|
|
147
|
+
* campo (ver acima); o valor de sempre (`dev`, `prod`) é o vocabulário do
|
|
148
|
+
* `--env` do deploy para `dev-local` e `prod`, e agora sai da tabela
|
|
149
|
+
* `VOCABULARIOS_DE_AMBIENTE` em vez de uma lista à mão (P105) — mesmo payload.
|
|
150
|
+
*/
|
|
151
|
+
const AMBIENTES_PEDIDOS_POR_PADRAO = TENANT_ENVIRONMENT_IDS.filter((e) => e !== 'staging').map((id) => VOCABULARIOS_DE_AMBIENTE[id].deploy);
|
|
131
152
|
/**
|
|
132
153
|
* Deriva um slug `kebab-case` a partir do `name`. Mantém apenas [a-z0-9-]
|
|
133
154
|
* + colapsa hífens + corta hífens nas pontas. Não verifica unicidade — isso é
|
|
@@ -164,7 +185,9 @@ export async function runProductsCreate(opts) {
|
|
|
164
185
|
let description = opts.description ?? '';
|
|
165
186
|
let ownerTeam = opts.ownerTeam?.trim() || 'neetru-eng';
|
|
166
187
|
let planTemplate = (opts.planTemplate ?? 'free').toLowerCase();
|
|
167
|
-
|
|
188
|
+
// Só o que o usuário PEDIU. O nome deixa explícito que isso nunca é resposta
|
|
189
|
+
// do servidor — ver bug bug_db50b21f84e8400f9c3f6286b69c353b.
|
|
190
|
+
const environmentsRequested = parseEnvCsv(opts.env, [...AMBIENTES_PEDIDOS_POR_PADRAO]);
|
|
168
191
|
const interactive = !opts.yes && !opts.json && process.stdin.isTTY;
|
|
169
192
|
if (interactive) {
|
|
170
193
|
const answers = await inquirer.prompt([
|
|
@@ -204,13 +227,11 @@ export async function runProductsCreate(opts) {
|
|
|
204
227
|
choices: VALID_PLAN_TEMPLATES,
|
|
205
228
|
default: 'free',
|
|
206
229
|
},
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
default: environments.join(','),
|
|
213
|
-
},
|
|
230
|
+
// Não existe mais pergunta de "Ambientes (csv)" aqui.
|
|
231
|
+
// Motivo (bug bug_db50b21f84e8400f9c3f6286b69c353b): a resposta era
|
|
232
|
+
// descartada pelo servidor e ainda assim impressa como se tivesse valido.
|
|
233
|
+
// Perguntar algo que não é honrado é pior que não perguntar — o Core
|
|
234
|
+
// auto-provisiona staging+prod sozinho.
|
|
214
235
|
]);
|
|
215
236
|
if (answers.name)
|
|
216
237
|
name = answers.name.trim();
|
|
@@ -222,8 +243,6 @@ export async function runProductsCreate(opts) {
|
|
|
222
243
|
ownerTeam = answers.ownerTeam.trim();
|
|
223
244
|
if (answers.planTemplate)
|
|
224
245
|
planTemplate = answers.planTemplate;
|
|
225
|
-
if (answers.env)
|
|
226
|
-
environments = parseEnvCsv(answers.env, environments);
|
|
227
246
|
}
|
|
228
247
|
// Validação final
|
|
229
248
|
if (!name) {
|
|
@@ -271,8 +290,13 @@ export async function runProductsCreate(opts) {
|
|
|
271
290
|
slug,
|
|
272
291
|
description: description || undefined,
|
|
273
292
|
ownerTeam,
|
|
293
|
+
// `planTemplate` e `environments` continuam no corpo de propósito: o dia
|
|
294
|
+
// que o Core declarar esses campos no `cliProductCreateSchema` e
|
|
295
|
+
// devolvê-los na resposta, a CLI passa a reportar o valor REAL sem novo
|
|
296
|
+
// deploy. Enquanto isso o zod do Core os descarta em silêncio — por isso
|
|
297
|
+
// nada abaixo trata o que mandamos como se tivesse sido gravado.
|
|
274
298
|
planTemplate,
|
|
275
|
-
environments,
|
|
299
|
+
environments: environmentsRequested,
|
|
276
300
|
},
|
|
277
301
|
});
|
|
278
302
|
spinner?.stop();
|
|
@@ -292,14 +316,30 @@ export async function runProductsCreate(opts) {
|
|
|
292
316
|
handleApiError(error, opts.json);
|
|
293
317
|
}
|
|
294
318
|
const productId = res.productId ?? res.id ?? slug;
|
|
319
|
+
// BUG bug_db50b21f84e8400f9c3f6286b69c353b — POR QUE isto deixou de ser
|
|
320
|
+
// `res.environments ?? environments`:
|
|
321
|
+
// o POST /api/cli/v1/products do Core valida o corpo com `cliProductCreateSchema`,
|
|
322
|
+
// que NÃO declara `environments` nem `planTemplate`; o zod (não-strict) descarta
|
|
323
|
+
// chave desconhecida em silêncio, sem 400. A resposta também não devolve esses
|
|
324
|
+
// campos. O fallback antigo caía no valor PEDIDO e a tabela imprimia como se
|
|
325
|
+
// fosse o gravado — quem usou `--env dev,prod` saía convencido de ter escolhido
|
|
326
|
+
// ambientes que nunca existiram. Agora só é verdade o que o servidor devolveu;
|
|
327
|
+
// o resto vai marcado como não confirmado, aqui e no `--json`.
|
|
328
|
+
const environmentsConfirmed = Array.isArray(res.environments);
|
|
329
|
+
const planTemplateConfirmed = typeof res.planTemplate === 'string';
|
|
330
|
+
// `name` e `ownerTeam` seguem com fallback porque o schema do Core ACEITA os
|
|
331
|
+
// dois — eles foram gravados, o servidor só não os ecoa de volta.
|
|
295
332
|
const result = {
|
|
296
333
|
ok: true,
|
|
297
334
|
productId,
|
|
298
335
|
slug: res.slug ?? slug,
|
|
299
336
|
name: res.name ?? name,
|
|
300
337
|
ownerTeam: res.ownerTeam ?? ownerTeam,
|
|
301
|
-
planTemplate: res.planTemplate
|
|
302
|
-
environments: res.environments
|
|
338
|
+
planTemplate: planTemplateConfirmed ? res.planTemplate : null,
|
|
339
|
+
environments: environmentsConfirmed ? res.environments : null,
|
|
340
|
+
environmentsRequested,
|
|
341
|
+
environmentsConfirmed,
|
|
342
|
+
planTemplateConfirmed,
|
|
303
343
|
createdAt: res.createdAt ?? null,
|
|
304
344
|
dashboardUrl: buildDashboardUrl(productId),
|
|
305
345
|
};
|
|
@@ -316,12 +356,40 @@ export async function runProductsCreate(opts) {
|
|
|
316
356
|
{ k: 'slug', v: chalk.cyan(result.slug) },
|
|
317
357
|
{ k: 'name', v: result.name },
|
|
318
358
|
{ k: 'ownerTeam', v: result.ownerTeam },
|
|
319
|
-
{
|
|
320
|
-
|
|
359
|
+
{
|
|
360
|
+
k: 'planTemplate',
|
|
361
|
+
v: result.planTemplate
|
|
362
|
+
? chalk.yellow(result.planTemplate)
|
|
363
|
+
: chalk.dim('— (não confirmado pelo servidor)'),
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
k: 'environments',
|
|
367
|
+
v: result.environments
|
|
368
|
+
? result.environments.join(', ')
|
|
369
|
+
: // Curto de propósito: a coluna trunca em 60 e o recado longo some
|
|
370
|
+
// num `…`. A explicação inteira vai na linha abaixo da tabela.
|
|
371
|
+
chalk.dim(`${AMBIENTES_AUTO_PROVISIONADOS.join(', ')} (não confirmados)`),
|
|
372
|
+
},
|
|
321
373
|
{ k: 'createdAt', v: result.createdAt ? String(result.createdAt) : '—' },
|
|
322
374
|
{ k: 'dashboardUrl', v: chalk.blue(result.dashboardUrl) },
|
|
323
375
|
]);
|
|
324
376
|
console.log();
|
|
377
|
+
// Estas duas linhas existem por causa do bug: o usuário precisa sair do
|
|
378
|
+
// comando sabendo o que NÃO foi decidido por ele.
|
|
379
|
+
// (Só no caminho humano: `log.warn`/`log.dim` escrevem em stdout e sujariam
|
|
380
|
+
// o `--json`, que já carrega `environmentsConfirmed` pra quem é máquina.)
|
|
381
|
+
if (!result.environmentsConfirmed) {
|
|
382
|
+
log.dim(`environments: o Core provisiona ${AMBIENTES_AUTO_PROVISIONADOS.join(' e ')} por conta própria ` +
|
|
383
|
+
`e não devolve o campo nesta resposta — confira o que existe de fato com ` +
|
|
384
|
+
`"neetru workspaces list --product ${result.productId}".`);
|
|
385
|
+
}
|
|
386
|
+
// Quem digitou `--env` merece saber que o pedido foi pro lixo, em vez de
|
|
387
|
+
// descobrir depois procurando um ambiente que nunca foi criado.
|
|
388
|
+
if (!result.environmentsConfirmed && opts.env) {
|
|
389
|
+
log.warn(`--env (${result.environmentsRequested.join(', ')}) foi IGNORADO pelo Core — ele não aceita escolha de ambientes na criação do produto.`);
|
|
390
|
+
}
|
|
391
|
+
if (!result.environmentsConfirmed)
|
|
392
|
+
console.log();
|
|
325
393
|
log.dim(`Abra no painel: neetru workspaces open ${result.productId}`);
|
|
326
394
|
return result;
|
|
327
395
|
}
|