@open-mercato/cli 0.7.0 → 0.7.1-develop.7103.1.41ff100d93

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 (184) hide show
  1. package/.turbo/turbo-build.log +7 -1
  2. package/dist/agentic/guides/module-facts.json +5509 -599
  3. package/dist/agentic/guides/module-facts.v2.json +5595 -599
  4. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  5. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  6. package/dist/agentic/guides/modules/api_keys/di-registrations-rich.md +11 -0
  7. package/dist/agentic/guides/modules/api_keys/di-service-tokens.md +11 -0
  8. package/dist/agentic/guides/modules/api_keys/exact-override-targets.md +1 -0
  9. package/dist/agentic/guides/modules/api_keys/index.md +4 -2
  10. package/dist/agentic/guides/modules/attachments/di-registrations-rich.md +6 -5
  11. package/dist/agentic/guides/modules/attachments/di-service-tokens.md +5 -4
  12. package/dist/agentic/guides/modules/attachments/exact-override-targets.md +6 -5
  13. package/dist/agentic/guides/modules/attachments/index.md +2 -2
  14. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  15. package/dist/agentic/guides/modules/auth/di-registrations-rich.md +3 -2
  16. package/dist/agentic/guides/modules/auth/di-service-tokens.md +3 -2
  17. package/dist/agentic/guides/modules/auth/exact-override-targets.md +3 -2
  18. package/dist/agentic/guides/modules/auth/index.md +1 -1
  19. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  20. package/dist/agentic/guides/modules/catalog/incoming-installed-contributions.md +3 -0
  21. package/dist/agentic/guides/modules/catalog/index.md +2 -2
  22. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  23. package/dist/agentic/guides/modules/channel_discord/acl-features.md +13 -0
  24. package/dist/agentic/guides/modules/channel_discord/ai-agents.md +11 -0
  25. package/dist/agentic/guides/modules/channel_discord/backend-pages.md +11 -0
  26. package/dist/agentic/guides/modules/channel_discord/cli-commands.md +13 -0
  27. package/dist/agentic/guides/modules/channel_discord/contribution-resolutions.md +14 -0
  28. package/dist/agentic/guides/modules/channel_discord/di-registrations-rich.md +12 -0
  29. package/dist/agentic/guides/modules/channel_discord/exact-override-targets.md +26 -0
  30. package/dist/agentic/guides/modules/channel_discord/index.md +22 -0
  31. package/dist/agentic/guides/modules/channel_discord/owned-contract-module-metadata.md +11 -0
  32. package/dist/agentic/guides/modules/channel_discord/setup.md +11 -0
  33. package/dist/agentic/guides/modules/channel_discord/umes-contributions.md +14 -0
  34. package/dist/agentic/guides/modules/channel_discord/workers.md +12 -0
  35. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  36. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  37. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  38. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  39. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  40. package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +9 -8
  41. package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +9 -8
  42. package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +2 -0
  43. package/dist/agentic/guides/modules/communication_channels/index.md +3 -3
  44. package/dist/agentic/guides/modules/configs/index.md +1 -1
  45. package/dist/agentic/guides/modules/content/index.md +1 -1
  46. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  47. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  48. package/dist/agentic/guides/modules/customers/active-extension-bindings.md +6 -3
  49. package/dist/agentic/guides/modules/customers/contribution-resolutions.md +1 -0
  50. package/dist/agentic/guides/modules/customers/events.md +2 -1
  51. package/dist/agentic/guides/modules/customers/incoming-installed-contributions.md +7 -0
  52. package/dist/agentic/guides/modules/customers/index.md +4 -4
  53. package/dist/agentic/guides/modules/customers/umes-contributions.md +1 -0
  54. package/dist/agentic/guides/modules/customers/umes-hosts.md +1 -0
  55. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  56. package/dist/agentic/guides/modules/data_sync/exact-override-targets.md +2 -2
  57. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  58. package/dist/agentic/guides/modules/data_sync/workers.md +2 -2
  59. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  60. package/dist/agentic/guides/modules/devices/index.md +1 -1
  61. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  62. package/dist/agentic/guides/modules/directory/di-registrations-rich.md +12 -0
  63. package/dist/agentic/guides/modules/directory/di-service-tokens.md +12 -0
  64. package/dist/agentic/guides/modules/directory/exact-override-targets.md +2 -0
  65. package/dist/agentic/guides/modules/directory/index.md +5 -3
  66. package/dist/agentic/guides/modules/documents/acl-features.md +17 -0
  67. package/dist/agentic/guides/modules/documents/backend-pages.md +13 -0
  68. package/dist/agentic/guides/modules/documents/contribution-resolutions.md +27 -0
  69. package/dist/agentic/guides/modules/documents/di-registrations-rich.md +21 -0
  70. package/dist/agentic/guides/modules/documents/domain-commands.md +37 -0
  71. package/dist/agentic/guides/modules/documents/encryption.md +11 -0
  72. package/dist/agentic/guides/modules/documents/entities.md +21 -0
  73. package/dist/agentic/guides/modules/documents/events.md +25 -0
  74. package/dist/agentic/guides/modules/documents/exact-override-targets.md +39 -0
  75. package/dist/agentic/guides/modules/documents/index.md +25 -0
  76. package/dist/agentic/guides/modules/documents/notifications.md +13 -0
  77. package/dist/agentic/guides/modules/documents/owned-contract-module-metadata.md +11 -0
  78. package/dist/agentic/guides/modules/documents/setup.md +11 -0
  79. package/dist/agentic/guides/modules/documents/umes-contributions.md +27 -0
  80. package/dist/agentic/guides/modules/documents/umes-hosts.md +36 -0
  81. package/dist/agentic/guides/modules/entities/index.md +1 -1
  82. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  83. package/dist/agentic/guides/modules/events/index.md +1 -1
  84. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  85. package/dist/agentic/guides/modules/gateway_stripe/exact-override-targets.md +1 -1
  86. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  87. package/dist/agentic/guides/modules/gateway_stripe/workers.md +1 -1
  88. package/dist/agentic/guides/modules/generators/index.md +1 -1
  89. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  90. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  91. package/dist/agentic/guides/modules/messages/index.md +1 -1
  92. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  93. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  94. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  95. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  96. package/dist/agentic/guides/modules/planner/index.md +1 -1
  97. package/dist/agentic/guides/modules/portal/index.md +1 -1
  98. package/dist/agentic/guides/modules/progress/index.md +1 -1
  99. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  100. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  101. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  102. package/dist/agentic/guides/modules/resources/index.md +1 -1
  103. package/dist/agentic/guides/modules/sales/active-extension-bindings.md +1 -0
  104. package/dist/agentic/guides/modules/sales/contribution-resolutions.md +1 -0
  105. package/dist/agentic/guides/modules/sales/exact-override-targets.md +5 -4
  106. package/dist/agentic/guides/modules/sales/host-extension-points.md +1 -1
  107. package/dist/agentic/guides/modules/sales/incoming-installed-contributions.md +2 -0
  108. package/dist/agentic/guides/modules/sales/index.md +3 -3
  109. package/dist/agentic/guides/modules/sales/setup.md +1 -1
  110. package/dist/agentic/guides/modules/sales/umes-contributions.md +1 -0
  111. package/dist/agentic/guides/modules/sales/umes-diagnostics.md +1 -0
  112. package/dist/agentic/guides/modules/sales/umes-hosts.md +9 -0
  113. package/dist/agentic/guides/modules/scheduler/cli-commands.md +1 -0
  114. package/dist/agentic/guides/modules/scheduler/exact-override-targets.md +3 -1
  115. package/dist/agentic/guides/modules/scheduler/index.md +2 -2
  116. package/dist/agentic/guides/modules/scheduler/workers.md +2 -1
  117. package/dist/agentic/guides/modules/search/index.md +1 -1
  118. package/dist/agentic/guides/modules/security/index.md +1 -1
  119. package/dist/agentic/guides/modules/security/owned-contract-module-metadata.md +1 -1
  120. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  121. package/dist/agentic/guides/modules/sso/index.md +1 -1
  122. package/dist/agentic/guides/modules/staff/index.md +1 -1
  123. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  124. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  125. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  126. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  127. package/dist/agentic/guides/modules/translations/index.md +1 -1
  128. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  129. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  130. package/dist/agentic/guides/modules/wms/index.md +1 -1
  131. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  132. package/dist/agentic/guides/reference-module-facts.json +2 -2
  133. package/dist/agentic/guides/reference-modules/example/index.md +1 -1
  134. package/dist/agentic/guides/upstream/manifest.json +1 -1
  135. package/dist/agentic/shared/AGENTS.md.template +3 -3
  136. package/dist/agentic/shared/ai/harness/README.md +2 -2
  137. package/dist/agentic/shared/ai/harness/RELEASE.md +8 -6
  138. package/dist/agentic/shared/ai/harness/cases.json +83 -0
  139. package/dist/agentic/shared/ai/harness/cases.schema.json +4 -4
  140. package/dist/agentic/shared/ai/harness/validators.json +1 -1
  141. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +37 -11
  142. package/dist/lib/generators/ast/index.js +4 -1
  143. package/dist/lib/generators/ast/index.js.map +2 -2
  144. package/dist/lib/generators/ast/source-file.js +8 -1
  145. package/dist/lib/generators/ast/source-file.js.map +2 -2
  146. package/dist/lib/generators/ast/writers.js +9 -1
  147. package/dist/lib/generators/ast/writers.js.map +2 -2
  148. package/dist/lib/generators/module-registry.js +424 -189
  149. package/dist/lib/generators/module-registry.js.map +2 -2
  150. package/dist/lib/generators/route-manifest-shards.js +4 -1
  151. package/dist/lib/generators/route-manifest-shards.js.map +2 -2
  152. package/dist/lib/spawn.js +3 -2
  153. package/dist/lib/spawn.js.map +2 -2
  154. package/dist/lib/testing/integration.js +162 -24
  155. package/dist/lib/testing/integration.js.map +2 -2
  156. package/dist/mercato.js +14 -0
  157. package/dist/mercato.js.map +2 -2
  158. package/package.json +8 -7
  159. package/src/__tests__/resolve-queue-abandon-hook.test.ts +39 -0
  160. package/src/lib/__tests__/spawn.test.ts +23 -0
  161. package/src/lib/generators/__tests__/__snapshots__/bootstrap-registrations.test.ts.snap +14 -0
  162. package/src/lib/generators/__tests__/__snapshots__/command-loaders.test.ts.snap +23 -0
  163. package/src/lib/generators/__tests__/ast-writers-escaping.test.ts +64 -0
  164. package/src/lib/generators/__tests__/bootstrap-registrations.test.ts +176 -0
  165. package/src/lib/generators/__tests__/command-loaders.test.ts +239 -0
  166. package/src/lib/generators/__tests__/generated-registry-imports.test.ts +128 -0
  167. package/src/lib/generators/__tests__/metadata-extraction.test.ts +44 -0
  168. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +32 -8
  169. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +4 -1
  170. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +1 -1
  171. package/src/lib/generators/__tests__/module-subset.test.ts +96 -72
  172. package/src/lib/generators/__tests__/output-snapshots.test.ts +1 -1
  173. package/src/lib/generators/__tests__/registry-variant-parity.test.ts +258 -0
  174. package/src/lib/generators/__tests__/route-manifest-shards.test.ts +15 -0
  175. package/src/lib/generators/__tests__/structural-contracts.test.ts +7 -3
  176. package/src/lib/generators/ast/index.ts +2 -1
  177. package/src/lib/generators/ast/source-file.ts +13 -0
  178. package/src/lib/generators/ast/writers.ts +26 -1
  179. package/src/lib/generators/module-registry.ts +534 -227
  180. package/src/lib/generators/route-manifest-shards.ts +8 -1
  181. package/src/lib/spawn.ts +5 -4
  182. package/src/lib/testing/__tests__/integration.test.ts +435 -0
  183. package/src/lib/testing/integration.ts +254 -32
  184. package/src/mercato.ts +27 -0
