@open-mercato/cli 0.6.7-develop.6566.1.15385fa179 → 0.6.7-develop.6568.1.9f57ffc393

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 (65) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +5 -3
  3. package/dist/agentic/guides/module-facts.json +53 -53
  4. package/dist/agentic/guides/modules/ai_assistant.md +1 -1
  5. package/dist/agentic/guides/modules/api_docs.md +1 -1
  6. package/dist/agentic/guides/modules/api_keys.md +1 -1
  7. package/dist/agentic/guides/modules/attachments.md +1 -1
  8. package/dist/agentic/guides/modules/audit_logs.md +1 -1
  9. package/dist/agentic/guides/modules/auth.md +1 -1
  10. package/dist/agentic/guides/modules/business_rules.md +1 -1
  11. package/dist/agentic/guides/modules/catalog.md +1 -1
  12. package/dist/agentic/guides/modules/channel_gmail.md +1 -1
  13. package/dist/agentic/guides/modules/channel_imap.md +1 -1
  14. package/dist/agentic/guides/modules/checkout.md +1 -1
  15. package/dist/agentic/guides/modules/communication_channels.md +1 -1
  16. package/dist/agentic/guides/modules/configs.md +1 -1
  17. package/dist/agentic/guides/modules/content.md +1 -1
  18. package/dist/agentic/guides/modules/currencies.md +1 -1
  19. package/dist/agentic/guides/modules/customer_accounts.md +1 -1
  20. package/dist/agentic/guides/modules/customers.md +1 -1
  21. package/dist/agentic/guides/modules/dashboards.md +1 -1
  22. package/dist/agentic/guides/modules/data_sync.md +1 -1
  23. package/dist/agentic/guides/modules/dictionaries.md +1 -1
  24. package/dist/agentic/guides/modules/directory.md +1 -1
  25. package/dist/agentic/guides/modules/entities.md +1 -1
  26. package/dist/agentic/guides/modules/events.md +1 -1
  27. package/dist/agentic/guides/modules/feature_toggles.md +1 -1
  28. package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
  29. package/dist/agentic/guides/modules/generators.md +1 -1
  30. package/dist/agentic/guides/modules/inbox_ops.md +1 -1
  31. package/dist/agentic/guides/modules/integrations.md +1 -1
  32. package/dist/agentic/guides/modules/messages.md +1 -1
  33. package/dist/agentic/guides/modules/notifications.md +1 -1
  34. package/dist/agentic/guides/modules/onboarding.md +1 -1
  35. package/dist/agentic/guides/modules/payment_gateways.md +1 -1
  36. package/dist/agentic/guides/modules/perspectives.md +1 -1
  37. package/dist/agentic/guides/modules/planner.md +1 -1
  38. package/dist/agentic/guides/modules/portal.md +1 -1
  39. package/dist/agentic/guides/modules/progress.md +1 -1
  40. package/dist/agentic/guides/modules/query_index.md +1 -1
  41. package/dist/agentic/guides/modules/record_locks.md +1 -1
  42. package/dist/agentic/guides/modules/resources.md +1 -1
  43. package/dist/agentic/guides/modules/sales.md +1 -1
  44. package/dist/agentic/guides/modules/scheduler.md +1 -1
  45. package/dist/agentic/guides/modules/search.md +1 -1
  46. package/dist/agentic/guides/modules/security.md +1 -1
  47. package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
  48. package/dist/agentic/guides/modules/sso.md +1 -1
  49. package/dist/agentic/guides/modules/staff.md +1 -1
  50. package/dist/agentic/guides/modules/storage_s3.md +1 -1
  51. package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
  52. package/dist/agentic/guides/modules/sync_excel.md +1 -1
  53. package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
  54. package/dist/agentic/guides/modules/translations.md +1 -1
  55. package/dist/agentic/guides/modules/webhooks.md +1 -1
  56. package/dist/agentic/guides/modules/workflows.md +1 -1
  57. package/dist/lib/post-generate-invalidation.js +49 -0
  58. package/dist/lib/post-generate-invalidation.js.map +7 -0
  59. package/dist/mercato.js +20 -20
  60. package/dist/mercato.js.map +2 -2
  61. package/package.json +5 -5
  62. package/src/__tests__/mercato.test.ts +63 -21
  63. package/src/lib/__tests__/post-generate-invalidation.test.ts +81 -0
  64. package/src/lib/post-generate-invalidation.ts +62 -0
  65. package/src/mercato.ts +20 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.6.7-develop.6566.1.15385fa179",
