@open-mercato/cli 0.6.8-develop.6930.1.1e5976efc3 → 0.6.8-develop.6940.1.177ea30c6e
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 +65 -1
- package/build.mjs +57 -7
- package/dist/agentic/guides/backend-ui.md +7 -1
- package/dist/agentic/guides/module-facts.json +565 -224
- package/dist/agentic/guides/module-facts.v2.json +122933 -0
- package/dist/agentic/guides/modules/ai_assistant.md +2 -2
- package/dist/agentic/guides/modules/api_docs.md +2 -2
- package/dist/agentic/guides/modules/api_keys.md +2 -2
- package/dist/agentic/guides/modules/attachments.md +2 -2
- package/dist/agentic/guides/modules/audit_logs.md +2 -2
- package/dist/agentic/guides/modules/auth.md +2 -2
- package/dist/agentic/guides/modules/business_rules.md +2 -2
- package/dist/agentic/guides/modules/catalog.md +11 -4
- package/dist/agentic/guides/modules/channel_gmail.md +2 -2
- package/dist/agentic/guides/modules/channel_imap.md +2 -2
- package/dist/agentic/guides/modules/checkout.md +8 -2
- package/dist/agentic/guides/modules/communication_channels.md +6 -6
- package/dist/agentic/guides/modules/configs.md +2 -2
- package/dist/agentic/guides/modules/content.md +2 -2
- package/dist/agentic/guides/modules/currencies.md +2 -2
- package/dist/agentic/guides/modules/customer_accounts.md +2 -2
- package/dist/agentic/guides/modules/customers.md +8 -8
- package/dist/agentic/guides/modules/dashboards.md +12 -12
- package/dist/agentic/guides/modules/data_sync.md +4 -4
- package/dist/agentic/guides/modules/design_system.md +2 -2
- package/dist/agentic/guides/modules/dictionaries.md +2 -2
- package/dist/agentic/guides/modules/directory.md +2 -2
- package/dist/agentic/guides/modules/entities.md +3 -3
- package/dist/agentic/guides/modules/eudr.md +3 -3
- package/dist/agentic/guides/modules/events.md +2 -2
- package/dist/agentic/guides/modules/feature_toggles.md +2 -2
- package/dist/agentic/guides/modules/gateway_stripe.md +2 -2
- package/dist/agentic/guides/modules/generators.md +2 -2
- package/dist/agentic/guides/modules/inbox_ops.md +2 -2
- package/dist/agentic/guides/modules/integrations.md +5 -2
- package/dist/agentic/guides/modules/messages.md +2 -2
- package/dist/agentic/guides/modules/notifications.md +2 -2
- package/dist/agentic/guides/modules/onboarding.md +2 -2
- package/dist/agentic/guides/modules/payment_gateways.md +7 -3
- package/dist/agentic/guides/modules/perspectives.md +2 -2
- package/dist/agentic/guides/modules/planner.md +2 -2
- package/dist/agentic/guides/modules/portal.md +2 -2
- package/dist/agentic/guides/modules/progress.md +2 -2
- package/dist/agentic/guides/modules/query_index.md +2 -2
- package/dist/agentic/guides/modules/record_locks.md +3 -3
- package/dist/agentic/guides/modules/resources.md +2 -2
- package/dist/agentic/guides/modules/sales.md +13 -4
- package/dist/agentic/guides/modules/scheduler.md +2 -2
- package/dist/agentic/guides/modules/search.md +4 -4
- package/dist/agentic/guides/modules/security.md +3 -3
- package/dist/agentic/guides/modules/shipping_carriers.md +2 -2
- package/dist/agentic/guides/modules/sso.md +2 -2
- package/dist/agentic/guides/modules/staff.md +7 -4
- package/dist/agentic/guides/modules/storage_s3.md +2 -2
- package/dist/agentic/guides/modules/sync_akeneo.md +4 -4
- package/dist/agentic/guides/modules/sync_excel.md +2 -2
- package/dist/agentic/guides/modules/system_status_overlays.md +2 -2
- package/dist/agentic/guides/modules/translations.md +2 -2
- package/dist/agentic/guides/modules/webhooks.md +2 -2
- package/dist/agentic/guides/modules/wms.md +31 -4
- package/dist/agentic/guides/modules/workflows.md +2 -2
- package/dist/agentic/guides/reference-module-facts.json +8335 -0
- package/dist/agentic/guides/reference-modules/example.md +489 -0
- package/dist/agentic/guides/spec-delivery.md +34 -0
- package/dist/agentic/guides/testing-debugging.md +2 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +3 -2
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/agentic/shared/AGENTS.md.template +10 -14
- package/dist/agentic/shared/ai/harness/README.md +4 -4
- package/dist/agentic/shared/ai/harness/RELEASE.md +4 -4
- package/dist/agentic/shared/ai/harness/cases.json +11350 -5088
- package/dist/agentic/shared/ai/harness/cases.schema.json +70 -7
- package/dist/agentic/shared/ai/harness/design-system-inventory.json +6697 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +3 -1
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +4 -0
- package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +9 -4
- package/dist/agentic/shared/ai/harness/result.schema.json +60 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +10 -0
- package/dist/agentic/shared/ai/harness/source-link-inventory.json +3231 -0
- package/dist/agentic/shared/ai/harness/validators.json +8 -4
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +5 -2
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +3 -0
- package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +879 -0
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.7-to-0.7.0/SKILL.md +102 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +15 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +6 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +4 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +2 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +2 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +3 -1
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +2 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +2 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +3 -3
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +1 -1
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +133 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +11 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +1 -1
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +2 -2
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +14 -1
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +2 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +19 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +1 -4
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +24 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +3 -1
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +13 -0
- package/dist/agentic/shared/ai/skills/tiers.json +2 -1
- package/dist/agentic/shared/ai/specs/2026-08-06-reference-module-activation.md +248 -0
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +39 -2
- package/dist/agentic/shared/scripts/design-source-contract.mjs +108 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +935 -28
- package/dist/agentic/shared/scripts/framework-context.mjs +4 -1
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1 -1
- package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +1511 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js +1 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-setup.js +9 -1
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +93 -26
- package/dist/lib/generators/module-extension-facts.js.map +3 -3
- package/dist/lib/generators/module-facts-discovery.js +23 -0
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +969 -41
- package/dist/lib/generators/module-facts.js.map +2 -2
- package/dist/lib/generators/module-override-targets.js +35 -11
- package/dist/lib/generators/module-override-targets.js.map +2 -2
- package/dist/lib/generators/module-registry.js +105 -2
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/resolver.js +5 -0
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/testing/integration-discovery.js +34 -0
- package/dist/lib/testing/integration-discovery.js.map +2 -2
- package/dist/lib/testing/integration.js +10 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +4 -4
- package/dist/mercato.js.map +2 -2
- package/package.json +6 -6
- package/src/lib/__integration__/TC-INT-008.spec.ts +4 -2
- package/src/lib/__tests__/resolve-environment.test.ts +18 -0
- package/src/lib/agentic-setup.ts +10 -2
- package/src/lib/generators/__tests__/helpers/override-target-diagnostic-fixtures.ts +150 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +290 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +72 -7
- package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
- package/src/lib/generators/__tests__/module-facts.example-fact-coverage.test.ts +502 -0
- package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +414 -0
- package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +42 -1
- package/src/lib/generators/__tests__/module-override-targets.diagnostic-wiring.test.ts +92 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +24 -0
- package/src/lib/generators/__tests__/module-override-targets.unknown-mode.test.ts +117 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +60 -0
- package/src/lib/generators/module-extension-facts.ts +181 -25
- package/src/lib/generators/module-facts-discovery.ts +46 -0
- package/src/lib/generators/module-facts.ts +1330 -86
- package/src/lib/generators/module-override-targets.ts +70 -26
- package/src/lib/generators/module-registry.ts +135 -2
- package/src/lib/resolver.ts +6 -1
- package/src/lib/testing/__tests__/integration-discovery.test.ts +35 -0
- package/src/lib/testing/integration-discovery.ts +50 -0
- package/src/lib/testing/integration.ts +10 -2
- package/src/mercato.ts +3 -3
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
ModuleOwnedContractFact,
|
|
10
10
|
} from './module-facts'
|
|
11
11
|
import { buildFactSourceLookup, type FactSourceLookup } from './module-fact-sources'
|
|
12
|
-
import {
|
|
12
|
+
import { getFrameworkOverrideHostOperations } from './module-extension-facts'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Exact module-specific unified override targets.
|
|
@@ -26,11 +26,26 @@ import { getFrameworkOverrideModes } from './module-extension-facts'
|
|
|
26
26
|
* `nav.groupOrder` are never module targets.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Closed mode/note/diagnostic sets, published as `as const` ledgers whose types
|
|
31
|
+
* derive from them. The `factCoverage` ledger enumerates these at runtime, so a
|
|
32
|
+
* value added here without a ledger row fails the coverage contract test instead
|
|
33
|
+
* of silently widening the public surface.
|
|
34
|
+
*/
|
|
35
|
+
export const MODULE_OVERRIDE_MODES = ['disable-replace', 'replace', 'additive'] as const
|
|
36
|
+
|
|
37
|
+
export type ModuleOverrideMode = (typeof MODULE_OVERRIDE_MODES)[number]
|
|
38
|
+
|
|
39
|
+
export const MODULE_OVERRIDE_TARGET_NOTES = [
|
|
40
|
+
'safe-metadata-only',
|
|
41
|
+
'page-middleware-not-mutation-guard',
|
|
42
|
+
] as const
|
|
30
43
|
|
|
31
|
-
export type ModuleOverrideTargetNote =
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
export type ModuleOverrideTargetNote = (typeof MODULE_OVERRIDE_TARGET_NOTES)[number]
|
|
45
|
+
|
|
46
|
+
function isModuleOverrideMode(value: string): value is ModuleOverrideMode {
|
|
47
|
+
return (MODULE_OVERRIDE_MODES as readonly string[]).includes(value)
|
|
48
|
+
}
|
|
34
49
|
|
|
35
50
|
export type ModuleOverrideTarget = {
|
|
36
51
|
id: string
|
|
@@ -43,11 +58,15 @@ export type ModuleOverrideTarget = {
|
|
|
43
58
|
notes?: ModuleOverrideTargetNote[]
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
export const MODULE_OVERRIDE_TARGET_DIAGNOSTIC_CODES = [
|
|
62
|
+
'missing-owned-fact',
|
|
63
|
+
'missing-source',
|
|
64
|
+
'unsupported-dynamic-key',
|
|
65
|
+
'unknown-framework-domain',
|
|
66
|
+
'unknown-framework-mode',
|
|
67
|
+
] as const
|
|
68
|
+
|
|
69
|
+
export type ModuleOverrideTargetDiagnosticCode = (typeof MODULE_OVERRIDE_TARGET_DIAGNOSTIC_CODES)[number]
|
|
51
70
|
|
|
52
71
|
export type ModuleOverrideTargetDiagnostic = {
|
|
53
72
|
code: ModuleOverrideTargetDiagnosticCode
|
|
@@ -82,17 +101,35 @@ type InternalAdapter = {
|
|
|
82
101
|
run(moduleFacts: ModuleFactsJsonEntry, resolveFactSource: FactSourceLookup): AdapterResult
|
|
83
102
|
}
|
|
84
103
|
|
|
85
|
-
const
|
|
104
|
+
const OVERRIDE_HOST_OPERATIONS = getFrameworkOverrideHostOperations()
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Why a dotted host produced no modes. The two failure causes are distinct and a
|
|
108
|
+
* downstream app needs to tell them apart: an absent host means the catalog never
|
|
109
|
+
* described this override surface, while an unrecognized mode means the catalog
|
|
110
|
+
* *does* describe it but names an operation this generator cannot map to a public
|
|
111
|
+
* {@link ModuleOverrideMode}. Collapsing the second into the first would report a
|
|
112
|
+
* catalog the generator has fallen behind as a surface that does not exist.
|
|
113
|
+
*/
|
|
114
|
+
export type FrameworkOverrideModeResolution =
|
|
115
|
+
| { outcome: 'resolved'; modes: ModuleOverrideMode[] }
|
|
116
|
+
| { outcome: 'unknown-framework-domain' }
|
|
117
|
+
| { outcome: 'unknown-framework-mode'; operation: string }
|
|
86
118
|
|
|
87
119
|
/**
|
|
88
120
|
* Supported modes come from the framework catalog only. A host the catalog does not
|
|
89
|
-
* describe is never guessed
|
|
90
|
-
*
|
|
91
|
-
* a mode the runtime may not support.
|
|
121
|
+
* describe is never guessed, and neither is a host whose declared operation is not a
|
|
122
|
+
* known mode: both produce no target, so a downstream app is never told a key is
|
|
123
|
+
* overridable in a mode the runtime may not support.
|
|
92
124
|
*/
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
return
|
|
125
|
+
export function resolveFrameworkOverrideModes(operation: string | undefined): FrameworkOverrideModeResolution {
|
|
126
|
+
if (operation === undefined) return { outcome: 'unknown-framework-domain' }
|
|
127
|
+
if (!isModuleOverrideMode(operation)) return { outcome: 'unknown-framework-mode', operation }
|
|
128
|
+
return { outcome: 'resolved', modes: [operation] }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function modesFor(dottedHost: string): FrameworkOverrideModeResolution {
|
|
132
|
+
return resolveFrameworkOverrideModes(OVERRIDE_HOST_OPERATIONS[dottedHost])
|
|
96
133
|
}
|
|
97
134
|
|
|
98
135
|
/**
|
|
@@ -146,9 +183,13 @@ type MakeTargetInput = {
|
|
|
146
183
|
notes?: ModuleOverrideTargetNote[]
|
|
147
184
|
}
|
|
148
185
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
186
|
+
/** The resolution outcomes that yield no target; each names its own diagnostic code. */
|
|
187
|
+
type UnresolvedOverrideMode = Exclude<FrameworkOverrideModeResolution, { outcome: 'resolved' }>
|
|
188
|
+
|
|
189
|
+
function makeTarget(input: MakeTargetInput): ModuleOverrideTarget | UnresolvedOverrideMode {
|
|
190
|
+
const resolution = modesFor(input.dottedHost)
|
|
191
|
+
if (resolution.outcome !== 'resolved') return resolution
|
|
192
|
+
const { modes } = resolution
|
|
152
193
|
const target: ModuleOverrideTarget = {
|
|
153
194
|
id: computeOverrideTargetId(input.moduleId, input.domain, input.path),
|
|
154
195
|
domain: input.domain,
|
|
@@ -163,21 +204,24 @@ function makeTarget(input: MakeTargetInput): ModuleOverrideTarget | null {
|
|
|
163
204
|
}
|
|
164
205
|
|
|
165
206
|
/**
|
|
166
|
-
* Emits the target, or
|
|
167
|
-
* catalog does not describe the target's
|
|
207
|
+
* Emits the target, or the diagnostic matching why the framework catalog yielded no
|
|
208
|
+
* modes: `unknown-framework-domain` when the catalog does not describe the target's
|
|
209
|
+
* dotted host at all, `unknown-framework-mode` when it describes the host but names
|
|
210
|
+
* an operation that is not a public {@link ModuleOverrideMode}. Neither case guesses
|
|
211
|
+
* a target.
|
|
168
212
|
*/
|
|
169
213
|
function pushTarget(
|
|
170
214
|
targets: ModuleOverrideTarget[],
|
|
171
215
|
diagnostics: ModuleOverrideTargetDiagnostic[],
|
|
172
216
|
input: MakeTargetInput,
|
|
173
217
|
): void {
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
targets.push(
|
|
218
|
+
const result = makeTarget(input)
|
|
219
|
+
if (!('outcome' in result)) {
|
|
220
|
+
targets.push(result)
|
|
177
221
|
return
|
|
178
222
|
}
|
|
179
223
|
diagnostics.push({
|
|
180
|
-
code:
|
|
224
|
+
code: result.outcome,
|
|
181
225
|
moduleId: input.moduleId,
|
|
182
226
|
domain: input.domain,
|
|
183
227
|
candidatePath: input.path,
|
|
@@ -301,6 +301,131 @@ function buildCacheBustedSourceImportUrl(sourceFile: string): string {
|
|
|
301
301
|
return url.href
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
function assertGeneratorPluginUsesTypeOnlyImports(sourceFile: string): void {
|
|
305
|
+
const source = fs.readFileSync(sourceFile, 'utf8')
|
|
306
|
+
const scriptKind = sourceFile.endsWith('.js') || sourceFile.endsWith('.mjs') || sourceFile.endsWith('.cjs')
|
|
307
|
+
? ts.ScriptKind.JS
|
|
308
|
+
: ts.ScriptKind.TS
|
|
309
|
+
const parsed = ts.createSourceFile(sourceFile, source, ts.ScriptTarget.Latest, true, scriptKind)
|
|
310
|
+
const violations: string[] = []
|
|
311
|
+
|
|
312
|
+
for (const statement of parsed.statements) {
|
|
313
|
+
if (ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly) {
|
|
314
|
+
violations.push(statement.getText(parsed))
|
|
315
|
+
}
|
|
316
|
+
if (ts.isImportEqualsDeclaration(statement)) {
|
|
317
|
+
violations.push(statement.getText(parsed))
|
|
318
|
+
}
|
|
319
|
+
if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && !statement.isTypeOnly) {
|
|
320
|
+
violations.push(statement.getText(parsed))
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const visit = (node: ts.Node): void => {
|
|
325
|
+
if (
|
|
326
|
+
ts.isCallExpression(node)
|
|
327
|
+
&& (node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
328
|
+
|| (ts.isIdentifier(node.expression) && node.expression.text === 'require'))
|
|
329
|
+
) {
|
|
330
|
+
violations.push(node.getText(parsed))
|
|
331
|
+
}
|
|
332
|
+
ts.forEachChild(node, visit)
|
|
333
|
+
}
|
|
334
|
+
visit(parsed)
|
|
335
|
+
|
|
336
|
+
if (violations.length > 0) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Generator plugin ${sourceFile} contains runtime imports. generators.ts may use only \`import type\`: ${violations.join('; ')}`,
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const GENERATOR_PLUGIN_OUTPUT_MANIFEST = '.generator-plugin-outputs.json'
|
|
344
|
+
|
|
345
|
+
function normalizeGeneratorPluginOutputFileName(pluginId: string, outputFileName: string): string {
|
|
346
|
+
const normalized = outputFileName.replace(/\\/g, '/')
|
|
347
|
+
if (
|
|
348
|
+
path.isAbsolute(outputFileName)
|
|
349
|
+
|| outputFileName.includes('\\')
|
|
350
|
+
|| normalized.startsWith('/')
|
|
351
|
+
|| normalized.startsWith('../')
|
|
352
|
+
|| normalized.includes('/../')
|
|
353
|
+
|| !normalized.endsWith('.ts')
|
|
354
|
+
) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`Generator plugin '${pluginId}' has an invalid outputFileName '${outputFileName}'; expected a relative .ts path inside the generated directory.`,
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
return normalized
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function resolveGeneratorPluginOutputNames(
|
|
363
|
+
plugins: ReadonlyMap<string, import('@open-mercato/shared/modules/generators').GeneratorPlugin>,
|
|
364
|
+
): Map<string, string> {
|
|
365
|
+
const outputNames = new Map<string, string>()
|
|
366
|
+
const ownersByOutput = new Map<string, string>()
|
|
367
|
+
for (const [pluginId, plugin] of plugins) {
|
|
368
|
+
const outputName = normalizeGeneratorPluginOutputFileName(pluginId, plugin.outputFileName)
|
|
369
|
+
const existingOwner = ownersByOutput.get(outputName)
|
|
370
|
+
if (existingOwner) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`Generator plugins '${existingOwner}' and '${pluginId}' declare the same output file '${outputName}'.`,
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
ownersByOutput.set(outputName, pluginId)
|
|
376
|
+
outputNames.set(pluginId, outputName)
|
|
377
|
+
}
|
|
378
|
+
return outputNames
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function reconcileGeneratorPluginOutputs(options: {
|
|
382
|
+
outputDir: string
|
|
383
|
+
outputNames: Iterable<string>
|
|
384
|
+
result: GeneratorResult
|
|
385
|
+
}): void {
|
|
386
|
+
const manifestPath = path.join(options.outputDir, GENERATOR_PLUGIN_OUTPUT_MANIFEST)
|
|
387
|
+
let previousOutputs: string[] = []
|
|
388
|
+
if (fs.existsSync(manifestPath)) {
|
|
389
|
+
const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown
|
|
390
|
+
if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== 'string')) {
|
|
391
|
+
throw new Error(`Invalid generator plugin output manifest: ${manifestPath}`)
|
|
392
|
+
}
|
|
393
|
+
previousOutputs = parsed.map((entry) => normalizeGeneratorPluginOutputFileName('manifest', entry))
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const currentOutputs = new Set(options.outputNames)
|
|
397
|
+
for (const staleOutput of previousOutputs) {
|
|
398
|
+
if (currentOutputs.has(staleOutput)) continue
|
|
399
|
+
for (const stalePath of [
|
|
400
|
+
path.join(options.outputDir, staleOutput),
|
|
401
|
+
path.join(options.outputDir, staleOutput.replace(/\.ts$/, '.checksum')),
|
|
402
|
+
]) {
|
|
403
|
+
if (!fs.existsSync(stalePath)) continue
|
|
404
|
+
fs.unlinkSync(stalePath)
|
|
405
|
+
options.result.filesWritten.push(stalePath)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (currentOutputs.size === 0) {
|
|
410
|
+
if (fs.existsSync(manifestPath)) {
|
|
411
|
+
fs.unlinkSync(manifestPath)
|
|
412
|
+
options.result.filesWritten.push(manifestPath)
|
|
413
|
+
}
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const manifestContent = `${JSON.stringify(
|
|
418
|
+
[...currentOutputs].sort((left, right) => left.localeCompare(right)),
|
|
419
|
+
null,
|
|
420
|
+
2,
|
|
421
|
+
)}\n`
|
|
422
|
+
if (!fs.existsSync(manifestPath) || fs.readFileSync(manifestPath, 'utf8') !== manifestContent) {
|
|
423
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
|
|
424
|
+
fs.writeFileSync(manifestPath, manifestContent)
|
|
425
|
+
options.result.filesWritten.push(manifestPath)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
304
429
|
function unwrapObjectLiteralExpression(
|
|
305
430
|
expression: ts.Expression | undefined,
|
|
306
431
|
): ts.ObjectLiteralExpression | undefined {
|
|
@@ -3143,6 +3268,7 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3143
3268
|
for (const discovered of discovery.modules) {
|
|
3144
3269
|
const resolved = discovered.resolve('generators.ts')
|
|
3145
3270
|
if (!resolved) continue
|
|
3271
|
+
assertGeneratorPluginUsesTypeOnlyImports(resolved.absolutePath)
|
|
3146
3272
|
try {
|
|
3147
3273
|
const pluginMod = await import(buildCacheBustedSourceImportUrl(resolved.absolutePath))
|
|
3148
3274
|
const plugins: import('@open-mercato/shared/modules/generators').GeneratorPlugin[] =
|
|
@@ -3156,6 +3282,8 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3156
3282
|
} catch {}
|
|
3157
3283
|
}
|
|
3158
3284
|
|
|
3285
|
+
const pluginOutputNames = resolveGeneratorPluginOutputNames(pluginRegistry)
|
|
3286
|
+
|
|
3159
3287
|
const imports: string[] = []
|
|
3160
3288
|
const runtimeImports: string[] = []
|
|
3161
3289
|
const frontendRouteManifestImports: string[] = []
|
|
@@ -3716,10 +3844,15 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3716
3844
|
const importSection = state.imports.join('\n')
|
|
3717
3845
|
const entriesLiteral = state.configs.join(',\n ')
|
|
3718
3846
|
const content = plugin.buildOutput({ importSection, entriesLiteral })
|
|
3719
|
-
const outFile = path.join(outputDir,
|
|
3720
|
-
const checksumFile = outFile.replace(
|
|
3847
|
+
const outFile = path.join(outputDir, pluginOutputNames.get(pluginId)!)
|
|
3848
|
+
const checksumFile = outFile.replace(/\.ts$/, '.checksum')
|
|
3721
3849
|
writeGeneratedFile({ outFile, checksumFile, content, structureChecksum, result, quiet })
|
|
3722
3850
|
}
|
|
3851
|
+
reconcileGeneratorPluginOutputs({
|
|
3852
|
+
outputDir,
|
|
3853
|
+
outputNames: pluginOutputNames.values(),
|
|
3854
|
+
result,
|
|
3855
|
+
})
|
|
3723
3856
|
|
|
3724
3857
|
// Bootstrap registrations: aggregate core registrations + plugin bootstrap-registration hooks
|
|
3725
3858
|
// into one file. Always written (with at least the core backend route registration) so bootstrap.ts
|
package/src/lib/resolver.ts
CHANGED
|
@@ -334,7 +334,7 @@ function parseModulesFromSource(source: string, env: NodeJS.ProcessEnv = process
|
|
|
334
334
|
return modules
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
function readEnabledModulesFromConfig(cfgPath: string): ModuleEntry[] {
|
|
337
|
+
export function readEnabledModulesFromConfig(cfgPath: string): ModuleEntry[] {
|
|
338
338
|
const source = fs.readFileSync(cfgPath, 'utf8')
|
|
339
339
|
return parseModulesFromSource(source)
|
|
340
340
|
}
|
|
@@ -489,6 +489,11 @@ function detectMonorepoFromNodeModules(appDir: string): { isMonorepo: boolean; m
|
|
|
489
489
|
|
|
490
490
|
const corePkgPath = path.join(nodeModulesRoot, 'node_modules', '@open-mercato', 'core')
|
|
491
491
|
|
|
492
|
+
const workspaceCoreModules = path.join(nodeModulesRoot, 'packages', 'core', 'src', 'modules')
|
|
493
|
+
if (fs.existsSync(workspaceCoreModules)) {
|
|
494
|
+
return { isMonorepo: true, monorepoRoot: nodeModulesRoot, nodeModulesRoot }
|
|
495
|
+
}
|
|
496
|
+
|
|
492
497
|
try {
|
|
493
498
|
const stat = fs.lstatSync(corePkgPath)
|
|
494
499
|
if (stat.isSymbolicLink()) {
|
|
@@ -16,6 +16,7 @@ describe('integration discovery', () => {
|
|
|
16
16
|
'OM_TEST_DISCOVERY_AI_KEY',
|
|
17
17
|
'OM_TEST_DISCOVERY_FALLBACK_AI_KEY',
|
|
18
18
|
'OM_TEST_DISCOVERY_REQUIRED_KEY',
|
|
19
|
+
'OM_TEST_APP_ROOT',
|
|
19
20
|
] as const
|
|
20
21
|
const previousTestEnvValues = new Map<string, string | undefined>()
|
|
21
22
|
|
|
@@ -159,6 +160,40 @@ describe('integration discovery', () => {
|
|
|
159
160
|
])
|
|
160
161
|
})
|
|
161
162
|
|
|
163
|
+
it('uses OM_TEST_APP_ROOT and its actual modules.ts for dependency filtering', async () => {
|
|
164
|
+
const standaloneRoot = path.join(tempRoot, 'standalone-app')
|
|
165
|
+
await writeTestFile(
|
|
166
|
+
tempRoot,
|
|
167
|
+
'apps/mercato/src/modules/example/__integration__/meta.ts',
|
|
168
|
+
"export const integrationMeta = { requiredModules: ['example'] }\n",
|
|
169
|
+
)
|
|
170
|
+
await writeTestFile(
|
|
171
|
+
tempRoot,
|
|
172
|
+
'apps/mercato/src/modules/example/__integration__/TC-EXAMPLE-016.spec.ts',
|
|
173
|
+
)
|
|
174
|
+
await writeTestFile(
|
|
175
|
+
tempRoot,
|
|
176
|
+
'apps/mercato/src/modules/example/__integration__/TC-EXAMPLE-018.meta.ts',
|
|
177
|
+
"export const integrationMeta = { requiredModules: ['design_system'] }\n",
|
|
178
|
+
)
|
|
179
|
+
await writeTestFile(
|
|
180
|
+
tempRoot,
|
|
181
|
+
'apps/mercato/src/modules/example/__integration__/TC-EXAMPLE-018.spec.ts',
|
|
182
|
+
)
|
|
183
|
+
await writeTestFile(
|
|
184
|
+
tempRoot,
|
|
185
|
+
'standalone-app/src/modules.ts',
|
|
186
|
+
`const moduleOverrideExamples = [{ id: 'design_system', from: '@open-mercato/core' }]\n\nexport const enabledModules = [{\n id: 'example',\n from: '@app',\n overrides: { routes: { api: { 'GET /api/example/probe': null } } },\n}]\n`,
|
|
187
|
+
)
|
|
188
|
+
await writeTestFile(tempRoot, 'standalone-app/src/modules/design_system/.gitkeep')
|
|
189
|
+
process.env.OM_TEST_APP_ROOT = standaloneRoot
|
|
190
|
+
|
|
191
|
+
const discovered = discoverIntegrationSpecFiles(tempRoot, path.join(tempRoot, '.ai', 'qa', 'tests'))
|
|
192
|
+
expect(discovered.map((entry) => entry.path)).toEqual([
|
|
193
|
+
'apps/mercato/src/modules/example/__integration__/TC-EXAMPLE-016.spec.ts',
|
|
194
|
+
])
|
|
195
|
+
})
|
|
196
|
+
|
|
162
197
|
it('applies folder and per-test metadata environment requirements', async () => {
|
|
163
198
|
await writeTestFile(tempRoot, 'packages/ai-assistant/src/modules/ai_assistant/.gitkeep')
|
|
164
199
|
await writeTestFile(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, readdirSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import ts from 'typescript-js'
|
|
3
4
|
|
|
4
5
|
export type IntegrationSpecDiscoveryItem = {
|
|
5
6
|
path: string
|
|
@@ -106,7 +107,56 @@ function collectModuleIdsFromModulesRoot(modulesRoot: string, enabledModules: Se
|
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
function readDeclaredModuleIdsFromConfig(registryPath: string): string[] {
|
|
111
|
+
const source = readFileSync(registryPath, 'utf8')
|
|
112
|
+
const sourceFile = ts.createSourceFile(
|
|
113
|
+
registryPath,
|
|
114
|
+
source,
|
|
115
|
+
ts.ScriptTarget.Latest,
|
|
116
|
+
true,
|
|
117
|
+
ts.ScriptKind.TS,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
for (const statement of sourceFile.statements) {
|
|
121
|
+
if (!ts.isVariableStatement(statement)) continue
|
|
122
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
123
|
+
if (
|
|
124
|
+
!ts.isIdentifier(declaration.name)
|
|
125
|
+
|| declaration.name.text !== 'enabledModules'
|
|
126
|
+
|| !declaration.initializer
|
|
127
|
+
|| !ts.isArrayLiteralExpression(declaration.initializer)
|
|
128
|
+
) {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return declaration.initializer.elements.flatMap((element) => {
|
|
133
|
+
if (!ts.isObjectLiteralExpression(element)) return []
|
|
134
|
+
const idProperty = element.properties.find((property): property is ts.PropertyAssignment =>
|
|
135
|
+
ts.isPropertyAssignment(property)
|
|
136
|
+
&& (
|
|
137
|
+
(ts.isIdentifier(property.name) && property.name.text === 'id')
|
|
138
|
+
|| (ts.isStringLiteralLike(property.name) && property.name.text === 'id')
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
return idProperty && ts.isStringLiteralLike(idProperty.initializer)
|
|
142
|
+
? [idProperty.initializer.text]
|
|
143
|
+
: []
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return []
|
|
149
|
+
}
|
|
150
|
+
|
|
109
151
|
function resolveEnabledModuleIds(projectRoot: string): Set<string> {
|
|
152
|
+
const configuredTestAppRoot = process.env.OM_TEST_APP_ROOT?.trim()
|
|
153
|
+
if (configuredTestAppRoot) {
|
|
154
|
+
const registryPath = path.join(path.resolve(configuredTestAppRoot), 'src', 'modules.ts')
|
|
155
|
+
return new Set(
|
|
156
|
+
readDeclaredModuleIdsFromConfig(registryPath).map(normalizeModuleId),
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
110
160
|
const enabledModules = new Set<string>()
|
|
111
161
|
const appModulesRoot = path.join(projectRoot, 'src', 'modules')
|
|
112
162
|
const appsRoot = path.join(projectRoot, 'apps')
|
|
@@ -1977,6 +1977,12 @@ function buildReusableEnvironment(
|
|
|
1977
1977
|
// not have to call flushPendingCrudAccessLogs() explicitly.
|
|
1978
1978
|
OM_CRUD_ACCESS_LOG_BLOCKING: process.env.OM_CRUD_ACCESS_LOG_BLOCKING ?? '1',
|
|
1979
1979
|
OM_WEBHOOKS_ALLOW_PRIVATE_URLS: process.env.OM_WEBHOOKS_ALLOW_PRIVATE_URLS ?? '1',
|
|
1980
|
+
// TC-ONB-001/002 drive the self-service signup flow, whose routes are not
|
|
1981
|
+
// mounted while the feature is off. `apps/mercato/.env` ships it disabled,
|
|
1982
|
+
// so both specs 404'd on every local run while CI stayed green — it exports
|
|
1983
|
+
// the var at the workflow level, the same gap the MOCK_INBOUND_WEBHOOK_SECRET
|
|
1984
|
+
// note below describes. Keep in sync with the app-server env block.
|
|
1985
|
+
SELF_SERVICE_ONBOARDING_ENABLED: process.env.SELF_SERVICE_ONBOARDING_ENABLED ?? 'true',
|
|
1980
1986
|
// Keep the bus in the Playwright process (used by in-test queue-drain helpers)
|
|
1981
1987
|
// on the same delivery mode as the app server it drives: inline persistent
|
|
1982
1988
|
// delivery so event side effects are deterministic for assertions. See the
|
|
@@ -1986,7 +1992,6 @@ function buildReusableEnvironment(
|
|
|
1986
1992
|
MOCK_GATEWAY_WEBHOOK_SECRET: 'open-mercato-mock-dev-webhook-secret',
|
|
1987
1993
|
MOCK_CARRIER_WEBHOOK_SECRET: 'open-mercato-mock-dev-carrier-webhook-secret',
|
|
1988
1994
|
MOCK_INBOUND_WEBHOOK_SECRET: 'open-mercato-mock-dev-inbound-webhook-secret',
|
|
1989
|
-
NEXT_PUBLIC_OM_EXAMPLE_INJECTION_WIDGETS_ENABLED: 'true',
|
|
1990
1995
|
NEXT_PUBLIC_UMES_DEVTOOLS: 'true',
|
|
1991
1996
|
CI: 'true',
|
|
1992
1997
|
TENANT_DATA_ENCRYPTION_FALLBACK_KEY: process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY ?? 'om-ephemeral-integration-fallback-key',
|
|
@@ -3329,6 +3334,10 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
3329
3334
|
OM_TEST_AUTH_RATE_LIMIT_MODE: 'opt-in',
|
|
3330
3335
|
OM_DISABLE_EMAIL_DELIVERY: '1',
|
|
3331
3336
|
OM_WEBHOOKS_ALLOW_PRIVATE_URLS: process.env.OM_WEBHOOKS_ALLOW_PRIVATE_URLS ?? '1',
|
|
3337
|
+
// Read at build time as well as at runtime, so this block has to carry it:
|
|
3338
|
+
// the app build and `yarn start` both run with this environment. See the
|
|
3339
|
+
// matching note on the Playwright-process env block above.
|
|
3340
|
+
SELF_SERVICE_ONBOARDING_ENABLED: process.env.SELF_SERVICE_ONBOARDING_ENABLED ?? 'true',
|
|
3332
3341
|
ENABLE_CRUD_API_CACHE: 'true',
|
|
3333
3342
|
MOCK_GATEWAY_WEBHOOK_SECRET: 'open-mercato-mock-dev-webhook-secret',
|
|
3334
3343
|
MOCK_CARRIER_WEBHOOK_SECRET: 'open-mercato-mock-dev-carrier-webhook-secret',
|
|
@@ -3338,7 +3347,6 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
3338
3347
|
// var at the workflow level, masking the gap). Keep in sync with the
|
|
3339
3348
|
// Playwright-process env block above.
|
|
3340
3349
|
MOCK_INBOUND_WEBHOOK_SECRET: 'open-mercato-mock-dev-inbound-webhook-secret',
|
|
3341
|
-
NEXT_PUBLIC_OM_EXAMPLE_INJECTION_WIDGETS_ENABLED: 'true',
|
|
3342
3350
|
NEXT_PUBLIC_UMES_DEVTOOLS: 'true',
|
|
3343
3351
|
CI: 'true',
|
|
3344
3352
|
TENANT_DATA_ENCRYPTION_FALLBACK_KEY: process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY ?? 'om-ephemeral-integration-fallback-key',
|
package/src/mercato.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { getCliModules, hasCliModules, registerCliModules } from './registry'
|
|
|
7
7
|
export { getCliModules, hasCliModules, registerCliModules }
|
|
8
8
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
9
9
|
import { getSslConfig } from '@open-mercato/shared/lib/db/ssl'
|
|
10
|
-
import { getRedisUrl, getRedisUrlOrThrow,
|
|
10
|
+
import { getRedisUrl, getRedisUrlOrThrow, parseRedisUrl } from '@open-mercato/shared/lib/redis/connection'
|
|
11
11
|
import { resolveInitDerivedSecrets } from './lib/init-secrets'
|
|
12
12
|
import {
|
|
13
13
|
resolveAutoSpawnWorkersMode,
|
|
@@ -994,13 +994,13 @@ export async function run(argv = process.argv) {
|
|
|
994
994
|
const redisUrl = getRedisUrl()
|
|
995
995
|
if (redisUrl) {
|
|
996
996
|
const Redis = (await import('ioredis')).default
|
|
997
|
-
const redis = new Redis(
|
|
997
|
+
const redis = new Redis({
|
|
998
|
+
...parseRedisUrl(redisUrl),
|
|
998
999
|
lazyConnect: true,
|
|
999
1000
|
connectTimeout: 3000,
|
|
1000
1001
|
maxRetriesPerRequest: 1,
|
|
1001
1002
|
retryStrategy: () => null,
|
|
1002
1003
|
enableOfflineQueue: false,
|
|
1003
|
-
protocol: REDIS_WIRE_PROTOCOL,
|
|
1004
1004
|
})
|
|
1005
1005
|
redis.on('error', () => {})
|
|
1006
1006
|
try {
|