@neetru/cli 2.29.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.
Files changed (259) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/README.md +63 -17
  3. package/dist/cli-kit/choices.d.ts +33 -0
  4. package/dist/cli-kit/choices.js +32 -0
  5. package/dist/cli-kit/command-context.d.ts +98 -0
  6. package/dist/cli-kit/command-context.js +108 -0
  7. package/dist/cli-kit/errors.d.ts +79 -0
  8. package/dist/cli-kit/errors.js +94 -0
  9. package/dist/cli-kit/index.d.ts +8 -0
  10. package/dist/cli-kit/index.js +8 -0
  11. package/dist/cli-kit/suggest.d.ts +74 -0
  12. package/dist/cli-kit/suggest.js +207 -0
  13. package/dist/commands/admin/oidc-login.js +1 -1
  14. package/dist/commands/admin/sdk-credential.js +1 -1
  15. package/dist/commands/autocomplete.d.ts +1 -1
  16. package/dist/commands/autocomplete.js +1 -0
  17. package/dist/commands/bug.d.ts +59 -0
  18. package/dist/commands/bug.js +114 -2
  19. package/dist/commands/build.d.ts +24 -1
  20. package/dist/commands/build.js +195 -73
  21. package/dist/commands/chat.d.ts +94 -1
  22. package/dist/commands/chat.js +258 -26
  23. package/dist/commands/customers.js +1 -1
  24. package/dist/commands/db-move.js +3 -3
  25. package/dist/commands/db.js +17 -24
  26. package/dist/commands/deploy.d.ts +136 -2
  27. package/dist/commands/deploy.js +547 -69
  28. package/dist/commands/deployments.js +11 -6
  29. package/dist/commands/dev-context.js +15 -5
  30. package/dist/commands/dev-up.d.ts +7 -5
  31. package/dist/commands/dev-up.js +24 -23
  32. package/dist/commands/docs-produto.d.ts +90 -0
  33. package/dist/commands/docs-produto.js +384 -0
  34. package/dist/commands/doctor.js +36 -1
  35. package/dist/commands/domains.js +85 -4
  36. package/dist/commands/env.d.ts +9 -3
  37. package/dist/commands/env.js +23 -3
  38. package/dist/commands/infra-read.js +2 -2
  39. package/dist/commands/init.d.ts +2 -0
  40. package/dist/commands/init.js +14 -1
  41. package/dist/commands/login.js +35 -8
  42. package/dist/commands/logs.js +12 -4
  43. package/dist/commands/mobile.js +33 -9
  44. package/dist/commands/new.d.ts +31 -1
  45. package/dist/commands/new.js +94 -4
  46. package/dist/commands/open.js +16 -6
  47. package/dist/commands/ops.js +1 -1
  48. package/dist/commands/products-db.d.ts +1 -0
  49. package/dist/commands/products-db.js +41 -8
  50. package/dist/commands/products.d.ts +17 -3
  51. package/dist/commands/products.js +84 -16
  52. package/dist/commands/promote.js +30 -5
  53. package/dist/commands/secrets.d.ts +91 -0
  54. package/dist/commands/secrets.js +471 -0
  55. package/dist/commands/servers.js +6 -4
  56. package/dist/commands/status.js +2 -2
  57. package/dist/commands/support.js +5 -3
  58. package/dist/commands/tenants.js +28 -16
  59. package/dist/commands/upgrade.d.ts +9 -0
  60. package/dist/commands/upgrade.js +50 -1
  61. package/dist/commands/vm-memory.js +1 -0
  62. package/dist/commands/webhooks.d.ts +2 -0
  63. package/dist/commands/webhooks.js +9 -3
  64. package/dist/commands/workspaces.js +42 -11
  65. package/dist/index.js +142 -20
  66. package/dist/lib/api-client.d.ts +22 -1
  67. package/dist/lib/api-client.js +79 -8
  68. package/dist/lib/api-error.d.ts +14 -0
  69. package/dist/lib/api-error.js +107 -46
  70. package/dist/lib/build-targets.d.ts +127 -0
  71. package/dist/lib/build-targets.js +348 -0
  72. package/dist/lib/cli-error.d.ts +13 -0
  73. package/dist/lib/cli-error.js +24 -0
  74. package/dist/lib/cli-write.d.ts +43 -9
  75. package/dist/lib/cli-write.js +154 -45
  76. package/dist/lib/compose-generator.d.ts +13 -9
  77. package/dist/lib/compose-generator.js +28 -60
  78. package/dist/lib/config-schema.d.ts +36 -2
  79. package/dist/lib/config-schema.js +11 -0
  80. package/dist/lib/db-local/db-json.d.ts +1 -1
  81. package/dist/lib/db-local/db-json.js +13 -5
  82. package/dist/lib/db-local/env.d.ts +15 -3
  83. package/dist/lib/db-local/env.js +37 -10
  84. package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
  85. package/dist/lib/db-pipeline/build-deps.js +23 -3
  86. package/dist/lib/db-pipeline/types.d.ts +6 -2
  87. package/dist/lib/db-pipeline/types.js +0 -18
  88. package/dist/lib/deploy-target-contract.d.ts +152 -0
  89. package/dist/lib/deploy-target-contract.js +411 -0
  90. package/dist/lib/env-file-do-build.d.ts +19 -0
  91. package/dist/lib/env-file-do-build.js +44 -0
  92. package/dist/lib/envs.d.ts +108 -0
  93. package/dist/lib/envs.js +127 -0
  94. package/dist/lib/fs-seguro.d.ts +63 -0
  95. package/dist/lib/fs-seguro.js +108 -0
  96. package/dist/lib/mobile-local/mobile-json.js +10 -4
  97. package/dist/lib/pickers.d.ts +44 -7
  98. package/dist/lib/pickers.js +60 -4
  99. package/dist/lib/product-docs/check.d.ts +62 -0
  100. package/dist/lib/product-docs/check.js +489 -0
  101. package/dist/lib/product-docs/config.d.ts +66 -0
  102. package/dist/lib/product-docs/config.js +234 -0
  103. package/dist/lib/product-docs/dates.d.ts +17 -0
  104. package/dist/lib/product-docs/dates.js +41 -0
  105. package/dist/lib/product-docs/frontmatter.d.ts +43 -0
  106. package/dist/lib/product-docs/frontmatter.js +302 -0
  107. package/dist/lib/product-docs/git.d.ts +16 -0
  108. package/dist/lib/product-docs/git.js +92 -0
  109. package/dist/lib/product-docs/markdown.d.ts +27 -0
  110. package/dist/lib/product-docs/markdown.js +98 -0
  111. package/dist/lib/product-docs/ops.d.ts +133 -0
  112. package/dist/lib/product-docs/ops.js +975 -0
  113. package/dist/lib/product-docs/structure.d.ts +61 -0
  114. package/dist/lib/product-docs/structure.js +239 -0
  115. package/dist/lib/product-docs/sync.d.ts +107 -0
  116. package/dist/lib/product-docs/sync.js +322 -0
  117. package/dist/lib/product-docs/taxonomy.d.ts +46 -0
  118. package/dist/lib/product-docs/taxonomy.js +163 -0
  119. package/dist/lib/product-docs/templates.d.ts +53 -0
  120. package/dist/lib/product-docs/templates.js +245 -0
  121. package/dist/lib/public-secret-scan.d.ts +117 -0
  122. package/dist/lib/public-secret-scan.js +771 -0
  123. package/dist/lib/render.d.ts +16 -0
  124. package/dist/lib/render.js +34 -4
  125. package/dist/lib/segredo-produto.d.ts +124 -0
  126. package/dist/lib/segredo-produto.js +336 -0
  127. package/dist/lib/texto-utf8.d.ts +96 -0
  128. package/dist/lib/texto-utf8.js +196 -0
  129. package/package.json +2 -1
  130. package/dist/cli-kit/format.js.map +0 -1
  131. package/dist/cli-kit/glyphs.js.map +0 -1
  132. package/dist/cli-kit/index.js.map +0 -1
  133. package/dist/cli-kit/palette.js.map +0 -1
  134. package/dist/commands/add.js.map +0 -1
  135. package/dist/commands/admin/iam-check.js.map +0 -1
  136. package/dist/commands/admin/oidc-login.js.map +0 -1
  137. package/dist/commands/admin/sdk-credential.js.map +0 -1
  138. package/dist/commands/agent-release.js.map +0 -1
  139. package/dist/commands/agent-write.js.map +0 -1
  140. package/dist/commands/ai-models.js.map +0 -1
  141. package/dist/commands/ai.js.map +0 -1
  142. package/dist/commands/api-catalog.js.map +0 -1
  143. package/dist/commands/archive.js.map +0 -1
  144. package/dist/commands/artifact-registry.js.map +0 -1
  145. package/dist/commands/audit.js.map +0 -1
  146. package/dist/commands/auth.js.map +0 -1
  147. package/dist/commands/autocomplete.js.map +0 -1
  148. package/dist/commands/billing.js.map +0 -1
  149. package/dist/commands/bootstrap.js.map +0 -1
  150. package/dist/commands/bug.js.map +0 -1
  151. package/dist/commands/build.js.map +0 -1
  152. package/dist/commands/chat-menu.js.map +0 -1
  153. package/dist/commands/chat.js.map +0 -1
  154. package/dist/commands/cloud-run.js.map +0 -1
  155. package/dist/commands/code.js.map +0 -1
  156. package/dist/commands/config.js.map +0 -1
  157. package/dist/commands/customers.js.map +0 -1
  158. package/dist/commands/db-move.js.map +0 -1
  159. package/dist/commands/db.js.map +0 -1
  160. package/dist/commands/deploy.js.map +0 -1
  161. package/dist/commands/deployments.js.map +0 -1
  162. package/dist/commands/dev-context.js.map +0 -1
  163. package/dist/commands/dev-up.js.map +0 -1
  164. package/dist/commands/dev.js.map +0 -1
  165. package/dist/commands/docs.js.map +0 -1
  166. package/dist/commands/doctor.js.map +0 -1
  167. package/dist/commands/domains.js.map +0 -1
  168. package/dist/commands/dr.js.map +0 -1
  169. package/dist/commands/env.js.map +0 -1
  170. package/dist/commands/hosting-write.js.map +0 -1
  171. package/dist/commands/infra-read.js.map +0 -1
  172. package/dist/commands/init.js.map +0 -1
  173. package/dist/commands/login.js.map +0 -1
  174. package/dist/commands/logout.js.map +0 -1
  175. package/dist/commands/logs.js.map +0 -1
  176. package/dist/commands/mobile.js.map +0 -1
  177. package/dist/commands/mocks.js.map +0 -1
  178. package/dist/commands/new.js.map +0 -1
  179. package/dist/commands/open.js.map +0 -1
  180. package/dist/commands/ops.js.map +0 -1
  181. package/dist/commands/products-db.js.map +0 -1
  182. package/dist/commands/products.js.map +0 -1
  183. package/dist/commands/promote.js.map +0 -1
  184. package/dist/commands/publish.js.map +0 -1
  185. package/dist/commands/schema.js.map +0 -1
  186. package/dist/commands/servers.js.map +0 -1
  187. package/dist/commands/smoke.js.map +0 -1
  188. package/dist/commands/status.js.map +0 -1
  189. package/dist/commands/support.js.map +0 -1
  190. package/dist/commands/surface-status.js.map +0 -1
  191. package/dist/commands/tenants.js.map +0 -1
  192. package/dist/commands/ui.js.map +0 -1
  193. package/dist/commands/upgrade.js.map +0 -1
  194. package/dist/commands/validate.js.map +0 -1
  195. package/dist/commands/vm-memory.js.map +0 -1
  196. package/dist/commands/vm.js.map +0 -1
  197. package/dist/commands/webhooks.js.map +0 -1
  198. package/dist/commands/whoami.js.map +0 -1
  199. package/dist/commands/workspaces.js.map +0 -1
  200. package/dist/index.js.map +0 -1
  201. package/dist/lib/agent-artifact-url.js.map +0 -1
  202. package/dist/lib/agent-commands.js.map +0 -1
  203. package/dist/lib/ai/auth-discovery.js.map +0 -1
  204. package/dist/lib/ai/context.js.map +0 -1
  205. package/dist/lib/ai/orchestrator.js.map +0 -1
  206. package/dist/lib/api-client.js.map +0 -1
  207. package/dist/lib/api-error.js.map +0 -1
  208. package/dist/lib/auth.js.map +0 -1
  209. package/dist/lib/cli-read.js.map +0 -1
  210. package/dist/lib/cli-write.js.map +0 -1
  211. package/dist/lib/code-agent-budget.js.map +0 -1
  212. package/dist/lib/code-agent-files.js.map +0 -1
  213. package/dist/lib/code-agent-gateway.js.map +0 -1
  214. package/dist/lib/code-agent-git-tools.js.map +0 -1
  215. package/dist/lib/code-agent-protocol.js.map +0 -1
  216. package/dist/lib/code-agent-session.js.map +0 -1
  217. package/dist/lib/code-agent-settings.js.map +0 -1
  218. package/dist/lib/code-agent-skills.js.map +0 -1
  219. package/dist/lib/code-agent-subagents.js.map +0 -1
  220. package/dist/lib/code-agent-tools.js.map +0 -1
  221. package/dist/lib/code-agent-tui.js.map +0 -1
  222. package/dist/lib/code-agent-types.js.map +0 -1
  223. package/dist/lib/code-agent-ui.js.map +0 -1
  224. package/dist/lib/code-agent-web-tools.js.map +0 -1
  225. package/dist/lib/code-agent-write-tools.js.map +0 -1
  226. package/dist/lib/compose-generator.js.map +0 -1
  227. package/dist/lib/config-schema.js.map +0 -1
  228. package/dist/lib/config.js.map +0 -1
  229. package/dist/lib/db-local/db-json.js.map +0 -1
  230. package/dist/lib/db-local/env.js.map +0 -1
  231. package/dist/lib/db-local/fingerprint.js.map +0 -1
  232. package/dist/lib/db-local/index.js.map +0 -1
  233. package/dist/lib/db-pipeline/build-deps.js.map +0 -1
  234. package/dist/lib/db-pipeline/errors.js.map +0 -1
  235. package/dist/lib/db-pipeline/index.js.map +0 -1
  236. package/dist/lib/db-pipeline/pipeline.js.map +0 -1
  237. package/dist/lib/db-pipeline/rehearse.js.map +0 -1
  238. package/dist/lib/db-pipeline/types.js.map +0 -1
  239. package/dist/lib/dev-local-context.js.map +0 -1
  240. package/dist/lib/iam-preflight.js.map +0 -1
  241. package/dist/lib/mobile-local/index.js.map +0 -1
  242. package/dist/lib/mobile-local/mobile-json.js.map +0 -1
  243. package/dist/lib/pickers.js.map +0 -1
  244. package/dist/lib/product-config.js.map +0 -1
  245. package/dist/lib/render.js.map +0 -1
  246. package/dist/lib/schema-cache.js.map +0 -1
  247. package/dist/lib/sse.js.map +0 -1
  248. package/dist/lib/telemetry.js.map +0 -1
  249. package/dist/lib/token-resolver.js.map +0 -1
  250. package/dist/lib/url-allowlist.js.map +0 -1
  251. package/dist/lib/version-check.js.map +0 -1
  252. package/dist/utils/logger.js.map +0 -1
  253. package/dist/utils/logo.js.map +0 -1
  254. package/dist/utils/safe-exit.js.map +0 -1
  255. package/dist/utils/spinner-registry.js.map +0 -1
  256. package/dist/version.js.map +0 -1
  257. package/templates/dev/Dockerfile.agent +0 -39
  258. package/templates/dev/agent-entrypoint.sh +0 -41
  259. package/templates/dev/docker-compose.dev.yml +0 -63
