@open-mercato/cli 0.8.1-develop.7296.1.2111d779db → 0.9.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.
- package/.turbo/turbo-build.log +8 -2
- package/AGENTS.md +2 -0
- package/dist/agentic/guides/module-facts.json +7612 -848
- package/dist/agentic/guides/module-facts.v2.json +7612 -848
- package/dist/agentic/guides/modules/agent_orchestrator/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
- package/dist/agentic/guides/modules/ai_assistant/encryption.md +13 -0
- package/dist/agentic/guides/modules/ai_assistant/exact-override-targets.md +3 -0
- package/dist/agentic/guides/modules/ai_assistant/index.md +4 -3
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/domain-commands.md +12 -0
- package/dist/agentic/guides/modules/api_keys/index.md +3 -2
- package/dist/agentic/guides/modules/attachments/index.md +1 -1
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/availability/acl-features.md +13 -0
- package/dist/agentic/guides/modules/availability/backend-pages.md +14 -0
- package/dist/agentic/guides/modules/availability/di-registrations-rich.md +11 -0
- package/dist/agentic/guides/modules/availability/domain-commands.md +13 -0
- package/dist/agentic/guides/modules/availability/entities.md +11 -0
- package/dist/agentic/guides/modules/availability/events.md +13 -0
- package/dist/agentic/guides/modules/availability/exact-override-targets.md +19 -0
- package/dist/agentic/guides/modules/availability/host-extension-points.md +10 -0
- package/dist/agentic/guides/modules/availability/index.md +22 -0
- package/dist/agentic/guides/modules/availability/owned-contract-module-metadata.md +11 -0
- package/dist/agentic/guides/modules/availability/setup.md +11 -0
- package/dist/agentic/guides/modules/availability/umes-hosts.md +14 -0
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/catalog/di-registrations-rich.md +1 -0
- package/dist/agentic/guides/modules/catalog/exact-override-targets.md +3 -0
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/workers.md +2 -0
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
- 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/channel_resend/index.md +1 -1
- package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/acl-features.md +2 -1
- package/dist/agentic/guides/modules/communication_channels/contribution-resolutions.md +1 -0
- package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +11 -10
- package/dist/agentic/guides/modules/communication_channels/domain-commands.md +1 -0
- package/dist/agentic/guides/modules/communication_channels/events.md +2 -1
- package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +12 -10
- package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +1 -0
- package/dist/agentic/guides/modules/communication_channels/index.md +2 -2
- package/dist/agentic/guides/modules/communication_channels/umes-contributions.md +1 -0
- package/dist/agentic/guides/modules/communication_channels/umes-hosts.md +1 -0
- 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/customer_groups/acl-features.md +16 -0
- package/dist/agentic/guides/modules/customer_groups/active-extension-bindings.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/backend-pages.md +14 -0
- package/dist/agentic/guides/modules/customer_groups/cli-commands.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/contribution-resolutions.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/di-registrations-rich.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/di-service-tokens.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/domain-commands.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/entities.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/events.md +17 -0
- package/dist/agentic/guides/modules/customer_groups/exact-override-targets.md +27 -0
- package/dist/agentic/guides/modules/customer_groups/index.md +26 -0
- package/dist/agentic/guides/modules/customer_groups/owned-contract-module-metadata.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/setup.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/umes-contributions.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/umes-hosts.md +21 -0
- package/dist/agentic/guides/modules/customers/acl-features.md +2 -1
- package/dist/agentic/guides/modules/customers/active-extension-bindings.md +8 -7
- package/dist/agentic/guides/modules/customers/contribution-resolutions.md +1 -0
- package/dist/agentic/guides/modules/customers/domain-commands.md +1 -0
- package/dist/agentic/guides/modules/customers/entities.md +1 -0
- package/dist/agentic/guides/modules/customers/events.md +2 -1
- package/dist/agentic/guides/modules/customers/exact-override-targets.md +2 -0
- package/dist/agentic/guides/modules/customers/index.md +3 -3
- package/dist/agentic/guides/modules/customers/umes-contributions.md +1 -0
- package/dist/agentic/guides/modules/customers/umes-hosts.md +3 -0
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- 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/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/directory/index.md +1 -1
- package/dist/agentic/guides/modules/documents/index.md +1 -1
- package/dist/agentic/guides/modules/entities/active-extension-bindings.md +1 -1
- 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/forms/acl-features.md +16 -0
- package/dist/agentic/guides/modules/forms/active-extension-bindings.md +14 -0
- package/dist/agentic/guides/modules/forms/backend-pages.md +17 -0
- package/dist/agentic/guides/modules/forms/contribution-resolutions.md +21 -0
- package/dist/agentic/guides/modules/forms/di-registrations-rich.md +27 -0
- package/dist/agentic/guides/modules/forms/di-service-tokens.md +19 -0
- package/dist/agentic/guides/modules/forms/domain-commands.md +30 -0
- package/dist/agentic/guides/modules/forms/encryption.md +11 -0
- package/dist/agentic/guides/modules/forms/entities.md +21 -0
- package/dist/agentic/guides/modules/forms/events.md +28 -0
- package/dist/agentic/guides/modules/forms/exact-override-targets.md +59 -0
- package/dist/agentic/guides/modules/forms/frontend-pages.md +16 -0
- package/dist/agentic/guides/modules/forms/index.md +28 -0
- package/dist/agentic/guides/modules/forms/owned-contract-module-metadata.md +11 -0
- package/dist/agentic/guides/modules/forms/setup.md +11 -0
- package/dist/agentic/guides/modules/forms/umes-contributions.md +21 -0
- package/dist/agentic/guides/modules/forms/umes-hosts.md +43 -0
- package/dist/agentic/guides/modules/forms/workers.md +12 -0
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/di-registrations-rich.md +8 -8
- package/dist/agentic/guides/modules/integrations/di-service-tokens.md +4 -4
- package/dist/agentic/guides/modules/integrations/exact-override-targets.md +8 -8
- 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/phone_calls/index.md +1 -1
- package/dist/agentic/guides/modules/planner/active-extension-bindings.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/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/sales/exact-override-targets.md +4 -4
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/sales/setup.md +1 -1
- package/dist/agentic/guides/modules/scheduler/index.md +1 -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/seeds/index.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/active-extension-bindings.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/active-extension-bindings.md +11 -0
- package/dist/agentic/guides/modules/sync_excel/index.md +3 -2
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/telemetry/index.md +1 -1
- package/dist/agentic/guides/modules/tillio/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/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/wms/contribution-resolutions.md +3 -0
- package/dist/agentic/guides/modules/wms/di-registrations-rich.md +8 -8
- package/dist/agentic/guides/modules/wms/exact-override-targets.md +11 -8
- package/dist/agentic/guides/modules/wms/index.md +3 -3
- package/dist/agentic/guides/modules/wms/umes-contributions.md +3 -0
- package/dist/agentic/guides/modules/workflows/active-extension-bindings.md +3 -3
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +1 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +73 -1
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.8.0-to-0.9.0/SKILL.md +112 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +1 -1
- package/dist/agentic/shared/ai/skills/tiers.json +2 -1
- package/dist/bin.js +2 -2
- package/dist/bin.js.map +2 -2
- package/dist/lib/db/commands.js +84 -5
- package/dist/lib/db/commands.js.map +2 -2
- package/dist/lib/db/encryption-backfill-check.js +112 -0
- package/dist/lib/db/encryption-backfill-check.js.map +7 -0
- package/dist/lib/db/migration-reindex.js +2 -1
- package/dist/lib/db/migration-reindex.js.map +2 -2
- package/dist/lib/events-single-delivery.js.map +2 -2
- package/dist/lib/flush-before-exit.js +19 -0
- package/dist/lib/flush-before-exit.js.map +7 -0
- package/dist/lib/generate-watch-structure.js +113 -19
- package/dist/lib/generate-watch-structure.js.map +3 -3
- package/dist/lib/generators/module-registry.js +4 -2
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/generators/web-research-adapters.js +32 -11
- package/dist/lib/generators/web-research-adapters.js.map +2 -2
- package/dist/lib/telemetry-init.js +69 -11
- package/dist/lib/telemetry-init.js.map +3 -3
- package/dist/lib/testing/integration.js +29 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/package.json +8 -8
- package/src/bin.ts +4 -4
- package/src/lib/__tests__/flush-before-exit.test.ts +32 -0
- package/src/lib/__tests__/generate-watch-structure.test.ts +261 -3
- package/src/lib/__tests__/telemetry-init.test.ts +89 -0
- package/src/lib/db/__tests__/commands.test.ts +43 -0
- package/src/lib/db/__tests__/dynamic-import-specifier.test.ts +36 -0
- package/src/lib/db/__tests__/encryption-backfill-check.test.ts +146 -0
- package/src/lib/db/__tests__/migration-reindex.test.ts +48 -3
- package/src/lib/db/commands.ts +113 -10
- package/src/lib/db/encryption-backfill-check.ts +163 -0
- package/src/lib/db/migration-reindex.ts +2 -1
- package/src/lib/events-single-delivery.ts +5 -2
- package/src/lib/flush-before-exit.ts +39 -0
- package/src/lib/generate-watch-structure.ts +134 -19
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +14 -3
- package/src/lib/generators/__tests__/module-package-sources.test.ts +2 -2
- package/src/lib/generators/__tests__/registry-variant-parity.test.ts +16 -0
- package/src/lib/generators/__tests__/web-research-adapters.test.ts +141 -0
- package/src/lib/generators/module-registry.ts +2 -0
- package/src/lib/generators/web-research-adapters.ts +41 -20
- package/src/lib/telemetry-init.ts +68 -11
- package/src/lib/testing/__tests__/integration.test.ts +26 -0
- package/src/lib/testing/integration.ts +32 -2
|
@@ -4,6 +4,7 @@ import path from 'node:path'
|
|
|
4
4
|
import {
|
|
5
5
|
MODULE_CODE_EXTENSIONS,
|
|
6
6
|
SCAN_CONFIGS,
|
|
7
|
+
resolveStandaloneSourceMirrorBase,
|
|
7
8
|
scanModuleDir,
|
|
8
9
|
stripModuleCodeExtension,
|
|
9
10
|
type ModuleRoots,
|
|
@@ -66,6 +67,13 @@ function checksum(value: string): string {
|
|
|
66
67
|
return crypto.createHash('md5').update(value).digest('hex')
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
function moduleRootIdentity(roots: ModuleRoots): string {
|
|
71
|
+
return JSON.stringify([
|
|
72
|
+
roots.pkgBase.replace(/\\/g, '/'),
|
|
73
|
+
roots.appBase.replace(/\\/g, '/'),
|
|
74
|
+
])
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
function fileRecord(filePath: string, base: string, mode: 'content' | 'shape'): string | null {
|
|
70
78
|
if (!fs.existsSync(filePath)) return null
|
|
71
79
|
let stat: fs.Stats
|
|
@@ -86,6 +94,17 @@ function fileRecord(filePath: string, base: string, mode: 'content' | 'shape'):
|
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
|
|
97
|
+
function addFileRecord(
|
|
98
|
+
records: string[],
|
|
99
|
+
prefix: string,
|
|
100
|
+
filePath: string,
|
|
101
|
+
base: string,
|
|
102
|
+
mode: 'content' | 'shape',
|
|
103
|
+
): void {
|
|
104
|
+
const record = fileRecord(filePath, base, mode)
|
|
105
|
+
if (record) records.push(`${prefix}:${record}`)
|
|
106
|
+
}
|
|
107
|
+
|
|
89
108
|
function resolveCodeFile(base: string, relativePath: string): string | null {
|
|
90
109
|
const stripped = stripModuleCodeExtension(relativePath)
|
|
91
110
|
const candidates = MODULE_CODE_EXTENSIONS.map((extension) => `${stripped}${extension}`)
|
|
@@ -96,6 +115,16 @@ function resolveCodeFile(base: string, relativePath: string): string | null {
|
|
|
96
115
|
return null
|
|
97
116
|
}
|
|
98
117
|
|
|
118
|
+
function resolveRuntimeCounterpart(
|
|
119
|
+
pkgBase: string,
|
|
120
|
+
sourceBase: string,
|
|
121
|
+
sourceFile: string,
|
|
122
|
+
): string | null {
|
|
123
|
+
if (sourceBase === pkgBase) return null
|
|
124
|
+
const relativePath = path.relative(sourceBase, sourceFile).replace(/\\/g, '/')
|
|
125
|
+
return resolveCodeFile(pkgBase, relativePath)
|
|
126
|
+
}
|
|
127
|
+
|
|
99
128
|
function hasInlinePageMetadata(filePath: string): boolean {
|
|
100
129
|
try {
|
|
101
130
|
const source = fs.readFileSync(filePath, 'utf8')
|
|
@@ -107,48 +136,131 @@ function hasInlinePageMetadata(filePath: string): boolean {
|
|
|
107
136
|
}
|
|
108
137
|
|
|
109
138
|
function addConventionRecords(records: string[], roots: ModuleRoots): void {
|
|
139
|
+
const packageSourceBase = resolveStandaloneSourceMirrorBase(roots.pkgBase) ?? roots.pkgBase
|
|
110
140
|
for (const base of [roots.pkgBase, roots.appBase]) {
|
|
111
141
|
records.push(`module-root:${base}:${fs.existsSync(base) ? 'present' : 'missing'}`)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
}
|
|
143
|
+
if (packageSourceBase !== roots.pkgBase) {
|
|
144
|
+
records.push(
|
|
145
|
+
`module-root:${packageSourceBase}:${fs.existsSync(packageSourceBase) ? 'present' : 'missing'}`,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const relativePath of STRUCTURAL_CONVENTION_FILES) {
|
|
150
|
+
const appFile = resolveCodeFile(roots.appBase, relativePath)
|
|
151
|
+
if (appFile) {
|
|
152
|
+
addFileRecord(records, 'convention:app', appFile, roots.appBase, 'content')
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const sourceFile = resolveCodeFile(packageSourceBase, relativePath)
|
|
157
|
+
if (!sourceFile) {
|
|
158
|
+
const runtimeFile = packageSourceBase === roots.pkgBase
|
|
159
|
+
? null
|
|
160
|
+
: resolveCodeFile(roots.pkgBase, relativePath)
|
|
161
|
+
if (runtimeFile) {
|
|
162
|
+
addFileRecord(records, 'convention:package-runtime', runtimeFile, roots.pkgBase, 'content')
|
|
163
|
+
} else {
|
|
164
|
+
records.push(`convention:missing:${relativePath}`)
|
|
117
165
|
}
|
|
118
|
-
|
|
119
|
-
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const packagePrefix = packageSourceBase === roots.pkgBase ? 'package' : 'package-source'
|
|
170
|
+
addFileRecord(records, `convention:${packagePrefix}`, sourceFile, packageSourceBase, 'content')
|
|
171
|
+
const runtimeFile = resolveRuntimeCounterpart(roots.pkgBase, packageSourceBase, sourceFile)
|
|
172
|
+
if (runtimeFile) {
|
|
173
|
+
addFileRecord(records, 'convention:package-runtime', runtimeFile, roots.pkgBase, 'content')
|
|
174
|
+
} else if (packageSourceBase !== roots.pkgBase) {
|
|
175
|
+
records.push(`convention:package-runtime:missing:${relativePath}`)
|
|
120
176
|
}
|
|
121
177
|
}
|
|
122
178
|
}
|
|
123
179
|
|
|
124
180
|
function addScannedRecords(records: string[], roots: ModuleRoots): void {
|
|
181
|
+
const packageSourceBase = resolveStandaloneSourceMirrorBase(roots.pkgBase) ?? roots.pkgBase
|
|
125
182
|
for (const config of CONTENT_SENSITIVE_SCAN_CONFIGS) {
|
|
126
183
|
for (const scanned of scanModuleDir(roots, config)) {
|
|
127
|
-
const base = scanned.fromApp ? roots.appBase :
|
|
184
|
+
const base = scanned.fromApp ? roots.appBase : packageSourceBase
|
|
128
185
|
const filePath = path.join(base, ...config.folder.split('/'), ...scanned.relPath.split('/'))
|
|
129
|
-
|
|
130
|
-
if (
|
|
186
|
+
let sourceOrigin = 'app'
|
|
187
|
+
if (!scanned.fromApp) {
|
|
188
|
+
sourceOrigin = packageSourceBase === roots.pkgBase ? 'package' : 'package-source'
|
|
189
|
+
}
|
|
190
|
+
addFileRecord(records, `${config.folder}:${sourceOrigin}`, filePath, base, 'content')
|
|
191
|
+
|
|
192
|
+
if (!scanned.fromApp && packageSourceBase !== roots.pkgBase) {
|
|
193
|
+
const runtimeFile = resolveRuntimeCounterpart(roots.pkgBase, packageSourceBase, filePath)
|
|
194
|
+
if (runtimeFile) {
|
|
195
|
+
addFileRecord(
|
|
196
|
+
records,
|
|
197
|
+
`${config.folder}:package-runtime`,
|
|
198
|
+
runtimeFile,
|
|
199
|
+
roots.pkgBase,
|
|
200
|
+
'content',
|
|
201
|
+
)
|
|
202
|
+
} else {
|
|
203
|
+
records.push(`${config.folder}:package-runtime:missing:${scanned.relPath}`)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
131
206
|
}
|
|
132
207
|
}
|
|
133
208
|
|
|
134
209
|
for (const config of ROUTE_SHAPE_SCAN_CONFIGS) {
|
|
135
210
|
for (const scanned of scanModuleDir(roots, config)) {
|
|
136
|
-
const base = scanned.fromApp ? roots.appBase :
|
|
211
|
+
const base = scanned.fromApp ? roots.appBase : packageSourceBase
|
|
137
212
|
const folderPath = path.join(base, ...config.folder.split('/'))
|
|
138
213
|
const filePath = path.join(folderPath, ...scanned.relPath.split('/'))
|
|
139
|
-
|
|
140
|
-
if (
|
|
214
|
+
let sourceOrigin = 'app'
|
|
215
|
+
if (!scanned.fromApp) {
|
|
216
|
+
sourceOrigin = packageSourceBase === roots.pkgBase ? 'package' : 'package-source'
|
|
217
|
+
}
|
|
218
|
+
addFileRecord(
|
|
219
|
+
records,
|
|
220
|
+
`${config.folder}:${sourceOrigin}`,
|
|
221
|
+
filePath,
|
|
222
|
+
base,
|
|
223
|
+
hasInlinePageMetadata(filePath) ? 'content' : 'shape',
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
let runtimeFile: string | null = null
|
|
227
|
+
if (!scanned.fromApp && packageSourceBase !== roots.pkgBase) {
|
|
228
|
+
runtimeFile = resolveRuntimeCounterpart(roots.pkgBase, packageSourceBase, filePath)
|
|
229
|
+
if (runtimeFile) {
|
|
230
|
+
addFileRecord(
|
|
231
|
+
records,
|
|
232
|
+
`${config.folder}:package-runtime`,
|
|
233
|
+
runtimeFile,
|
|
234
|
+
roots.pkgBase,
|
|
235
|
+
hasInlinePageMetadata(runtimeFile) ? 'content' : 'shape',
|
|
236
|
+
)
|
|
237
|
+
} else {
|
|
238
|
+
records.push(`${config.folder}:package-runtime:missing:${scanned.relPath}`)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
141
241
|
|
|
142
|
-
const dir = path.dirname(filePath)
|
|
143
242
|
const stem = stripModuleCodeExtension(path.basename(filePath))
|
|
144
243
|
const metaCandidates = stem === 'page'
|
|
145
244
|
? ['page.meta', 'meta']
|
|
146
245
|
: [`${stem}.meta`, 'meta']
|
|
147
246
|
for (const candidate of metaCandidates) {
|
|
148
|
-
const metaPath = resolveCodeFile(
|
|
247
|
+
const metaPath = resolveCodeFile(path.dirname(filePath), candidate)
|
|
149
248
|
if (!metaPath) continue
|
|
150
|
-
|
|
151
|
-
|
|
249
|
+
addFileRecord(records, `${config.folder}:meta:${sourceOrigin}`, metaPath, base, 'content')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (runtimeFile) {
|
|
253
|
+
for (const candidate of metaCandidates) {
|
|
254
|
+
const runtimeMetaPath = resolveCodeFile(path.dirname(runtimeFile), candidate)
|
|
255
|
+
if (!runtimeMetaPath) continue
|
|
256
|
+
addFileRecord(
|
|
257
|
+
records,
|
|
258
|
+
`${config.folder}:meta:package-runtime`,
|
|
259
|
+
runtimeMetaPath,
|
|
260
|
+
roots.pkgBase,
|
|
261
|
+
'content',
|
|
262
|
+
)
|
|
263
|
+
}
|
|
152
264
|
}
|
|
153
265
|
}
|
|
154
266
|
}
|
|
@@ -163,8 +275,11 @@ export function calculateGenerateWatchStructureChecksum(options: {
|
|
|
163
275
|
records.push(modulesRecord ?? `missing:${options.modulesFile}`)
|
|
164
276
|
|
|
165
277
|
for (const roots of options.moduleRoots) {
|
|
166
|
-
|
|
167
|
-
|
|
278
|
+
const moduleRecords: string[] = []
|
|
279
|
+
addConventionRecords(moduleRecords, roots)
|
|
280
|
+
addScannedRecords(moduleRecords, roots)
|
|
281
|
+
const rootIdentity = moduleRootIdentity(roots)
|
|
282
|
+
records.push(...moduleRecords.map((record) => `${rootIdentity}:${record}`))
|
|
168
283
|
}
|
|
169
284
|
|
|
170
285
|
return checksum(records.sort((a, b) => a.localeCompare(b)).join('\n'))
|
|
@@ -18,7 +18,11 @@ describe('module-facts customers fixture (T1 anti-drift guard)', () => {
|
|
|
18
18
|
|
|
19
19
|
it('locks the source-derived customers entity surface in colon-form ids', () => {
|
|
20
20
|
expect(facts.module).toBe('customers')
|
|
21
|
-
|
|
21
|
+
// 26 since the email conversation-share feature added
|
|
22
|
+
// `CustomerEmailConversationShare` (the read grant backing owner-controlled
|
|
23
|
+
// conversation sharing). Bump deliberately: this guard exists to make an
|
|
24
|
+
// entity-surface change visible, not to be silenced.
|
|
25
|
+
expect(facts.entities).toHaveLength(26)
|
|
22
26
|
expect(facts.entities.every((entity) => entity.id.startsWith('customers:'))).toBe(true)
|
|
23
27
|
expect(facts.entities[0]).toMatchObject({ id: 'customers:customer_entity' })
|
|
24
28
|
for (const entity of facts.entities) {
|
|
@@ -31,8 +35,15 @@ describe('module-facts customers fixture (T1 anti-drift guard)', () => {
|
|
|
31
35
|
// 50 since #5114 added `customers.person.company_assignment.detached` — the profile-only
|
|
32
36
|
// sibling of the person_company_link CRUD events. Bump deliberately: this guard exists to
|
|
33
37
|
// make an event-surface change visible, not to be silenced.
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
//
|
|
39
|
+
// 51 since the email conversation-share feature added
|
|
40
|
+
// `customers.email.conversation_visibility_changed` (audit trail for the
|
|
41
|
+
// owner-controlled share/un-share act), and 22 ACL features since it added
|
|
42
|
+
// `customers.email.share_conversation` (owner-scoped: it only ever lets you
|
|
43
|
+
// share your OWN conversation, so the existing `customers.*` admin wildcard
|
|
44
|
+
// grants no escalation).
|
|
45
|
+
expect(facts.events).toHaveLength(51)
|
|
46
|
+
expect(facts.aclFeatures).toHaveLength(22)
|
|
36
47
|
expect(facts.searchEntities).toEqual([
|
|
37
48
|
'customers:customer_person_profile',
|
|
38
49
|
'customers:customer_company_profile',
|
|
@@ -86,7 +86,7 @@ describe('generateModulePackageSources', () => {
|
|
|
86
86
|
await generateModulePackageSources({ resolver, quiet: true })
|
|
87
87
|
const outputBefore = fs.readFileSync(outFile, 'utf8')
|
|
88
88
|
const checksumBefore = readChecksumRecord(checksumFile)
|
|
89
|
-
const pinnedTime = new Date(
|
|
89
|
+
const pinnedTime = new Date('2020-01-01T00:00:00.000Z')
|
|
90
90
|
fs.utimesSync(outFile, pinnedTime, pinnedTime)
|
|
91
91
|
fs.utimesSync(checksumFile, pinnedTime, pinnedTime)
|
|
92
92
|
|
|
@@ -121,7 +121,7 @@ describe('generateModulePackageSources', () => {
|
|
|
121
121
|
await generateModulePackageSources({ resolver: initialResolver, quiet: true })
|
|
122
122
|
const outputBefore = fs.readFileSync(outFile, 'utf8')
|
|
123
123
|
const initialStructure = readChecksumRecord(checksumFile)?.structure
|
|
124
|
-
const pinnedTime = new Date(
|
|
124
|
+
const pinnedTime = new Date('2020-01-01T00:00:00.000Z')
|
|
125
125
|
fs.utimesSync(outFile, pinnedTime, pinnedTime)
|
|
126
126
|
|
|
127
127
|
await generateModulePackageSources({ resolver: alternateResolver, quiet: true })
|
|
@@ -263,6 +263,22 @@ describe('module registry variant parity', () => {
|
|
|
263
263
|
expect(carriesRuntime).toEqual(registryFiles.map((fileName) => [fileName, true, false]))
|
|
264
264
|
})
|
|
265
265
|
|
|
266
|
+
// The IIFE `namespaceFallback` emits narrows `unknown` to `{}`. That satisfies `setup`, whose
|
|
267
|
+
// every member is optional, but `ModuleRuntime.start` is required — so without a cast the three
|
|
268
|
+
// namespace-lookup registries fail `tsc` the moment any module ships a runtime.ts. `generate`
|
|
269
|
+
// stays green either way, which is what let this ship.
|
|
270
|
+
it('casts the emitted runtime, so its required `start` does not break typecheck', () => {
|
|
271
|
+
const namespaceLookupRegistries = [
|
|
272
|
+
'modules.app.generated.ts',
|
|
273
|
+
'modules.bootstrap.generated.ts',
|
|
274
|
+
'modules.cli.generated.ts',
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
for (const fileName of namespaceLookupRegistries) {
|
|
278
|
+
expect(readGenerated(fileName)).toContain("as Module['runtime']")
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
|
|
266
282
|
describe('known, intentional divergences', () => {
|
|
267
283
|
it('only the main variant carries eager api handlers and the cli entry point', () => {
|
|
268
284
|
expect(mainVariant.get('orders')!.has('apis')).toBe(true)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import ts from 'typescript-js'
|
|
6
|
+
import type { PackageResolver } from '../../resolver'
|
|
7
|
+
import { generateWebResearchAdapters } from '../web-research-adapters'
|
|
8
|
+
|
|
9
|
+
let tmpRoot: string
|
|
10
|
+
let appDir: string
|
|
11
|
+
let outputDir: string
|
|
12
|
+
|
|
13
|
+
function createMockResolver(): PackageResolver {
|
|
14
|
+
return {
|
|
15
|
+
getAppDir: () => appDir,
|
|
16
|
+
getOutputDir: () => outputDir,
|
|
17
|
+
} as unknown as PackageResolver
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeAdapterPackage(
|
|
21
|
+
packageDir: string,
|
|
22
|
+
packageName: string,
|
|
23
|
+
adapterId: string,
|
|
24
|
+
source = `export const id = '${adapterId}'\n`,
|
|
25
|
+
): void {
|
|
26
|
+
fs.mkdirSync(packageDir, { recursive: true })
|
|
27
|
+
fs.writeFileSync(
|
|
28
|
+
path.join(packageDir, 'package.json'),
|
|
29
|
+
JSON.stringify({
|
|
30
|
+
name: packageName,
|
|
31
|
+
type: 'module',
|
|
32
|
+
exports: './index.js',
|
|
33
|
+
openMercato: { webResearchAdapter: { id: adapterId } },
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
fs.writeFileSync(path.join(packageDir, 'index.js'), source)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeWorkspaceOnlyAdapter(directoryName: string, packageName: string, adapterId: string): void {
|
|
40
|
+
writeAdapterPackage(path.join(tmpRoot, 'packages', directoryName), packageName, adapterId)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeInstalledAdapter(packageName: string, adapterId: string, source?: string): void {
|
|
44
|
+
writeAdapterPackage(path.join(tmpRoot, 'node_modules', ...packageName.split('/')), packageName, adapterId, source)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function generate(): Promise<string> {
|
|
48
|
+
await generateWebResearchAdapters({ resolver: createMockResolver(), quiet: true })
|
|
49
|
+
return fs.readFileSync(path.join(outputDir, 'web-research-adapters.generated.ts'), 'utf8')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function runGeneratedRegistry(content: string): { status: number | null; stdout: string; stderr: string } {
|
|
53
|
+
const compiled = ts.transpileModule(content, {
|
|
54
|
+
compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
|
|
55
|
+
}).outputText
|
|
56
|
+
const registryFile = path.join(outputDir, 'web-research-adapters.generated.mjs')
|
|
57
|
+
fs.writeFileSync(registryFile, compiled)
|
|
58
|
+
const script = [
|
|
59
|
+
`const { webResearchAdapterEntries } = await import(${JSON.stringify(registryFile)})`,
|
|
60
|
+
'console.log(JSON.stringify(webResearchAdapterEntries.map((entry) => [entry.packageName, entry.module.id])))',
|
|
61
|
+
].join('\n')
|
|
62
|
+
const result = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
|
|
63
|
+
cwd: appDir,
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
})
|
|
66
|
+
return { status: result.status, stdout: result.stdout.trim(), stderr: result.stderr }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
tmpRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'web-research-adapters-test-')))
|
|
71
|
+
appDir = path.join(tmpRoot, 'apps', 'mercato')
|
|
72
|
+
outputDir = path.join(appDir, '.mercato', 'generated')
|
|
73
|
+
fs.mkdirSync(outputDir, { recursive: true })
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('generateWebResearchAdapters', () => {
|
|
81
|
+
it('emits an empty registry when no adapter package is discovered', async () => {
|
|
82
|
+
const content = await generate()
|
|
83
|
+
|
|
84
|
+
expect(content).toContain('export const webResearchAdapterEntries: AdapterRegistryEntry[] = []')
|
|
85
|
+
expect(content).not.toMatch(/import\(/)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('imports every discovered adapter with a literal specifier and no static import', async () => {
|
|
89
|
+
writeInstalledAdapter('@acme/web-research-installed', 'installed')
|
|
90
|
+
writeWorkspaceOnlyAdapter('web-research-workspace-only', '@acme/web-research-workspace-only', 'workspace-only')
|
|
91
|
+
|
|
92
|
+
const content = await generate()
|
|
93
|
+
|
|
94
|
+
expect(content).toContain("import('@acme/web-research-installed')")
|
|
95
|
+
expect(content).toContain("import('@acme/web-research-workspace-only')")
|
|
96
|
+
expect(content).not.toMatch(/^import \* as /m)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('loads installed adapters and skips workspace packages the app cannot resolve', async () => {
|
|
100
|
+
writeInstalledAdapter('@acme/web-research-installed', 'installed')
|
|
101
|
+
writeWorkspaceOnlyAdapter('web-research-workspace-only', '@acme/web-research-workspace-only', 'workspace-only')
|
|
102
|
+
|
|
103
|
+
const result = runGeneratedRegistry(await generate())
|
|
104
|
+
|
|
105
|
+
expect(result.stderr).toBe('')
|
|
106
|
+
expect(result.status).toBe(0)
|
|
107
|
+
expect(JSON.parse(result.stdout)).toEqual([['@acme/web-research-installed', 'installed']])
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('still surfaces an installed adapter that fails while loading', async () => {
|
|
111
|
+
writeInstalledAdapter('@acme/web-research-broken', 'broken', "throw new Error('adapter exploded')\n")
|
|
112
|
+
|
|
113
|
+
const result = runGeneratedRegistry(await generate())
|
|
114
|
+
|
|
115
|
+
expect(result.status).not.toBe(0)
|
|
116
|
+
expect(result.stderr).toContain('adapter exploded')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('still surfaces an installed adapter whose own dependency is missing', async () => {
|
|
120
|
+
writeInstalledAdapter(
|
|
121
|
+
'@acme/web-research-needs-dep',
|
|
122
|
+
'needs-dep',
|
|
123
|
+
"import '@acme/missing-adapter-dependency'\nexport const id = 'needs-dep'\n",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const result = runGeneratedRegistry(await generate())
|
|
127
|
+
|
|
128
|
+
expect(result.status).not.toBe(0)
|
|
129
|
+
expect(result.stderr).toContain('@acme/missing-adapter-dependency')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('still surfaces an installed adapter whose entry file is missing', async () => {
|
|
133
|
+
writeInstalledAdapter('@acme/web-research-unbuilt', 'unbuilt')
|
|
134
|
+
fs.rmSync(path.join(tmpRoot, 'node_modules', '@acme', 'web-research-unbuilt', 'index.js'))
|
|
135
|
+
|
|
136
|
+
const result = runGeneratedRegistry(await generate())
|
|
137
|
+
|
|
138
|
+
expect(result.status).not.toBe(0)
|
|
139
|
+
expect(result.stderr).toMatch(/Cannot find module/)
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -4624,6 +4624,7 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
|
|
|
4624
4624
|
importName: moduleRuntimeImportName,
|
|
4625
4625
|
members: ['default', 'runtime'],
|
|
4626
4626
|
fallback: identifier('undefined'),
|
|
4627
|
+
castType: "Module['runtime']",
|
|
4627
4628
|
}),
|
|
4628
4629
|
})
|
|
4629
4630
|
}
|
|
@@ -5035,6 +5036,7 @@ async function generateModuleRegistryCliFromDiscovery(options: ModuleRegistryRen
|
|
|
5035
5036
|
importName: moduleRuntimeImportName,
|
|
5036
5037
|
members: ['default', 'runtime'],
|
|
5037
5038
|
fallback: identifier('undefined'),
|
|
5039
|
+
castType: "Module['runtime']",
|
|
5038
5040
|
}),
|
|
5039
5041
|
})
|
|
5040
5042
|
}
|
|
@@ -67,14 +67,48 @@ function scanDirectory(root: string, results: Map<string, DiscoveredAdapter>): v
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function
|
|
71
|
-
return
|
|
70
|
+
function renderEntries(adapters: readonly DiscoveredAdapter[]): string {
|
|
71
|
+
if (adapters.length === 0) return 'export const webResearchAdapterEntries: AdapterRegistryEntry[] = []\n'
|
|
72
|
+
const loaders = adapters
|
|
73
|
+
.map(
|
|
74
|
+
(adapter) =>
|
|
75
|
+
` loadAdapterEntry('${adapter.packageName}', () => import('${adapter.packageName}')),`,
|
|
76
|
+
)
|
|
77
|
+
.join('\n')
|
|
78
|
+
return `const MISSING_MODULE_CODES = new Set(['ERR_MODULE_NOT_FOUND', 'MODULE_NOT_FOUND'])
|
|
79
|
+
|
|
80
|
+
async function loadAdapterEntry(
|
|
81
|
+
packageName: string,
|
|
82
|
+
load: () => Promise<unknown>,
|
|
83
|
+
): Promise<AdapterRegistryEntry | null> {
|
|
84
|
+
try {
|
|
85
|
+
return { packageName, module: await load() }
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const { code, message } = (error ?? {}) as { code?: unknown; message?: unknown }
|
|
88
|
+
const isMissingCode = typeof code === 'string' && MISSING_MODULE_CODES.has(code)
|
|
89
|
+
const namesThisPackage = typeof message === 'string' && message.includes(\`'\${packageName}'\`)
|
|
90
|
+
if (isMissingCode && namesThisPackage) return null
|
|
91
|
+
throw error
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const loadedEntries = await Promise.all([
|
|
96
|
+
${loaders}
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
export const webResearchAdapterEntries: AdapterRegistryEntry[] = loadedEntries.filter(
|
|
100
|
+
(entry): entry is AdapterRegistryEntry => entry !== null,
|
|
101
|
+
)
|
|
102
|
+
`
|
|
72
103
|
}
|
|
73
104
|
|
|
74
105
|
/**
|
|
75
|
-
* Emits the
|
|
76
|
-
*
|
|
77
|
-
*
|
|
106
|
+
* Emits the adapter registry. Discovery happens here, at build time, because a
|
|
107
|
+
* bundler cannot follow `import()` with a runtime-computed specifier — a dynamic
|
|
108
|
+
* registry would resolve to nothing under Turbopack. Each adapter is imported
|
|
109
|
+
* with a literal specifier and a package that is not installed where the
|
|
110
|
+
* registry runs is skipped: discovery scans the whole workspace, but a process
|
|
111
|
+
* such as a worker in a production image only has the app's own dependencies.
|
|
78
112
|
*/
|
|
79
113
|
export async function generateWebResearchAdapters(
|
|
80
114
|
options: WebResearchAdaptersOptions,
|
|
@@ -99,26 +133,13 @@ export async function generateWebResearchAdapters(
|
|
|
99
133
|
left.packageName.localeCompare(right.packageName),
|
|
100
134
|
)
|
|
101
135
|
|
|
102
|
-
const imports = adapters
|
|
103
|
-
.map((adapter, index) => `import * as ${identifierFor(index)} from '${adapter.packageName}'`)
|
|
104
|
-
.join('\n')
|
|
105
|
-
const entries = adapters
|
|
106
|
-
.map(
|
|
107
|
-
(adapter, index) =>
|
|
108
|
-
` { packageName: '${adapter.packageName}', module: ${identifierFor(index)} },`,
|
|
109
|
-
)
|
|
110
|
-
.join('\n')
|
|
111
|
-
|
|
112
136
|
const content = `// AUTO-GENERATED — do not edit by hand.
|
|
113
137
|
// Source: packages declaring \`${MANIFEST_NAMESPACE}.${ADAPTER_MANIFEST_KEY}\` in package.json.
|
|
114
138
|
// Regenerate with: yarn generate
|
|
139
|
+
// Adapter packages that are not installed where this registry runs are skipped.
|
|
115
140
|
import type { AdapterRegistryEntry } from '@open-mercato/web-research'
|
|
116
|
-
${imports}
|
|
117
141
|
|
|
118
|
-
|
|
119
|
-
${entries}
|
|
120
|
-
]
|
|
121
|
-
`
|
|
142
|
+
${renderEntries(adapters)}`
|
|
122
143
|
|
|
123
144
|
writeGeneratedFile({
|
|
124
145
|
outFile,
|
|
@@ -102,17 +102,40 @@ export async function register(): Promise<void> {
|
|
|
102
102
|
// Initialize telemetry (no-op unless TELEMETRY_BACKEND is set). OTEL's NodeSDK
|
|
103
103
|
// is Node-only and incompatible with the edge runtime, so the telemetry
|
|
104
104
|
// bootstrap — which can pull in the SDK — is imported only on the Node.js
|
|
105
|
-
// runtime. The helper owns init + graceful degrade + shutdown flush
|
|
105
|
+
// runtime. The helper owns init + graceful degrade + shutdown flush; a
|
|
106
|
+
// deliberate configuration failure bubbles here so the host exits loudly.
|
|
106
107
|
if (
|
|
107
108
|
process.env.NEXT_RUNTIME === 'nodejs'
|
|
108
109
|
&& isTelemetryBackendEnabled()
|
|
109
110
|
) {
|
|
110
111
|
const { registerTelemetryForNextjs } = await import('@open-mercato/telemetry/nextjs')
|
|
111
|
-
|
|
112
|
+
try {
|
|
113
|
+
await registerTelemetryForNextjs()
|
|
114
|
+
} catch (err) {
|
|
115
|
+
const nodeProcess = process
|
|
116
|
+
nodeProcess.stderr.write(\`\${err instanceof Error ? err.message : String(err)}\\n\`)
|
|
117
|
+
nodeProcess.exit(1)
|
|
118
|
+
}
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
`
|
|
115
122
|
|
|
123
|
+
const MULTILINE_TELEMETRY_GUARD = ` if (
|
|
124
|
+
process.env.NEXT_RUNTIME === 'nodejs'
|
|
125
|
+
&& isTelemetryBackendEnabled()
|
|
126
|
+
) {`
|
|
127
|
+
const INLINE_TELEMETRY_GUARD = ` if (process.env.NEXT_RUNTIME === 'nodejs' && isTelemetryBackendEnabled()) {`
|
|
128
|
+
const LEGACY_NODE_TELEMETRY_GUARD = ` if (process.env.NEXT_RUNTIME === 'nodejs') {`
|
|
129
|
+
const TELEMETRY_DYNAMIC_IMPORT = ` const { registerTelemetryForNextjs } = await import('@open-mercato/telemetry/nextjs')`
|
|
130
|
+
const BARE_TELEMETRY_REGISTRATION = ` await registerTelemetryForNextjs()`
|
|
131
|
+
const GUARDED_TELEMETRY_REGISTRATION = ` try {
|
|
132
|
+
await registerTelemetryForNextjs()
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const nodeProcess = process
|
|
135
|
+
nodeProcess.stderr.write(\`\${err instanceof Error ? err.message : String(err)}\\n\`)
|
|
136
|
+
nodeProcess.exit(1)
|
|
137
|
+
}`
|
|
138
|
+
|
|
116
139
|
const DISPATCHER_SNIPPET = ` // add near the other imports:
|
|
117
140
|
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
118
141
|
|
|
@@ -331,17 +354,45 @@ function patchInstrumentation(appDir: string, options: TelemetryInitOptions): St
|
|
|
331
354
|
}
|
|
332
355
|
const content = readFileSync(path, 'utf8')
|
|
333
356
|
if (content.includes('registerTelemetryForNextjs')) {
|
|
334
|
-
|
|
357
|
+
const guardedBootstraps = [MULTILINE_TELEMETRY_GUARD, INLINE_TELEMETRY_GUARD]
|
|
358
|
+
const safeBootstrap = guardedBootstraps
|
|
359
|
+
.map((guard) => `${guard}\n${TELEMETRY_DYNAMIC_IMPORT}\n${GUARDED_TELEMETRY_REGISTRATION}`)
|
|
360
|
+
.find((bootstrap) => content.includes(bootstrap))
|
|
361
|
+
if (safeBootstrap) {
|
|
335
362
|
return { file, status: 'skipped', detail: 'telemetry already wired' }
|
|
336
363
|
}
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
content.
|
|
340
|
-
|
|
341
|
-
|
|
364
|
+
const guardedLegacyBootstrap = guardedBootstraps
|
|
365
|
+
.map((guard) => `${guard}\n${TELEMETRY_DYNAMIC_IMPORT}\n${BARE_TELEMETRY_REGISTRATION}`)
|
|
366
|
+
.find((bootstrap) => content.includes(bootstrap))
|
|
367
|
+
if (guardedLegacyBootstrap) {
|
|
368
|
+
const migrated = content.replace(
|
|
369
|
+
guardedLegacyBootstrap,
|
|
370
|
+
guardedLegacyBootstrap.replace(BARE_TELEMETRY_REGISTRATION, GUARDED_TELEMETRY_REGISTRATION),
|
|
342
371
|
)
|
|
343
|
-
|
|
344
|
-
|
|
372
|
+
if (!options.dryRun) writeFileSync(path, migrated)
|
|
373
|
+
return { file, status: 'patched', detail: 'wrapped OTLP dependency failures with host termination' }
|
|
374
|
+
}
|
|
375
|
+
const nodeOnlyLegacyBootstrap = `${LEGACY_NODE_TELEMETRY_GUARD}\n${TELEMETRY_DYNAMIC_IMPORT}\n${BARE_TELEMETRY_REGISTRATION}`
|
|
376
|
+
if (content.includes(nodeOnlyLegacyBootstrap)) {
|
|
377
|
+
const guardedBootstrap = nodeOnlyLegacyBootstrap
|
|
378
|
+
.replace(LEGACY_NODE_TELEMETRY_GUARD, INLINE_TELEMETRY_GUARD)
|
|
379
|
+
.replace(BARE_TELEMETRY_REGISTRATION, GUARDED_TELEMETRY_REGISTRATION)
|
|
380
|
+
const importLine = `import { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\n`
|
|
381
|
+
const withBootstrap = content.replace(nodeOnlyLegacyBootstrap, guardedBootstrap)
|
|
382
|
+
const migrated = withBootstrap.includes(importLine) ? withBootstrap : importLine + withBootstrap
|
|
383
|
+
if (!options.dryRun) writeFileSync(path, migrated)
|
|
384
|
+
return {
|
|
385
|
+
file,
|
|
386
|
+
status: 'patched',
|
|
387
|
+
detail: 'added the explicit backend guard and wrapped OTLP dependency failures with host termination',
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
file,
|
|
392
|
+
status: 'manual',
|
|
393
|
+
detail: 'existing telemetry bootstrap needs the canonical Node guard and fail-closed host wrapper',
|
|
394
|
+
manualSnippet: INSTRUMENTATION_TS,
|
|
395
|
+
}
|
|
345
396
|
}
|
|
346
397
|
const anchor = content.match(/export\s+async\s+function\s+register\s*\([^)]*\)\s*:\s*Promise<void>\s*\{/)
|
|
347
398
|
if (!anchor || anchor.index === undefined) {
|
|
@@ -357,7 +408,13 @@ function patchInstrumentation(appDir: string, options: TelemetryInitOptions): St
|
|
|
357
408
|
`\n // Initialize telemetry (no-op unless TELEMETRY_BACKEND is set); Node-only.` +
|
|
358
409
|
`\n if (process.env.NEXT_RUNTIME === 'nodejs' && isTelemetryBackendEnabled()) {` +
|
|
359
410
|
`\n const { registerTelemetryForNextjs } = await import('@open-mercato/telemetry/nextjs')` +
|
|
360
|
-
`\n
|
|
411
|
+
`\n try {` +
|
|
412
|
+
`\n await registerTelemetryForNextjs()` +
|
|
413
|
+
`\n } catch (err) {` +
|
|
414
|
+
`\n const nodeProcess = process` +
|
|
415
|
+
`\n nodeProcess.stderr.write(\`\${err instanceof Error ? err.message : String(err)}\\n\`)` +
|
|
416
|
+
`\n nodeProcess.exit(1)` +
|
|
417
|
+
`\n }` +
|
|
361
418
|
`\n }`
|
|
362
419
|
const importLine = `import { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\n`
|
|
363
420
|
const withImport = content.includes('isTelemetryBackendEnabled')
|