3
+ "version": "0.6.7-develop.6568.1.9f57ffc393",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,8 +60,8 @@
60
60
  "@mikro-orm/decorators": "^7.1.5",
61
61
  "@mikro-orm/migrations": "^7.1.5",
62
62
  "@mikro-orm/postgresql": "^7.1.5",
63
- "@open-mercato/queue": "0.6.7-develop.6566.1.15385fa179",
64
- "@open-mercato/shared": "0.6.7-develop.6566.1.15385fa179",
63
+ "@open-mercato/queue": "0.6.7-develop.6568.1.9f57ffc393",
64
+ "@open-mercato/shared": "0.6.7-develop.6568.1.9f57ffc393",
65
65
  "cross-spawn": "^7.0.6",
66
66
  "pg": "8.22.0",
67
67
  "semver": "^7.8.5",
@@ -72,10 +72,10 @@
72
72
  "typescript-js": "npm:typescript@6.0.3"
73
73
  },
74
74
  "peerDependencies": {
75
- "@open-mercato/shared": "0.6.7-develop.6566.1.15385fa179"
75
+ "@open-mercato/shared": "0.6.7-develop.6568.1.9f57ffc393"
76
76
  },
77
77
  "devDependencies": {
78
- "@open-mercato/shared": "0.6.7-develop.6566.1.15385fa179",
78
+ "@open-mercato/shared": "0.6.7-develop.6568.1.9f57ffc393",
79
79
  "@types/jest": "^30.0.0",
80
80
  "jest": "^30.4.2",
81
81
  "ts-jest": "^29.4.11"
@@ -531,7 +531,7 @@ describe('init command failure output', () => {
531
531
  })
532
532
  })
533
533
 