@@ -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
- const env = opts.env ??
43
- (await promptChoice('Ambiente:', [
44
- { name: 'dev', value: 'dev' },
45
- { name: 'staging', value: 'staging' },
46
- { name: 'prod', value: 'prod' },
47
- ], { default: 'dev', json: opts.json }));
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
- function normalizeEnv(value) {
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 === 'dev' || raw === 'dev-local' || raw === 'dev-core')
16
+ if (SYNC_ENVS.includes(raw))
13
17
  return raw;
14
- log.error(`Ambiente "${value}" inválido para dev-local. Use: dev | dev-local | dev-core.`);
15
- safeExit(1);
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,10 +1,12 @@
1
1
  /**
2
- * `neetru dev up` — ambiente dev-local (banco + agente) via Docker Compose.
2
+ * `neetru dev up` — banco do produto em container via Docker Compose.
3
3
  *
4
- * Pivô 2026-07 (do emulador Firebase pro emulador de VM): detecta o banco do
5
- * projeto em `neetru.config.json`, gera `neetru-compose.dev.yml` + `Dockerfile.agent`
6
- * a partir dos templates embutidos e sobe tudo com `docker compose up -d --wait`.
7
- * O app SaaS + banco + agente rodam em containers, espelhando a VM de produção.
4
+ * Detecta o banco do projeto em `neetru.config.json`, gera
5
+ * `neetru-compose.dev.yml` e sobe com `docker compose up -d --wait`. O app roda
6
+ * fora do container (`npm run dev`), apontando pra URL que o comando imprime.
7
+ *
8
+ * O agente local saiu em 2026-10-09 (nunca chegou a subir — ver o cabeçalho de
9
+ * `generateComposeDev`); volta quando o agente tiver modo dev de verdade.
8
10
  *
9
11
  * `runDevExec` (`neetru dev exec`) segue no caminho de emulador Firebase
10
12
  * (`firebase emulators:exec`) — é outro comando, pra rodar scripts de CI.
@@ -1,10 +1,12 @@
1
1
  /**
2
- * `neetru dev up` — ambiente dev-local (banco + agente) via Docker Compose.
2
+ * `neetru dev up` — banco do produto em container via Docker Compose.
3
3
  *
4
- * Pivô 2026-07 (do emulador Firebase pro emulador de VM): detecta o banco do
5
- * projeto em `neetru.config.json`, gera `neetru-compose.dev.yml` + `Dockerfile.agent`
6
- * a partir dos templates embutidos e sobe tudo com `docker compose up -d --wait`.
7
- * O app SaaS + banco + agente rodam em containers, espelhando a VM de produção.
4
+ * Detecta o banco do projeto em `neetru.config.json`, gera
5
+ * `neetru-compose.dev.yml` e sobe com `docker compose up -d --wait`. O app roda
6
+ * fora do container (`npm run dev`), apontando pra URL que o comando imprime.
7
+ *
8
+ * O agente local saiu em 2026-10-09 (nunca chegou a subir — ver o cabeçalho de
9
+ * `generateComposeDev`); volta quando o agente tiver modo dev de verdade.
8
10
  *
9
11
  * `runDevExec` (`neetru dev exec`) segue no caminho de emulador Firebase
10
12
  * (`firebase emulators:exec`) — é outro comando, pra rodar scripts de CI.
@@ -22,7 +24,7 @@ import { log } from '../utils/logger.js';
22
24
  import { safeExit, setSafeExitCode } from '../utils/safe-exit.js';
23
25
  import { describeDevContext, resolveDevContext, loadLocalProductConfig } from '../lib/dev-local-context.js';
24
26
  import { neetruConfigSchema } from '../lib/config-schema.js';
25
- import { generateComposeDev } from '../lib/compose-generator.js';
27
+ import { generateComposeDev, localDatabaseUrl } from '../lib/compose-generator.js';
26
28
  // ---------------------------------------------------------------------------
27
29
  // Constantes
28
30
  // ---------------------------------------------------------------------------
@@ -187,20 +189,14 @@ export async function runDevUp(opts = {}, deps = buildDevUpDeps()) {
187
189
  log.info(' 3. Acesse http://localhost:3000');
188
190
  return;
189
191
  }
190
- // ── Produto: gera e sobe Docker Compose com banco + agente ──────────────────
192
+ // ── Produto: gera e sobe Docker Compose com o banco ─────────────────────────
191
193
  const localConfig = loadLocalProductConfig(process.cwd());
192
194
  const slug = localConfig?.slug ?? 'app';
193
- log.info(`Emulador de VM local — ${chalk.bold(dbEngine)}`);
195
+ log.info(`Banco local — ${chalk.bold(dbEngine)}`);
194
196
  log.dim(` Produto: ${chalk.bold(slug)}`);
195
- // Gera neetru-compose.dev.yml + Dockerfile.agent
196
197
  const composePath = path.join(process.cwd(), 'neetru-compose.dev.yml');
197
198
  const appPort = opts.corePort ?? 3000;
198
- const composeContent = generateComposeDev({
199
- productSlug: slug,
200
- dbEngine,
201
- appPort,
202
- coreUrl: devContext?.corePath ? `http://localhost:${opts.corePort ?? 9003}` : 'https://core.neetru.com',
203
- });
199
+ const composeContent = generateComposeDev({ productSlug: slug, dbEngine });
204
200
  fsSync.writeFileSync(composePath, composeContent, 'utf8');
205
201
  log.info(`Compose gerado: ${chalk.bold('neetru-compose.dev.yml')}`);
206
202
  // Sobe com Docker Compose. Detecção cross-platform (antes: paths fixos Unix).
@@ -213,7 +209,7 @@ export async function runDevUp(opts = {}, deps = buildDevUpDeps()) {
213
209
  setSafeExitCode(1);
214
210
  return;
215
211
  }
216
- const composeSpinner = ora({ text: 'Subindo containers (banco + agente)…', color: 'blue' }).start();
212
+ const composeSpinner = ora({ text: 'Subindo o banco…', color: 'blue' }).start();
217
213
  try {
218
214
  // Docker Compose up — síncrono, espera healthy
219
215
  const { execSync } = await import('node:child_process');
@@ -222,16 +218,21 @@ export async function runDevUp(opts = {}, deps = buildDevUpDeps()) {
222
218
  stdio: 'pipe',
223
219
  timeout: 120_000,
224
220
  });
225
- composeSpinner.succeed('Containers prontos');
221
+ composeSpinner.succeed('Banco pronto');
222
+ const dbUrl = localDatabaseUrl(dbEngine);
226
223
  log.info('');
227
- log.info(chalk.bold.green('✓ Ambiente dev-local ativo!'));
228
- log.info(` App SaaS: http://localhost:${appPort}`);
229
- log.info(` Banco: ${dbEngine} (porta local padrão)`);
230
- log.info(` Agente: conectado ao Core`);
224
+ log.info(chalk.bold.green('✓ Banco local ativo!'));
225
+ log.info(` Banco: ${dbEngine}`);
226
+ if (dbUrl)
227
+ log.info(` Conexão: ${dbUrl}`);
228
+ log.info('');
229
+ log.info(chalk.bold('Próximos passos:'));
230
+ if (dbUrl)
231
+ log.info(' 1. Use a conexão acima como DATABASE_URL no .env.local');
232
+ log.info(` ${dbUrl ? '2' : '1'}. Rode \`npm run dev\` e acesse http://localhost:${appPort}`);
231
233
  log.info('');
232
234
  log.dim(' Rode `docker compose -f neetru-compose.dev.yml down` para parar.');
233
- log.dim(` Rode \`neetru deploy --target vm --server dev-local-${slug}\` para deployar.`);
234
- log.dim(' O agente vai receber o bundle, npm install, e subir o app automaticamente.');
235
+ log.dim(' O agente local ainda não existe: deploy e migração pelo agente só na VM.');
235
236
  }
236
237
  catch (err) {
237
238
  composeSpinner.fail('Docker Compose falhou.');
@@ -0,0 +1,90 @@
1
+ import { type CheckResult } from '../lib/product-docs/check.js';
2
+ import { type InitDocsResult } from '../lib/product-docs/ops.js';
3
+ import { type SyncResult } from '../lib/product-docs/sync.js';
4
+ import { NeetruCliError } from '../cli-kit/errors.js';
5
+ export interface DocsInitOptions {
6
+ comApi?: boolean;
7
+ migrar?: boolean;
8
+ dryRun?: boolean;
9
+ json?: boolean;
10
+ }
11
+ export declare function runDocsInit(opts: DocsInitOptions): Promise<void>;
12
+ /**
13
+ * Chamado por `neetru init`/`neetru new` depois do scaffold. Nunca derruba o
14
+ * comando chamador: erro vira aviso.
15
+ */
16
+ export declare function initDocsAfterScaffold(dir: string, opts?: {
17
+ quiet?: boolean;
18
+ }): Promise<InitDocsResult | null>;
19
+ export interface DocsNewOptions {
20
+ categoria?: string;
21
+ subcategoria?: string;
22
+ audience?: string;
23
+ descricao?: string;
24
+ data?: string;
25
+ dryRun?: boolean;
26
+ json?: boolean;
27
+ }
28
+ export declare function runDocsNew(tipo: string, titulo: string, opts: DocsNewOptions): Promise<void>;
29
+ export interface DocsCheckOptions {
30
+ changed?: boolean;
31
+ strict?: boolean;
32
+ offline?: boolean;
33
+ json?: boolean;
34
+ }
35
+ export declare function runDocsCheck(opts: DocsCheckOptions): Promise<void>;
36
+ export interface DocsSyncOptions {
37
+ dryRun?: boolean;
38
+ offline?: boolean;
39
+ json?: boolean;
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;
65
+ export declare function runDocsSync(opts: DocsSyncOptions): Promise<void>;
66
+ export interface DeployDocsPrecheck {
67
+ ran: boolean;
68
+ ok?: boolean;
69
+ counts?: CheckResult['counts'];
70
+ /** Itens do mínimo do primeiro dia / CHANGELOG que faltam (PR002/PR004). */
71
+ diaUm?: string[];
72
+ error?: string;
73
+ }
74
+ /**
75
+ * Antes do deploy: roda `docs check` e SÓ AVISA (decisão do dono 2026-10-03).
76
+ * Nunca lança, nunca encerra o processo.
77
+ */
78
+ export declare function deployDocsPrecheck(cwd: string, envName: string, json?: boolean): Promise<DeployDocsPrecheck>;
79
+ export interface DeployDocsSync {
80
+ attempted: boolean;
81
+ reason?: string;
82
+ status?: SyncResult['status'];
83
+ message?: string;
84
+ enviados?: number;
85
+ }
86
+ /**
87
+ * Depois de um deploy de PRODUÇÃO bem-sucedido: publica os docs se
88
+ * `docs.publishOnDeploy` estiver ligado. Falha aqui NUNCA derruba o deploy.
89
+ */
90
+ export declare function deployDocsPostSync(cwd: string, envName: string, json?: boolean): Promise<DeployDocsSync>;
@@ -0,0 +1,384 @@
1
+ /**
2
+ * `neetru docs init | new | check | sync` — documentação do PRODUTO SaaS
3
+ * (PROTOCOLO_DOCS_PRODUTOS_SAAS v1, 2026-10-03).
4
+ *
5
+ * Os outros subcomandos de `neetru docs` (open/publish/list/get/delete) são o
6
+ * control plane dos docs do CORE e ficam em `docs.ts`.
7
+ *
8
+ * Decisões do dono (2026-10-03):
9
+ * - `neetru deploy` só AVISA sobre docs — nunca bloqueia.
10
+ * - Publicar no Core é opcional: `docs.publishOnDeploy: true` (deploy de
11
+ * produção publica no fim) ou manual com `neetru docs sync`.
12
+ * - `docs init --migrar` não move nem renomeia nada.
13
+ */
14
+ import chalk from 'chalk';
15
+ import { log } from '../utils/logger.js';
16
+ import { safeExit } from '../utils/safe-exit.js';
17
+ import { loadProductRepo, ProductConfigInvalidError, ProductConfigMissingError, } from '../lib/product-docs/config.js';
18
+ import { checkProductDocs } from '../lib/product-docs/check.js';
19
+ import { resolveTaxonomy } from '../lib/product-docs/taxonomy.js';
20
+ import { createProductDoc, DocsNewError, initProductDocs, migrateProductDocs, } from '../lib/product-docs/ops.js';
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';
26
+ function fail(json, code, message) {
27
+ if (json)
28
+ console.log(JSON.stringify({ ok: false, error: code, message }));
29
+ else
30
+ log.error(message);
31
+ safeExit(1);
32
+ }
33
+ async function loadRepoOrExit(json, cwd = process.cwd()) {
34
+ try {
35
+ return await loadProductRepo(cwd);
36
+ }
37
+ catch (e) {
38
+ if (e instanceof ProductConfigMissingError)
39
+ fail(json, 'no_product_config', e.message);
40
+ if (e instanceof ProductConfigInvalidError)
41
+ fail(json, 'invalid_product_config', e.message);
42
+ throw e;
43
+ }
44
+ }
45
+ function printInit(r) {
46
+ const verb = r.dryRun ? 'Criaria' : 'Criado';
47
+ if (r.configKeysAdded.length)
48
+ log.success(`neetru.config.json: chave docs (${r.configKeysAdded.join(', ')})${r.dryRun ? ' — simulação' : ''}.`);
49
+ if (r.folders.length)
50
+ log.dim(` ${r.dryRun ? 'Pastas que seriam criadas' : 'Pastas criadas'}: ${r.folders.length}`);
51
+ for (const f of r.created)
52
+ log.success(`${verb}: ${f}`);
53
+ if (r.skipped.length)
54
+ log.dim(` Já existiam (não mexi): ${r.skipped.join(', ')}`);
55
+ if (!r.created.length && !r.folders.length && !r.configKeysAdded.length)
56
+ log.info('Nada a fazer: o mínimo do primeiro dia já existe.');
57
+ if (!r.withApi)
58
+ log.dim(' API_V1.md só entra com --com-api (ou quando o produto declara app móvel/integração).');
59
+ }
60
+ export async function runDocsInit(opts) {
61
+ const repo = await loadRepoOrExit(opts.json);
62
+ if (opts.migrar) {
63
+ const { taxonomy } = await resolveTaxonomy({ offline: true });
64
+ const r = await migrateProductDocs(repo, { dryRun: opts.dryRun, taxonomy });
65
+ if (opts.json) {
66
+ console.log(JSON.stringify({ ok: true, mode: 'migrar', ...r }));
67
+ return;
68
+ }
69
+ log.heading(`Migração dos docs de ${repo.slug}${r.dryRun ? ' (simulação — nada foi escrito)' : ''}`);
70
+ const added = r.files.filter((f) => f.acao === 'frontmatter-adicionado');
71
+ log.info(`${r.files.length} arquivo(s) analisado(s); frontmatter ${r.dryRun ? 'seria acrescentado' : 'acrescentado'} em ${added.length}.`);
72
+ const kept = r.files.filter((f) => f.acao !== 'frontmatter-adicionado');
73
+ if (kept.length)
74
+ log.dim(` ${kept.length} já tinham frontmatter e ficaram intocados.`);
75
+ const low = added.reduce((n, f) => n + f.inferencias.filter((i) => i.confianca === 'baixa').length, 0);
76
+ if (low)
77
+ log.warn(`${low} inferência(s) de confiança baixa — revise no relatório.`);
78
+ const moves = r.files.filter((f) => f.sugestaoDestino).length;
79
+ if (moves)
80
+ log.info(`${moves} arquivo(s) com sugestão de novo lugar (nada foi movido).`);
81
+ if (r.legacyNamesFile) {
82
+ log.info(r.legacyNamesFile.created
83
+ ? `${r.legacyNamesFile.path}: ${r.legacyNamesFile.count} nome(s) fora da regra tolerados.`
84
+ : `${r.legacyNamesFile.path} já existia — não foi alterado.`);
85
+ }
86
+ if (r.reportPath)
87
+ log.success(`Relatório: ${r.reportPath}`);
88
+ if (r.faltandoPrimeiroDia.length) {
89
+ log.dim(` Falta do mínimo do primeiro dia: ${r.faltandoPrimeiroDia.join(', ')}.`);
90
+ log.dim(' Depois de mover os arquivos, rode `neetru docs init` (sem --migrar) para criar o que falta.');
91
+ }
92
+ log.dim(' Próximo passo: revisar o relatório, mover num PR à parte, `neetru docs check`.');
93
+ return;
94
+ }
95
+ const r = await initProductDocs(repo, { comApi: opts.comApi, dryRun: opts.dryRun });
96
+ if (opts.json) {
97
+ console.log(JSON.stringify({ ok: true, mode: 'init', ...r }));
98
+ return;
99
+ }
100
+ log.heading(`Docs de ${repo.slug}${r.dryRun ? ' (simulação — nada foi escrito)' : ''}`);
101
+ printInit(r);
102
+ log.dim(' Próximos passos: completar os rascunhos, `neetru docs check`.');
103
+ }
104
+ /**
105
+ * Chamado por `neetru init`/`neetru new` depois do scaffold. Nunca derruba o
106
+ * comando chamador: erro vira aviso.
107
+ */
108
+ export async function initDocsAfterScaffold(dir, opts = {}) {
109
+ try {
110
+ const repo = await loadProductRepo(dir);
111
+ const r = await initProductDocs(repo);
112
+ if (!opts.quiet) {
113
+ log.info(`Docs: ${r.created.length} arquivo(s) do mínimo do primeiro dia criados em ${r.docsRoot}/ (\`neetru docs check\` confere).`);
114
+ }
115
+ return r;
116
+ }
117
+ catch (e) {
118
+ if (!opts.quiet)
119
+ log.warn(`Não deu para criar os docs (${e.message}). Rode \`neetru docs init\` depois.`);
120
+ return null;
121
+ }
122
+ }
123
+ export async function runDocsNew(tipo, titulo, opts) {
124
+ const repo = await loadRepoOrExit(opts.json);
125
+ try {
126
+ const r = await createProductDoc(repo, tipo, titulo, opts);
127
+ if (opts.json) {
128
+ const { content: _content, ...rest } = r;
129
+ console.log(JSON.stringify({ ok: true, ...rest }));
130
+ return;
131
+ }
132
+ log.success(`${r.dryRun ? 'Criaria' : 'Criado'}: ${chalk.bold(r.path)}`);
133
+ log.dim(` tipo=${r.type} · ${r.category}/${r.subcategory} · audience=${r.audience} · status=${r.status}`);
134
+ if (r.categoriaPadrao)
135
+ log.dim(' Categoria padrão da pasta — ajuste com --categoria/--subcategoria se o assunto for outro.');
136
+ if (!opts.descricao)
137
+ log.dim(' Troque a `description` provisória por uma frase sua (ou use --descricao).');
138
+ }
139
+ catch (e) {
140
+ if (e instanceof DocsNewError)
141
+ fail(opts.json, e.code, e.message);
142
+ throw e;
143
+ }
144
+ }
145
+ const LEVEL_LABEL = { erro: 'erro', aviso: 'aviso', sugestao: 'sugestão' };
146
+ function printFindings(res, maxPerLevel = Infinity) {
147
+ const byFile = new Map();
148
+ for (const f of res.findings) {
149
+ if (!byFile.has(f.file))
150
+ byFile.set(f.file, []);
151
+ byFile.get(f.file).push(f);
152
+ }
153
+ let shown = 0;
154
+ for (const [file, list] of byFile) {
155
+ if (shown >= maxPerLevel)
156
+ break;
157
+ console.log(`\n ${chalk.bold(file)}`);
158
+ for (const f of list) {
159
+ const color = f.level === 'erro' ? chalk.red : f.level === 'aviso' ? chalk.yellow : chalk.dim;
160
+ console.log(` ${color(`${f.code} ${LEVEL_LABEL[f.level]}`)}${f.line ? chalk.dim(` :${f.line}`) : ''} ${f.message}`);
161
+ shown++;
162
+ }
163
+ }
164
+ }
165
+ export async function runDocsCheck(opts) {
166
+ const repo = await loadRepoOrExit(opts.json);
167
+ const tax = await resolveTaxonomy({ offline: opts.offline });
168
+ const res = await checkProductDocs(repo, { changed: opts.changed, strict: opts.strict, taxonomy: tax.taxonomy });
169
+ if (opts.json) {
170
+ console.log(JSON.stringify({
171
+ ok: res.ok,
172
+ product: repo.slug,
173
+ counts: res.counts,
174
+ filesChecked: res.filesChecked,
175
+ changed: !!opts.changed,
176
+ changedBase: res.changedBase ?? null,
177
+ changedFallback: !!res.changedFallback,
178
+ strict: !!opts.strict,
179
+ taxonomy: { source: tax.source, protocolVersion: tax.taxonomy.protocolVersion, aviso: tax.aviso ?? null },
180
+ findings: res.findings,
181
+ }));
182
+ if (!res.ok)
183
+ safeExit(1);
184
+ return;
185
+ }
186
+ log.heading(`Docs de ${repo.slug}${opts.changed ? ' (só os alterados)' : ''}`);
187
+ if (tax.aviso)
188
+ log.warn(tax.aviso);
189
+ if (res.changedFallback)
190
+ log.warn('Não deu para descobrir os arquivos alterados (sem git ou sem a branch principal) — chequei todos.');
191
+ if (res.findings.length)
192
+ printFindings(res);
193
+ console.log();
194
+ const summary = `${res.filesChecked} doc(s) · ${res.counts.erro} erro(s) · ${res.counts.aviso} aviso(s) · ${res.counts.sugestao} sugestão(ões)`;
195
+ log.dim(` taxonomia ${tax.source === 'core' ? 'do Core' : 'embutida'} (protocolo v${tax.taxonomy.protocolVersion})`);
196
+ if (res.ok) {
197
+ log.success(summary);
198
+ }
199
+ else {
200
+ log.error(summary + (opts.strict && res.counts.erro === 0 ? ' — --strict trata aviso como erro' : ''));
201
+ safeExit(1);
202
+ }
203
+ }
204
+ function printSync(r) {
205
+ for (const e of r.enviados) {
206
+ log.success(`${r.dryRun ? 'Enviaria' : 'Enviado'}: ${e.slug}${e.version ? ` (v${e.version})` : ''}${e.visibility === 'public' ? chalk.dim(' · público') : ''}`);
207
+ }
208
+ if (r.inalterados.length)
209
+ log.dim(` Sem mudança: ${r.inalterados.length}`);
210
+ for (const b of r.bloqueados)
211
+ log.warn(`Não enviado (erro no check: ${b.codes.join(', ')}): ${b.file}`);
212
+ for (const x of r.recusados)
213
+ log.warn(`Não pode subir: ${x.file} — ${x.motivo}.`);
214
+ // As falhas de envio saem no erro tipado (`erroDeSyncComFalhas`), uma vez só.
215
+ if (r.orfaos.length) {
216
+ log.warn(`${r.orfaos.length} doc(s) existem no Core mas não no repositório (nada foi apagado): ${r.orfaos.join(', ')}`);
217
+ log.dim(' A remoção de docs de produto ainda não existe no Core — eles continuam lá até o Core oferecer isso.');
218
+ }
219
+ }
220
+ /** Status de sync que contam como falha do comando `neetru docs sync`. */
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
+ }
268
+ export async function runDocsSync(opts) {
269
+ const repo = await loadRepoOrExit(opts.json);
270
+ const tax = await resolveTaxonomy({ offline: opts.offline });
271
+ const r = await syncProductDocs(repo, {
272
+ taxonomy: tax.taxonomy,
273
+ dryRun: opts.dryRun,
274
+ onWait: opts.json
275
+ ? undefined
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…`),
277
+ });
278
+ const desfecho = desfechoDoSync(r);
279
+ if (opts.json) {
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);
290
+ return;
291
+ }
292
+ log.heading(`Publicar docs de ${repo.slug} no Core${r.dryRun ? ' (simulação)' : ''}`);
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
+ }
300
+ if (r.status === 'core-sem-suporte' || r.status === 'desligado' || r.status === 'nada-a-enviar') {
301
+ log.info(r.message);
302
+ return;
303
+ }
304
+ if (desfecho.exit !== 0) {
305
+ log.error(r.message);
306
+ safeExit(desfecho.exit);
307
+ }
308
+ log.success(r.message);
309
+ }
310
+ /**
311
+ * Antes do deploy: roda `docs check` e SÓ AVISA (decisão do dono 2026-10-03).
312
+ * Nunca lança, nunca encerra o processo.
313
+ */
314
+ export async function deployDocsPrecheck(cwd, envName, json) {
315
+ let repo;
316
+ try {
317
+ repo = await loadProductRepo(cwd);
318
+ }
319
+ catch {
320
+ return { ran: false };
321
+ }
322
+ try {
323
+ const tax = await resolveTaxonomy({ offline: true });
324
+ const res = await checkProductDocs(repo, { taxonomy: tax.taxonomy });
325
+ const diaUm = res.findings
326
+ .filter((f) => (f.code === 'PR002' || f.code === 'PR004') && f.level !== 'sugestao')
327
+ .map((f) => f.message);
328
+ if (!json) {
329
+ const prod = envName === 'prod' || envName === 'production';
330
+ if (res.counts.erro || res.counts.aviso) {
331
+ log.warn(`Docs: ${res.counts.erro} erro(s) e ${res.counts.aviso} aviso(s) — o deploy segue; rode \`neetru docs check\` para ver.`);
332
+ if (prod && diaUm.length) {
333
+ log.warn('Docs: falta o mínimo do primeiro dia para produção:');
334
+ for (const m of diaUm.slice(0, 6))
335
+ log.dim(` · ${m}`);
336
+ }
337
+ }
338
+ else {
339
+ log.dim(' Docs: check sem erros.');
340
+ }
341
+ }
342
+ return { ran: true, ok: res.ok, counts: res.counts, diaUm };
343
+ }
344
+ catch (e) {
345
+ return { ran: false, error: e.message };
346
+ }
347
+ }
348
+ /**
349
+ * Depois de um deploy de PRODUÇÃO bem-sucedido: publica os docs se
350
+ * `docs.publishOnDeploy` estiver ligado. Falha aqui NUNCA derruba o deploy.
351
+ */
352
+ export async function deployDocsPostSync(cwd, envName, json) {
353
+ const prod = envName === 'prod' || envName === 'production';
354
+ if (!prod)
355
+ return { attempted: false, reason: 'nao-producao' };
356
+ let repo;
357
+ try {
358
+ repo = await loadProductRepo(cwd);
359
+ }
360
+ catch {
361
+ return { attempted: false, reason: 'sem-config' };
362
+ }
363
+ if (!repo.docs.publishOnDeploy)
364
+ return { attempted: false, reason: 'publishOnDeploy-desligado' };
365
+ try {
366
+ const tax = await resolveTaxonomy({ offline: true });
367
+ const r = await syncProductDocs(repo, { taxonomy: tax.taxonomy });
368
+ if (!json) {
369
+ if (r.status === 'ok')
370
+ log.success(`Docs publicados no Core: ${r.message}`);
371
+ else if (r.status === 'core-sem-suporte' || r.status === 'nada-a-enviar' || r.status === 'desligado')
372
+ log.info(`Docs: ${r.message}`);
373
+ else
374
+ log.warn(`Docs não publicados (${r.message}) — o deploy está OK; tente \`neetru docs sync\`.`);
375
+ }
376
+ return { attempted: true, status: r.status, message: r.message, enviados: r.enviados.length };
377
+ }
378
+ catch (e) {
379
+ if (!json)
380
+ log.warn(`Docs não publicados (${e.message}) — o deploy está OK; tente \`neetru docs sync\`.`);
381
+ return { attempted: true, status: 'falha', message: e.message };
382
+ }
383
+ }
384
+ //# sourceMappingURL=docs-produto.js.map