@open-mercato/cli 0.6.8-develop.7100.1.fbf66fca35 → 0.7.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 (185) hide show
  1. package/.turbo/turbo-build.log +1 -7
  2. package/dist/agentic/guides/module-facts.json +598 -5508
  3. package/dist/agentic/guides/module-facts.v2.json +598 -5594
  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/exact-override-targets.md +0 -1
  7. package/dist/agentic/guides/modules/api_keys/index.md +2 -4
  8. package/dist/agentic/guides/modules/attachments/di-registrations-rich.md +5 -6
  9. package/dist/agentic/guides/modules/attachments/di-service-tokens.md +4 -5
  10. package/dist/agentic/guides/modules/attachments/exact-override-targets.md +5 -6
  11. package/dist/agentic/guides/modules/attachments/index.md +2 -2
  12. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  13. package/dist/agentic/guides/modules/auth/di-registrations-rich.md +2 -3
  14. package/dist/agentic/guides/modules/auth/di-service-tokens.md +2 -3
  15. package/dist/agentic/guides/modules/auth/exact-override-targets.md +2 -3
  16. package/dist/agentic/guides/modules/auth/index.md +1 -1
  17. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  18. package/dist/agentic/guides/modules/catalog/incoming-installed-contributions.md +0 -3
  19. package/dist/agentic/guides/modules/catalog/index.md +2 -2
  20. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  21. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  22. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  23. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  24. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  25. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  26. package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +8 -9
  27. package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +8 -9
  28. package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +0 -2
  29. package/dist/agentic/guides/modules/communication_channels/index.md +3 -3
  30. package/dist/agentic/guides/modules/configs/index.md +1 -1
  31. package/dist/agentic/guides/modules/content/index.md +1 -1
  32. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  33. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  34. package/dist/agentic/guides/modules/customers/active-extension-bindings.md +3 -6
  35. package/dist/agentic/guides/modules/customers/contribution-resolutions.md +0 -1
  36. package/dist/agentic/guides/modules/customers/events.md +1 -2
  37. package/dist/agentic/guides/modules/customers/incoming-installed-contributions.md +0 -7
  38. package/dist/agentic/guides/modules/customers/index.md +4 -4
  39. package/dist/agentic/guides/modules/customers/umes-contributions.md +0 -1
  40. package/dist/agentic/guides/modules/customers/umes-hosts.md +0 -1
  41. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  42. package/dist/agentic/guides/modules/data_sync/exact-override-targets.md +2 -2
  43. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  44. package/dist/agentic/guides/modules/data_sync/workers.md +2 -2
  45. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  46. package/dist/agentic/guides/modules/devices/index.md +1 -1
  47. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  48. package/dist/agentic/guides/modules/directory/exact-override-targets.md +0 -2
  49. package/dist/agentic/guides/modules/directory/index.md +3 -5
  50. package/dist/agentic/guides/modules/entities/index.md +1 -1
  51. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  52. package/dist/agentic/guides/modules/events/index.md +1 -1
  53. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  54. package/dist/agentic/guides/modules/gateway_stripe/exact-override-targets.md +1 -1
  55. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  56. package/dist/agentic/guides/modules/gateway_stripe/workers.md +1 -1
  57. package/dist/agentic/guides/modules/generators/index.md +1 -1
  58. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  59. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  60. package/dist/agentic/guides/modules/messages/index.md +1 -1
  61. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  62. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  63. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  64. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  65. package/dist/agentic/guides/modules/planner/index.md +1 -1
  66. package/dist/agentic/guides/modules/portal/index.md +1 -1
  67. package/dist/agentic/guides/modules/progress/index.md +1 -1
  68. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  69. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  70. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  71. package/dist/agentic/guides/modules/resources/index.md +1 -1
  72. package/dist/agentic/guides/modules/sales/active-extension-bindings.md +0 -1
  73. package/dist/agentic/guides/modules/sales/contribution-resolutions.md +0 -1
  74. package/dist/agentic/guides/modules/sales/exact-override-targets.md +4 -5
  75. package/dist/agentic/guides/modules/sales/host-extension-points.md +1 -1
  76. package/dist/agentic/guides/modules/sales/incoming-installed-contributions.md +0 -2
  77. package/dist/agentic/guides/modules/sales/index.md +3 -3
  78. package/dist/agentic/guides/modules/sales/setup.md +1 -1
  79. package/dist/agentic/guides/modules/sales/umes-contributions.md +0 -1
  80. package/dist/agentic/guides/modules/sales/umes-diagnostics.md +0 -1
  81. package/dist/agentic/guides/modules/sales/umes-hosts.md +0 -9
  82. package/dist/agentic/guides/modules/scheduler/cli-commands.md +0 -1
  83. package/dist/agentic/guides/modules/scheduler/exact-override-targets.md +1 -3
  84. package/dist/agentic/guides/modules/scheduler/index.md +2 -2
  85. package/dist/agentic/guides/modules/scheduler/workers.md +1 -2
  86. package/dist/agentic/guides/modules/search/index.md +1 -1
  87. package/dist/agentic/guides/modules/security/index.md +1 -1
  88. package/dist/agentic/guides/modules/security/owned-contract-module-metadata.md +1 -1
  89. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  90. package/dist/agentic/guides/modules/sso/index.md +1 -1
  91. package/dist/agentic/guides/modules/staff/index.md +1 -1
  92. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  93. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  94. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  95. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  96. package/dist/agentic/guides/modules/translations/index.md +1 -1
  97. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  98. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  99. package/dist/agentic/guides/modules/wms/index.md +1 -1
  100. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  101. package/dist/agentic/guides/reference-module-facts.json +2 -2
  102. package/dist/agentic/guides/reference-modules/example/index.md +1 -1
  103. package/dist/agentic/guides/upstream/manifest.json +1 -1
  104. package/dist/agentic/shared/AGENTS.md.template +3 -3
  105. package/dist/agentic/shared/ai/harness/README.md +2 -2
  106. package/dist/agentic/shared/ai/harness/RELEASE.md +6 -8
  107. package/dist/agentic/shared/ai/harness/cases.json +0 -83
  108. package/dist/agentic/shared/ai/harness/cases.schema.json +4 -4
  109. package/dist/agentic/shared/ai/harness/validators.json +1 -1
  110. package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.7-to-0.7.0/SKILL.md +12 -3
  111. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +11 -37
  112. package/dist/lib/generators/ast/index.js +1 -4
  113. package/dist/lib/generators/ast/index.js.map +2 -2
  114. package/dist/lib/generators/ast/source-file.js +1 -8
  115. package/dist/lib/generators/ast/source-file.js.map +2 -2
  116. package/dist/lib/generators/ast/writers.js +1 -9
  117. package/dist/lib/generators/ast/writers.js.map +2 -2
  118. package/dist/lib/generators/module-registry.js +189 -424
  119. package/dist/lib/generators/module-registry.js.map +2 -2
  120. package/dist/lib/generators/route-manifest-shards.js +1 -4
  121. package/dist/lib/generators/route-manifest-shards.js.map +2 -2
  122. package/dist/lib/spawn.js +2 -3
  123. package/dist/lib/spawn.js.map +2 -2
  124. package/dist/lib/testing/integration.js +24 -162
  125. package/dist/lib/testing/integration.js.map +2 -2
  126. package/dist/mercato.js +0 -14
  127. package/dist/mercato.js.map +2 -2
  128. package/package.json +7 -8
  129. package/src/lib/__tests__/spawn.test.ts +0 -23
  130. package/src/lib/generators/__tests__/metadata-extraction.test.ts +0 -44
  131. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +8 -32
  132. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -4
  133. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +1 -1
  134. package/src/lib/generators/__tests__/module-subset.test.ts +72 -96
  135. package/src/lib/generators/__tests__/output-snapshots.test.ts +1 -1
  136. package/src/lib/generators/__tests__/route-manifest-shards.test.ts +0 -15
  137. package/src/lib/generators/__tests__/structural-contracts.test.ts +3 -7
  138. package/src/lib/generators/ast/index.ts +1 -2
  139. package/src/lib/generators/ast/source-file.ts +0 -13
  140. package/src/lib/generators/ast/writers.ts +1 -26
  141. package/src/lib/generators/module-registry.ts +227 -534
  142. package/src/lib/generators/route-manifest-shards.ts +1 -8
  143. package/src/lib/spawn.ts +4 -5
  144. package/src/lib/testing/__tests__/integration.test.ts +0 -435
  145. package/src/lib/testing/integration.ts +32 -254
  146. package/src/mercato.ts +0 -27
  147. package/dist/agentic/guides/modules/api_keys/di-registrations-rich.md +0 -11
  148. package/dist/agentic/guides/modules/api_keys/di-service-tokens.md +0 -11
  149. package/dist/agentic/guides/modules/channel_discord/acl-features.md +0 -13
  150. package/dist/agentic/guides/modules/channel_discord/ai-agents.md +0 -11
  151. package/dist/agentic/guides/modules/channel_discord/backend-pages.md +0 -11
  152. package/dist/agentic/guides/modules/channel_discord/cli-commands.md +0 -13
  153. package/dist/agentic/guides/modules/channel_discord/contribution-resolutions.md +0 -14
  154. package/dist/agentic/guides/modules/channel_discord/di-registrations-rich.md +0 -12
  155. package/dist/agentic/guides/modules/channel_discord/exact-override-targets.md +0 -26
  156. package/dist/agentic/guides/modules/channel_discord/index.md +0 -22
  157. package/dist/agentic/guides/modules/channel_discord/owned-contract-module-metadata.md +0 -11
  158. package/dist/agentic/guides/modules/channel_discord/setup.md +0 -11
  159. package/dist/agentic/guides/modules/channel_discord/umes-contributions.md +0 -14
  160. package/dist/agentic/guides/modules/channel_discord/workers.md +0 -12
  161. package/dist/agentic/guides/modules/directory/di-registrations-rich.md +0 -12
  162. package/dist/agentic/guides/modules/directory/di-service-tokens.md +0 -12
  163. package/dist/agentic/guides/modules/documents/acl-features.md +0 -17
  164. package/dist/agentic/guides/modules/documents/backend-pages.md +0 -13
  165. package/dist/agentic/guides/modules/documents/contribution-resolutions.md +0 -27
  166. package/dist/agentic/guides/modules/documents/di-registrations-rich.md +0 -21
  167. package/dist/agentic/guides/modules/documents/domain-commands.md +0 -37
  168. package/dist/agentic/guides/modules/documents/encryption.md +0 -11
  169. package/dist/agentic/guides/modules/documents/entities.md +0 -21
  170. package/dist/agentic/guides/modules/documents/events.md +0 -25
  171. package/dist/agentic/guides/modules/documents/exact-override-targets.md +0 -39
  172. package/dist/agentic/guides/modules/documents/index.md +0 -25
  173. package/dist/agentic/guides/modules/documents/notifications.md +0 -13
  174. package/dist/agentic/guides/modules/documents/owned-contract-module-metadata.md +0 -11
  175. package/dist/agentic/guides/modules/documents/setup.md +0 -11
  176. package/dist/agentic/guides/modules/documents/umes-contributions.md +0 -27
  177. package/dist/agentic/guides/modules/documents/umes-hosts.md +0 -36
  178. package/src/__tests__/resolve-queue-abandon-hook.test.ts +0 -39
  179. package/src/lib/generators/__tests__/__snapshots__/bootstrap-registrations.test.ts.snap +0 -14
  180. package/src/lib/generators/__tests__/__snapshots__/command-loaders.test.ts.snap +0 -23
  181. package/src/lib/generators/__tests__/ast-writers-escaping.test.ts +0 -64
  182. package/src/lib/generators/__tests__/bootstrap-registrations.test.ts +0 -176
  183. package/src/lib/generators/__tests__/command-loaders.test.ts +0 -239
  184. package/src/lib/generators/__tests__/generated-registry-imports.test.ts +0 -128
  185. package/src/lib/generators/__tests__/registry-variant-parity.test.ts +0 -258
