@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
@@ -16,23 +16,29 @@
16
16
  *
17
17
  * Imprime a mensagem adequada (texto humano ou JSON com `--json`) e encerra o
18
18
  * processo — nunca retorna (`never`).
19
+ *
20
+ * bug_89d2497e (2026-10-17): o erro de API vira um `NeetruCliError` (cli-kit)
21
+ * com o exit code da tabela acima e o erro original em `cause`; quem imprime e
22
+ * encerra é `exitWithCliError`. O formato do JSON e os códigos de saída são os
23
+ * mesmos de antes. Um `NeetruCliError` que chegue aqui (ex.: lançado dentro do
24
+ * `try` de um comando) sai com o código DELE, sem ser reclassificado.
19
25
  */
20
26
  import { CliApiError, CliNetworkError } from './api-client.js';
21
- import { log } from '../utils/logger.js';
22
- import { safeExit } from '../utils/safe-exit.js';
23
- /** Exit codes canônicos, exportados pra teste/documentação. */
24
- export const API_ERROR_EXIT = {
25
- client: 1, // 4xx de input/recurso
26
- unauthenticated: 2, // 401
27
- forbidden: 3, // 403
28
- server: 4, // 5xx / rede / desconhecido
29
- };
30
- function emit(json, payload, text) {
31
- if (json)
32
- console.log(JSON.stringify(payload));
33
- else
34
- log.error(text);
27
+ import { EXIT_CODES, NeetruCliError } from '../cli-kit/errors.js';
28
+ import { adviseCommand, adviseFlag } from '../cli-kit/command-context.js';
29
+ import { exitWithCliError } from './cli-error.js';
30
+ /**
31
+ * Reconhece o `SafeExitHalt` de `utils/safe-exit.ts` pela marca, sem importar
32
+ * `isSafeExitHalt`: dezenas de testes mockam `safe-exit.js` só com `safeExit`,
33
+ * e ler outra exportação de um mock incompleto lança no vitest.
34
+ */
35
+ function isSafeExitHalt(x) {
36
+ return (typeof x === 'object' &&
37
+ x !== null &&
38
+ x.__neetruSafeExit === true);
35
39
  }
40
+ /** Exit codes canônicos, exportados pra teste/documentação. */
41
+ export const API_ERROR_EXIT = EXIT_CODES;
36
42
  /**
37
43
  * A mensagem do 401 vale ser exibida? O Core legado mandava só
38
44
  * `{ error: 'Unauthorized' }` (o parser vira message='Unauthorized') — aí o
@@ -45,7 +51,30 @@ function isInformative401Message(message) {
45
51
  return false;
46
52
  return !/^(unauthori[sz]ed|erro http 401)$/i.test(m);
47
53
  }
48
- export function handleApiError(error, json) {
54
+ /**
55
+ * O que fazer diante de `401 mfa_required`. bug_757b9746 nasceu aqui: o texto
56
+ * mandava passar `--mfa-token` a qualquer comando, inclusive aos que não
57
+ * declaravam a flag. Agora a flag só é citada se o comando em execução a
58
+ * declara; senão, a alternativa que existe sempre — a sessão de 1h.
59
+ */
60
+ export function mfaRequiredAdvice() {
61
+ // P105: o comando alternativo também é validado — contra a árvore do
62
+ // Commander. Sem ele (ou sem árvore conhecida), a dica não o cita.
63
+ const sessao = (withCommand, withoutCommand) => adviseCommand({ command: 'auth mfa-session', withCommand, withoutCommand });
64
+ return adviseFlag({
65
+ flag: '--mfa-token',
66
+ withFlag: sessao(' Passe --mfa-token <código TOTP> ou rode `neetru auth mfa-session` (sessão de 1h).', ' Passe --mfa-token <código TOTP>.'),
67
+ withoutFlag: sessao(' Rode `neetru auth mfa-session` (sessão de 1h) e repita o comando.', ' Este comando exige step-up MFA e esta CLI não tem, nele, como enviá-lo.'),
68
+ });
69
+ }
70
+ /**
71
+ * Traduz qualquer erro para o `NeetruCliError` canônico — sem imprimir nem
72
+ * encerrar. Exportado para teste e para quem precise agregar erros de API
73
+ * como filhos de uma falha parcial.
74
+ */
75
+ export function cliErrorFromApiError(error) {
76
+ if (error instanceof NeetruCliError)
77
+ return error;
49
78
  if (error instanceof CliApiError) {
50
79
  const s = error.status;
51
80
  if (s === 401) {
@@ -54,48 +83,80 @@ export function handleApiError(error, json) {
54
83
  // Antes TODO 401 virava "Token inválido ou revogado" — inclusive o de MFA
55
84
  // faltando, mandando o operador fazer `neetru login` à toa.
56
85
  if (error.code === 'mfa_required') {
57
- emit(json, { ok: false, error: 'mfa_required', code: error.code, status: s, message: error.message }, `Step-up MFA exigido: ${error.message}
58
- ` +
59
- ' Passe --mfa-token <código TOTP> ou rode `neetru auth mfa-session` (sessão de 1h).');
60
- safeExit(API_ERROR_EXIT.unauthenticated);
86
+ return new NeetruCliError(error.message, {
87
+ code: 'mfa_required',
88
+ exit: EXIT_CODES.unauthenticated,
89
+ cause: error,
90
+ details: { code: error.code, status: s },
91
+ human: `Step-up MFA exigido: ${error.message}\n${mfaRequiredAdvice()}`,
92
+ });
61
93
  }
62
94
  const serverMsg = isInformative401Message(error.message) ? error.message : '';
63
- emit(json, {
64
- ok: false,
65
- error: 'unauthorized',
66
- ...(error.code && error.code !== 'http_401' ? { code: error.code } : {}),
67
- status: s,
68
- message: error.message,
69
- }, serverMsg
70
- ? `Não autenticado: ${serverMsg}${/neetru login/.test(serverMsg) ? '' : ' — execute: neetru login'}`
71
- : 'Token inválido ou revogado. Execute: neetru login');
72
- safeExit(API_ERROR_EXIT.unauthenticated);
95
+ return new NeetruCliError(error.message, {
96
+ code: 'unauthorized',
97
+ exit: EXIT_CODES.unauthenticated,
98
+ cause: error,
99
+ details: {
100
+ ...(error.code && error.code !== 'http_401' ? { code: error.code } : {}),
101
+ status: s,
102
+ },
103
+ human: serverMsg
104
+ ? `Não autenticado: ${serverMsg}${/neetru login/.test(serverMsg) ? '' : ' — execute: neetru login'}`
105
+ : 'Token inválido ou revogado. Execute: neetru login',
106
+ });
73
107
  }
74
108
  if (s === 403) {
75
- emit(json, { ok: false, error: 'forbidden', status: s, message: error.message }, `Acesso negado: ${error.message}`);
76
- safeExit(API_ERROR_EXIT.forbidden);
109
+ return new NeetruCliError(error.message, {
110
+ code: 'forbidden',
111
+ exit: EXIT_CODES.forbidden,
112
+ cause: error,
113
+ details: { status: s },
114
+ human: `Acesso negado: ${error.message}`,
115
+ });
77
116
  }
78
117
  if (s >= 400 && s < 500) {
79
- emit(json, {
80
- ok: false,
81
- error: s === 404 ? 'not_found' : (error.code ?? 'client_error'),
82
- status: s,
83
- message: error.message,
84
- }, s === 404
85
- ? error.message || 'Recurso não encontrado.'
86
- : `Requisição rejeitada (${s}): ${error.message}`);
87
- safeExit(API_ERROR_EXIT.client);
118
+ // Valores válidos de um enum recusado pelo Core (ex.: `environmentId
119
+ // inválido — use: dev-local | staging | prod.`) vêm no `message` do
120
+ // próprio Core, que é a fonte que validou: o texto passa inteiro, sem
121
+ // corte e sem lista copiada do lado de cá.
122
+ return new NeetruCliError(error.message, {
123
+ code: s === 404 ? 'not_found' : (error.code ?? 'client_error'),
124
+ exit: EXIT_CODES.client,
125
+ cause: error,
126
+ details: { status: s },
127
+ human: s === 404
128
+ ? error.message || 'Recurso não encontrado.'
129
+ : `Requisição rejeitada (${s}): ${error.message}`,
130
+ });
88
131
  }
89
132
  // 5xx (incl. 501 — não implementado).
90
- emit(json, { ok: false, error: error.code ?? 'server_error', status: s, message: error.message }, `Erro do servidor (${s}): ${error.message}`);
91
- safeExit(API_ERROR_EXIT.server);
133
+ return new NeetruCliError(error.message, {
134
+ code: error.code ?? 'server_error',
135
+ exit: EXIT_CODES.server,
136
+ cause: error,
137
+ details: { status: s },
138
+ human: `Erro do servidor (${s}): ${error.message}`,
139
+ });
92
140
  }
93
141
  if (error instanceof CliNetworkError) {
94
- emit(json, { ok: false, error: 'network', message: error.message }, `Falha de rede: ${error.message}`);
95
- safeExit(API_ERROR_EXIT.server);
142
+ return new NeetruCliError(error.message, {
143
+ code: 'network',
144
+ exit: EXIT_CODES.server,
145
+ cause: error,
146
+ human: `Falha de rede: ${error.message}`,
147
+ });
96
148
  }
97
149
  const message = error instanceof Error ? error.message : String(error);
98
- emit(json, { ok: false, error: 'unknown', message }, message);
99
- safeExit(API_ERROR_EXIT.server);
150
+ return new NeetruCliError(message, { code: 'unknown', exit: EXIT_CODES.server, cause: error });
151
+ }
152
+ export function handleApiError(error, json) {
153
+ // `safeExit` lança `SafeExitHalt` em produção para parar o caller. Dentro de
154
+ // `try { …; safeExit(n) } catch (e) { handleApiError(e) }` o sinal caía aqui
155
+ // e virava um segundo erro "[object Object]" — e, em `--json`, uma SEGUNDA
156
+ // linha de JSON no stdout. A saída já está agendada com o código certo:
157
+ // o sinal só segue adiante.
158
+ if (isSafeExitHalt(error))
159
+ throw error;
160
+ return exitWithCliError(cliErrorFromApiError(error), json);
100
161
  }
101
162
  //# sourceMappingURL=api-error.js.map
@@ -0,0 +1,127 @@
1
+ export type PackageManagerName = 'npm' | 'pnpm' | 'yarn' | 'bun';
2
+ export interface PackageManagerInfo {
3
+ name: PackageManagerName;
4
+ /**
5
+ * Diretório onde o `install` deve rodar. Em monorepo é a RAIZ do workspace
6
+ * (onde mora o lockfile), não o diretório do app — `pnpm install` dentro de
7
+ * `apps/web` sem o lockfile por perto instala um projeto solto e quebra os
8
+ * links dos pacotes internos do workspace.
9
+ */
10
+ rootDir: string;
11
+ /** De onde veio a decisão — entra no log pra o dev conseguir auditar. */
12
+ source: 'campo packageManager' | 'lockfile' | 'default';
13
+ /** Lockfile encontrado (basename), quando `source === 'lockfile'`. */
14
+ lockfile?: string;
15
+ }
16
+ /**
17
+ * `packageManager: "pnpm@9.1.0"` (padrão corepack) → `'pnpm'`.
18
+ * Qualquer coisa fora do enum conhecido devolve `null` em vez de chutar.
19
+ */
20
+ export declare function parseCampoPackageManager(valor: unknown): PackageManagerName | null;
21
+ /**
22
+ * Um diretório só conta como RAIZ DE WORKSPACE se ele se declara como tal.
23
+ *
24
+ * Isso é a trava contra o pior modo de falha da subida na árvore: um app em
25
+ * `~/projetos/meu-app` sem lockfile próprio NÃO pode herdar o
26
+ * `package-lock.json` que por acaso existe em `~/projetos` — instalar lá
27
+ * mexeria num projeto que não é o do build.
28
+ */
29
+ export declare function ehRaizDeWorkspace(dir: string): boolean;
30
+ /**
31
+ * Descobre QUAL gerenciador de pacote usar e ONDE rodar o `install`, partindo
32
+ * do diretório do app.
33
+ *
34
+ * Ordem de decisão (da mais forte pra mais fraca):
35
+ * 1. `packageManager` no package.json do próprio app (corepack) — é uma
36
+ * declaração explícita do dono do projeto;
37
+ * 2. lockfile NO PRÓPRIO diretório do app (repo de app único);
38
+ * 3. subindo a árvore: o primeiro ancestral que tem lockfile **e** se declara
39
+ * raiz de workspace (`pnpm-workspace.yaml` ou `workspaces` no package.json).
40
+ * O `packageManager` desse ancestral, se houver, vence o lockfile dele;
41
+ * 4. nada encontrado → `npm` no próprio diretório do app (comportamento
42
+ * histórico — não quebra ninguém que já usava o CLI).
43
+ *
44
+ * A subida PARA no diretório que contém `.git` (inclusive): fora do repo não
45
+ * existe workspace que interesse a este build.
46
+ */
47
+ export declare function detectPackageManager(appDir: string): PackageManagerInfo;
48
+ export interface Comando {
49
+ cmd: string;
50
+ args: string[];
51
+ }
52
+ /**
53
+ * `install` NÃO-DESTRUTIVO por gerenciador.
54
+ *
55
+ * bug_93f984a já tinha estabelecido a regra pro npm: nunca `npm ci` (apaga
56
+ * node_modules e briga com os file locks do `next dev` no Windows). A mesma
57
+ * regra vale pros outros — nada de `--frozen-lockfile` nem `--immutable`, que
58
+ * falham de cara quando o dev acabou de mexer num package.json do workspace.
59
+ */
60
+ export declare function comandoInstall(pm: PackageManagerName): Comando;
61
+ /** Roda um script do package.json (`build`, `start`, …). */
62
+ export declare function comandoRunScript(pm: PackageManagerName, script: string): Comando;
63
+ /**
64
+ * Executa um binário de `node_modules/.bin` (`next`, `vite`, `tsc`…).
65
+ *
66
+ * Usado só no caminho Windows do bug_0cf14fba/bug_893891f, onde o CLI desmonta
67
+ * o prefixo de env estilo Unix do script e roda o comando real direto. Com pnpm
68
+ * o `npx` NÃO serve: os binários ficam atrás do store de links do pnpm e o npx
69
+ * acabaria BAIXANDO o pacote do registry em vez de usar o local.
70
+ */
71
+ export declare function comandoExecBin(pm: PackageManagerName, bin: string, args: string[]): Comando;
72
+ export interface LayoutStandalone {
73
+ /** `<appDir>/.next/standalone` — sempre a RAIZ do tar. */
74
+ standaloneDir: string;
75
+ /**
76
+ * Caminho POSIX do app DENTRO do standalone. `''` em repo de app único;
77
+ * `'apps/web'` num monorepo com `outputFileTracingRoot` na raiz.
78
+ */
79
+ appPrefix: string;
80
+ /** Entrypoint relativo à raiz do tar: `server.js` ou `apps/web/server.js`. */
81
+ entrypoint: string;
82
+ }
83
+ /**
84
+ * Acha ONDE o `server.js` do Next.js ficou dentro de `.next/standalone`.
85
+ *
86
+ * Em repo de app único ele está na raiz; em monorepo o Next.js aninha o app sob
87
+ * o caminho dele relativo ao `outputFileTracingRoot` (`apps/web/server.js`).
88
+ * Não dá pra deduzir esse prefixo do `next.config` (o `outputFileTracingRoot`
89
+ * pode ser implícito, vindo da detecção de workspace do próprio Next.js), então
90
+ * a verdade é a árvore PÓS-BUILD — a mesma lógica do `isStandaloneBuildDir`.
91
+ *
92
+ * Varredura em largura, pulando `node_modules` (pacote dependente pode ter um
93
+ * `server.js` qualquer e envenenar a busca). Entre candidatos, ganha o que
94
+ * TAMBÉM tem um diretório `.next` ao lado — é o par que o Next.js sempre emite
95
+ * junto (`server.js` + `.next/` com os chunks de servidor). Empate real resolve
96
+ * pelo mais raso e, em seguida, por ordem alfabética (determinismo: dois builds
97
+ * da mesma árvore têm que dar o mesmo tarball).
98
+ *
99
+ * Devolve `null` quando não há standalone nenhum (build não-standalone) ou
100
+ * quando há standalone mas nenhum `server.js` — nesse segundo caso quem chama
101
+ * precisa FALHAR, nunca empacotar um tarball sem entrypoint.
102
+ */
103
+ export declare function resolveStandaloneLayout(appDir: string): LayoutStandalone | null;
104
+ /**
105
+ * Resolve o `-C` do `tar` que faz `.next/static` e `public` caírem DENTRO do
106
+ * prefixo do app no tarball.
107
+ *
108
+ * O Next.js não copia nenhum dos dois pro standalone (limitação conhecida), e o
109
+ * runtime os procura ao lado do `server.js` — ou seja, dentro do prefixo do app,
110
+ * não na raiz do tar. Era exatamente isso que saía errado em monorepo
111
+ * (bug_678ddc98): o tar levava `./.next/static` e `./public` na raiz enquanto o
112
+ * server rodava de `apps/web/`, e o app subia sem CSS/JS e sem os estáticos.
113
+ *
114
+ * O truque pra gravar o membro já aninhado SEM `--transform` (GNU) nem `-s`
115
+ * (bsdtar), que não são portáveis entre os dois tars: o `appPrefix` é, por
116
+ * construção, `path.relative(outputFileTracingRoot, appDir)` — então o mesmo
117
+ * caminho EXISTE no disco a partir da raiz de tracing. Basta dar `-C <raiz de
118
+ * tracing>` e pedir o membro `apps/web/.next/static`, que o tar grava com o
119
+ * caminho completo.
120
+ *
121
+ * Devolve `null` quando o prefixo não casa com o disco (não deveria acontecer —
122
+ * é a trava contra empacotar caminho inventado).
123
+ */
124
+ export declare function baseDeInjecaoNoTar(appDir: string, appPrefix: string): {
125
+ chdir: string;
126
+ prefixo: string;
127
+ } | null;
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Detecção de ALVO de build — o diretório do app, o gerenciador de pacote e o
3
+ * layout do output `standalone` do Next.js.
4
+ *
5
+ * bug_678ddc988497475d9160d714a5a10992: o `neetru build` nasceu assumindo
6
+ * "um repo = um app na raiz = npm". Três premissas, todas cravadas:
7
+ *
8
+ * 1. `cwd = process.cwd()` (build.ts:905) — não dava pra apontar pra um app
9
+ * dentro de `apps/<app>` sem `cd`;
10
+ * 2. `npm install` (build.ts:616) e `npm run build` (build.ts:664) literais —
11
+ * num repo pnpm/yarn isso resolve o workspace ERRADO (ou nem resolve);
12
+ * 3. `tarStandalone` assumia `.next/standalone/server.js` no TOPO e injetava
13
+ * `.next/static`/`public` na RAIZ do tar (build.ts:412-443), e o
14
+ * entrypoint era a string fixa `'server.js'` (build.ts:673).
15
+ *
16
+ * Num monorepo (pnpm workspaces + Turborepo) o Next.js, ao tracejar a partir
17
+ * de `outputFileTracingRoot`, ANINHA o app dentro do standalone:
18
+ *
19
+ * apps/web/.next/standalone/
20
+ * node_modules/… ← fica na RAIZ do standalone (tracejado/hoisted)
21
+ * packages/ui/… ← pacotes do workspace que o app importa
22
+ * apps/web/server.js ← o entrypoint REAL, aninhado
23
+ * apps/web/.next/…
24
+ *
25
+ * Por isso NÃO dá pra "re-enraizar" o tar em `apps/web`: `server.js` resolve
26
+ * `node_modules` subindo a árvore, e esses `node_modules` moram ACIMA dele.
27
+ * O formato correto é preservar a árvore inteira do standalone e apontar o
28
+ * entrypoint pro caminho aninhado (`apps/web/server.js`), injetando
29
+ * `.next/static` e `public` DENTRO do prefixo do app.
30
+ *
31
+ * Este módulo é propositalmente puro (só `fs` de leitura, zero subprocesso)
32
+ * pra ser testável sem rodar build nenhum.
33
+ */
34
+ import * as fs from 'node:fs';
35
+ import * as path from 'node:path';
36
+ /**
37
+ * Lockfiles conhecidos, em ordem de PRECEDÊNCIA dentro de um mesmo diretório.
38
+ *
39
+ * pnpm/yarn/bun vêm antes de npm de propósito: `package-lock.json` é o que mais
40
+ * aparece por acidente (um `npm install` distraído num repo pnpm gera um), então
41
+ * ele é o sinal MENOS confiável quando há concorrência. O campo `packageManager`
42
+ * do package.json (padrão corepack) ganha de todos — ver `detectPackageManager`.
43
+ */
44
+ const LOCKFILES = [
45
+ { file: 'pnpm-lock.yaml', name: 'pnpm' },
46
+ { file: 'yarn.lock', name: 'yarn' },
47
+ { file: 'bun.lockb', name: 'bun' },
48
+ { file: 'bun.lock', name: 'bun' },
49
+ { file: 'package-lock.json', name: 'npm' },
50
+ { file: 'npm-shrinkwrap.json', name: 'npm' },
51
+ ];
52
+ /** Teto de subida na árvore — monorepo nenhum aninha app a 12 níveis da raiz. */
53
+ const MAX_SUBIDA = 12;
54
+ function existe(p) {
55
+ try {
56
+ fs.statSync(p);
57
+ return true;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ function lerJson(p) {
64
+ try {
65
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ /**
72
+ * `packageManager: "pnpm@9.1.0"` (padrão corepack) → `'pnpm'`.
73
+ * Qualquer coisa fora do enum conhecido devolve `null` em vez de chutar.
74
+ */
75
+ export function parseCampoPackageManager(valor) {
76
+ if (typeof valor !== 'string')
77
+ return null;
78
+ const nome = valor.trim().split('@')[0].toLowerCase();
79
+ if (nome === 'npm' || nome === 'pnpm' || nome === 'yarn' || nome === 'bun')
80
+ return nome;
81
+ return null;
82
+ }
83
+ /**
84
+ * Um diretório só conta como RAIZ DE WORKSPACE se ele se declara como tal.
85
+ *
86
+ * Isso é a trava contra o pior modo de falha da subida na árvore: um app em
87
+ * `~/projetos/meu-app` sem lockfile próprio NÃO pode herdar o
88
+ * `package-lock.json` que por acaso existe em `~/projetos` — instalar lá
89
+ * mexeria num projeto que não é o do build.
90
+ */
91
+ export function ehRaizDeWorkspace(dir) {
92
+ if (existe(path.join(dir, 'pnpm-workspace.yaml')))
93
+ return true;
94
+ const pkg = lerJson(path.join(dir, 'package.json'));
95
+ if (!pkg)
96
+ return false;
97
+ const ws = pkg.workspaces;
98
+ // npm/yarn/bun aceitam `workspaces: []` OU `workspaces: { packages: [] }`.
99
+ if (Array.isArray(ws))
100
+ return true;
101
+ if (ws && typeof ws === 'object' && Array.isArray(ws.packages))
102
+ return true;
103
+ return false;
104
+ }
105
+ function lockfileDoDir(dir) {
106
+ for (const lf of LOCKFILES) {
107
+ if (existe(path.join(dir, lf.file)))
108
+ return lf;
109
+ }
110
+ return null;
111
+ }
112
+ /**
113
+ * Descobre QUAL gerenciador de pacote usar e ONDE rodar o `install`, partindo
114
+ * do diretório do app.
115
+ *
116
+ * Ordem de decisão (da mais forte pra mais fraca):
117
+ * 1. `packageManager` no package.json do próprio app (corepack) — é uma
118
+ * declaração explícita do dono do projeto;
119
+ * 2. lockfile NO PRÓPRIO diretório do app (repo de app único);
120
+ * 3. subindo a árvore: o primeiro ancestral que tem lockfile **e** se declara
121
+ * raiz de workspace (`pnpm-workspace.yaml` ou `workspaces` no package.json).
122
+ * O `packageManager` desse ancestral, se houver, vence o lockfile dele;
123
+ * 4. nada encontrado → `npm` no próprio diretório do app (comportamento
124
+ * histórico — não quebra ninguém que já usava o CLI).
125
+ *
126
+ * A subida PARA no diretório que contém `.git` (inclusive): fora do repo não
127
+ * existe workspace que interesse a este build.
128
+ */
129
+ export function detectPackageManager(appDir) {
130
+ const campoLocal = parseCampoPackageManager(lerJson(path.join(appDir, 'package.json'))?.packageManager);
131
+ const lockLocal = lockfileDoDir(appDir);
132
+ // O campo local é a declaração mais explícita que existe — mas o `rootDir`
133
+ // ainda pode ser um ancestral (app de monorepo que declara `packageManager`
134
+ // e deixa o lockfile na raiz). Por isso o rootDir é resolvido à parte.
135
+ if (lockLocal) {
136
+ return {
137
+ name: campoLocal ?? lockLocal.name,
138
+ rootDir: appDir,
139
+ source: campoLocal ? 'campo packageManager' : 'lockfile',
140
+ lockfile: campoLocal ? undefined : lockLocal.file,
141
+ };
142
+ }
143
+ let dir = appDir;
144
+ for (let i = 0; i < MAX_SUBIDA; i++) {
145
+ const temGit = existe(path.join(dir, '.git'));
146
+ const pai = path.dirname(dir);
147
+ if (temGit || pai === dir)
148
+ break;
149
+ dir = pai;
150
+ const lock = lockfileDoDir(dir);
151
+ if (!lock) {
152
+ if (existe(path.join(dir, '.git')))
153
+ break;
154
+ continue;
155
+ }
156
+ // Lockfile de ancestral só vale se o ancestral É raiz de workspace — ver
157
+ // `ehRaizDeWorkspace` pro porquê (não sequestrar projeto vizinho).
158
+ if (!ehRaizDeWorkspace(dir))
159
+ break;
160
+ const campoRaiz = parseCampoPackageManager(lerJson(path.join(dir, 'package.json'))?.packageManager);
161
+ return {
162
+ name: campoLocal ?? campoRaiz ?? lock.name,
163
+ rootDir: dir,
164
+ source: campoLocal || campoRaiz ? 'campo packageManager' : 'lockfile',
165
+ lockfile: campoLocal || campoRaiz ? undefined : lock.file,
166
+ };
167
+ }
168
+ return {
169
+ name: campoLocal ?? 'npm',
170
+ rootDir: appDir,
171
+ source: campoLocal ? 'campo packageManager' : 'default',
172
+ };
173
+ }
174
+ /**
175
+ * `install` NÃO-DESTRUTIVO por gerenciador.
176
+ *
177
+ * bug_93f984a já tinha estabelecido a regra pro npm: nunca `npm ci` (apaga
178
+ * node_modules e briga com os file locks do `next dev` no Windows). A mesma
179
+ * regra vale pros outros — nada de `--frozen-lockfile` nem `--immutable`, que
180
+ * falham de cara quando o dev acabou de mexer num package.json do workspace.
181
+ */
182
+ export function comandoInstall(pm) {
183
+ switch (pm) {
184
+ case 'pnpm':
185
+ return { cmd: 'pnpm', args: ['install', '--no-frozen-lockfile'] };
186
+ case 'yarn':
187
+ // Serve pro yarn classic (1.x) e pro berry — ambos aceitam `install` puro.
188
+ return { cmd: 'yarn', args: ['install'] };
189
+ case 'bun':
190
+ return { cmd: 'bun', args: ['install'] };
191
+ case 'npm':
192
+ default:
193
+ return { cmd: 'npm', args: ['install', '--no-save', '--no-audit', '--no-fund'] };
194
+ }
195
+ }
196
+ /** Roda um script do package.json (`build`, `start`, …). */
197
+ export function comandoRunScript(pm, script) {
198
+ switch (pm) {
199
+ case 'pnpm':
200
+ return { cmd: 'pnpm', args: ['run', script] };
201
+ case 'yarn':
202
+ return { cmd: 'yarn', args: ['run', script] };
203
+ case 'bun':
204
+ return { cmd: 'bun', args: ['run', script] };
205
+ case 'npm':
206
+ default:
207
+ return { cmd: 'npm', args: ['run', script] };
208
+ }
209
+ }
210
+ /**
211
+ * Executa um binário de `node_modules/.bin` (`next`, `vite`, `tsc`…).
212
+ *
213
+ * Usado só no caminho Windows do bug_0cf14fba/bug_893891f, onde o CLI desmonta
214
+ * o prefixo de env estilo Unix do script e roda o comando real direto. Com pnpm
215
+ * o `npx` NÃO serve: os binários ficam atrás do store de links do pnpm e o npx
216
+ * acabaria BAIXANDO o pacote do registry em vez de usar o local.
217
+ */
218
+ export function comandoExecBin(pm, bin, args) {
219
+ switch (pm) {
220
+ case 'pnpm':
221
+ return { cmd: 'pnpm', args: ['exec', bin, ...args] };
222
+ case 'yarn':
223
+ return { cmd: 'yarn', args: ['exec', bin, ...args] };
224
+ case 'bun':
225
+ return { cmd: 'bunx', args: [bin, ...args] };
226
+ case 'npm':
227
+ default:
228
+ return { cmd: 'npx', args: [bin, ...args] };
229
+ }
230
+ }
231
+ /** Profundidade máxima de varredura dentro do standalone à procura do server.js. */
232
+ const MAX_PROFUNDIDADE_STANDALONE = 6;
233
+ /**
234
+ * Acha ONDE o `server.js` do Next.js ficou dentro de `.next/standalone`.
235
+ *
236
+ * Em repo de app único ele está na raiz; em monorepo o Next.js aninha o app sob
237
+ * o caminho dele relativo ao `outputFileTracingRoot` (`apps/web/server.js`).
238
+ * Não dá pra deduzir esse prefixo do `next.config` (o `outputFileTracingRoot`
239
+ * pode ser implícito, vindo da detecção de workspace do próprio Next.js), então
240
+ * a verdade é a árvore PÓS-BUILD — a mesma lógica do `isStandaloneBuildDir`.
241
+ *
242
+ * Varredura em largura, pulando `node_modules` (pacote dependente pode ter um
243
+ * `server.js` qualquer e envenenar a busca). Entre candidatos, ganha o que
244
+ * TAMBÉM tem um diretório `.next` ao lado — é o par que o Next.js sempre emite
245
+ * junto (`server.js` + `.next/` com os chunks de servidor). Empate real resolve
246
+ * pelo mais raso e, em seguida, por ordem alfabética (determinismo: dois builds
247
+ * da mesma árvore têm que dar o mesmo tarball).
248
+ *
249
+ * Devolve `null` quando não há standalone nenhum (build não-standalone) ou
250
+ * quando há standalone mas nenhum `server.js` — nesse segundo caso quem chama
251
+ * precisa FALHAR, nunca empacotar um tarball sem entrypoint.
252
+ */
253
+ export function resolveStandaloneLayout(appDir) {
254
+ const standaloneDir = path.join(appDir, '.next', 'standalone');
255
+ try {
256
+ if (!fs.statSync(standaloneDir).isDirectory())
257
+ return null;
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ const candidatos = [];
263
+ const fila = [
264
+ { dir: standaloneDir, prefix: '', profundidade: 0 },
265
+ ];
266
+ while (fila.length > 0) {
267
+ const atual = fila.shift();
268
+ let entradas;
269
+ try {
270
+ entradas = fs.readdirSync(atual.dir, { withFileTypes: true });
271
+ }
272
+ catch {
273
+ continue;
274
+ }
275
+ const temServer = entradas.some((e) => e.name === 'server.js' && e.isFile());
276
+ if (temServer) {
277
+ candidatos.push({
278
+ prefix: atual.prefix,
279
+ temNext: entradas.some((e) => e.name === '.next' && e.isDirectory()),
280
+ profundidade: atual.profundidade,
281
+ });
282
+ // Não desce mais por este ramo: achado o app, o que houver abaixo é
283
+ // output dele, não outro app.
284
+ continue;
285
+ }
286
+ if (atual.profundidade >= MAX_PROFUNDIDADE_STANDALONE)
287
+ continue;
288
+ for (const e of entradas) {
289
+ if (!e.isDirectory())
290
+ continue;
291
+ // `node_modules` nunca hospeda o app; `.next` abaixo da raiz é output.
292
+ if (e.name === 'node_modules' || e.name === '.next')
293
+ continue;
294
+ fila.push({
295
+ dir: path.join(atual.dir, e.name),
296
+ prefix: atual.prefix ? `${atual.prefix}/${e.name}` : e.name,
297
+ profundidade: atual.profundidade + 1,
298
+ });
299
+ }
300
+ }
301
+ if (candidatos.length === 0)
302
+ return null;
303
+ candidatos.sort((a, b) => {
304
+ if (a.temNext !== b.temNext)
305
+ return a.temNext ? -1 : 1;
306
+ if (a.profundidade !== b.profundidade)
307
+ return a.profundidade - b.profundidade;
308
+ return a.prefix.localeCompare(b.prefix);
309
+ });
310
+ const vencedor = candidatos[0];
311
+ return {
312
+ standaloneDir,
313
+ appPrefix: vencedor.prefix,
314
+ entrypoint: vencedor.prefix ? `${vencedor.prefix}/server.js` : 'server.js',
315
+ };
316
+ }
317
+ /**
318
+ * Resolve o `-C` do `tar` que faz `.next/static` e `public` caírem DENTRO do
319
+ * prefixo do app no tarball.
320
+ *
321
+ * O Next.js não copia nenhum dos dois pro standalone (limitação conhecida), e o
322
+ * runtime os procura ao lado do `server.js` — ou seja, dentro do prefixo do app,
323
+ * não na raiz do tar. Era exatamente isso que saía errado em monorepo
324
+ * (bug_678ddc98): o tar levava `./.next/static` e `./public` na raiz enquanto o
325
+ * server rodava de `apps/web/`, e o app subia sem CSS/JS e sem os estáticos.
326
+ *
327
+ * O truque pra gravar o membro já aninhado SEM `--transform` (GNU) nem `-s`
328
+ * (bsdtar), que não são portáveis entre os dois tars: o `appPrefix` é, por
329
+ * construção, `path.relative(outputFileTracingRoot, appDir)` — então o mesmo
330
+ * caminho EXISTE no disco a partir da raiz de tracing. Basta dar `-C <raiz de
331
+ * tracing>` e pedir o membro `apps/web/.next/static`, que o tar grava com o
332
+ * caminho completo.
333
+ *
334
+ * Devolve `null` quando o prefixo não casa com o disco (não deveria acontecer —
335
+ * é a trava contra empacotar caminho inventado).
336
+ */
337
+ export function baseDeInjecaoNoTar(appDir, appPrefix) {
338
+ if (!appPrefix)
339
+ return { chdir: appDir, prefixo: '' };
340
+ const niveis = appPrefix.split('/').filter(Boolean);
341
+ const chdir = path.resolve(appDir, ...niveis.map(() => '..'));
342
+ // A igualdade é a prova de que `<chdir>/<appPrefix>` é o MESMO diretório que
343
+ // `appDir` — sem isso o tar gravaria um caminho que não existe.
344
+ if (path.resolve(chdir, ...niveis) !== path.resolve(appDir))
345
+ return null;
346
+ return { chdir, prefixo: `${appPrefix}/` };
347
+ }
348
+ //# sourceMappingURL=build-targets.js.map