534
- describe('generate post-step structural cache purge', () => {
534
+ describe('generate post-step structural invalidation', () => {
535
535
  beforeEach(() => {
536
536
  jest.restoreAllMocks()
537
537
  jest.resetModules()
@@ -540,11 +540,12 @@ describe('generate post-step structural cache purge', () => {
540
540
  afterEach(() => {
541
541
  jest.dontMock('../lib/generators')
542
542
  jest.dontMock('../lib/resolver')
543
+ jest.dontMock('../lib/post-generate-invalidation')
543
544
  jest.dontMock('@open-mercato/shared/lib/bootstrap/dynamicLoader')
544
545
  jest.resetModules()
545
546
  })
546
547
 
547
- it('runs structural cache purge after successful generation when configs cache CLI is available', async () => {
548
+ it('uses the lightweight invalidation helper after successful generation', async () => {
548
549
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
549
550
  const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
550
551
  const generateEntityIds = jest.fn().mockResolvedValue({
@@ -559,7 +560,12 @@ describe('generate post-step structural cache purge', () => {
559
560
  const generateModuleDi = jest.fn().mockResolvedValue(undefined)
560
561
  const generateModulePackageSources = jest.fn().mockResolvedValue(undefined)
561
562
  const generateOpenApi = jest.fn().mockResolvedValue(undefined)
562
- const cacheRun = jest.fn().mockResolvedValue(undefined)
563
+ const invalidate = jest.fn().mockResolvedValue({
564
+ cacheEntriesDeleted: 2,
565
+ generatedFilesTouched: ['/tmp/test-app/.mercato/generated/modules.generated.ts'],
566
+ cacheError: null,
567
+ generatedFilesError: null,
568
+ })
563
569
 
564
570
  jest.doMock('../lib/generators', () => ({
565
571
  generateEntityIds,
@@ -574,25 +580,22 @@ describe('generate post-step structural cache purge', () => {
574
580
  jest.doMock('../lib/resolver', () => ({
575
581
  createResolver: () => ({
576
582
  getAppDir: () => '/tmp/test-app',
583
+ loadEnabledModules: () => [{ id: 'configs', from: '@open-mercato/core' }],
577
584
  }),
578
585
  }))
579
- jest.doMock('@open-mercato/shared/lib/bootstrap/dynamicLoader', () => ({
580
- bootstrapFromAppRoot: jest.fn().mockResolvedValue({
581
- modules: [
582
- {
583
- id: 'configs',
584
- cli: [{ command: 'cache', run: cacheRun }],
585
- },
586
- ],
587
- }),
586
+ jest.doMock('../lib/post-generate-invalidation', () => ({
587
+ runPostGenerateStructuralInvalidation: invalidate,
588
588
  }))
589
+ jest.doMock('@open-mercato/shared/lib/bootstrap/dynamicLoader', () => {
590
+ throw new Error('post-generation invalidation must not bootstrap generated modules')
591
+ })
589
592
 
590
593
  const mercato = await import('../mercato')
591
594
  const exitCode = await mercato.run(['node', 'mercato', 'generate'])
592
595
 
593
596
  expect(exitCode).toBe(0)
594
597
  expect(generateEntityIds).toHaveBeenCalled()
595
- expect(cacheRun).toHaveBeenCalledWith(['structural', '--all-tenants', '--quiet'])
598
+ expect(invalidate).toHaveBeenCalledWith('/tmp/test-app')
596
599
 
597
600
  consoleErrorSpy.mockRestore()
598
601
  consoleLogSpy.mockRestore()
@@ -616,32 +619,33 @@ describe('generate post-step structural cache purge', () => {
616
619
  generateModulePackageSources: jest.fn().mockResolvedValue(unchangedResult),
617
620
  generateOpenApi: jest.fn().mockResolvedValue(unchangedResult),
618
621
  }
619
- const bootstrapFromAppRoot = jest.fn()
622
+ const invalidate = jest.fn()
620
623
 
621
624
  jest.doMock('../lib/generators', () => generators)
622
625
  jest.doMock('../lib/resolver', () => ({
623
626
  createResolver: () => ({
624
627
  getAppDir: () => '/tmp/test-app',
628
+ loadEnabledModules: () => [{ id: 'configs', from: '@open-mercato/core' }],
625
629
  }),
626
630
  }))
627
- jest.doMock('@open-mercato/shared/lib/bootstrap/dynamicLoader', () => ({
628
- bootstrapFromAppRoot,
631
+ jest.doMock('../lib/post-generate-invalidation', () => ({
632
+ runPostGenerateStructuralInvalidation: invalidate,
629
633
  }))
630
634
 
631
635
  const mercato = await import('../mercato')
632
636
  const exitCode = await mercato.run(['node', 'mercato', 'generate'])
633
637
 
634
638
  expect(exitCode).toBe(0)
635
- expect(bootstrapFromAppRoot).not.toHaveBeenCalled()
639
+ expect(invalidate).not.toHaveBeenCalled()
636
640
  expect(consoleLogSpy).toHaveBeenCalledWith(
637
- '[generate] Generated outputs unchanged; skipping structural cache purge.',
641
+ '[generate] Generated outputs unchanged; skipping structural invalidation.',
638
642
  )
639
643
 
640
644
  consoleErrorSpy.mockRestore()
641
645
  consoleLogSpy.mockRestore()
642
646
  })
643
647
 
644
- it('keeps generation successful when the post-generate cache purge bootstrap fails', async () => {
648
+ it('keeps generation successful when lightweight invalidation fails', async () => {
645
649
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
646
650
  const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
647
651
  const generateEntityIds = jest.fn().mockResolvedValue({
@@ -656,6 +660,7 @@ describe('generate post-step structural cache purge', () => {
656
660
  const generateModuleDi = jest.fn().mockResolvedValue(undefined)
657
661
  const generateModulePackageSources = jest.fn().mockResolvedValue(undefined)
658
662
  const generateOpenApi = jest.fn().mockResolvedValue(undefined)
663
+ const invalidate = jest.fn().mockRejectedValue(new Error('cache maintenance unavailable'))
659
664
 
660
665
  jest.doMock('../lib/generators', () => ({
661
666
  generateEntityIds,
@@ -670,10 +675,11 @@ describe('generate post-step structural cache purge', () => {
670
675
  jest.doMock('../lib/resolver', () => ({
671
676
  createResolver: () => ({
672
677
  getAppDir: () => '/tmp/test-app',
678
+ loadEnabledModules: () => [{ id: 'configs', from: '@open-mercato/core' }],
673
679
  }),
674
680
  }))
675
- jest.doMock('@open-mercato/shared/lib/bootstrap/dynamicLoader', () => ({
676
- bootstrapFromAppRoot: jest.fn().mockRejectedValue(new Error('generated cli bootstrap unavailable')),
681
+ jest.doMock('../lib/post-generate-invalidation', () => ({
682
+ runPostGenerateStructuralInvalidation: invalidate,
677
683
  }))
678
684
 
679
685
  const mercato = await import('../mercato')
@@ -681,6 +687,42 @@ describe('generate post-step structural cache purge', () => {
681
687
 
682
688
  expect(exitCode).toBe(0)
683
689
  expect(generateEntityIds).toHaveBeenCalled()
690
+ expect(invalidate).toHaveBeenCalledWith('/tmp/test-app')
691
+
692
+ consoleErrorSpy.mockRestore()
693
+ consoleLogSpy.mockRestore()
694
+ })
695
+
696
+ it('skips cache infrastructure entirely when configs is not enabled', async () => {
697
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
698
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
699
+ const generate = jest.fn().mockResolvedValue(undefined)
700
+
701
+ jest.doMock('../lib/generators', () => ({
702
+ generateEntityIds: generate,
703
+ generateModuleRegistry: generate,
704
+ generateModuleRegistryApp: generate,
705
+ generateModuleRegistryCli: generate,
706
+ generateModuleEntities: generate,
707
+ generateModuleDi: generate,
708
+ generateModulePackageSources: generate,
709
+ generateOpenApi: generate,
710
+ }))
711
+ jest.doMock('../lib/resolver', () => ({
712
+ createResolver: () => ({
713
+ getAppDir: () => '/tmp/test-app',
714
+ loadEnabledModules: () => [{ id: 'auth', from: '@open-mercato/core' }],
715
+ }),
716
+ }))
717
+ jest.doMock('../lib/post-generate-invalidation', () => {
718
+ throw new Error('cache infrastructure must stay unloaded without configs')
719
+ })
720
+
721
+ const mercato = await import('../mercato')
722
+ const exitCode = await mercato.run(['node', 'mercato', 'generate'])
723
+
724
+ expect(exitCode).toBe(0)
725
+ expect(generate).toHaveBeenCalled()
684
726
 
685
727
  consoleErrorSpy.mockRestore()
686
728
  consoleLogSpy.mockRestore()
@@ -0,0 +1,81 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import { purgeConfiguredCachePatternsAcrossTenantScopes } from '@open-mercato/shared/lib/cache/maintenance'
5
+ import { runPostGenerateStructuralInvalidation } from '../post-generate-invalidation'
6
+
7
+ jest.mock('@open-mercato/shared/lib/cache/maintenance', () => ({
8
+ purgeConfiguredCachePatternsAcrossTenantScopes: jest.fn(),
9
+ }))
10
+
11
+ const purgeCache = jest.mocked(purgeConfiguredCachePatternsAcrossTenantScopes)
12
+
13
+ describe('runPostGenerateStructuralInvalidation', () => {
14
+ beforeEach(() => {
15
+ purgeCache.mockReset().mockResolvedValue(3)
16
+ })
17
+
18
+ it('purges structural cache patterns and refreshes only generated artifacts', async () => {
19
+ const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'post-generate-invalidation-'))
20
+ try {
21
+ const generatedDir = path.join(appDir, '.mercato', 'generated')
22
+ fs.mkdirSync(generatedDir, { recursive: true })
23
+ const generatedTs = path.join(generatedDir, 'modules.app.generated.ts')
24
+ const generatedChecksum = path.join(generatedDir, 'modules.app.generated.checksum')
25
+ const unrelated = path.join(generatedDir, 'README.md')
26
+ fs.writeFileSync(generatedTs, 'export const modules = []\n')
27
+ fs.writeFileSync(generatedChecksum, 'abc\n')
28
+ fs.writeFileSync(unrelated, 'keep\n')
29
+
30
+ const result = await runPostGenerateStructuralInvalidation(appDir)
31
+
32
+ expect(purgeCache).toHaveBeenCalledWith([
33
+ 'nav:*',
34
+ 'crud|*|*|*/admin/nav|*',
35
+ 'crud|*|*|*/portal/nav|*',
36
+ ])
37
+ expect(result).toEqual({
38
+ cacheEntriesDeleted: 3,
39
+ generatedFilesTouched: [generatedChecksum, generatedTs],
40
+ cacheError: null,
41
+ generatedFilesError: null,
42
+ })
43
+ expect(fs.readFileSync(generatedTs, 'utf8')).toBe('export const modules = []\n')
44
+ expect(fs.readFileSync(unrelated, 'utf8')).toBe('keep\n')
45
+ } finally {
46
+ fs.rmSync(appDir, { recursive: true, force: true })
47
+ }
48
+ })
49
+
50
+ it('still refreshes generated artifacts when cache maintenance fails', async () => {
51
+ const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'post-generate-invalidation-failure-'))
52
+ const error = new Error('cache unavailable')
53
+ try {
54
+ const generatedDir = path.join(appDir, '.mercato', 'generated')
55
+ fs.mkdirSync(generatedDir, { recursive: true })
56
+ const generatedTs = path.join(generatedDir, 'modules.generated.ts')
57
+ fs.writeFileSync(generatedTs, 'export {}\n')
58
+ purgeCache.mockRejectedValueOnce(error)
59
+
60
+ const result = await runPostGenerateStructuralInvalidation(appDir)
61
+
62
+ expect(result.cacheEntriesDeleted).toBe(0)
63
+ expect(result.cacheError).toBe(error)
64
+ expect(result.generatedFilesTouched).toEqual([generatedTs])
65
+ expect(result.generatedFilesError).toBeNull()
66
+ } finally {
67
+ fs.rmSync(appDir, { recursive: true, force: true })
68
+ }
69
+ })
70
+
71
+ it('treats a missing generated directory as a successful no-op', async () => {
72
+ const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'post-generate-invalidation-empty-'))
73
+ try {
74
+ const result = await runPostGenerateStructuralInvalidation(appDir)
75
+ expect(result.generatedFilesTouched).toEqual([])
76
+ expect(result.generatedFilesError).toBeNull()
77
+ } finally {
78
+ fs.rmSync(appDir, { recursive: true, force: true })
79
+ }
80
+ })
81
+ })
@@ -0,0 +1,62 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { purgeConfiguredCachePatternsAcrossTenantScopes } from '@open-mercato/shared/lib/cache/maintenance'
4
+
5
+ const STRUCTURAL_CACHE_PATTERNS = [
6
+ 'nav:*',
7
+ 'crud|*|*|*/admin/nav|*',
8
+ 'crud|*|*|*/portal/nav|*',
9
+ ]
10
+ const TOUCHABLE_GENERATED_FILE_PATTERN = /\.generated(?:\.[a-z0-9]+)?(?:\.ts|\.checksum)$/i
11
+
12
+ export type PostGenerateStructuralInvalidationResult = {
13
+ cacheEntriesDeleted: number
14
+ generatedFilesTouched: string[]
15
+ cacheError: unknown | null
16
+ generatedFilesError: unknown | null
17
+ }
18
+
19
+ function touchGeneratedFiles(appDir: string): string[] {
20
+ const generatedDir = path.join(appDir, '.mercato', 'generated')
21
+ if (!fs.existsSync(generatedDir) || !fs.statSync(generatedDir).isDirectory()) return []
22
+
23
+ const touched: string[] = []
24
+ const entries = fs.readdirSync(generatedDir, { withFileTypes: true })
25
+ .sort((left, right) => left.name.localeCompare(right.name))
26
+ for (const entry of entries) {
27
+ if (!entry.isFile() || !TOUCHABLE_GENERATED_FILE_PATTERN.test(entry.name)) continue
28
+ const filePath = path.join(generatedDir, entry.name)
29
+ const contents = fs.readFileSync(filePath)
30
+ fs.writeFileSync(filePath, contents)
31
+ touched.push(filePath)
32
+ }
33
+ return touched
34
+ }
35
+
36
+ export async function runPostGenerateStructuralInvalidation(
37
+ appDir: string,
38
+ ): Promise<PostGenerateStructuralInvalidationResult> {
39
+ let cacheEntriesDeleted = 0
40
+ let generatedFilesTouched: string[] = []
41
+ let cacheError: unknown | null = null
42
+ let generatedFilesError: unknown | null = null
43
+
44
+ try {
45
+ generatedFilesTouched = touchGeneratedFiles(appDir)
46
+ } catch (error) {
47
+ generatedFilesError = error
48
+ }
49
+
50
+ try {
51
+ cacheEntriesDeleted = await purgeConfiguredCachePatternsAcrossTenantScopes(STRUCTURAL_CACHE_PATTERNS)
52
+ } catch (error) {
53
+ cacheError = error
54
+ }
55
+
56
+ return {
57
+ cacheEntriesDeleted,
58
+ generatedFilesTouched,
59
+ cacheError,
60
+ generatedFilesError,
61
+ }
62
+ }
package/src/mercato.ts CHANGED
@@ -706,40 +706,40 @@ async function runModuleCommand(
706
706
  return true
707
707
  }
708
708
 
709
- async function runPostGenerateStructuralCachePurge(quiet: boolean): Promise<void> {
709
+ async function runPostGenerateStructuralInvalidation(quiet: boolean): Promise<void> {
710
710
  try {
711
- const [{ bootstrapFromAppRoot }, { createResolver }] = await Promise.all([
712
- import('@open-mercato/shared/lib/bootstrap/dynamicLoader'),
713
- import('./lib/resolver'),
714
- ])
711
+ const { createResolver } = await import('./lib/resolver')
715
712
  const resolver = createResolver()
716
713
  const appDir = resolver.getAppDir()
717
- const data = await bootstrapFromAppRoot(appDir)
718
- registerCliModules(data.modules)
719
- const configsModule = data.modules.find((mod) => mod.id === 'configs')
720
- const hasCacheCommand = configsModule?.cli?.some((command) => command.command === 'cache') ?? false
714
+ const hasConfigsModule = resolver.loadEnabledModules().some((module) => module.id === 'configs')
721
715
 
722
- if (!hasCacheCommand) {
716
+ if (!hasConfigsModule) {
723
717
  if (!quiet) {
724
- console.log('[generate] Skipping structural cache purge: "configs cache" is not available in this app.')
718
+ console.log('[generate] Skipping structural invalidation: the "configs" module is not enabled in this app.')
725
719
  }
726
720
  return
727
721
  }
728
722
 
723
+ const { runPostGenerateStructuralInvalidation: invalidate } = await import('./lib/post-generate-invalidation')
729
724
  if (!quiet) {
730
- console.log('[generate] Purging structural cache for all tenants...')
725
+ console.log('[generate] Invalidating generated structure...')
731
726
  }
732
- await runModuleCommand(data.modules, 'configs', 'cache', ['structural', '--all-tenants', '--quiet'], {
733
- optional: true,
734
- silentOptional: quiet,
735
- })
727
+ const result = await invalidate(appDir)
736
728
  if (!quiet) {
737
- console.log('[generate] Structural cache purge completed.')
729
+ if (result.cacheError) {
730
+ console.log(`[generate] Structural cache purge skipped: ${formatCliFailureMessage('configs', 'cache', result.cacheError)}`)
731
+ }
732
+ if (result.generatedFilesError) {
733
+ console.log(`[generate] Generated artifact refresh skipped: ${formatCliFailureMessage('generate', 'refresh', result.generatedFilesError)}`)
734
+ }
735
+ console.log(
736
+ `[generate] Structural invalidation completed (${result.cacheEntriesDeleted} cache entr${result.cacheEntriesDeleted === 1 ? 'y' : 'ies'}, ${result.generatedFilesTouched.length} generated artifact${result.generatedFilesTouched.length === 1 ? '' : 's'} refreshed).`,
737
+ )
738
738
  }
739
739
  } catch (error) {
740
740
  if (!quiet) {
741
741
  const message = formatCliFailureMessage('configs', 'cache', error)
742
- console.log(`[generate] Skipping structural cache purge: ${message}`)
742
+ console.log(`[generate] Skipping structural invalidation: ${message}`)
743
743
  }
744
744
  }
745
745
  }
@@ -780,11 +780,11 @@ async function runGeneratorSuiteWithStructuralInvalidation(quiet: boolean): Prom
780
780
  const generatedFilesChanged = await runGeneratorSuite(quiet)
781
781
  if (!generatedFilesChanged) {
782
782
  if (!quiet) {
783
- console.log('[generate] Generated outputs unchanged; skipping structural cache purge.')
783
+ console.log('[generate] Generated outputs unchanged; skipping structural invalidation.')
784
784
  }
785
785
  return
786
786
  }
787
- await runPostGenerateStructuralCachePurge(quiet)
787
+ await runPostGenerateStructuralInvalidation(quiet)
788
788
  }
789
789
 
790
790
  /**