@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.
- package/.turbo/turbo-build.log +7 -1
- package/dist/agentic/guides/module-facts.json +5509 -599
- package/dist/agentic/guides/module-facts.v2.json +5595 -599
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/di-registrations-rich.md +11 -0
- package/dist/agentic/guides/modules/api_keys/di-service-tokens.md +11 -0
- package/dist/agentic/guides/modules/api_keys/exact-override-targets.md +1 -0
- package/dist/agentic/guides/modules/api_keys/index.md +4 -2
- package/dist/agentic/guides/modules/attachments/di-registrations-rich.md +6 -5
- package/dist/agentic/guides/modules/attachments/di-service-tokens.md +5 -4
- package/dist/agentic/guides/modules/attachments/exact-override-targets.md +6 -5
- package/dist/agentic/guides/modules/attachments/index.md +2 -2
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/di-registrations-rich.md +3 -2
- package/dist/agentic/guides/modules/auth/di-service-tokens.md +3 -2
- package/dist/agentic/guides/modules/auth/exact-override-targets.md +3 -2
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/incoming-installed-contributions.md +3 -0
- package/dist/agentic/guides/modules/catalog/index.md +2 -2
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_discord/acl-features.md +13 -0
- package/dist/agentic/guides/modules/channel_discord/ai-agents.md +11 -0
- package/dist/agentic/guides/modules/channel_discord/backend-pages.md +11 -0
- package/dist/agentic/guides/modules/channel_discord/cli-commands.md +13 -0
- package/dist/agentic/guides/modules/channel_discord/contribution-resolutions.md +14 -0
- package/dist/agentic/guides/modules/channel_discord/di-registrations-rich.md +12 -0
- package/dist/agentic/guides/modules/channel_discord/exact-override-targets.md +26 -0
- package/dist/agentic/guides/modules/channel_discord/index.md +22 -0
- package/dist/agentic/guides/modules/channel_discord/owned-contract-module-metadata.md +11 -0
- package/dist/agentic/guides/modules/channel_discord/setup.md +11 -0
- package/dist/agentic/guides/modules/channel_discord/umes-contributions.md +14 -0
- package/dist/agentic/guides/modules/channel_discord/workers.md +12 -0
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +9 -8
- package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +9 -8
- package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +2 -0
- package/dist/agentic/guides/modules/communication_channels/index.md +3 -3
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customers/active-extension-bindings.md +6 -3
- package/dist/agentic/guides/modules/customers/contribution-resolutions.md +1 -0
- package/dist/agentic/guides/modules/customers/events.md +2 -1
- package/dist/agentic/guides/modules/customers/incoming-installed-contributions.md +7 -0
- package/dist/agentic/guides/modules/customers/index.md +4 -4
- package/dist/agentic/guides/modules/customers/umes-contributions.md +1 -0
- package/dist/agentic/guides/modules/customers/umes-hosts.md +1 -0
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/exact-override-targets.md +2 -2
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/workers.md +2 -2
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/di-registrations-rich.md +12 -0
- package/dist/agentic/guides/modules/directory/di-service-tokens.md +12 -0
- package/dist/agentic/guides/modules/directory/exact-override-targets.md +2 -0
- package/dist/agentic/guides/modules/directory/index.md +5 -3
- package/dist/agentic/guides/modules/documents/acl-features.md +17 -0
- package/dist/agentic/guides/modules/documents/backend-pages.md +13 -0
- package/dist/agentic/guides/modules/documents/contribution-resolutions.md +27 -0
- package/dist/agentic/guides/modules/documents/di-registrations-rich.md +21 -0
- package/dist/agentic/guides/modules/documents/domain-commands.md +37 -0
- package/dist/agentic/guides/modules/documents/encryption.md +11 -0
- package/dist/agentic/guides/modules/documents/entities.md +21 -0
- package/dist/agentic/guides/modules/documents/events.md +25 -0
- package/dist/agentic/guides/modules/documents/exact-override-targets.md +39 -0
- package/dist/agentic/guides/modules/documents/index.md +25 -0
- package/dist/agentic/guides/modules/documents/notifications.md +13 -0
- package/dist/agentic/guides/modules/documents/owned-contract-module-metadata.md +11 -0
- package/dist/agentic/guides/modules/documents/setup.md +11 -0
- package/dist/agentic/guides/modules/documents/umes-contributions.md +27 -0
- package/dist/agentic/guides/modules/documents/umes-hosts.md +36 -0
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/exact-override-targets.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/workers.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/active-extension-bindings.md +1 -0
- package/dist/agentic/guides/modules/sales/contribution-resolutions.md +1 -0
- package/dist/agentic/guides/modules/sales/exact-override-targets.md +5 -4
- package/dist/agentic/guides/modules/sales/host-extension-points.md +1 -1
- package/dist/agentic/guides/modules/sales/incoming-installed-contributions.md +2 -0
- package/dist/agentic/guides/modules/sales/index.md +3 -3
- package/dist/agentic/guides/modules/sales/setup.md +1 -1
- package/dist/agentic/guides/modules/sales/umes-contributions.md +1 -0
- package/dist/agentic/guides/modules/sales/umes-diagnostics.md +1 -0
- package/dist/agentic/guides/modules/sales/umes-hosts.md +9 -0
- package/dist/agentic/guides/modules/scheduler/cli-commands.md +1 -0
- package/dist/agentic/guides/modules/scheduler/exact-override-targets.md +3 -1
- package/dist/agentic/guides/modules/scheduler/index.md +2 -2
- package/dist/agentic/guides/modules/scheduler/workers.md +2 -1
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/security/owned-contract-module-metadata.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +2 -2
- package/dist/agentic/guides/reference-modules/example/index.md +1 -1
- package/dist/agentic/guides/upstream/manifest.json +1 -1
- package/dist/agentic/shared/AGENTS.md.template +3 -3
- package/dist/agentic/shared/ai/harness/README.md +2 -2
- package/dist/agentic/shared/ai/harness/RELEASE.md +8 -6
- package/dist/agentic/shared/ai/harness/cases.json +83 -0
- package/dist/agentic/shared/ai/harness/cases.schema.json +4 -4
- package/dist/agentic/shared/ai/harness/validators.json +1 -1
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +37 -11
- package/dist/lib/generators/ast/index.js +4 -1
- package/dist/lib/generators/ast/index.js.map +2 -2
- package/dist/lib/generators/ast/source-file.js +8 -1
- package/dist/lib/generators/ast/source-file.js.map +2 -2
- package/dist/lib/generators/ast/writers.js +9 -1
- package/dist/lib/generators/ast/writers.js.map +2 -2
- package/dist/lib/generators/module-registry.js +424 -189
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/generators/route-manifest-shards.js +4 -1
- package/dist/lib/generators/route-manifest-shards.js.map +2 -2
- package/dist/lib/spawn.js +3 -2
- package/dist/lib/spawn.js.map +2 -2
- package/dist/lib/testing/integration.js +162 -24
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +14 -0
- package/dist/mercato.js.map +2 -2
- package/package.json +8 -7
- package/src/__tests__/resolve-queue-abandon-hook.test.ts +39 -0
- package/src/lib/__tests__/spawn.test.ts +23 -0
- package/src/lib/generators/__tests__/__snapshots__/bootstrap-registrations.test.ts.snap +14 -0
- package/src/lib/generators/__tests__/__snapshots__/command-loaders.test.ts.snap +23 -0
- package/src/lib/generators/__tests__/ast-writers-escaping.test.ts +64 -0
- package/src/lib/generators/__tests__/bootstrap-registrations.test.ts +176 -0
- package/src/lib/generators/__tests__/command-loaders.test.ts +239 -0
- package/src/lib/generators/__tests__/generated-registry-imports.test.ts +128 -0
- package/src/lib/generators/__tests__/metadata-extraction.test.ts +44 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +32 -8
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +4 -1
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +1 -1
- package/src/lib/generators/__tests__/module-subset.test.ts +96 -72
- package/src/lib/generators/__tests__/output-snapshots.test.ts +1 -1
- package/src/lib/generators/__tests__/registry-variant-parity.test.ts +258 -0
- package/src/lib/generators/__tests__/route-manifest-shards.test.ts +15 -0
- package/src/lib/generators/__tests__/structural-contracts.test.ts +7 -3
- package/src/lib/generators/ast/index.ts +2 -1
- package/src/lib/generators/ast/source-file.ts +13 -0
- package/src/lib/generators/ast/writers.ts +26 -1
- package/src/lib/generators/module-registry.ts +534 -227
- package/src/lib/generators/route-manifest-shards.ts +8 -1
- package/src/lib/spawn.ts +5 -4
- package/src/lib/testing/__tests__/integration.test.ts +435 -0
- package/src/lib/testing/integration.ts +254 -32
- 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) =>
|
|
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) =>
|
|
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,YAAY,SAA4C;AAC/D,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO;AAAA,EAAM,QAAQ,IAAI,CAAC,UAAU,
|
|
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 {
|
package/dist/lib/spawn.js.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
processHandle.
|
|
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 = () =>
|
|
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
|
-
|
|
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(
|
|
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
|