@@ -15,13 +15,10 @@ 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
- }
21
18
  function renderArray(entries) {
22
19
  if (entries.length === 0) return "[]";
23
20
  return `[
24
- ${entries.map((entry) => indentDeclaration(entry.declaration)).join(",\n")}
21
+ ${entries.map((entry) => ` ${entry.declaration}`).join(",\n")}
25
22
  ]`;
26
23
  }
27
24
  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 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": "AAiBA,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AAEvB,SAAS,mBAAmB,MAA8B,UAA4B;AACpF,QAAM,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AACjE,MAAI,SAAS,aAAa,SAAS,CAAC,GAAG,YAAY,MAAM,WAAW;AAClE,aAAS,MAAM;AAAA,EACjB;AACA,SAAO;AACT;AAEO,SAAS,yBAAyB,MAA8B,UAA0B;AAC/F,QAAM,eAAe,mBAAmB,MAAM,QAAQ,EAAE,CAAC;AACzD,MAAI,CAAC,aAAc,QAAO;AAC1B,MACE,aAAa,WAAW,GAAG,KACxB,aAAa,WAAW,GAAG,KAC3B,iBAAiB,KACpB;AACA,WAAO;AAAA,EACT;AACA,SAAO,aAAa,YAAY;AAClC;AAEA,SAAS,kBAAkB,aAA6B;AACtD,SAAO,YACJ,MAAM,IAAI,EACV,IAAI,CAAC,SAAU,KAAK,SAAS,IAAI,KAAK,IAAI,KAAK,IAAK,EACpD,KAAK,IAAI;AACd;AAEA,SAAS,YAAY,SAA4C;AAC/D,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO;AAAA,EAAM,QAAQ,IAAI,CAAC,UAAU,kBAAkB,MAAM,WAAW,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA;AACvF;AAEA,SAAS,cAAc,SAA8C;AACnE,SAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,QAAQ,CAAC,UAAU,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC;AAC5E;AAEA,SAAS,UAAU,MAA8B,OAAe,KAAqB;AACnF,QAAM,UAAU,IAAI,QAAQ,mBAAmB,GAAG;AAClD,SAAO,GAAG,IAAI,gBAAgB,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO;AACzE;AAEA,SAAS,gBAAgB,MAA8B,SAAmD;AACxG,QAAM,WAAW,SAAS,QAAQ,0BAA0B;AAC5D,QAAM,eAAe,SAAS,YAAY,+BAA+B;AACzE,QAAM,UAAU,cAAc,OAAO;AACrC,SAAO;AAAA,WAAyF,YAAY,QAAQ,QAAQ;AAAA,EAAoD,QAAQ,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAC;AAAA,IAAO,EAAE;AAAA,uBAA0B,QAAQ,QAAQ,YAAY,OAAO,CAAC;AAAA,8BAAiC,QAAQ,IAAI,CAAC,UAAU,MAAM,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAC9W;AAEA,SAAS,iBACP,MACA,QACQ;AACR,QAAM,QAAQ,SAAS;AACvB,QAAM,WAAW,SAAS,QAAQ,0BAA0B;AAC5D,QAAM,eAAe,SAAS,QAAQ,8BAA8B;AACpE,QAAM,sBAAsB,QAAQ,kCAAkC;AACtE,QAAM,mBAAmB,QAAQ,oBAAoB;AACrD,QAAM,qBAAqB,QAAQ,qBAAqB;AACxD,QAAM,mBAAmB,QAAQ,iCAAiC;AAClE,QAAM,aAAa,OAChB,IAAI,CAAC,EAAE,KAAK,KAAK,MAAM,YAAY,KAAK,UAAU,GAAG,CAAC;AAAA,yBAA6B,IAAI,gFAAgF,EACvK,KAAK,IAAI;AACZ,QAAM,kBAAkB,OAAO,KAAK,CAAC,EAAE,IAAI,MAAM,QAAQ,iBAAiB;AAC1E,QAAM,iBAA2B,CAAC;AAClC,aAAW,SAAS,QAAQ;AAC1B,eAAW,SAAS,MAAM,OAAQ,gBAAe,KAAK,IAAI,MAAM;AAAA,EAClE;AACA,QAAM,sBAAsB,SAAS,YACjC;AAAA,IACA;AACJ,QAAM,cAAc,kBAChB,0CAA0C,KAAK,UAAU,iBAAiB,CAAC;AAAA,IAC3E;AAAA;AAEJ,SAAO;AAAA,gBAA+F,QAAQ;AAAA,WAA6D,kBAAkB,cAAc,gBAAgB;AAAA;AAAA,oCAA0C,QAAQ;AAAA;AAAA,2BAAgI,KAAK,UAAU,cAAc,CAAC;AAAA;AAAA;AAAA;AAAA,EAAmI,mBAAmB,0CAA0C,KAAK,UAAU,cAAc,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAAgG,aAAa,GAAG,UAAU;AAAA,IAAO,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,wBAA2E,mBAAmB,8BAA8B,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAAgb,gBAAgB,KAAK,QAAQ,QAAQ,kBAAkB;AAAA;AAAA;AAAA,0BAAkG,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,wBAA0E,YAAY,+BAA+B,QAAQ;AAAA;AAAA,EAA2E,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AACjsD;AAEO,SAAS,gCACd,MACA,SAC4B;AAC5B,QAAM,UAAU,oBAAI,IAA8C;AAClE,aAAW,CAAC,OAAO,KAAK,KAAK,QAAQ,QAAQ,GAAG;AAC9C,UAAM,MAAM,yBAAyB,MAAM,MAAM,IAAI;AACrD,UAAM,WAAW,QAAQ,IAAI,GAAG;AAChC,UAAM,eAAe,EAAE,GAAG,OAAO,MAAM;AACvC,QAAI,SAAU,UAAS,KAAK,YAAY;AAAA,QACnC,SAAQ,IAAI,KAAK,CAAC,YAAY,CAAC;AAAA,EACtC;AAEA,QAAM,eAAe,MAAM,KAAK,QAAQ,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AACxF,QAAM,SAAS,aAAa,IAAI,CAAC,CAAC,KAAK,YAAY,GAAG,UAAU;AAC9D,UAAM,OAAO,UAAU,MAAM,OAAO,GAAG;AACvC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,QAAQ,aAAa,IAAI,CAAC,UAAU,MAAM,KAAK;AAAA,MAC/C,QAAQ;AAAA,QACN,UAAU,GAAG,IAAI;AAAA,QACjB,SAAS,gBAAgB,MAAM,YAAY;AAAA,MAC7C;AAAA,IACF;AAAA,EACF,CAAC;AACD,QAAM,iBAAiB,SAAS,QAC5B,kCACA;AAEJ,SAAO;AAAA,IACL,GAAG,OAAO,IAAI,CAAC,EAAE,OAAO,MAAM,MAAM;AAAA,IACpC;AAAA,MACE,UAAU;AAAA,MACV,SAAS,iBAAiB,MAAM,MAAM;AAAA,IACxC;AAAA,EACF;AACF;AAEO,SAAS,0BACd,MACA,SAC0B;AAC1B,QAAM,QAAQ,SAAS;AACvB,QAAM,eAAe,QAAQ,0BAA0B;AACvD,QAAM,eAAe,QAAQ,0BAA0B;AACvD,QAAM,aAAa,QAAQ,qBAAqB;AAChD,QAAM,WAAW,QAAQ,oCAAoC;AAC7D,QAAM,eAAe,QAAQ,KAAK;AAClC,QAAM,UAAU,cAAc,OAAO;AACrC,QAAM,UAAU;AAAA,WAAuG,YAAY,QAAQ,YAAY;AAAA,EAAoD,QAAQ,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAC;AAAA,IAAO,EAAE;AAAA,cAAiB,YAAY,WAAW,YAAY;AAAA;AAAA,eAA6B,UAAU,KAAK,YAAY,QAAQ,YAAY,OAAO,CAAC;AAAA;AAAA,iBAAsB,UAAU;AAAA;AACla,SAAO,EAAE,UAAU,QAAQ;AAC7B;AAEO,MAAM,gCAAgC;",
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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",
6
6
  "names": []