@@ -15,10 +15,13 @@ function getRouteManifestShardKey(kind, pathname) {
15
15
  }
16
16
  return firstSegment.toLowerCase();
17
17
  }
18
+ function indentDeclaration(declaration) {
19
+ return declaration.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
20
+ }
18
21
  function renderArray(entries) {
19
22
  if (entries.length === 0) return "[]";
20
23
  return `[
21
- ${entries.map((entry) => ` ${entry.declaration}`).join(",\n")}
24
+ ${entries.map((entry) => indentDeclaration(entry.declaration)).join(",\n")}
22
25
  ]`;
23
26
  }
24
27
  function uniqueImports(entries) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/generators/route-manifest-shards.ts"],
4
- "sourcesContent": ["export type RouteManifestShardKind = 'api' | 'backend'\n\nexport type RouteManifestShardEntry = {\n path: string\n declaration: string\n imports?: string[]\n}\n\nexport type RouteManifestShardOutput = {\n fileName: string\n content: string\n}\n\ntype IndexedRouteManifestShardEntry = RouteManifestShardEntry & {\n order: number\n}\n\nconst DYNAMIC_SHARD_KEY = '__dynamic'\nconst ROOT_SHARD_KEY = '__root'\n\nfunction normalizeRoutePath(kind: RouteManifestShardKind, pathname: string): string[] {\n const segments = pathname.split('?')[0].split('/').filter(Boolean)\n if (kind === 'backend' && segments[0]?.toLowerCase() === 'backend') {\n segments.shift()\n }\n return segments\n}\n\nexport function getRouteManifestShardKey(kind: RouteManifestShardKind, pathname: string): string {\n const firstSegment = normalizeRoutePath(kind, pathname)[0]\n if (!firstSegment) return ROOT_SHARD_KEY\n if (\n firstSegment.startsWith('[')\n || firstSegment.startsWith(':')\n || firstSegment === '*'\n ) {\n return DYNAMIC_SHARD_KEY\n }\n return firstSegment.toLowerCase()\n}\n\nfunction renderArray(entries: RouteManifestShardEntry[]): string {\n if (entries.length === 0) return '[]'\n return `[\\n${entries.map((entry) => ` ${entry.declaration}`).join(',\\n')}\\n]`\n}\n\nfunction uniqueImports(entries: RouteManifestShardEntry[]): string[] {\n return Array.from(new Set(entries.flatMap((entry) => entry.imports ?? [])))\n}\n\nfunction shardStem(kind: RouteManifestShardKind, index: number, key: string): string {\n const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, '_')\n return `${kind}-route-shard.${String(index).padStart(3, '0')}.${safeKey}.generated`\n}\n\nfunction renderShardFile(kind: RouteManifestShardKind, entries: IndexedRouteManifestShardEntry[]): string {\n const typeName = kind === 'api' ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'\n const helperImport = kind === 'backend' ? 'resolvePageRouteMetadata, ' : ''\n const imports = uniqueImports(entries)\n return `// AUTO-GENERATED by mercato generate registry (request-scoped route shard)\\nimport { ${helperImport}type ${typeName} } from '@open-mercato/shared/modules/registry'\\n${imports.length ? `${imports.join('\\n')}\\n` : ''}\\nexport const routes: ${typeName}[] = ${renderArray(entries)}\\nexport const routeOrders = [${entries.map((entry) => entry.order).join(', ')}]\\n\\nexport default routes\\n`\n}\n\nfunction renderLoaderFile(\n kind: RouteManifestShardKind,\n shards: Array<{ key: string; stem: string; orders: number[] }>,\n): string {\n const isApi = kind === 'api'\n const typeName = kind === 'api' ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'\n const functionName = kind === 'api' ? 'loadApiRouteManifestShard' : 'loadBackendRouteManifestShard'\n const ordinalFunctionName = isApi ? 'loadApiRouteManifestByOrdinal' : 'loadBackendRouteManifestByOrdinal'\n const facadeExportName = isApi ? 'apiRouteFacades' : 'backendRouteFacades'\n const metadataExportName = isApi ? 'apiRouteMetadata' : 'backendRouteMetadata'\n const metadataFileName = isApi ? 'api-route-metadata.generated' : 'backend-route-metadata.generated'\n const shardCases = shards\n .map(({ key, stem }) => ` case ${JSON.stringify(key)}:\\n return import('./${stem}').then((entry) => ({ routes: entry.routes, routeOrders: entry.routeOrders }))`)\n .join('\\n')\n const hasDynamicShard = shards.some(({ key }) => key === DYNAMIC_SHARD_KEY)\n const routeShardKeys: string[] = []\n for (const shard of shards) {\n for (const order of shard.orders) routeShardKeys[order] = shard.key\n }\n const prefixNormalization = kind === 'backend'\n ? ` if (segments[0]?.toLowerCase() === 'backend') segments.shift()\\n`\n : ''\n const loadDynamic = hasDynamicShard\n ? ` const dynamicShard = await loadShard(${JSON.stringify(DYNAMIC_SHARD_KEY)})\\n`\n : ` const dynamicShard: LoadedRouteShard = EMPTY_SHARD\\n`\n\n return `// AUTO-GENERATED by mercato generate registry (request-scoped route loader)\\nimport type { ${typeName} } from '@open-mercato/shared/modules/registry'\\nimport { ${metadataExportName} } from './${metadataFileName}'\\n\\ntype LoadedRouteShard = { routes: ${typeName}[]; routeOrders: number[] }\\nconst EMPTY_SHARD: LoadedRouteShard = { routes: [], routeOrders: [] }\\nconst ROUTE_SHARD_KEYS = ${JSON.stringify(routeShardKeys)}\\n\\nfunction resolveShardKey(pathname: string): string {\\n const segments = pathname.split('?')[0].split('/').filter(Boolean)\\n${prefixNormalization} return segments[0]?.toLowerCase() ?? ${JSON.stringify(ROOT_SHARD_KEY)}\\n}\\n\\nasync function loadShard(key: string): Promise<LoadedRouteShard> {\\n switch (key) {\\n${shardCases ? `${shardCases}\\n` : ''} default:\\n return EMPTY_SHARD\\n }\\n}\\n\\nexport async function ${ordinalFunctionName}(ordinal: number): Promise<${typeName}> {\\n const shardKey = ROUTE_SHARD_KEYS[ordinal]\\n if (shardKey === undefined) throw new Error('Unknown generated route manifest ordinal: ' + ordinal)\\n const shard = await loadShard(shardKey)\\n const index = shard.routeOrders.indexOf(ordinal)\\n if (index < 0 || !shard.routes[index]) throw new Error('Generated route manifest ordinal is missing from its shard: ' + ordinal)\\n return shard.routes[index]\\n}\\n\\nexport const ${facadeExportName}: ${typeName}[] = ${metadataExportName}.map((metadata, ordinal) => ({\\n ...metadata,\\n load: async () => {\\n const route = await ${ordinalFunctionName}(ordinal)\\n return route.load()\\n },\\n}))\\n\\nexport async function ${functionName}(pathname: string): Promise<${typeName}[]> {\\n const exactShard = await loadShard(resolveShardKey(pathname))\\n${loadDynamic} return [\\n ...exactShard.routes.map((route, index) => ({ route, order: exactShard.routeOrders[index] })),\\n ...dynamicShard.routes.map((route, index) => ({ route, order: dynamicShard.routeOrders[index] })),\\n ].sort((a, b) => a.order - b.order).map(({ route }) => route)\\n}\\n`\n}\n\nexport function renderRouteManifestShardOutputs(\n kind: RouteManifestShardKind,\n entries: RouteManifestShardEntry[],\n): RouteManifestShardOutput[] {\n const grouped = new Map<string, IndexedRouteManifestShardEntry[]>()\n for (const [order, entry] of entries.entries()) {\n const key = getRouteManifestShardKey(kind, entry.path)\n const existing = grouped.get(key)\n const indexedEntry = { ...entry, order }\n if (existing) existing.push(indexedEntry)\n else grouped.set(key, [indexedEntry])\n }\n\n const sortedGroups = Array.from(grouped.entries()).sort(([a], [b]) => a.localeCompare(b))\n const shards = sortedGroups.map(([key, shardEntries], index) => {\n const stem = shardStem(kind, index, key)\n return {\n key,\n stem,\n orders: shardEntries.map((entry) => entry.order),\n output: {\n fileName: `${stem}.ts`,\n content: renderShardFile(kind, shardEntries),\n },\n }\n })\n const loaderFileName = kind === 'api'\n ? 'api-route-shards.generated.ts'\n : 'backend-route-shards.generated.ts'\n\n return [\n ...shards.map(({ output }) => output),\n {\n fileName: loaderFileName,\n content: renderLoaderFile(kind, shards),\n },\n ]\n}\n\nexport function renderRouteMetadataOutput(\n kind: RouteManifestShardKind,\n entries: RouteManifestShardEntry[],\n): RouteManifestShardOutput {\n const isApi = kind === 'api'\n const manifestType = isApi ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'\n const metadataType = isApi ? 'ApiRouteMetadataEntry' : 'BackendRouteMetadataEntry'\n const exportName = isApi ? 'apiRouteMetadata' : 'backendRouteMetadata'\n const fileName = isApi ? 'api-route-metadata.generated.ts' : 'backend-route-metadata.generated.ts'\n const helperImport = isApi ? '' : 'resolvePageRouteMetadata, '\n const imports = uniqueImports(entries)\n const content = `// AUTO-GENERATED by mercato generate registry (route metadata without component loaders)\\nimport { ${helperImport}type ${manifestType} } from '@open-mercato/shared/modules/registry'\\n${imports.length ? `${imports.join('\\n')}\\n` : ''}\\nexport type ${metadataType} = Omit<${manifestType}, 'load'>\\n\\nexport const ${exportName}: ${metadataType}[] = ${renderArray(entries)}\\n\\nexport default ${exportName}\\n`\n return { fileName, content }\n}\n\nexport const routeManifestShardFilePattern = /^(?:api|backend)-route-shard\\..+\\.generated\\.(?:ts|checksum)$/\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["export type RouteManifestShardKind = 'api' | 'backend'\n\nexport type RouteManifestShardEntry = {\n path: string\n declaration: string\n imports?: string[]\n}\n\nexport type RouteManifestShardOutput = {\n fileName: string\n content: string\n}\n\ntype IndexedRouteManifestShardEntry = RouteManifestShardEntry & {\n order: number\n}\n\nconst DYNAMIC_SHARD_KEY = '__dynamic'\nconst ROOT_SHARD_KEY = '__root'\n\nfunction normalizeRoutePath(kind: RouteManifestShardKind, pathname: string): string[] {\n const segments = pathname.split('?')[0].split('/').filter(Boolean)\n if (kind === 'backend' && segments[0]?.toLowerCase() === 'backend') {\n segments.shift()\n }\n return segments\n}\n\nexport function getRouteManifestShardKey(kind: RouteManifestShardKind, pathname: string): string {\n const firstSegment = normalizeRoutePath(kind, pathname)[0]\n if (!firstSegment) return ROOT_SHARD_KEY\n if (\n firstSegment.startsWith('[')\n || firstSegment.startsWith(':')\n || firstSegment === '*'\n ) {\n return DYNAMIC_SHARD_KEY\n }\n return firstSegment.toLowerCase()\n}\n\nfunction indentDeclaration(declaration: string): string {\n return declaration\n .split('\\n')\n .map((line) => (line.length > 0 ? ` ${line}` : line))\n .join('\\n')\n}\n\nfunction renderArray(entries: RouteManifestShardEntry[]): string {\n if (entries.length === 0) return '[]'\n return `[\\n${entries.map((entry) => indentDeclaration(entry.declaration)).join(',\\n')}\\n]`\n}\n\nfunction uniqueImports(entries: RouteManifestShardEntry[]): string[] {\n return Array.from(new Set(entries.flatMap((entry) => entry.imports ?? [])))\n}\n\nfunction shardStem(kind: RouteManifestShardKind, index: number, key: string): string {\n const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, '_')\n return `${kind}-route-shard.${String(index).padStart(3, '0')}.${safeKey}.generated`\n}\n\nfunction renderShardFile(kind: RouteManifestShardKind, entries: IndexedRouteManifestShardEntry[]): string {\n const typeName = kind === 'api' ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'\n const helperImport = kind === 'backend' ? 'resolvePageRouteMetadata, ' : ''\n const imports = uniqueImports(entries)\n return `// AUTO-GENERATED by mercato generate registry (request-scoped route shard)\\nimport { ${helperImport}type ${typeName} } from '@open-mercato/shared/modules/registry'\\n${imports.length ? `${imports.join('\\n')}\\n` : ''}\\nexport const routes: ${typeName}[] = ${renderArray(entries)}\\nexport const routeOrders = [${entries.map((entry) => entry.order).join(', ')}]\\n\\nexport default routes\\n`\n}\n\nfunction renderLoaderFile(\n kind: RouteManifestShardKind,\n shards: Array<{ key: string; stem: string; orders: number[] }>,\n): string {\n const isApi = kind === 'api'\n const typeName = kind === 'api' ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'\n const functionName = kind === 'api' ? 'loadApiRouteManifestShard' : 'loadBackendRouteManifestShard'\n const ordinalFunctionName = isApi ? 'loadApiRouteManifestByOrdinal' : 'loadBackendRouteManifestByOrdinal'\n const facadeExportName = isApi ? 'apiRouteFacades' : 'backendRouteFacades'\n const metadataExportName = isApi ? 'apiRouteMetadata' : 'backendRouteMetadata'\n const metadataFileName = isApi ? 'api-route-metadata.generated' : 'backend-route-metadata.generated'\n const shardCases = shards\n .map(({ key, stem }) => ` case ${JSON.stringify(key)}:\\n return import('./${stem}').then((entry) => ({ routes: entry.routes, routeOrders: entry.routeOrders }))`)\n .join('\\n')\n const hasDynamicShard = shards.some(({ key }) => key === DYNAMIC_SHARD_KEY)\n const routeShardKeys: string[] = []\n for (const shard of shards) {\n for (const order of shard.orders) routeShardKeys[order] = shard.key\n }\n const prefixNormalization = kind === 'backend'\n ? ` if (segments[0]?.toLowerCase() === 'backend') segments.shift()\\n`\n : ''\n const loadDynamic = hasDynamicShard\n ? ` const dynamicShard = await loadShard(${JSON.stringify(DYNAMIC_SHARD_KEY)})\\n`\n : ` const dynamicShard: LoadedRouteShard = EMPTY_SHARD\\n`\n\n return `// AUTO-GENERATED by mercato generate registry (request-scoped route loader)\\nimport type { ${typeName} } from '@open-mercato/shared/modules/registry'\\nimport { ${metadataExportName} } from './${metadataFileName}'\\n\\ntype LoadedRouteShard = { routes: ${typeName}[]; routeOrders: number[] }\\nconst EMPTY_SHARD: LoadedRouteShard = { routes: [], routeOrders: [] }\\nconst ROUTE_SHARD_KEYS = ${JSON.stringify(routeShardKeys)}\\n\\nfunction resolveShardKey(pathname: string): string {\\n const segments = pathname.split('?')[0].split('/').filter(Boolean)\\n${prefixNormalization} return segments[0]?.toLowerCase() ?? ${JSON.stringify(ROOT_SHARD_KEY)}\\n}\\n\\nasync function loadShard(key: string): Promise<LoadedRouteShard> {\\n switch (key) {\\n${shardCases ? `${shardCases}\\n` : ''} default:\\n return EMPTY_SHARD\\n }\\n}\\n\\nexport async function ${ordinalFunctionName}(ordinal: number): Promise<${typeName}> {\\n const shardKey = ROUTE_SHARD_KEYS[ordinal]\\n if (shardKey === undefined) throw new Error('Unknown generated route manifest ordinal: ' + ordinal)\\n const shard = await loadShard(shardKey)\\n const index = shard.routeOrders.indexOf(ordinal)\\n if (index < 0 || !shard.routes[index]) throw new Error('Generated route manifest ordinal is missing from its shard: ' + ordinal)\\n return shard.routes[index]\\n}\\n\\nexport const ${facadeExportName}: ${typeName}[] = ${metadataExportName}.map((metadata, ordinal) => ({\\n ...metadata,\\n load: async () => {\\n const route = await ${ordinalFunctionName}(ordinal)\\n return route.load()\\n },\\n}))\\n\\nexport async function ${functionName}(pathname: string): Promise<${typeName}[]> {\\n const exactShard = await loadShard(resolveShardKey(pathname))\\n${loadDynamic} return [\\n ...exactShard.routes.map((route, index) => ({ route, order: exactShard.routeOrders[index] })),\\n ...dynamicShard.routes.map((route, index) => ({ route, order: dynamicShard.routeOrders[index] })),\\n ].sort((a, b) => a.order - b.order).map(({ route }) => route)\\n}\\n`\n}\n\nexport function renderRouteManifestShardOutputs(\n kind: RouteManifestShardKind,\n entries: RouteManifestShardEntry[],\n): RouteManifestShardOutput[] {\n const grouped = new Map<string, IndexedRouteManifestShardEntry[]>()\n for (const [order, entry] of entries.entries()) {\n const key = getRouteManifestShardKey(kind, entry.path)\n const existing = grouped.get(key)\n const indexedEntry = { ...entry, order }\n if (existing) existing.push(indexedEntry)\n else grouped.set(key, [indexedEntry])\n }\n\n const sortedGroups = Array.from(grouped.entries()).sort(([a], [b]) => a.localeCompare(b))\n const shards = sortedGroups.map(([key, shardEntries], index) => {\n const stem = shardStem(kind, index, key)\n return {\n key,\n stem,\n orders: shardEntries.map((entry) => entry.order),\n output: {\n fileName: `${stem}.ts`,\n content: renderShardFile(kind, shardEntries),\n },\n }\n })\n const loaderFileName = kind === 'api'\n ? 'api-route-shards.generated.ts'\n : 'backend-route-shards.generated.ts'\n\n return [\n ...shards.map(({ output }) => output),\n {\n fileName: loaderFileName,\n content: renderLoaderFile(kind, shards),\n },\n ]\n}\n\nexport function renderRouteMetadataOutput(\n kind: RouteManifestShardKind,\n entries: RouteManifestShardEntry[],\n): RouteManifestShardOutput {\n const isApi = kind === 'api'\n const manifestType = isApi ? 'ApiRouteManifestEntry' : 'BackendRouteManifestEntry'\n const metadataType = isApi ? 'ApiRouteMetadataEntry' : 'BackendRouteMetadataEntry'\n const exportName = isApi ? 'apiRouteMetadata' : 'backendRouteMetadata'\n const fileName = isApi ? 'api-route-metadata.generated.ts' : 'backend-route-metadata.generated.ts'\n const helperImport = isApi ? '' : 'resolvePageRouteMetadata, '\n const imports = uniqueImports(entries)\n const content = `// AUTO-GENERATED by mercato generate registry (route metadata without component loaders)\\nimport { ${helperImport}type ${manifestType} } from '@open-mercato/shared/modules/registry'\\n${imports.length ? `${imports.join('\\n')}\\n` : ''}\\nexport type ${metadataType} = Omit<${manifestType}, 'load'>\\n\\nexport const ${exportName}: ${metadataType}[] = ${renderArray(entries)}\\n\\nexport default ${exportName}\\n`\n return { fileName, content }\n}\n\nexport const routeManifestShardFilePattern = /^(?:api|backend)-route-shard\\..+\\.generated\\.(?:ts|checksum)$/\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/lib/spawn.js CHANGED
@@ -21,11 +21,12 @@ function resolveSpawnCommand(command, commandArgs = [], options = {}) {
21
21
  const platform = options.platform ?? process.platform;
22
22
  const safeCommand = assertProcessSafeValue(command, "Process command");
23
23
  const safeArgs = commandArgs.map((arg, index) => assertProcessSafeValue(arg, `Process argument #${index + 1}`));
24
+ const detachedSpawnOptions = options.detached ? { detached: true } : {};
24
25
  if (!isWindowsCmdScript(safeCommand, platform)) {
25
26
  return {
26
27
  command: safeCommand,
27
28
  args: safeArgs,
28
- spawnOptions: {}
29
+ spawnOptions: { ...detachedSpawnOptions }
29
30
  };
30
31
  }
31
32
  return {
@@ -33,7 +34,7 @@ function resolveSpawnCommand(command, commandArgs = [], options = {}) {
33
34
  args: safeArgs.map(
34
35
  (arg, index) => assertWindowsCmdSafeValue(arg, `Windows command argument #${index + 1}`)
35
36
  ),
36
- spawnOptions: {}
37
+ spawnOptions: { ...detachedSpawnOptions }
37
38
  };
38
39
  }
39
40
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/spawn.ts"],
4
- "sourcesContent": ["export function isWindowsCmdScript(command: string, platform = process.platform): boolean {\n return platform === 'win32' && /\\.(cmd|bat)$/i.test(String(command))\n}\n\nconst PROCESS_VALUE_UNSAFE_CHAR_PATTERN = /[\\u0000-\\u001f\\u007f]/\nconst WINDOWS_CMD_UNSAFE_CHAR_PATTERN = /[\\u0000-\\u001f\\u007f%!]/\n\nfunction assertProcessSafeValue(value: string, label: string): string {\n const stringValue = String(value)\n\n if (PROCESS_VALUE_UNSAFE_CHAR_PATTERN.test(stringValue)) {\n throw new Error(`${label} contains unsupported control characters.`)\n }\n\n return stringValue\n}\n\nfunction assertWindowsCmdSafeValue(value: string, label: string): string {\n const stringValue = assertProcessSafeValue(value, label)\n\n if (WINDOWS_CMD_UNSAFE_CHAR_PATTERN.test(stringValue)) {\n throw new Error(`${label} contains unsupported characters for Windows command execution.`)\n }\n\n return stringValue\n}\n\nexport function resolveSpawnCommand(\n command: string,\n commandArgs: string[] = [],\n options: { platform?: NodeJS.Platform } = {},\n): {\n command: string\n args: string[]\n spawnOptions: { shell?: boolean; windowsVerbatimArguments?: boolean }\n} {\n const platform = options.platform ?? process.platform\n const safeCommand = assertProcessSafeValue(command, 'Process command')\n const safeArgs = commandArgs.map((arg, index) => assertProcessSafeValue(arg, `Process argument #${index + 1}`))\n\n if (!isWindowsCmdScript(safeCommand, platform)) {\n return {\n command: safeCommand,\n args: safeArgs,\n spawnOptions: {},\n }\n }\n\n return {\n command: assertWindowsCmdSafeValue(safeCommand, 'Windows command path'),\n args: safeArgs.map((arg, index) =>\n assertWindowsCmdSafeValue(arg, `Windows command argument #${index + 1}`),\n ),\n spawnOptions: {},\n }\n}\n"],
5
- "mappings": "AAAO,SAAS,mBAAmB,SAAiB,WAAW,QAAQ,UAAmB;AACxF,SAAO,aAAa,WAAW,gBAAgB,KAAK,OAAO,OAAO,CAAC;AACrE;AAEA,MAAM,oCAAoC;AAC1C,MAAM,kCAAkC;AAExC,SAAS,uBAAuB,OAAe,OAAuB;AACpE,QAAM,cAAc,OAAO,KAAK;AAEhC,MAAI,kCAAkC,KAAK,WAAW,GAAG;AACvD,UAAM,IAAI,MAAM,GAAG,KAAK,2CAA2C;AAAA,EACrE;AAEA,SAAO;AACT;AAEA,SAAS,0BAA0B,OAAe,OAAuB;AACvE,QAAM,cAAc,uBAAuB,OAAO,KAAK;AAEvD,MAAI,gCAAgC,KAAK,WAAW,GAAG;AACrD,UAAM,IAAI,MAAM,GAAG,KAAK,iEAAiE;AAAA,EAC3F;AAEA,SAAO;AACT;AAEO,SAAS,oBACd,SACA,cAAwB,CAAC,GACzB,UAA0C,CAAC,GAK3C;AACA,QAAM,WAAW,QAAQ,YAAY,QAAQ;AAC7C,QAAM,cAAc,uBAAuB,SAAS,iBAAiB;AACrE,QAAM,WAAW,YAAY,IAAI,CAAC,KAAK,UAAU,uBAAuB,KAAK,qBAAqB,QAAQ,CAAC,EAAE,CAAC;AAE9G,MAAI,CAAC,mBAAmB,aAAa,QAAQ,GAAG;AAC9C,WAAO;AAAA,MACL,SAAS;AAAA,MACT,MAAM;AAAA,MACN,cAAc,CAAC;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,0BAA0B,aAAa,sBAAsB;AAAA,IACtE,MAAM,SAAS;AAAA,MAAI,CAAC,KAAK,UACvB,0BAA0B,KAAK,6BAA6B,QAAQ,CAAC,EAAE;AAAA,IACzE;AAAA,IACA,cAAc,CAAC;AAAA,EACjB;AACF;",
4
+ "sourcesContent": ["export function isWindowsCmdScript(command: string, platform = process.platform): boolean {\n return platform === 'win32' && /\\.(cmd|bat)$/i.test(String(command))\n}\n\nconst PROCESS_VALUE_UNSAFE_CHAR_PATTERN = /[\\u0000-\\u001f\\u007f]/\nconst WINDOWS_CMD_UNSAFE_CHAR_PATTERN = /[\\u0000-\\u001f\\u007f%!]/\n\nfunction assertProcessSafeValue(value: string, label: string): string {\n const stringValue = String(value)\n\n if (PROCESS_VALUE_UNSAFE_CHAR_PATTERN.test(stringValue)) {\n throw new Error(`${label} contains unsupported control characters.`)\n }\n\n return stringValue\n}\n\nfunction assertWindowsCmdSafeValue(value: string, label: string): string {\n const stringValue = assertProcessSafeValue(value, label)\n\n if (WINDOWS_CMD_UNSAFE_CHAR_PATTERN.test(stringValue)) {\n throw new Error(`${label} contains unsupported characters for Windows command execution.`)\n }\n\n return stringValue\n}\n\nexport function resolveSpawnCommand(\n command: string,\n commandArgs: string[] = [],\n options: { platform?: NodeJS.Platform; detached?: boolean } = {},\n): {\n command: string\n args: string[]\n spawnOptions: { shell?: boolean; windowsVerbatimArguments?: boolean; detached?: boolean }\n} {\n const platform = options.platform ?? process.platform\n const safeCommand = assertProcessSafeValue(command, 'Process command')\n const safeArgs = commandArgs.map((arg, index) => assertProcessSafeValue(arg, `Process argument #${index + 1}`))\n const detachedSpawnOptions = options.detached ? { detached: true } : {}\n\n if (!isWindowsCmdScript(safeCommand, platform)) {\n return {\n command: safeCommand,\n args: safeArgs,\n spawnOptions: { ...detachedSpawnOptions },\n }\n }\n\n return {\n command: assertWindowsCmdSafeValue(safeCommand, 'Windows command path'),\n args: safeArgs.map((arg, index) =>\n assertWindowsCmdSafeValue(arg, `Windows command argument #${index + 1}`),\n ),\n spawnOptions: { ...detachedSpawnOptions },\n }\n}\n"],
5
+ "mappings": "AAAO,SAAS,mBAAmB,SAAiB,WAAW,QAAQ,UAAmB;AACxF,SAAO,aAAa,WAAW,gBAAgB,KAAK,OAAO,OAAO,CAAC;AACrE;AAEA,MAAM,oCAAoC;AAC1C,MAAM,kCAAkC;AAExC,SAAS,uBAAuB,OAAe,OAAuB;AACpE,QAAM,cAAc,OAAO,KAAK;AAEhC,MAAI,kCAAkC,KAAK,WAAW,GAAG;AACvD,UAAM,IAAI,MAAM,GAAG,KAAK,2CAA2C;AAAA,EACrE;AAEA,SAAO;AACT;AAEA,SAAS,0BAA0B,OAAe,OAAuB;AACvE,QAAM,cAAc,uBAAuB,OAAO,KAAK;AAEvD,MAAI,gCAAgC,KAAK,WAAW,GAAG;AACrD,UAAM,IAAI,MAAM,GAAG,KAAK,iEAAiE;AAAA,EAC3F;AAEA,SAAO;AACT;AAEO,SAAS,oBACd,SACA,cAAwB,CAAC,GACzB,UAA8D,CAAC,GAK/D;AACA,QAAM,WAAW,QAAQ,YAAY,QAAQ;AAC7C,QAAM,cAAc,uBAAuB,SAAS,iBAAiB;AACrE,QAAM,WAAW,YAAY,IAAI,CAAC,KAAK,UAAU,uBAAuB,KAAK,qBAAqB,QAAQ,CAAC,EAAE,CAAC;AAC9G,QAAM,uBAAuB,QAAQ,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;AAEtE,MAAI,CAAC,mBAAmB,aAAa,QAAQ,GAAG;AAC9C,WAAO;AAAA,MACL,SAAS;AAAA,MACT,MAAM;AAAA,MACN,cAAc,EAAE,GAAG,qBAAqB;AAAA,IAC1C;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,0BAA0B,aAAa,sBAAsB;AAAA,IACtE,MAAM,SAAS;AAAA,MAAI,CAAC,KAAK,UACvB,0BAA0B,KAAK,6BAA6B,QAAQ,CAAC,EAAE;AAAA,IACzE;AAAA,IACA,cAAc,EAAE,GAAG,qBAAqB;AAAA,EAC1C;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  import { createServer } from "node:net";
2
3
  import { existsSync, readFileSync } from "node:fs";
3
4
  import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
@@ -452,9 +453,31 @@ function runNpxCommand(args, environment) {
452
453
  });
