@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.
Files changed (71) hide show
  1. package/dist/agentic/guides/module-facts.json +110 -110
  2. package/dist/agentic/guides/modules/ai_assistant.md +1 -1
  3. package/dist/agentic/guides/modules/api_docs.md +1 -1
  4. package/dist/agentic/guides/modules/api_keys.md +1 -1
  5. package/dist/agentic/guides/modules/attachments.md +1 -1
  6. package/dist/agentic/guides/modules/audit_logs.md +1 -1
  7. package/dist/agentic/guides/modules/auth.md +1 -1
  8. package/dist/agentic/guides/modules/business_rules.md +1 -1
  9. package/dist/agentic/guides/modules/catalog.md +1 -1
  10. package/dist/agentic/guides/modules/channel_gmail.md +1 -1
  11. package/dist/agentic/guides/modules/channel_imap.md +1 -1
  12. package/dist/agentic/guides/modules/checkout.md +1 -1
  13. package/dist/agentic/guides/modules/communication_channels.md +1 -1
  14. package/dist/agentic/guides/modules/configs.md +1 -1
  15. package/dist/agentic/guides/modules/content.md +1 -1
  16. package/dist/agentic/guides/modules/currencies.md +1 -1
  17. package/dist/agentic/guides/modules/customer_accounts.md +1 -1
  18. package/dist/agentic/guides/modules/customers.md +1 -1
  19. package/dist/agentic/guides/modules/dashboards.md +1 -1
  20. package/dist/agentic/guides/modules/data_sync.md +1 -1
  21. package/dist/agentic/guides/modules/design_system.md +1 -1
  22. package/dist/agentic/guides/modules/dictionaries.md +1 -1
  23. package/dist/agentic/guides/modules/directory.md +1 -1
  24. package/dist/agentic/guides/modules/entities.md +1 -1
  25. package/dist/agentic/guides/modules/events.md +1 -1
  26. package/dist/agentic/guides/modules/feature_toggles.md +1 -1
  27. package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
  28. package/dist/agentic/guides/modules/generators.md +1 -1
  29. package/dist/agentic/guides/modules/inbox_ops.md +1 -1
  30. package/dist/agentic/guides/modules/integrations.md +1 -1
  31. package/dist/agentic/guides/modules/messages.md +1 -1
  32. package/dist/agentic/guides/modules/notifications.md +1 -1
  33. package/dist/agentic/guides/modules/onboarding.md +1 -1
  34. package/dist/agentic/guides/modules/payment_gateways.md +1 -1
  35. package/dist/agentic/guides/modules/perspectives.md +1 -1
  36. package/dist/agentic/guides/modules/planner.md +1 -1
  37. package/dist/agentic/guides/modules/portal.md +1 -1
  38. package/dist/agentic/guides/modules/progress.md +1 -1
  39. package/dist/agentic/guides/modules/query_index.md +1 -1
  40. package/dist/agentic/guides/modules/record_locks.md +1 -1
  41. package/dist/agentic/guides/modules/resources.md +1 -1
  42. package/dist/agentic/guides/modules/sales.md +1 -1
  43. package/dist/agentic/guides/modules/scheduler.md +1 -1
  44. package/dist/agentic/guides/modules/search.md +1 -1
  45. package/dist/agentic/guides/modules/security.md +1 -1
  46. package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
  47. package/dist/agentic/guides/modules/sso.md +1 -1
  48. package/dist/agentic/guides/modules/staff.md +1 -1
  49. package/dist/agentic/guides/modules/storage_s3.md +1 -1
  50. package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
  51. package/dist/agentic/guides/modules/sync_excel.md +1 -1
  52. package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
  53. package/dist/agentic/guides/modules/translations.md +1 -1
  54. package/dist/agentic/guides/modules/webhooks.md +1 -1
  55. package/dist/agentic/guides/modules/wms.md +1 -1
  56. package/dist/agentic/guides/modules/workflows.md +1 -1
  57. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +17 -0
  58. package/dist/agentic/guides/upstream/manifest.json +2 -2
  59. package/dist/agentic/shared/ai/harness/RELEASE.md +2 -0
  60. package/dist/lib/generate-watch-events.js +15 -0
  61. package/dist/lib/generate-watch-events.js.map +2 -2
  62. package/dist/lib/in-process-generate-watcher.js +12 -3
  63. package/dist/lib/in-process-generate-watcher.js.map +2 -2
  64. package/dist/mercato.js +3 -2
  65. package/dist/mercato.js.map +2 -2
  66. package/package.json +6 -6
  67. package/src/lib/__tests__/generate-watch-events.test.ts +76 -0
  68. package/src/lib/__tests__/in-process-generate-watcher.test.ts +52 -1
  69. package/src/lib/generate-watch-events.ts +14 -0
  70. package/src/lib/in-process-generate-watcher.ts +13 -4
  71. 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.6898.1.8b42ed5e41",
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.6898.1.8b42ed5e41",
64
- "@open-mercato/shared": "0.6.8-develop.6898.1.8b42ed5e41",
65
- "@open-mercato/telemetry": "0.6.8-develop.6898.1.8b42ed5e41",
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.6898.1.8b42ed5e41"
76
+ "@open-mercato/shared": "0.6.8-develop.6903.1.0ec850a22b"
77
77
  },
78
78
  "devDependencies": {
79
- "@open-mercato/shared": "0.6.8-develop.6898.1.8b42ed5e41",
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: jest.fn(async () => undefined),
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
- if (
135
+ const eventGatedIdle = Boolean(
134
136
  changeSignal
135
137
  && !changeSignal.usesPollingFallback()
136
138
  && changeVersion === observedChangeVersion
137
- ) {
138
- return
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
- await changeSignal?.refresh()
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,