7
7
  }
package/dist/lib/spawn.js CHANGED
@@ -21,12 +21,11 @@ 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 } : {};
25
24
  if (!isWindowsCmdScript(safeCommand, platform)) {
26
25
  return {
27
26
  command: safeCommand,
28
27
  args: safeArgs,
29
- spawnOptions: { ...detachedSpawnOptions }
28
+ spawnOptions: {}
30
29
  };
31
30
  }
32
31
  return {
@@ -34,7 +33,7 @@ function resolveSpawnCommand(command, commandArgs = [], options = {}) {
34
33
  args: safeArgs.map(
35
34
  (arg, index) => assertWindowsCmdSafeValue(arg, `Windows command argument #${index + 1}`)
36
35
  ),
37
- spawnOptions: { ...detachedSpawnOptions }
36
+ spawnOptions: {}
38
37
  };
39
38
  }
40
39
  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; 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;",
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;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,3 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import { createServer } from "node:net";
3
2
  import { existsSync, readFileSync } from "node:fs";
4
3
  import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
@@ -453,31 +452,9 @@ function runNpxCommand(args, environment) {
453
452
  });
454
453
  });
455
454
  }
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
- }
478
455
  function startYarnRawCommand(commandArgs, environment, opts = {}, cwd = projectRootDirectory) {
479
456
  const outputMode = opts.silent ? ["ignore", "pipe", "pipe"] : "inherit";
480
- const resolvedSpawn = resolveSpawnCommand(resolveYarnBinary(), commandArgs, { detached: opts.detached });
457
+ const resolvedSpawn = resolveSpawnCommand(resolveYarnBinary(), commandArgs);
481
458
  const processHandle = spawn(resolvedSpawn.command, resolvedSpawn.args, {
482
459
  cwd,
483
460
  env: environment,
@@ -485,11 +462,10 @@ function startYarnRawCommand(commandArgs, environment, opts = {}, cwd = projectR
485
462
  ...resolvedSpawn.spawnOptions
486
463
  });
487
464
  if (opts.silent) {
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());
465
+ processHandle.stdout?.on("data", () => {
466
+ });
467
+ processHandle.stderr?.on("data", () => {
468
+ });
493
469
  }
494
470
  return processHandle;
495
471
  }