453
454
  });
454
455
  }
456
+ const CAPTURED_OUTPUT_MAX_LENGTH = 64 * 1024;
457
+ function createBoundedOutputBuffer() {
458
+ let buffered = "";
459
+ return {
460
+ append: (chunk) => {
461
+ buffered += chunk.toString();
462
+ if (buffered.length > CAPTURED_OUTPUT_MAX_LENGTH) {
463
+ buffered = `\u2026(truncated)\u2026${buffered.slice(-CAPTURED_OUTPUT_MAX_LENGTH)}`;
464
+ }
465
+ },
466
+ read: () => buffered
467
+ };
468
+ }
469
+ function formatCapturedOutput(stderrText, stdoutText) {
470
+ if (stderrText && stdoutText) {
471
+ return `--- stderr ---
472
+ ${stderrText}
473
+ --- stdout ---
474
+ ${stdoutText}`;
475
+ }
476
+ return stderrText || stdoutText;
477
+ }
455
478
  function startYarnRawCommand(commandArgs, environment, opts = {}, cwd = projectRootDirectory) {
456
479
  const outputMode = opts.silent ? ["ignore", "pipe", "pipe"] : "inherit";
457
- const resolvedSpawn = resolveSpawnCommand(resolveYarnBinary(), commandArgs);
480
+ const resolvedSpawn = resolveSpawnCommand(resolveYarnBinary(), commandArgs, { detached: opts.detached });
458
481
  const processHandle = spawn(resolvedSpawn.command, resolvedSpawn.args, {
459
482
  cwd,
460
483
  env: environment,
@@ -462,10 +485,11 @@ function startYarnRawCommand(commandArgs, environment, opts = {}, cwd = projectR
462
485
  ...resolvedSpawn.spawnOptions
463
486
  });
464
487
  if (opts.silent) {
465
- processHandle.stdout?.on("data", () => {
466
- });
467
- processHandle.stderr?.on("data", () => {
468
- });
488
+ const stdoutBuffer = createBoundedOutputBuffer();
489
+ const stderrBuffer = createBoundedOutputBuffer();
490
+ processHandle.stdout?.on("data", (chunk) => stdoutBuffer.append(chunk));
491
+ processHandle.stderr?.on("data", (chunk) => stderrBuffer.append(chunk));
492
+ processHandle.readCapturedOutput = () => formatCapturedOutput(stderrBuffer.read().trim(), stdoutBuffer.read().trim());
469
493
  }
470
494
  return processHandle;
471
495
  }
@@ -1305,6 +1329,97 @@ function isProcessRunning(processId) {
1305
1329
  return error.code !== "ESRCH";
1306
1330
  }
1307
1331
  }
