@open-mercato/cli 0.6.8-develop.6898.1.8b42ed5e41 → 0.6.8-develop.6903.1.0ec850a22b
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/dist/agentic/guides/module-facts.json +110 -110
- package/dist/agentic/guides/modules/ai_assistant.md +1 -1
- package/dist/agentic/guides/modules/api_docs.md +1 -1
- package/dist/agentic/guides/modules/api_keys.md +1 -1
- package/dist/agentic/guides/modules/attachments.md +1 -1
- package/dist/agentic/guides/modules/audit_logs.md +1 -1
- package/dist/agentic/guides/modules/auth.md +1 -1
- package/dist/agentic/guides/modules/business_rules.md +1 -1
- package/dist/agentic/guides/modules/catalog.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail.md +1 -1
- package/dist/agentic/guides/modules/channel_imap.md +1 -1
- package/dist/agentic/guides/modules/checkout.md +1 -1
- package/dist/agentic/guides/modules/communication_channels.md +1 -1
- package/dist/agentic/guides/modules/configs.md +1 -1
- package/dist/agentic/guides/modules/content.md +1 -1
- package/dist/agentic/guides/modules/currencies.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts.md +1 -1
- package/dist/agentic/guides/modules/customers.md +1 -1
- package/dist/agentic/guides/modules/dashboards.md +1 -1
- package/dist/agentic/guides/modules/data_sync.md +1 -1
- package/dist/agentic/guides/modules/design_system.md +1 -1
- package/dist/agentic/guides/modules/dictionaries.md +1 -1
- package/dist/agentic/guides/modules/directory.md +1 -1
- package/dist/agentic/guides/modules/entities.md +1 -1
- package/dist/agentic/guides/modules/events.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
- package/dist/agentic/guides/modules/generators.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops.md +1 -1
- package/dist/agentic/guides/modules/integrations.md +1 -1
- package/dist/agentic/guides/modules/messages.md +1 -1
- package/dist/agentic/guides/modules/notifications.md +1 -1
- package/dist/agentic/guides/modules/onboarding.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways.md +1 -1
- package/dist/agentic/guides/modules/perspectives.md +1 -1
- package/dist/agentic/guides/modules/planner.md +1 -1
- package/dist/agentic/guides/modules/portal.md +1 -1
- package/dist/agentic/guides/modules/progress.md +1 -1
- package/dist/agentic/guides/modules/query_index.md +1 -1
- package/dist/agentic/guides/modules/record_locks.md +1 -1
- package/dist/agentic/guides/modules/resources.md +1 -1
- package/dist/agentic/guides/modules/sales.md +1 -1
- package/dist/agentic/guides/modules/scheduler.md +1 -1
- package/dist/agentic/guides/modules/search.md +1 -1
- package/dist/agentic/guides/modules/security.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
- package/dist/agentic/guides/modules/sso.md +1 -1
- package/dist/agentic/guides/modules/staff.md +1 -1
- package/dist/agentic/guides/modules/storage_s3.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
- package/dist/agentic/guides/modules/sync_excel.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
- package/dist/agentic/guides/modules/translations.md +1 -1
- package/dist/agentic/guides/modules/webhooks.md +1 -1
- package/dist/agentic/guides/modules/wms.md +1 -1
- package/dist/agentic/guides/modules/workflows.md +1 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +17 -0
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/agentic/shared/ai/harness/RELEASE.md +2 -0
- package/dist/lib/generate-watch-events.js +15 -0
- package/dist/lib/generate-watch-events.js.map +2 -2
- package/dist/lib/in-process-generate-watcher.js +12 -3
- package/dist/lib/in-process-generate-watcher.js.map +2 -2
- package/dist/mercato.js +3 -2
- package/dist/mercato.js.map +2 -2
- package/package.json +6 -6
- package/src/lib/__tests__/generate-watch-events.test.ts +76 -0
- package/src/lib/__tests__/in-process-generate-watcher.test.ts +52 -1
- package/src/lib/generate-watch-events.ts +14 -0
- package/src/lib/in-process-generate-watcher.ts +13 -4
- package/src/mercato.ts +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.6903.1.0ec850a22b",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"@mikro-orm/decorators": "^7.1.8",
|
|
61
61
|
"@mikro-orm/migrations": "^7.1.8",
|
|
62
62
|
"@mikro-orm/postgresql": "^7.1.8",
|
|
63
|
-
"@open-mercato/queue": "0.6.8-develop.
|
|
64
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
65
|
-
"@open-mercato/telemetry": "0.6.8-develop.
|
|
63
|
+
"@open-mercato/queue": "0.6.8-develop.6903.1.0ec850a22b",
|
|
64
|
+
"@open-mercato/shared": "0.6.8-develop.6903.1.0ec850a22b",
|
|
65
|
+
"@open-mercato/telemetry": "0.6.8-develop.6903.1.0ec850a22b",
|
|
66
66
|
"cross-spawn": "^7.0.6",
|
|
67
67
|
"pg": "8.22.0",
|
|
68
68
|
"semver": "^7.8.5",
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"typescript-js": "npm:typescript@6.0.3"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
76
|
+
"@open-mercato/shared": "0.6.8-develop.6903.1.0ec850a22b"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
79
|
+
"@open-mercato/shared": "0.6.8-develop.6903.1.0ec850a22b",
|
|
80
80
|
"@types/jest": "^30.0.0",
|
|
81
81
|
"jest": "^30.4.2",
|
|
82
82
|
"ts-jest": "^29.4.12"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
1
3
|
import path from 'node:path'
|
|
2
4
|
import {
|
|
3
5
|
createGenerateWatchChangeSignal,
|
|
@@ -26,6 +28,7 @@ function createWatchHarness(initialTargets: GenerateWatchTarget[]) {
|
|
|
26
28
|
const signal = createGenerateWatchChangeSignal({
|
|
27
29
|
getWatchTargets: () => targets,
|
|
28
30
|
watchDirectory,
|
|
31
|
+
directoryExists: () => true,
|
|
29
32
|
})
|
|
30
33
|
|
|
31
34
|
return {
|
|
@@ -37,6 +40,78 @@ function createWatchHarness(initialTargets: GenerateWatchTarget[]) {
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
describe('createGenerateWatchChangeSignal', () => {
|
|
43
|
+
it('skips missing directories without falling back and retries them on refresh', async () => {
|
|
44
|
+
const present = path.resolve('./modules-present')
|
|
45
|
+
const delayed = path.resolve('./modules-delayed')
|
|
46
|
+
const existing = new Set([present])
|
|
47
|
+
const registered: GenerateWatchTarget[] = []
|
|
48
|
+
const signal = createGenerateWatchChangeSignal({
|
|
49
|
+
getWatchTargets: () => [
|
|
50
|
+
{ directory: present, recursive: true },
|
|
51
|
+
{ directory: delayed, recursive: true },
|
|
52
|
+
],
|
|
53
|
+
directoryExists: (directory) => existing.has(directory),
|
|
54
|
+
watchDirectory: (target) => {
|
|
55
|
+
registered.push(target)
|
|
56
|
+
return { close: jest.fn() }
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await signal.refresh()
|
|
61
|
+
expect(registered.map((target) => target.directory)).toEqual([present])
|
|
62
|
+
expect(signal.hasSkippedTargets?.()).toBe(true)
|
|
63
|
+
expect(signal.usesPollingFallback()).toBe(false)
|
|
64
|
+
|
|
65
|
+
existing.add(delayed)
|
|
66
|
+
await signal.refresh()
|
|
67
|
+
expect(registered.map((target) => target.directory)).toEqual([present, delayed])
|
|
68
|
+
expect(signal.hasSkippedTargets?.()).toBe(false)
|
|
69
|
+
expect(signal.usesPollingFallback()).toBe(false)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('reports a missing directory once while it remains skipped', async () => {
|
|
73
|
+
const missing = path.resolve('./modules-missing')
|
|
74
|
+
const onSkippedDirectory = jest.fn()
|
|
75
|
+
const options = {
|
|
76
|
+
getWatchTargets: () => [{ directory: missing, recursive: true }],
|
|
77
|
+
directoryExists: () => false,
|
|
78
|
+
watchDirectory: jest.fn(() => ({ close: jest.fn() })),
|
|
79
|
+
onSkippedDirectory,
|
|
80
|
+
}
|
|
81
|
+
const signal = createGenerateWatchChangeSignal(options)
|
|
82
|
+
|
|
83
|
+
await signal.refresh()
|
|
84
|
+
await signal.refresh()
|
|
85
|
+
|
|
86
|
+
expect(onSkippedDirectory).toHaveBeenCalledTimes(1)
|
|
87
|
+
expect(onSkippedDirectory).toHaveBeenCalledWith(missing)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('uses filesystem existence checks by default', async () => {
|
|
91
|
+
const present = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-generate-watch-'))
|
|
92
|
+
const missing = path.join(present, 'missing')
|
|
93
|
+
const registered: string[] = []
|
|
94
|
+
const signal = createGenerateWatchChangeSignal({
|
|
95
|
+
getWatchTargets: () => [
|
|
96
|
+
{ directory: present, recursive: true },
|
|
97
|
+
{ directory: missing, recursive: true },
|
|
98
|
+
],
|
|
99
|
+
watchDirectory: (target) => {
|
|
100
|
+
registered.push(target.directory)
|
|
101
|
+
return { close: jest.fn() }
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await signal.refresh()
|
|
107
|
+
expect(registered).toEqual([present])
|
|
108
|
+
expect(signal.usesPollingFallback()).toBe(false)
|
|
109
|
+
} finally {
|
|
110
|
+
await signal.close()
|
|
111
|
+
fs.rmSync(present, { recursive: true, force: true })
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
40
115
|
it('deduplicates targets and increments its version on filesystem events', async () => {
|
|
41
116
|
const target = { directory: './modules', recursive: true }
|
|
42
117
|
const { signal, registered, watchDirectory } = createWatchHarness([target, target])
|
|
@@ -80,6 +155,7 @@ describe('createGenerateWatchChangeSignal', () => {
|
|
|
80
155
|
{ directory: './modules-b', recursive: true },
|
|
81
156
|
],
|
|
82
157
|
watchDirectory,
|
|
158
|
+
directoryExists: () => true,
|
|
83
159
|
})
|
|
84
160
|
|
|
85
161
|
await signal.refresh()
|
|
@@ -14,15 +14,26 @@ async function flushAsync(times = 5): Promise<void> {
|
|
|
14
14
|
function createFakeChangeSignal() {
|
|
15
15
|
let version = 0
|
|
16
16
|
let fallback = false
|
|
17
|
+
let skippedTargets = false
|
|
18
|
+
let discoverSkippedTargets = false
|
|
19
|
+
const refresh = jest.fn(async () => {
|
|
20
|
+
if (discoverSkippedTargets) {
|
|
21
|
+
skippedTargets = false
|
|
22
|
+
discoverSkippedTargets = false
|
|
23
|
+
}
|
|
24
|
+
})
|
|
17
25
|
const signal: GenerateWatcherChangeSignal = {
|
|
18
26
|
currentVersion: () => version,
|
|
19
|
-
refresh
|
|
27
|
+
refresh,
|
|
20
28
|
usesPollingFallback: () => fallback,
|
|
29
|
+
hasSkippedTargets: () => skippedTargets,
|
|
21
30
|
close: jest.fn(async () => undefined),
|
|
22
31
|
}
|
|
23
32
|
return {
|
|
24
33
|
signal,
|
|
25
34
|
markChanged: () => { version += 1 },
|
|
35
|
+
markSkippedTarget: () => { skippedTargets = true },
|
|
36
|
+
discoverSkippedTargetOnNextRefresh: () => { discoverSkippedTargets = true },
|
|
26
37
|
usePollingFallback: () => { fallback = true },
|
|
27
38
|
}
|
|
28
39
|
}
|
|
@@ -142,6 +153,46 @@ describe('startInProcessGenerateWatcher', () => {
|
|
|
142
153
|
await handle.close()
|
|
143
154
|
})
|
|
144
155
|
|
|
156
|
+
it('discovers skipped roots during event-gated idle polling', async () => {
|
|
157
|
+
const runGenerators = jest.fn(async () => undefined)
|
|
158
|
+
const computeStructureChecksum = jest.fn()
|
|
159
|
+
.mockResolvedValueOnce('before')
|
|
160
|
+
.mockResolvedValue('after')
|
|
161
|
+
const {
|
|
162
|
+
signal,
|
|
163
|
+
markSkippedTarget,
|
|
164
|
+
discoverSkippedTargetOnNextRefresh,
|
|
165
|
+
} = createFakeChangeSignal()
|
|
166
|
+
markSkippedTarget()
|
|
167
|
+
const handle = startInProcessGenerateWatcher({
|
|
168
|
+
pollMs: 1000,
|
|
169
|
+
skipInitial: true,
|
|
170
|
+
quiet: true,
|
|
171
|
+
logger: silentLogger,
|
|
172
|
+
changeSignal: signal,
|
|
173
|
+
computeStructureChecksum,
|
|
174
|
+
runGenerators,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
await flushAsync(8)
|
|
178
|
+
expect(computeStructureChecksum).toHaveBeenCalledTimes(1)
|
|
179
|
+
|
|
180
|
+
jest.advanceTimersByTime(1000)
|
|
181
|
+
await flushAsync(12)
|
|
182
|
+
|
|
183
|
+
expect(signal.refresh).toHaveBeenCalledTimes(2)
|
|
184
|
+
expect(computeStructureChecksum).toHaveBeenCalledTimes(1)
|
|
185
|
+
discoverSkippedTargetOnNextRefresh()
|
|
186
|
+
|
|
187
|
+
jest.advanceTimersByTime(1000)
|
|
188
|
+
await flushAsync(12)
|
|
189
|
+
|
|
190
|
+
expect(signal.refresh).toHaveBeenCalledTimes(3)
|
|
191
|
+
expect(computeStructureChecksum).toHaveBeenCalledTimes(2)
|
|
192
|
+
expect(runGenerators).toHaveBeenCalledWith('structure change')
|
|
193
|
+
await handle.close()
|
|
194
|
+
})
|
|
195
|
+
|
|
145
196
|
it('coalesces filesystem event bursts into one checksum and regeneration', async () => {
|
|
146
197
|
let currentChecksum = 'before'
|
|
147
198
|
const runGenerators = jest.fn(async () => undefined)
|
|
@@ -21,6 +21,8 @@ type WatchDirectory = (
|
|
|
21
21
|
export type GenerateWatchChangeSignalOptions = {
|
|
22
22
|
getWatchTargets: () => Promise<GenerateWatchTarget[]> | GenerateWatchTarget[]
|
|
23
23
|
watchDirectory?: WatchDirectory
|
|
24
|
+
directoryExists?: (directory: string) => boolean
|
|
25
|
+
onSkippedDirectory?: (directory: string) => void
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
function targetKey(target: GenerateWatchTarget): string {
|
|
@@ -49,7 +51,9 @@ export function createGenerateWatchChangeSignal(
|
|
|
49
51
|
options: GenerateWatchChangeSignalOptions,
|
|
50
52
|
): GenerateWatcherChangeSignal {
|
|
51
53
|
const watchDirectory = options.watchDirectory ?? defaultWatchDirectory
|
|
54
|
+
const directoryExists = options.directoryExists ?? fs.existsSync
|
|
52
55
|
const watchers = new Map<string, WatchHandle>()
|
|
56
|
+
let skippedDirectories = new Set<string>()
|
|
53
57
|
let version = 0
|
|
54
58
|
let pollingFallback = false
|
|
55
59
|
let closed = false
|
|
@@ -70,6 +74,7 @@ export function createGenerateWatchChangeSignal(
|
|
|
70
74
|
|
|
71
75
|
return {
|
|
72
76
|
currentVersion: () => version,
|
|
77
|
+
hasSkippedTargets: () => skippedDirectories.size > 0,
|
|
73
78
|
usesPollingFallback: () => pollingFallback,
|
|
74
79
|
refresh: async () => {
|
|
75
80
|
if (closed || pollingFallback) return
|
|
@@ -83,13 +88,22 @@ export function createGenerateWatchChangeSignal(
|
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
const normalizedTargets = new Map<string, GenerateWatchTarget>()
|
|
91
|
+
const nextSkippedDirectories = new Set<string>()
|
|
86
92
|
for (const target of targets) {
|
|
87
93
|
const normalized: GenerateWatchTarget = {
|
|
88
94
|
...target,
|
|
89
95
|
directory: path.resolve(target.directory),
|
|
90
96
|
}
|
|
97
|
+
if (!directoryExists(normalized.directory)) {
|
|
98
|
+
nextSkippedDirectories.add(normalized.directory)
|
|
99
|
+
if (!skippedDirectories.has(normalized.directory)) {
|
|
100
|
+
try { options.onSkippedDirectory?.(normalized.directory) } catch {}
|
|
101
|
+
}
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
91
104
|
normalizedTargets.set(targetKey(normalized), normalized)
|
|
92
105
|
}
|
|
106
|
+
skippedDirectories = nextSkippedDirectories
|
|
93
107
|
|
|
94
108
|
for (const [key, watcher] of watchers) {
|
|
95
109
|
if (normalizedTargets.has(key)) continue
|
|
@@ -24,6 +24,8 @@ export type GenerateWatcherChangeSignal = {
|
|
|
24
24
|
currentVersion(): number
|
|
25
25
|
/** Refresh filesystem subscriptions after module configuration changes. */
|
|
26
26
|
refresh(): Promise<void> | void
|
|
27
|
+
/** Whether configured roots are missing and should be retried on idle polls. */
|
|
28
|
+
hasSkippedTargets?(): boolean
|
|
27
29
|
/** When true, preserve the legacy full-checksum-on-every-poll behavior. */
|
|
28
30
|
usesPollingFallback(): boolean
|
|
29
31
|
/** Close filesystem subscriptions. Idempotent. */
|
|
@@ -130,16 +132,23 @@ export function startInProcessGenerateWatcher(
|
|
|
130
132
|
if (stopping) return
|
|
131
133
|
try {
|
|
132
134
|
const changeVersion = changeSignal?.currentVersion() ?? 0
|
|
133
|
-
|
|
135
|
+
const eventGatedIdle = Boolean(
|
|
134
136
|
changeSignal
|
|
135
137
|
&& !changeSignal.usesPollingFallback()
|
|
136
138
|
&& changeVersion === observedChangeVersion
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
+
)
|
|
140
|
+
let refreshedSkippedTargets = false
|
|
141
|
+
if (eventGatedIdle) {
|
|
142
|
+
if (!changeSignal?.hasSkippedTargets?.()) return
|
|
143
|
+
await changeSignal.refresh()
|
|
144
|
+
refreshedSkippedTargets = true
|
|
145
|
+
if (changeSignal.hasSkippedTargets?.()) return
|
|
139
146
|
}
|
|
140
147
|
const nextChecksum = await computeStructureChecksum()
|
|
141
148
|
observedChangeVersion = changeVersion
|
|
142
|
-
|
|
149
|
+
if (!refreshedSkippedTargets) {
|
|
150
|
+
await changeSignal?.refresh()
|
|
151
|
+
}
|
|
143
152
|
if (nextChecksum !== previousChecksum) {
|
|
144
153
|
previousChecksum = nextChecksum
|
|
145
154
|
await runOnce('structure change')
|
package/src/mercato.ts
CHANGED
|
@@ -794,7 +794,7 @@ async function runGeneratorSuiteWithStructuralInvalidation(quiet: boolean): Prom
|
|
|
794
794
|
* watchers. Filesystem events mark the tree dirty; the existing full content
|
|
795
795
|
* checksum remains the final authority and the fallback when watching fails.
|
|
796
796
|
*/
|
|
797
|
-
async function createGenerateWatchRuntime() {
|
|
797
|
+
async function createGenerateWatchRuntime(quiet = false) {
|
|
798
798
|
const [
|
|
799
799
|
{ createResolver },
|
|
800
800
|
{ calculateGenerateWatchStructureChecksum },
|
|
@@ -825,6 +825,9 @@ async function createGenerateWatchRuntime() {
|
|
|
825
825
|
return calculateGenerateWatchStructureChecksum(collectWatchState())
|
|
826
826
|
},
|
|
827
827
|
changeSignal: createGenerateWatchChangeSignal({
|
|
828
|
+
onSkippedDirectory: quiet
|
|
829
|
+
? undefined
|
|
830
|
+
: (directory) => console.log(`[generate:watch] Skipping missing watch directory: ${directory}`),
|
|
828
831
|
getWatchTargets: () => {
|
|
829
832
|
const state = collectWatchState()
|
|
830
833
|
const targets: Array<{ directory: string; recursive: boolean; fileName?: string }> = [{
|
|
@@ -1864,7 +1867,7 @@ export async function run(argv = process.argv) {
|
|
|
1864
1867
|
const parsedInterval = intervalArg ? Number.parseInt(intervalArg.split('=')[1] ?? '', 10) : NaN
|
|
1865
1868
|
const intervalMs = Number.isFinite(parsedInterval) && parsedInterval >= 250 ? parsedInterval : 1000
|
|
1866
1869
|
|
|
1867
|
-
const generateWatchRuntime = await createGenerateWatchRuntime()
|
|
1870
|
+
const generateWatchRuntime = await createGenerateWatchRuntime(quiet)
|
|
1868
1871
|
const watcher = startInProcessGenerateWatcher({
|
|
1869
1872
|
pollMs: intervalMs,
|
|
1870
1873
|
skipInitial,
|