@@ -1329,97 +1305,6 @@ function isProcessRunning(processId) {
1329
1305
  return error.code !== "ESRCH";
1330
1306
  }
1331
1307
  }
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
- }
1423
1308
  async function getPathAgeMilliseconds(targetPath) {
1424
1309
  try {
1425
1310
  const stats = await stat(targetPath);
@@ -1710,15 +1595,7 @@ async function waitForApplicationReadiness(baseUrl, appProcess, options) {
1710
1595
  return { exited: true };
1711
1596
  }
1712
1597
  );
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
- };
1598
+ const exitError = () => new Error(`Application process exited before readiness check (exit ${exitCode ?? "unknown"})`);
1722
1599
  while (Date.now() - startTimestamp < options.timeoutMs) {
1723
1600
  const cycle = await Promise.race([
1724
1601
  probeApplicationReadiness(baseUrl).then((probe) => ({ probe })),
@@ -2859,40 +2736,19 @@ async function startEphemeralEnvironment(options) {
2859
2736
  const runtimeLock = await acquireEphemeralRuntimeLock(options.logPrefix);
2860
2737
  let applicationProcess = null;
2861
2738
  let isStopped = false;
2862
- let shutdownHandlers = null;
2863
2739
  const stop = async () => {
2864
2740
  if (isStopped) return;
2865
2741
  isStopped = true;
2866
2742
  try {
2867
2743
  if (applicationProcess && !applicationProcess.killed) {
2868
- try {
2869
- await terminateProcessTree(applicationProcess);
2870
- } catch (error) {
2871
- console.error(`[${options.logPrefix}] Failed to terminate the application process tree:`, error);
2872
- }
2744
+ applicationProcess.kill("SIGTERM");
2873
2745
  }
2874
2746
  await databaseContainer.stop();
2875
2747
  await clearEphemeralEnvironmentState();
2876
2748
  } finally {
2877
2749
  await runtimeLock.release();
2878
- shutdownHandlers?.dispose();
2879
2750
  }
2880
2751
  };
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
- });
2896
2752
  try {
2897
2753
  const appReadyTimeoutMs = resolveAppReadyTimeoutMs(options.logPrefix);
2898
2754
  const buildCacheTtlSeconds = resolveBuildCacheTtlSeconds(options.logPrefix);
@@ -2962,8 +2818,7 @@ async function startEphemeralEnvironment(options) {
2962
2818
  }
2963
2819
  console.log(`[${options.logPrefix}] Starting application on ${applicationBaseUrl}...`);
2964
2820
  const startedAppProcess = startYarnCommand(["start"], commandEnvironment, {
2965
- silent: !options.verbose,
2966
- detached: true
2821
+ silent: !options.verbose
2967
2822
  }, appDirectory);
2968
2823
  applicationProcess = startedAppProcess;
2969
2824
  await runTimedStep(
@@ -2999,7 +2854,14 @@ async function startEphemeralEnvironment(options) {
2999
2854
  await setupLock.release();
3000
2855
  }
3001
2856
  }
3002
- async function keepEnvironmentRunningForever() {
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"));
3003
2865
  await new Promise(() => {
3004
2866
  });
3005
2867
  }
@@ -3045,7 +2907,10 @@ async function runIntegrationTestsInEphemeralEnvironment(rawArgs) {
3045
2907
  }
3046
2908
  if (options.keep) {
3047
2909
  console.log("[integration] --keep enabled: leaving app and database running. Press Ctrl+C to stop.");
3048
- await keepEnvironmentRunningForever();
2910
+ await keepEnvironmentRunningForever({
2911
+ logPrefix: "integration",
2912
+ stop: environment.stop
2913
+ });
3049
2914
  }
3050
2915
  } finally {
3051
2916
  if (!options.keep) {
@@ -3070,7 +2935,10 @@ async function runEphemeralAppForQa(rawArgs) {
3070
2935
  } else {
3071
2936
  console.log("[ephemeral] Reused existing environment. Press Ctrl+C to exit without stopping the shared runtime.");
3072
2937
  }
3073
- await keepEnvironmentRunningForever();
2938
+ await keepEnvironmentRunningForever({
2939
+ logPrefix: "ephemeral",
2940
+ stop: environment.stop
2941
+ });
3074
2942
  }
3075
2943
  async function runInteractiveIntegrationInEphemeralEnvironment(rawArgs) {
3076
2944
  const options = parseInteractiveIntegrationOptions(rawArgs);
@@ -3217,19 +3085,14 @@ async function runInteractiveIntegrationInEphemeralEnvironment(rawArgs) {
3217
3085
  }
3218
3086
  }
3219
3087
  export {
3220
- CAPTURED_OUTPUT_MAX_LENGTH,
3221
3088
  acquireEphemeralRuntimeLock,
3222
3089
  clearEphemeralEnvironmentState,
3223
- createBoundedOutputBuffer,
3224
3090
  ephemeralPostgresInitSql,
3225
- formatCapturedOutput,
3226
- killProcessTree,
3227
3091
  parseEphemeralAppOptions,
3228
3092
  parseIntegrationCoverageOptions,
3229
3093
  parseInteractiveIntegrationOptions,
3230
3094
  parseOptions,
3231
3095
  readEphemeralEnvironmentState,
3232
- registerEphemeralShutdownHandlers,
3233
3096
  resolveAppReadyTimeoutMs,
3234
3097
  resolveBuildCacheTtlSeconds,
3235
3098
  resolveEphemeralPostgresImage,
@@ -3242,7 +3105,6 @@ export {
3242
3105
  shouldReuseBuildArtifacts,
3243
3106
  shouldUseIsolatedPortForFreshEnvironment,
3244
3107
  startEphemeralEnvironment,
3245
- terminateProcessTree,
3246
3108
  tryReuseExistingEnvironment,
3247
3109
  waitForApplicationReadiness,
3248
3110
  writeEphemeralEnvironmentState