1332
+ function defaultKillPosixProcessGroup(pid, signal) {
1333
+ process.kill(-pid, signal);
1334
+ }
1335
+ function defaultKillWindowsProcessTree(pid, options) {
1336
+ spawnSync("taskkill", ["/pid", String(pid), "/t", ...options.forced ? ["/f"] : []]);
1337
+ }
1338
+ function killProcessTree(pid, signal, dependencies = {}) {
1339
+ const platform = dependencies.platform ?? process.platform;
1340
+ if (platform === "win32") {
1341
+ const killWindowsProcessTree = dependencies.killWindowsProcessTree ?? defaultKillWindowsProcessTree;
1342
+ killWindowsProcessTree(pid, { forced: signal === "SIGKILL" });
1343
+ return;
1344
+ }
1345
+ const killPosixProcessGroup = dependencies.killPosixProcessGroup ?? defaultKillPosixProcessGroup;
1346
+ killPosixProcessGroup(pid, signal);
1347
+ }
1348
+ const PROCESS_TREE_KILL_GRACE_PERIOD_MS = 3e3;
1349
+ function killProcessTreeIfRunning(pid, signal, dependencies) {
1350
+ try {
1351
+ killProcessTree(pid, signal, dependencies);
1352
+ } catch (error) {
1353
+ if (error.code !== "ESRCH") {
1354
+ throw error;
1355
+ }
1356
+ }
1357
+ }
1358
+ function waitForProcessExitWithin(childProcess, timeoutMs) {
1359
+ return new Promise((resolve) => {
1360
+ const settle = (exited) => {
1361
+ clearTimeout(gracePeriodTimer);
1362
+ childProcess.off("exit", onExit);
1363
+ childProcess.off("error", onError);
1364
+ resolve(exited);
1365
+ };
1366
+ const onExit = () => settle(true);
1367
+ const onError = () => settle(false);
1368
+ const gracePeriodTimer = setTimeout(() => settle(false), timeoutMs);
1369
+ childProcess.on("exit", onExit);
1370
+ childProcess.on("error", onError);
1371
+ });
1372
+ }
1373
+ function hasProcessAlreadyExited(childProcess) {
1374
+ return (childProcess.exitCode ?? null) !== null || (childProcess.signalCode ?? null) !== null;
1375
+ }
1376
+ async function terminateProcessTree(childProcess, dependencies = {}) {
1377
+ const pid = childProcess.pid;
1378
+ if (!pid) return;
1379
+ if (hasProcessAlreadyExited(childProcess)) {
1380
+ killProcessTreeIfRunning(pid, "SIGKILL", dependencies);
1381
+ return;
1382
+ }
1383
+ killProcessTreeIfRunning(pid, "SIGTERM", dependencies);
1384
+ const gracePeriodMs = dependencies.gracePeriodMs ?? PROCESS_TREE_KILL_GRACE_PERIOD_MS;
1385
+ const exitedBeforeGracePeriod = await waitForProcessExitWithin(childProcess, gracePeriodMs);
1386
+ if (!exitedBeforeGracePeriod && isProcessRunning(pid)) {
1387
+ killProcessTreeIfRunning(pid, "SIGKILL", dependencies);
1388
+ }
1389
+ }
1390
+ const EPHEMERAL_SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
1391
+ function registerEphemeralShutdownHandlers(options) {
1392
+ const processRef = options.processRef ?? process;
1393
+ const onProcessExit = () => options.killApplicationTree();
1394
+ const signalHandlers = /* @__PURE__ */ new Map();
1395
+ const dispose = () => {
1396
+ processRef.off("exit", onProcessExit);
1397
+ for (const [signal, handler] of signalHandlers) {
1398
+ processRef.off(signal, handler);
1399
+ }
1400
+ signalHandlers.clear();
1401
+ };
1402
+ for (const signal of EPHEMERAL_SHUTDOWN_SIGNALS) {
1403
+ const handler = () => {
1404
+ void (async () => {
1405
+ options.onSignal?.(signal);
1406
+ try {
1407
+ await options.stop();
1408
+ } catch (error) {
1409
+ console.error(`Failed to stop the ephemeral environment on ${signal}:`, error);
1410
+ } finally {
1411
+ dispose();
1412
+ processRef.removeAllListeners(signal);
1413
+ processRef.kill(processRef.pid, signal);
1414
+ }
1415
+ })();
1416
+ };
1417
+ signalHandlers.set(signal, handler);
1418
+ processRef.once(signal, handler);
1419
+ }
1420
+ processRef.once("exit", onProcessExit);
1421
+ return { dispose };
1422
+ }
1308
1423
  async function getPathAgeMilliseconds(targetPath) {
1309
1424
  try {
1310
1425
  const stats = await stat(targetPath);
@@ -1595,7 +1710,15 @@ async function waitForApplicationReadiness(baseUrl, appProcess, options) {
1595
1710
  return { exited: true };
1596
1711
  }
1597
1712
  );
1598
- const exitError = () => new Error(`Application process exited before readiness check (exit ${exitCode ?? "unknown"})`);
1713
+ const exitError = () => {
1714
+ const capturedOutput = appProcess.readCapturedOutput?.().trim();
1715
+ const capturedOutputTail = capturedOutput ? `
1716
+ Captured output:
1717
+ ${capturedOutput.split("\n").slice(-20).join("\n")}` : "";
1718
+ return new Error(
1719
+ `Application process exited before readiness check (exit ${exitCode ?? "unknown"})${capturedOutputTail}`
1720
+ );
1721
+ };
1599
1722
  while (Date.now() - startTimestamp < options.timeoutMs) {
1600
1723
  const cycle = await Promise.race([
1601
1724
  probeApplicationReadiness(baseUrl).then((probe) => ({ probe })),
@@ -2736,19 +2859,40 @@ async function startEphemeralEnvironment(options) {
2736
2859
  const runtimeLock = await acquireEphemeralRuntimeLock(options.logPrefix);
2737
2860
  let applicationProcess = null;
2738
2861
  let isStopped = false;
2862
+ let shutdownHandlers = null;
2739
2863
  const stop = async () => {
2740
2864
  if (isStopped) return;
2741
2865
  isStopped = true;
2742
2866
  try {
2743
2867
  if (applicationProcess && !applicationProcess.killed) {
2744
- applicationProcess.kill("SIGTERM");
2868
+ try {
2869
+ await terminateProcessTree(applicationProcess);
2870
+ } catch (error) {
2871
+ console.error(`[${options.logPrefix}] Failed to terminate the application process tree:`, error);
2872
+ }
2745
2873
  }
2746
2874
  await databaseContainer.stop();
2747
2875
  await clearEphemeralEnvironmentState();
2748
2876
  } finally {
2749
2877
  await runtimeLock.release();
2878
+ shutdownHandlers?.dispose();
2750
2879
  }
2751
2880
  };
2881
+ shutdownHandlers = registerEphemeralShutdownHandlers({
2882
+ stop,
2883
+ onSignal: (signal) => console.log(`[${options.logPrefix}] Received ${signal}, stopping ephemeral environment...`),
2884
+ // Deliberately not guarded on `isStopped`: `startEphemeralEnvironment`'s own catch already ran
2885
+ // `stop()` before rethrowing, so the guard made the sweep dead on exactly the paths that need
2886
+ // it. After a successful `stop()` the extra group kill is a harmless `ESRCH` this swallows.
2887
+ killApplicationTree: () => {
2888
+ const pid = applicationProcess?.pid;
2889
+ if (!pid) return;
2890
+ try {
2891
+ killProcessTree(pid, "SIGKILL");
2892
+ } catch {
2893
+ }
2894
+ }
2895
+ });
2752
2896
  try {
2753
2897
  const appReadyTimeoutMs = resolveAppReadyTimeoutMs(options.logPrefix);
2754
2898
  const buildCacheTtlSeconds = resolveBuildCacheTtlSeconds(options.logPrefix);
@@ -2818,7 +2962,8 @@ async function startEphemeralEnvironment(options) {
2818
2962
  }
2819
2963
  console.log(`[${options.logPrefix}] Starting application on ${applicationBaseUrl}...`);
2820
2964
  const startedAppProcess = startYarnCommand(["start"], commandEnvironment, {
2821
- silent: !options.verbose
2965
+ silent: !options.verbose,
2966
+ detached: true
2822
2967
  }, appDirectory);
2823
2968
  applicationProcess = startedAppProcess;
2824
2969
  await runTimedStep(
@@ -2854,14 +2999,7 @@ async function startEphemeralEnvironment(options) {
2854
2999
  await setupLock.release();
2855
3000
  }
2856
3001
  }
2857
- async function keepEnvironmentRunningForever(options) {
2858
- const onSignal = async (signal) => {
2859
- console.log(`[${options.logPrefix}] Received ${signal}, stopping ephemeral environment...`);
2860
- await options.stop();
2861
- process.exit(0);
2862
- };
2863
- process.once("SIGINT", () => void onSignal("SIGINT"));
2864
- process.once("SIGTERM", () => void onSignal("SIGTERM"));
3002
+ async function keepEnvironmentRunningForever() {
2865
3003
  await new Promise(() => {
2866
3004
  });
2867
3005
  }
@@ -2907,10 +3045,7 @@ async function runIntegrationTestsInEphemeralEnvironment(rawArgs) {
2907
3045
  }
2908
3046
  if (options.keep) {
2909
3047
  console.log("[integration] --keep enabled: leaving app and database running. Press Ctrl+C to stop.");
2910
- await keepEnvironmentRunningForever({
2911
- logPrefix: "integration",
2912
- stop: environment.stop
2913
- });
3048
+ await keepEnvironmentRunningForever();
2914
3049
  }
2915
3050
  } finally {
2916
3051
  if (!options.keep) {
@@ -2935,10 +3070,7 @@ async function runEphemeralAppForQa(rawArgs) {
2935
3070
  } else {
2936
3071
  console.log("[ephemeral] Reused existing environment. Press Ctrl+C to exit without stopping the shared runtime.");
2937
3072
  }
2938
- await keepEnvironmentRunningForever({
2939
- logPrefix: "ephemeral",
2940
- stop: environment.stop
2941
- });
3073
+ await keepEnvironmentRunningForever();
2942
3074
  }
2943
3075
  async function runInteractiveIntegrationInEphemeralEnvironment(rawArgs) {
2944
3076
  const options = parseInteractiveIntegrationOptions(rawArgs);
@@ -3085,14 +3217,19 @@ async function runInteractiveIntegrationInEphemeralEnvironment(rawArgs) {
3085
3217
  }
3086
3218
  }
3087
3219
  export {
3220
+ CAPTURED_OUTPUT_MAX_LENGTH,
3088
3221
  acquireEphemeralRuntimeLock,
3089
3222
  clearEphemeralEnvironmentState,
3223
+ createBoundedOutputBuffer,
3090
3224
  ephemeralPostgresInitSql,
3225
+ formatCapturedOutput,
3226
+ killProcessTree,
3091
3227
  parseEphemeralAppOptions,
3092
3228
  parseIntegrationCoverageOptions,
3093
3229
  parseInteractiveIntegrationOptions,
3094
3230
  parseOptions,
3095
3231
  readEphemeralEnvironmentState,
3232
+ registerEphemeralShutdownHandlers,
3096
3233
  resolveAppReadyTimeoutMs,
3097
3234
  resolveBuildCacheTtlSeconds,
3098
3235
  resolveEphemeralPostgresImage,
@@ -3105,6 +3242,7 @@ export {
3105
3242
  shouldReuseBuildArtifacts,
3106
3243
  shouldUseIsolatedPortForFreshEnvironment,
3107
3244
  startEphemeralEnvironment,
3245
+ terminateProcessTree,
3108
3246
  tryReuseExistingEnvironment,
3109
3247
  waitForApplicationReadiness,
3110
3248
  writeEphemeralEnvironmentState