@open-mercato/core 0.6.1-develop.3090.06ab462170 → 0.6.1-develop.3102.d6e7e6d57a

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.
@@ -1,4 +1,4 @@
1
- [build:core] found 2445 entry points
1
+ [build:core] found 2446 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 170 entry points
4
4
  [build:core:generated] built successfully
package/AGENTS.md CHANGED
@@ -373,10 +373,30 @@ Always reference generated ids (`E.<module>.<entity>`) so system entities stay a
373
373
 
374
374
  ### Helpers
375
375
 
376
- - **Shared helpers**: `splitCustomFieldPayload`, `normalizeCustomFieldValues`, `normalizeCustomFieldResponse` from `@open-mercato/shared`
376
+ - **Shared helpers**: `splitCustomFieldPayload`, `normalizeCustomFieldValues`, `normalizeCustomFieldResponse`, `applyCustomFieldsNormalization` from `@open-mercato/shared`
377
377
  - **Form collection**: `collectCustomFieldValues()` from `@open-mercato/ui/backend/utils/customFieldValues`
378
378
  - **Command undo**: capture custom field snapshots in `before`/`after` payloads (`snapshot.custom`), restore via `buildCustomFieldResetMap(before.custom, after.custom)`
379
379
 
380
+ ### Response Shape
381
+
382
+ `makeCrudRoute` already extracts custom field values into `customValues` (bare keys, e.g. `{ priority: 3 }`) and `customFields` (definition array) when `list.decorateCustomFields` is configured.
383
+
384
+ To opt into the canonical single-source response shape (no top-level `cf_*`/`cf:*` redundancy — the standardization requested in #1769), set `stripPrefixedKeys: true`:
385
+
386
+ ```typescript
387
+ list: {
388
+ // ...
389
+ decorateCustomFields: {
390
+ entityIds: E.example.todo,
391
+ stripPrefixedKeys: true,
392
+ },
393
+ }
394
+ ```
395
+
396
+ For non-CRUD routes (custom detail GETs, ad-hoc handlers), call `applyCustomFieldsNormalization(record, decorated, { stripPrefixedKeys: true })` to get the same shape.
397
+
398
+ The flag is opt-in to keep the existing wire format stable for callers that read `cf_*` from the top level — turn it on for new modules and migrate existing modules deliberately, with a deprecation note for any external consumer that still reads the prefixed keys.
399
+
380
400
  ### DSL Helpers
381
401
 
382
402
  ```typescript
@@ -8,6 +8,7 @@ import {
8
8
  executeCachePurge,
9
9
  previewCachePurge
10
10
  } from "./lib/cache-cli.js";
11
+ import { touchGeneratedBarrels } from "./lib/touchGeneratedBarrels.js";
11
12
  function parseArgs(rest) {
12
13
  const args = {};
13
14
  for (let i = 0; i < rest.length; i += 1) {
@@ -215,6 +216,16 @@ async function runStructuralCachePurge(args) {
215
216
  pattern: "nav:*"
216
217
  };
217
218
  await runCachePurge(nextArgs);
219
+ const quiet = flagEnabled(args, "quiet");
220
+ try {
221
+ touchGeneratedBarrels({ quiet });
222
+ } catch (err) {
223
+ if (!quiet) {
224
+ console.warn(
225
+ `[structural] failed to touch generated barrels: ${err.message ?? err}`
226
+ );
227
+ }
228
+ }
218
229
  }
219
230
  function envDisablesAutoIndexing() {
220
231
  const raw = process.env.OM_DISABLE_VECTOR_SEARCH_AUTOINDEXING ?? process.env.DISABLE_VECTOR_SEARCH_AUTOINDEXING;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/configs/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { ModuleConfigService } from './lib/module-config-service'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, NOTIFICATIONS_DELIVERY_CONFIG_KEY } from '../notifications/lib/deliveryConfig'\nimport { Tenant } from '../directory/data/entities'\nimport {\n collectCacheStats,\n executeCachePurge,\n previewCachePurge,\n type CachePurgeRequest,\n} from './lib/cache-cli'\n\ntype ParsedArgs = Record<string, string | boolean>\n\ntype CacheScope = {\n label: string\n tenantId: string | null\n}\n\nfunction parseArgs(rest: string[]): ParsedArgs {\n const args: ParsedArgs = {}\n for (let i = 0; i < rest.length; i += 1) {\n const part = rest[i]\n if (!part?.startsWith('--')) continue\n const [rawKey, rawValue] = part.slice(2).split('=')\n if (!rawKey) continue\n if (rawValue !== undefined) {\n args[rawKey] = rawValue\n } else if (i + 1 < rest.length && !rest[i + 1]!.startsWith('--')) {\n args[rawKey] = rest[i + 1]!\n i += 1\n } else {\n args[rawKey] = true\n }\n }\n return args\n}\n\nfunction stringOption(args: ParsedArgs, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (typeof raw !== 'string') continue\n const trimmed = raw.trim()\n if (trimmed.length > 0) return trimmed\n }\n return undefined\n}\n\nfunction flagEnabled(args: ParsedArgs, ...keys: string[]): boolean {\n for (const key of keys) {\n const raw = args[key]\n if (raw === undefined) continue\n if (raw === true) return true\n if (typeof raw === 'string') {\n const parsed = parseBooleanToken(raw)\n return parsed === null ? true : parsed\n }\n }\n return false\n}\n\nfunction splitListOption(raw: string | undefined): string[] {\n if (!raw) return []\n const seen = new Set<string>()\n const values: string[] = []\n for (const item of raw.split(',')) {\n const trimmed = item.trim()\n if (!trimmed || seen.has(trimmed)) continue\n seen.add(trimmed)\n values.push(trimmed)\n }\n return values\n}\n\nasync function resolveCacheScopes(\n em: EntityManager,\n args: ParsedArgs,\n): Promise<CacheScope[]> {\n const explicitTenantId = stringOption(args, 'tenant', 'tenantId')\n const globalOnly = flagEnabled(args, 'global')\n const allTenants = flagEnabled(args, 'all-tenants', 'allTenants')\n\n if (explicitTenantId && globalOnly) {\n throw new Error('Cannot combine `--tenant` with `--global`.')\n }\n if (explicitTenantId && allTenants) {\n throw new Error('Cannot combine `--tenant` with `--all-tenants`.')\n }\n if (globalOnly && allTenants) {\n throw new Error('Cannot combine `--global` with `--all-tenants`.')\n }\n\n if (explicitTenantId) {\n return [{ label: `tenant:${explicitTenantId}`, tenantId: explicitTenantId }]\n }\n\n if (globalOnly) {\n return [{ label: 'global', tenantId: null }]\n }\n\n if (!allTenants) {\n return [{ label: 'global', tenantId: null }]\n }\n\n const tenants = await em.find(Tenant, { deletedAt: null }, { orderBy: { name: 'asc' } })\n const scopes: CacheScope[] = [{ label: 'global', tenantId: null }]\n const seen = new Set<string>()\n for (const tenant of tenants) {\n const tenantId = typeof tenant.id === 'string' ? tenant.id : ''\n if (!tenantId || seen.has(tenantId)) continue\n seen.add(tenantId)\n scopes.push({ label: `tenant:${tenantId}`, tenantId })\n }\n return scopes\n}\n\nfunction resolveCachePurgeRequest(args: ParsedArgs): CachePurgeRequest {\n if (flagEnabled(args, 'all')) return { kind: 'all' }\n\n const segment = stringOption(args, 'segment')\n if (segment) return { kind: 'segment', segment }\n\n const tags = splitListOption(stringOption(args, 'tag', 'tags'))\n if (tags.length > 0) return { kind: 'tags', tags }\n\n const keys = splitListOption(stringOption(args, 'key', 'keys'))\n if (keys.length > 0) return { kind: 'keys', keys }\n\n const ids = splitListOption(stringOption(args, 'id', 'ids'))\n if (ids.length > 0) return { kind: 'ids', ids }\n\n const pattern = stringOption(args, 'pattern')\n if (pattern) return { kind: 'pattern', pattern }\n\n throw new Error(\n 'Choose a purge target: `--all`, `--segment <id>`, `--tag <tag1,tag2>`, `--key <key1,key2>`, `--id <token1,token2>`, or `--pattern <glob>`.',\n )\n}\n\nfunction printCacheHelp() {\n console.log('\uD83E\uDDF9 Cache CLI')\n console.log('')\n console.log('\uD83D\uDE80 Usage:')\n console.log(' yarn mercato configs cache stats [--tenant <id> | --global | --all-tenants] [--json]')\n console.log(' yarn mercato configs cache purge --all [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --segment <segment> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --tag <tag1,tag2> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --key <key1,key2> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --id <token1,token2> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --pattern <glob> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache structural [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log('')\n console.log('\u2139\uFE0F Notes:')\n console.log(' `stats` mirrors the cache admin page segment overview for CRUD/widget caches.')\n console.log(' `purge --id` removes every key whose name contains the provided token (for example a user id or entity id).')\n console.log(' `structural` targets navigation caches (`nav:*`) and is the recommended post-step after module/sidebar structure changes.')\n console.log(' When no scope flag is supplied, this command uses the global cache scope only.')\n}\n\nasync function disposeContainer(container: unknown) {\n const disposable = container as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n}\n\nasync function runCacheStats(args: ParsedArgs) {\n const json = flagEnabled(args, 'json')\n const container = await createRequestContainer()\n try {\n const em = container.resolve('em') as EntityManager\n const cache = container.resolve('cache') as CacheStrategy\n const scopes = await resolveCacheScopes(em, args)\n const results = []\n for (const scope of scopes) {\n const stats = await runWithCacheTenant(scope.tenantId, async () => collectCacheStats(cache))\n results.push({ scope: scope.label, ...stats })\n }\n\n if (json) {\n console.log(JSON.stringify(results, null, 2))\n return\n }\n\n for (const result of results) {\n console.log(`\uD83D\uDD0E [cache] scope=${result.scope} totalKeys=${result.totalKeys} generatedAt=${result.generatedAt}`)\n if (result.segments.length === 0) {\n console.log(' \u2205 segments: none')\n continue\n }\n for (const segment of result.segments) {\n console.log(` \u2022 ${segment.segment} (${segment.keyCount})${segment.path ? ` ${segment.path}` : ''}`)\n }\n }\n } finally {\n await disposeContainer(container)\n }\n}\n\nasync function runCachePurge(args: ParsedArgs) {\n const json = flagEnabled(args, 'json')\n const quiet = flagEnabled(args, 'quiet')\n const dryRun = flagEnabled(args, 'dry-run', 'dryRun')\n const request = resolveCachePurgeRequest(args)\n const container = await createRequestContainer()\n try {\n const em = container.resolve('em') as EntityManager\n const cache = container.resolve('cache') as CacheStrategy\n const scopes = await resolveCacheScopes(em, args)\n const results = []\n\n for (const scope of scopes) {\n const result = await runWithCacheTenant(scope.tenantId, async () =>\n dryRun ? previewCachePurge(cache, request) : executeCachePurge(cache, request)\n )\n results.push({\n scope: scope.label,\n dryRun,\n request,\n deleted: result.deleted,\n keyCount: result.keys.length,\n keys: result.keys,\n note: result.note,\n })\n }\n\n if (json) {\n console.log(JSON.stringify(results, null, 2))\n return\n }\n\n if (quiet) {\n return\n }\n\n for (const result of results) {\n console.log(`${result.dryRun ? '\uD83E\uDDEA' : '\uD83E\uDDF9'} [cache] scope=${result.scope} deleted=${result.deleted}${result.dryRun ? ' (dry-run)' : ''}`)\n if (result.note) console.log(` \u2139\uFE0F note: ${result.note}`)\n if (result.keys.length > 0) {\n for (const key of result.keys) {\n console.log(` \u2022 ${key}`)\n }\n }\n }\n } finally {\n await disposeContainer(container)\n }\n}\n\nasync function runStructuralCachePurge(args: ParsedArgs) {\n const nextArgs: ParsedArgs = {\n ...args,\n pattern: 'nav:*',\n }\n await runCachePurge(nextArgs)\n}\n\nfunction envDisablesAutoIndexing(): boolean {\n const raw =\n process.env.OM_DISABLE_VECTOR_SEARCH_AUTOINDEXING ??\n process.env.DISABLE_VECTOR_SEARCH_AUTOINDEXING\n if (!raw) return false\n return parseBooleanToken(raw) === true\n}\n\nconst restoreDefaults: ModuleCli = {\n command: 'restore-defaults',\n async run() {\n const container = await createRequestContainer()\n try {\n let service: ModuleConfigService\n try {\n service = (container.resolve('moduleConfigService') as ModuleConfigService)\n } catch {\n console.error('[configs] moduleConfigService is not registered in the container.')\n return\n }\n\n const disabledByEnv = envDisablesAutoIndexing()\n const defaultEnabled = !disabledByEnv\n await service.restoreDefaults(\n [\n {\n moduleId: 'vector',\n name: 'auto_index_enabled',\n value: defaultEnabled,\n },\n {\n moduleId: 'notifications',\n name: NOTIFICATIONS_DELIVERY_CONFIG_KEY,\n value: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n },\n ],\n { force: true },\n )\n console.log(\n `[configs] Vector auto-indexing default set to ${defaultEnabled ? 'enabled' : 'disabled'}${\n disabledByEnv\n ? ' (forced by OM_DISABLE_VECTOR_SEARCH_AUTOINDEXING or legacy DISABLE_VECTOR_SEARCH_AUTOINDEXING)'\n : ''\n }.`,\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst help: ModuleCli = {\n command: 'help',\n async run() {\n console.log('\u2699\uFE0F Configs CLI')\n console.log('')\n console.log('\uD83D\uDE80 Usage: yarn mercato configs restore-defaults')\n console.log(' Ensures global module configuration defaults exist.')\n console.log('')\n printCacheHelp()\n },\n}\n\nconst cacheCommand: ModuleCli = {\n command: 'cache',\n async run(rest) {\n const [subcommand, ...subRest] = rest\n const args = parseArgs(subRest)\n\n if (!subcommand || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {\n printCacheHelp()\n return\n }\n\n if (subcommand === 'stats') {\n await runCacheStats(args)\n return\n }\n\n if (subcommand === 'purge') {\n await runCachePurge(args)\n return\n }\n\n if (subcommand === 'structural') {\n await runStructuralCachePurge(args)\n return\n }\n\n throw new Error(`Unknown cache subcommand \"${subcommand}\".`)\n },\n}\n\nexport default [restoreDefaults, cacheCommand, help]\n"],
5
- "mappings": "AAEA,SAAS,0BAA8C;AACvD,SAAS,8BAA8B;AAEvC,SAAS,yBAAyB;AAClC,SAAS,sCAAsC,yCAAyC;AACxF,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AASP,SAAS,UAAU,MAA4B;AAC7C,QAAM,OAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,OAAO,KAAK,CAAC;AACnB,QAAI,CAAC,MAAM,WAAW,IAAI,EAAG;AAC7B,UAAM,CAAC,QAAQ,QAAQ,IAAI,KAAK,MAAM,CAAC,EAAE,MAAM,GAAG;AAClD,QAAI,CAAC,OAAQ;AACb,QAAI,aAAa,QAAW;AAC1B,WAAK,MAAM,IAAI;AAAA,IACjB,WAAW,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,CAAC,EAAG,WAAW,IAAI,GAAG;AAChE,WAAK,MAAM,IAAI,KAAK,IAAI,CAAC;AACzB,WAAK;AAAA,IACP,OAAO;AACL,WAAK,MAAM,IAAI;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,SAAqB,MAAoC;AAC7E,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,KAAK,GAAG;AACpB,QAAI,OAAO,QAAQ,SAAU;AAC7B,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,EACjC;AACA,SAAO;AACT;AAEA,SAAS,YAAY,SAAqB,MAAyB;AACjE,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,KAAK,GAAG;AACpB,QAAI,QAAQ,OAAW;AACvB,QAAI,QAAQ,KAAM,QAAO;AACzB,QAAI,OAAO,QAAQ,UAAU;AAC3B,YAAM,SAAS,kBAAkB,GAAG;AACpC,aAAO,WAAW,OAAO,OAAO;AAAA,IAClC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,KAAmC;AAC1D,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAmB,CAAC;AAC1B,aAAW,QAAQ,IAAI,MAAM,GAAG,GAAG;AACjC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,KAAK,IAAI,OAAO,EAAG;AACnC,SAAK,IAAI,OAAO;AAChB,WAAO,KAAK,OAAO;AAAA,EACrB;AACA,SAAO;AACT;AAEA,eAAe,mBACb,IACA,MACuB;AACvB,QAAM,mBAAmB,aAAa,MAAM,UAAU,UAAU;AAChE,QAAM,aAAa,YAAY,MAAM,QAAQ;AAC7C,QAAM,aAAa,YAAY,MAAM,eAAe,YAAY;AAEhE,MAAI,oBAAoB,YAAY;AAClC,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,MAAI,oBAAoB,YAAY;AAClC,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,MAAI,cAAc,YAAY;AAC5B,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,MAAI,kBAAkB;AACpB,WAAO,CAAC,EAAE,OAAO,UAAU,gBAAgB,IAAI,UAAU,iBAAiB,CAAC;AAAA,EAC7E;AAEA,MAAI,YAAY;AACd,WAAO,CAAC,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC;AAAA,EAC7C;AAEA,MAAI,CAAC,YAAY;AACf,WAAO,CAAC,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC;AAAA,EAC7C;AAEA,QAAM,UAAU,MAAM,GAAG,KAAK,QAAQ,EAAE,WAAW,KAAK,GAAG,EAAE,SAAS,EAAE,MAAM,MAAM,EAAE,CAAC;AACvF,QAAM,SAAuB,CAAC,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC;AACjE,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,OAAO,OAAO,OAAO,WAAW,OAAO,KAAK;AAC7D,QAAI,CAAC,YAAY,KAAK,IAAI,QAAQ,EAAG;AACrC,SAAK,IAAI,QAAQ;AACjB,WAAO,KAAK,EAAE,OAAO,UAAU,QAAQ,IAAI,SAAS,CAAC;AAAA,EACvD;AACA,SAAO;AACT;AAEA,SAAS,yBAAyB,MAAqC;AACrE,MAAI,YAAY,MAAM,KAAK,EAAG,QAAO,EAAE,MAAM,MAAM;AAEnD,QAAM,UAAU,aAAa,MAAM,SAAS;AAC5C,MAAI,QAAS,QAAO,EAAE,MAAM,WAAW,QAAQ;AAE/C,QAAM,OAAO,gBAAgB,aAAa,MAAM,OAAO,MAAM,CAAC;AAC9D,MAAI,KAAK,SAAS,EAAG,QAAO,EAAE,MAAM,QAAQ,KAAK;AAEjD,QAAM,OAAO,gBAAgB,aAAa,MAAM,OAAO,MAAM,CAAC;AAC9D,MAAI,KAAK,SAAS,EAAG,QAAO,EAAE,MAAM,QAAQ,KAAK;AAEjD,QAAM,MAAM,gBAAgB,aAAa,MAAM,MAAM,KAAK,CAAC;AAC3D,MAAI,IAAI,SAAS,EAAG,QAAO,EAAE,MAAM,OAAO,IAAI;AAE9C,QAAM,UAAU,aAAa,MAAM,SAAS;AAC5C,MAAI,QAAS,QAAO,EAAE,MAAM,WAAW,QAAQ;AAE/C,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB;AACxB,UAAQ,IAAI,qBAAc;AAC1B,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,kBAAW;AACvB,UAAQ,IAAI,wFAAwF;AACpG,UAAQ,IAAI,0GAA0G;AACtH,UAAQ,IAAI,wHAAwH;AACpI,UAAQ,IAAI,sHAAsH;AAClI,UAAQ,IAAI,sHAAsH;AAClI,UAAQ,IAAI,yHAAyH;AACrI,UAAQ,IAAI,qHAAqH;AACjI,UAAQ,IAAI,yGAAyG;AACrH,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,qBAAW;AACvB,UAAQ,IAAI,iFAAiF;AAC7F,UAAQ,IAAI,+GAA+G;AAC3H,UAAQ,IAAI,6HAA6H;AACzI,UAAQ,IAAI,kFAAkF;AAChG;AAEA,eAAe,iBAAiB,WAAoB;AAClD,QAAM,aAAa;AACnB,MAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,UAAM,WAAW,QAAQ;AAAA,EAC3B;AACF;AAEA,eAAe,cAAc,MAAkB;AAC7C,QAAM,OAAO,YAAY,MAAM,MAAM;AACrC,QAAM,YAAY,MAAM,uBAAuB;AAC/C,MAAI;AACF,UAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,UAAM,QAAQ,UAAU,QAAQ,OAAO;AACvC,UAAM,SAAS,MAAM,mBAAmB,IAAI,IAAI;AAChD,UAAM,UAAU,CAAC;AACjB,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,MAAM,mBAAmB,MAAM,UAAU,YAAY,kBAAkB,KAAK,CAAC;AAC3F,cAAQ,KAAK,EAAE,OAAO,MAAM,OAAO,GAAG,MAAM,CAAC;AAAA,IAC/C;AAEA,QAAI,MAAM;AACR,cAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC5C;AAAA,IACF;AAEA,eAAW,UAAU,SAAS;AAC5B,cAAQ,IAAI,2BAAoB,OAAO,KAAK,cAAc,OAAO,SAAS,gBAAgB,OAAO,WAAW,EAAE;AAC9G,UAAI,OAAO,SAAS,WAAW,GAAG;AAChC,gBAAQ,IAAI,yBAAoB;AAChC;AAAA,MACF;AACA,iBAAW,WAAW,OAAO,UAAU;AACrC,gBAAQ,IAAI,YAAO,QAAQ,OAAO,KAAK,QAAQ,QAAQ,IAAI,QAAQ,OAAO,IAAI,QAAQ,IAAI,KAAK,EAAE,EAAE;AAAA,MACrG;AAAA,IACF;AAAA,EACF,UAAE;AACA,UAAM,iBAAiB,SAAS;AAAA,EAClC;AACF;AAEA,eAAe,cAAc,MAAkB;AAC7C,QAAM,OAAO,YAAY,MAAM,MAAM;AACrC,QAAM,QAAQ,YAAY,MAAM,OAAO;AACvC,QAAM,SAAS,YAAY,MAAM,WAAW,QAAQ;AACpD,QAAM,UAAU,yBAAyB,IAAI;AAC7C,QAAM,YAAY,MAAM,uBAAuB;AAC/C,MAAI;AACF,UAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,UAAM,QAAQ,UAAU,QAAQ,OAAO;AACvC,UAAM,SAAS,MAAM,mBAAmB,IAAI,IAAI;AAChD,UAAM,UAAU,CAAC;AAEjB,eAAW,SAAS,QAAQ;AAC1B,YAAM,SAAS,MAAM;AAAA,QAAmB,MAAM;AAAA,QAAU,YACtD,SAAS,kBAAkB,OAAO,OAAO,IAAI,kBAAkB,OAAO,OAAO;AAAA,MAC/E;AACA,cAAQ,KAAK;AAAA,QACX,OAAO,MAAM;AAAA,QACb;AAAA,QACA;AAAA,QACA,SAAS,OAAO;AAAA,QAChB,UAAU,OAAO,KAAK;AAAA,QACtB,MAAM,OAAO;AAAA,QACb,MAAM,OAAO;AAAA,MACf,CAAC;AAAA,IACH;AAEA,QAAI,MAAM;AACR,cAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC5C;AAAA,IACF;AAEA,QAAI,OAAO;AACT;AAAA,IACF;AAEA,eAAW,UAAU,SAAS;AAC5B,cAAQ,IAAI,GAAG,OAAO,SAAS,cAAO,WAAI,kBAAkB,OAAO,KAAK,YAAY,OAAO,OAAO,GAAG,OAAO,SAAS,eAAe,EAAE,EAAE;AACxI,UAAI,OAAO,KAAM,SAAQ,IAAI,wBAAc,OAAO,IAAI,EAAE;AACxD,UAAI,OAAO,KAAK,SAAS,GAAG;AAC1B,mBAAW,OAAO,OAAO,MAAM;AAC7B,kBAAQ,IAAI,YAAO,GAAG,EAAE;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAAA,EACF,UAAE;AACA,UAAM,iBAAiB,SAAS;AAAA,EAClC;AACF;AAEA,eAAe,wBAAwB,MAAkB;AACvD,QAAM,WAAuB;AAAA,IAC3B,GAAG;AAAA,IACH,SAAS;AAAA,EACX;AACA,QAAM,cAAc,QAAQ;AAC9B;AAEA,SAAS,0BAAmC;AAC1C,QAAM,MACJ,QAAQ,IAAI,yCACZ,QAAQ,IAAI;AACd,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,kBAAkB,GAAG,MAAM;AACpC;AAEA,MAAM,kBAA6B;AAAA,EACjC,SAAS;AAAA,EACT,MAAM,MAAM;AACV,UAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAI;AACF,UAAI;AACJ,UAAI;AACF,kBAAW,UAAU,QAAQ,qBAAqB;AAAA,MACpD,QAAQ;AACN,gBAAQ,MAAM,mEAAmE;AACjF;AAAA,MACF;AAEA,YAAM,gBAAgB,wBAAwB;AAC9C,YAAM,iBAAiB,CAAC;AACxB,YAAM,QAAQ;AAAA,QACZ;AAAA,UACE;AAAA,YACE,UAAU;AAAA,YACV,MAAM;AAAA,YACN,OAAO;AAAA,UACT;AAAA,UACA;AAAA,YACE,UAAU;AAAA,YACV,MAAM;AAAA,YACN,OAAO;AAAA,UACT;AAAA,QACF;AAAA,QACA,EAAE,OAAO,KAAK;AAAA,MAChB;AACA,cAAQ;AAAA,QACN,iDAAiD,iBAAiB,YAAY,UAAU,GACtF,gBACI,oGACA,EACN;AAAA,MACF;AAAA,IACF,UAAE;AACA,YAAM,aAAa;AACnB,UAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,cAAM,WAAW,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,OAAkB;AAAA,EACtB,SAAS;AAAA,EACT,MAAM,MAAM;AACV,YAAQ,IAAI,0BAAgB;AAC5B,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,wDAAiD;AAC7D,YAAQ,IAAI,uDAAuD;AACnE,YAAQ,IAAI,EAAE;AACd,mBAAe;AAAA,EACjB;AACF;AAEA,MAAM,eAA0B;AAAA,EAC9B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,CAAC,YAAY,GAAG,OAAO,IAAI;AACjC,UAAM,OAAO,UAAU,OAAO;AAE9B,QAAI,CAAC,cAAc,eAAe,UAAU,eAAe,YAAY,eAAe,MAAM;AAC1F,qBAAe;AACf;AAAA,IACF;AAEA,QAAI,eAAe,SAAS;AAC1B,YAAM,cAAc,IAAI;AACxB;AAAA,IACF;AAEA,QAAI,eAAe,SAAS;AAC1B,YAAM,cAAc,IAAI;AACxB;AAAA,IACF;AAEA,QAAI,eAAe,cAAc;AAC/B,YAAM,wBAAwB,IAAI;AAClC;AAAA,IACF;AAEA,UAAM,IAAI,MAAM,6BAA6B,UAAU,IAAI;AAAA,EAC7D;AACF;AAEA,IAAO,cAAQ,CAAC,iBAAiB,cAAc,IAAI;",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { ModuleConfigService } from './lib/module-config-service'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, NOTIFICATIONS_DELIVERY_CONFIG_KEY } from '../notifications/lib/deliveryConfig'\nimport { Tenant } from '../directory/data/entities'\nimport {\n collectCacheStats,\n executeCachePurge,\n previewCachePurge,\n type CachePurgeRequest,\n} from './lib/cache-cli'\nimport { touchGeneratedBarrels } from './lib/touchGeneratedBarrels'\n\ntype ParsedArgs = Record<string, string | boolean>\n\ntype CacheScope = {\n label: string\n tenantId: string | null\n}\n\nfunction parseArgs(rest: string[]): ParsedArgs {\n const args: ParsedArgs = {}\n for (let i = 0; i < rest.length; i += 1) {\n const part = rest[i]\n if (!part?.startsWith('--')) continue\n const [rawKey, rawValue] = part.slice(2).split('=')\n if (!rawKey) continue\n if (rawValue !== undefined) {\n args[rawKey] = rawValue\n } else if (i + 1 < rest.length && !rest[i + 1]!.startsWith('--')) {\n args[rawKey] = rest[i + 1]!\n i += 1\n } else {\n args[rawKey] = true\n }\n }\n return args\n}\n\nfunction stringOption(args: ParsedArgs, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (typeof raw !== 'string') continue\n const trimmed = raw.trim()\n if (trimmed.length > 0) return trimmed\n }\n return undefined\n}\n\nfunction flagEnabled(args: ParsedArgs, ...keys: string[]): boolean {\n for (const key of keys) {\n const raw = args[key]\n if (raw === undefined) continue\n if (raw === true) return true\n if (typeof raw === 'string') {\n const parsed = parseBooleanToken(raw)\n return parsed === null ? true : parsed\n }\n }\n return false\n}\n\nfunction splitListOption(raw: string | undefined): string[] {\n if (!raw) return []\n const seen = new Set<string>()\n const values: string[] = []\n for (const item of raw.split(',')) {\n const trimmed = item.trim()\n if (!trimmed || seen.has(trimmed)) continue\n seen.add(trimmed)\n values.push(trimmed)\n }\n return values\n}\n\nasync function resolveCacheScopes(\n em: EntityManager,\n args: ParsedArgs,\n): Promise<CacheScope[]> {\n const explicitTenantId = stringOption(args, 'tenant', 'tenantId')\n const globalOnly = flagEnabled(args, 'global')\n const allTenants = flagEnabled(args, 'all-tenants', 'allTenants')\n\n if (explicitTenantId && globalOnly) {\n throw new Error('Cannot combine `--tenant` with `--global`.')\n }\n if (explicitTenantId && allTenants) {\n throw new Error('Cannot combine `--tenant` with `--all-tenants`.')\n }\n if (globalOnly && allTenants) {\n throw new Error('Cannot combine `--global` with `--all-tenants`.')\n }\n\n if (explicitTenantId) {\n return [{ label: `tenant:${explicitTenantId}`, tenantId: explicitTenantId }]\n }\n\n if (globalOnly) {\n return [{ label: 'global', tenantId: null }]\n }\n\n if (!allTenants) {\n return [{ label: 'global', tenantId: null }]\n }\n\n const tenants = await em.find(Tenant, { deletedAt: null }, { orderBy: { name: 'asc' } })\n const scopes: CacheScope[] = [{ label: 'global', tenantId: null }]\n const seen = new Set<string>()\n for (const tenant of tenants) {\n const tenantId = typeof tenant.id === 'string' ? tenant.id : ''\n if (!tenantId || seen.has(tenantId)) continue\n seen.add(tenantId)\n scopes.push({ label: `tenant:${tenantId}`, tenantId })\n }\n return scopes\n}\n\nfunction resolveCachePurgeRequest(args: ParsedArgs): CachePurgeRequest {\n if (flagEnabled(args, 'all')) return { kind: 'all' }\n\n const segment = stringOption(args, 'segment')\n if (segment) return { kind: 'segment', segment }\n\n const tags = splitListOption(stringOption(args, 'tag', 'tags'))\n if (tags.length > 0) return { kind: 'tags', tags }\n\n const keys = splitListOption(stringOption(args, 'key', 'keys'))\n if (keys.length > 0) return { kind: 'keys', keys }\n\n const ids = splitListOption(stringOption(args, 'id', 'ids'))\n if (ids.length > 0) return { kind: 'ids', ids }\n\n const pattern = stringOption(args, 'pattern')\n if (pattern) return { kind: 'pattern', pattern }\n\n throw new Error(\n 'Choose a purge target: `--all`, `--segment <id>`, `--tag <tag1,tag2>`, `--key <key1,key2>`, `--id <token1,token2>`, or `--pattern <glob>`.',\n )\n}\n\nfunction printCacheHelp() {\n console.log('\uD83E\uDDF9 Cache CLI')\n console.log('')\n console.log('\uD83D\uDE80 Usage:')\n console.log(' yarn mercato configs cache stats [--tenant <id> | --global | --all-tenants] [--json]')\n console.log(' yarn mercato configs cache purge --all [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --segment <segment> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --tag <tag1,tag2> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --key <key1,key2> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --id <token1,token2> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache purge --pattern <glob> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log(' yarn mercato configs cache structural [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')\n console.log('')\n console.log('\u2139\uFE0F Notes:')\n console.log(' `stats` mirrors the cache admin page segment overview for CRUD/widget caches.')\n console.log(' `purge --id` removes every key whose name contains the provided token (for example a user id or entity id).')\n console.log(' `structural` targets navigation caches (`nav:*`) and is the recommended post-step after module/sidebar structure changes.')\n console.log(' When no scope flag is supplied, this command uses the global cache scope only.')\n}\n\nasync function disposeContainer(container: unknown) {\n const disposable = container as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n}\n\nasync function runCacheStats(args: ParsedArgs) {\n const json = flagEnabled(args, 'json')\n const container = await createRequestContainer()\n try {\n const em = container.resolve('em') as EntityManager\n const cache = container.resolve('cache') as CacheStrategy\n const scopes = await resolveCacheScopes(em, args)\n const results = []\n for (const scope of scopes) {\n const stats = await runWithCacheTenant(scope.tenantId, async () => collectCacheStats(cache))\n results.push({ scope: scope.label, ...stats })\n }\n\n if (json) {\n console.log(JSON.stringify(results, null, 2))\n return\n }\n\n for (const result of results) {\n console.log(`\uD83D\uDD0E [cache] scope=${result.scope} totalKeys=${result.totalKeys} generatedAt=${result.generatedAt}`)\n if (result.segments.length === 0) {\n console.log(' \u2205 segments: none')\n continue\n }\n for (const segment of result.segments) {\n console.log(` \u2022 ${segment.segment} (${segment.keyCount})${segment.path ? ` ${segment.path}` : ''}`)\n }\n }\n } finally {\n await disposeContainer(container)\n }\n}\n\nasync function runCachePurge(args: ParsedArgs) {\n const json = flagEnabled(args, 'json')\n const quiet = flagEnabled(args, 'quiet')\n const dryRun = flagEnabled(args, 'dry-run', 'dryRun')\n const request = resolveCachePurgeRequest(args)\n const container = await createRequestContainer()\n try {\n const em = container.resolve('em') as EntityManager\n const cache = container.resolve('cache') as CacheStrategy\n const scopes = await resolveCacheScopes(em, args)\n const results = []\n\n for (const scope of scopes) {\n const result = await runWithCacheTenant(scope.tenantId, async () =>\n dryRun ? previewCachePurge(cache, request) : executeCachePurge(cache, request)\n )\n results.push({\n scope: scope.label,\n dryRun,\n request,\n deleted: result.deleted,\n keyCount: result.keys.length,\n keys: result.keys,\n note: result.note,\n })\n }\n\n if (json) {\n console.log(JSON.stringify(results, null, 2))\n return\n }\n\n if (quiet) {\n return\n }\n\n for (const result of results) {\n console.log(`${result.dryRun ? '\uD83E\uDDEA' : '\uD83E\uDDF9'} [cache] scope=${result.scope} deleted=${result.deleted}${result.dryRun ? ' (dry-run)' : ''}`)\n if (result.note) console.log(` \u2139\uFE0F note: ${result.note}`)\n if (result.keys.length > 0) {\n for (const key of result.keys) {\n console.log(` \u2022 ${key}`)\n }\n }\n }\n } finally {\n await disposeContainer(container)\n }\n}\n\nasync function runStructuralCachePurge(args: ParsedArgs) {\n const nextArgs: ParsedArgs = {\n ...args,\n pattern: 'nav:*',\n }\n await runCachePurge(nextArgs)\n const quiet = flagEnabled(args, 'quiet')\n try {\n touchGeneratedBarrels({ quiet })\n } catch (err) {\n if (!quiet) {\n console.warn(\n `[structural] failed to touch generated barrels: ${(err as Error).message ?? err}`,\n )\n }\n }\n}\n\nfunction envDisablesAutoIndexing(): boolean {\n const raw =\n process.env.OM_DISABLE_VECTOR_SEARCH_AUTOINDEXING ??\n process.env.DISABLE_VECTOR_SEARCH_AUTOINDEXING\n if (!raw) return false\n return parseBooleanToken(raw) === true\n}\n\nconst restoreDefaults: ModuleCli = {\n command: 'restore-defaults',\n async run() {\n const container = await createRequestContainer()\n try {\n let service: ModuleConfigService\n try {\n service = (container.resolve('moduleConfigService') as ModuleConfigService)\n } catch {\n console.error('[configs] moduleConfigService is not registered in the container.')\n return\n }\n\n const disabledByEnv = envDisablesAutoIndexing()\n const defaultEnabled = !disabledByEnv\n await service.restoreDefaults(\n [\n {\n moduleId: 'vector',\n name: 'auto_index_enabled',\n value: defaultEnabled,\n },\n {\n moduleId: 'notifications',\n name: NOTIFICATIONS_DELIVERY_CONFIG_KEY,\n value: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n },\n ],\n { force: true },\n )\n console.log(\n `[configs] Vector auto-indexing default set to ${defaultEnabled ? 'enabled' : 'disabled'}${\n disabledByEnv\n ? ' (forced by OM_DISABLE_VECTOR_SEARCH_AUTOINDEXING or legacy DISABLE_VECTOR_SEARCH_AUTOINDEXING)'\n : ''\n }.`,\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst help: ModuleCli = {\n command: 'help',\n async run() {\n console.log('\u2699\uFE0F Configs CLI')\n console.log('')\n console.log('\uD83D\uDE80 Usage: yarn mercato configs restore-defaults')\n console.log(' Ensures global module configuration defaults exist.')\n console.log('')\n printCacheHelp()\n },\n}\n\nconst cacheCommand: ModuleCli = {\n command: 'cache',\n async run(rest) {\n const [subcommand, ...subRest] = rest\n const args = parseArgs(subRest)\n\n if (!subcommand || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {\n printCacheHelp()\n return\n }\n\n if (subcommand === 'stats') {\n await runCacheStats(args)\n return\n }\n\n if (subcommand === 'purge') {\n await runCachePurge(args)\n return\n }\n\n if (subcommand === 'structural') {\n await runStructuralCachePurge(args)\n return\n }\n\n throw new Error(`Unknown cache subcommand \"${subcommand}\".`)\n },\n}\n\nexport default [restoreDefaults, cacheCommand, help]\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,46 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const GENERATED_DIR_RELATIVE = path.join(".mercato", "generated");
4
+ const TOUCHABLE_PATTERN = /\.generated(?:\.[a-z0-9]+)?(?:\.ts|\.checksum)$/i;
5
+ const MAX_PARENT_WALK = 5;
6
+ function findGeneratedDir(startDir) {
7
+ let current = path.resolve(startDir);
8
+ for (let depth = 0; depth <= MAX_PARENT_WALK; depth += 1) {
9
+ const candidate = path.join(current, GENERATED_DIR_RELATIVE);
10
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
11
+ return candidate;
12
+ }
13
+ const parent = path.dirname(current);
14
+ if (parent === current) break;
15
+ current = parent;
16
+ }
17
+ return null;
18
+ }
19
+ function touchGeneratedBarrels(options = {}) {
20
+ const cwd = options.cwd ?? process.cwd();
21
+ const log = options.log ?? ((message) => console.log(message));
22
+ const quiet = options.quiet === true;
23
+ const generatedDir = findGeneratedDir(cwd);
24
+ if (!generatedDir) {
25
+ return { generatedDir: null, files: [] };
26
+ }
27
+ const touched = [];
28
+ const entries = fs.readdirSync(generatedDir, { withFileTypes: true });
29
+ for (const entry of entries) {
30
+ if (!entry.isFile()) continue;
31
+ if (!TOUCHABLE_PATTERN.test(entry.name)) continue;
32
+ const filePath = path.join(generatedDir, entry.name);
33
+ const contents = fs.readFileSync(filePath);
34
+ fs.writeFileSync(filePath, contents);
35
+ touched.push(filePath);
36
+ }
37
+ if (!quiet && touched.length > 0) {
38
+ log(`\u{1F501} [structural] touched ${touched.length} generated barrel(s) \u2192 ${generatedDir}`);
39
+ }
40
+ return { generatedDir, files: touched };
41
+ }
42
+ export {
43
+ findGeneratedDir,
44
+ touchGeneratedBarrels
45
+ };
46
+ //# sourceMappingURL=touchGeneratedBarrels.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/configs/lib/touchGeneratedBarrels.ts"],
4
+ "sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\n\nconst GENERATED_DIR_RELATIVE = path.join('.mercato', 'generated')\nconst TOUCHABLE_PATTERN = /\\.generated(?:\\.[a-z0-9]+)?(?:\\.ts|\\.checksum)$/i\nconst MAX_PARENT_WALK = 5\n\nexport type TouchGeneratedBarrelsOptions = {\n cwd?: string\n quiet?: boolean\n log?: (message: string) => void\n}\n\nexport type TouchGeneratedBarrelsResult = {\n generatedDir: string | null\n files: string[]\n}\n\nexport function findGeneratedDir(startDir: string): string | null {\n let current = path.resolve(startDir)\n for (let depth = 0; depth <= MAX_PARENT_WALK; depth += 1) {\n const candidate = path.join(current, GENERATED_DIR_RELATIVE)\n if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {\n return candidate\n }\n const parent = path.dirname(current)\n if (parent === current) break\n current = parent\n }\n return null\n}\n\nexport function touchGeneratedBarrels(\n options: TouchGeneratedBarrelsOptions = {},\n): TouchGeneratedBarrelsResult {\n const cwd = options.cwd ?? process.cwd()\n const log = options.log ?? ((message: string) => console.log(message))\n const quiet = options.quiet === true\n\n const generatedDir = findGeneratedDir(cwd)\n if (!generatedDir) {\n return { generatedDir: null, files: [] }\n }\n\n const touched: string[] = []\n const entries = fs.readdirSync(generatedDir, { withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isFile()) continue\n if (!TOUCHABLE_PATTERN.test(entry.name)) continue\n const filePath = path.join(generatedDir, entry.name)\n const contents = fs.readFileSync(filePath)\n fs.writeFileSync(filePath, contents)\n touched.push(filePath)\n }\n\n if (!quiet && touched.length > 0) {\n log(`\uD83D\uDD01 [structural] touched ${touched.length} generated barrel(s) \u2192 ${generatedDir}`)\n }\n\n return { generatedDir, files: touched }\n}\n"],
5
+ "mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,MAAM,yBAAyB,KAAK,KAAK,YAAY,WAAW;AAChE,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AAajB,SAAS,iBAAiB,UAAiC;AAChE,MAAI,UAAU,KAAK,QAAQ,QAAQ;AACnC,WAAS,QAAQ,GAAG,SAAS,iBAAiB,SAAS,GAAG;AACxD,UAAM,YAAY,KAAK,KAAK,SAAS,sBAAsB;AAC3D,QAAI,GAAG,WAAW,SAAS,KAAK,GAAG,SAAS,SAAS,EAAE,YAAY,GAAG;AACpE,aAAO;AAAA,IACT;AACA,UAAM,SAAS,KAAK,QAAQ,OAAO;AACnC,QAAI,WAAW,QAAS;AACxB,cAAU;AAAA,EACZ;AACA,SAAO;AACT;AAEO,SAAS,sBACd,UAAwC,CAAC,GACZ;AAC7B,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,MAAM,QAAQ,QAAQ,CAAC,YAAoB,QAAQ,IAAI,OAAO;AACpE,QAAM,QAAQ,QAAQ,UAAU;AAEhC,QAAM,eAAe,iBAAiB,GAAG;AACzC,MAAI,CAAC,cAAc;AACjB,WAAO,EAAE,cAAc,MAAM,OAAO,CAAC,EAAE;AAAA,EACzC;AAEA,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAU,GAAG,YAAY,cAAc,EAAE,eAAe,KAAK,CAAC;AACpE,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,OAAO,EAAG;AACrB,QAAI,CAAC,kBAAkB,KAAK,MAAM,IAAI,EAAG;AACzC,UAAM,WAAW,KAAK,KAAK,cAAc,MAAM,IAAI;AACnD,UAAM,WAAW,GAAG,aAAa,QAAQ;AACzC,OAAG,cAAc,UAAU,QAAQ;AACnC,YAAQ,KAAK,QAAQ;AAAA,EACvB;AAEA,MAAI,CAAC,SAAS,QAAQ,SAAS,GAAG;AAChC,QAAI,kCAA2B,QAAQ,MAAM,+BAA0B,YAAY,EAAE;AAAA,EACvF;AAEA,SAAO,EAAE,cAAc,OAAO,QAAQ;AACxC;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.1-develop.3090.06ab462170",
3
+ "version": "0.6.1-develop.3102.d6e7e6d57a",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -223,9 +223,9 @@
223
223
  }
224
224
  },
225
225
  "dependencies": {
226
- "@mikro-orm/core": "^7.0.13",
227
- "@mikro-orm/decorators": "^7.0.13",
228
- "@mikro-orm/postgresql": "^7.0.13",
226
+ "@mikro-orm/core": "^7.0.14",
227
+ "@mikro-orm/decorators": "^7.0.14",
228
+ "@mikro-orm/postgresql": "^7.0.14",
229
229
  "@xyflow/react": "^12.10.2",
230
230
  "ai": "^6.0.168",
231
231
  "date-fns": "^4.1.0",
@@ -237,12 +237,12 @@
237
237
  "ts-pattern": "^5.0.0"
238
238
  },
239
239
  "peerDependencies": {
240
- "@open-mercato/ai-assistant": "0.6.1-develop.3090.06ab462170",
241
- "@open-mercato/shared": "0.6.1-develop.3090.06ab462170"
240
+ "@open-mercato/ai-assistant": "0.6.1-develop.3102.d6e7e6d57a",
241
+ "@open-mercato/shared": "0.6.1-develop.3102.d6e7e6d57a"
242
242
  },
243
243
  "devDependencies": {
244
- "@open-mercato/ai-assistant": "0.6.1-develop.3090.06ab462170",
245
- "@open-mercato/shared": "0.6.1-develop.3090.06ab462170",
244
+ "@open-mercato/ai-assistant": "0.6.1-develop.3102.d6e7e6d57a",
245
+ "@open-mercato/shared": "0.6.1-develop.3102.d6e7e6d57a",
246
246
  "@testing-library/dom": "^10.4.1",
247
247
  "@testing-library/jest-dom": "^6.9.1",
248
248
  "@testing-library/react": "^16.3.1",
@@ -12,6 +12,7 @@ import {
12
12
  previewCachePurge,
13
13
  type CachePurgeRequest,
14
14
  } from './lib/cache-cli'
15
+ import { touchGeneratedBarrels } from './lib/touchGeneratedBarrels'
15
16
 
16
17
  type ParsedArgs = Record<string, string | boolean>
17
18
 
@@ -256,6 +257,16 @@ async function runStructuralCachePurge(args: ParsedArgs) {
256
257
  pattern: 'nav:*',
257
258
  }
258
259
  await runCachePurge(nextArgs)
260
+ const quiet = flagEnabled(args, 'quiet')
261
+ try {
262
+ touchGeneratedBarrels({ quiet })
263
+ } catch (err) {
264
+ if (!quiet) {
265
+ console.warn(
266
+ `[structural] failed to touch generated barrels: ${(err as Error).message ?? err}`,
267
+ )
268
+ }
269
+ }
259
270
  }
260
271
 
261
272
  function envDisablesAutoIndexing(): boolean {
@@ -0,0 +1,61 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ const GENERATED_DIR_RELATIVE = path.join('.mercato', 'generated')
5
+ const TOUCHABLE_PATTERN = /\.generated(?:\.[a-z0-9]+)?(?:\.ts|\.checksum)$/i
6
+ const MAX_PARENT_WALK = 5
7
+
8
+ export type TouchGeneratedBarrelsOptions = {
9
+ cwd?: string
10
+ quiet?: boolean
11
+ log?: (message: string) => void
12
+ }
13
+
14
+ export type TouchGeneratedBarrelsResult = {
15
+ generatedDir: string | null
16
+ files: string[]
17
+ }
18
+
19
+ export function findGeneratedDir(startDir: string): string | null {
20
+ let current = path.resolve(startDir)
21
+ for (let depth = 0; depth <= MAX_PARENT_WALK; depth += 1) {
22
+ const candidate = path.join(current, GENERATED_DIR_RELATIVE)
23
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
24
+ return candidate
25
+ }
26
+ const parent = path.dirname(current)
27
+ if (parent === current) break
28
+ current = parent
29
+ }
30
+ return null
31
+ }
32
+
33
+ export function touchGeneratedBarrels(
34
+ options: TouchGeneratedBarrelsOptions = {},
35
+ ): TouchGeneratedBarrelsResult {
36
+ const cwd = options.cwd ?? process.cwd()
37
+ const log = options.log ?? ((message: string) => console.log(message))
38
+ const quiet = options.quiet === true
39
+
40
+ const generatedDir = findGeneratedDir(cwd)
41
+ if (!generatedDir) {
42
+ return { generatedDir: null, files: [] }
43
+ }
44
+
45
+ const touched: string[] = []
46
+ const entries = fs.readdirSync(generatedDir, { withFileTypes: true })
47
+ for (const entry of entries) {
48
+ if (!entry.isFile()) continue
49
+ if (!TOUCHABLE_PATTERN.test(entry.name)) continue
50
+ const filePath = path.join(generatedDir, entry.name)
51
+ const contents = fs.readFileSync(filePath)
52
+ fs.writeFileSync(filePath, contents)
53
+ touched.push(filePath)
54
+ }
55
+
56
+ if (!quiet && touched.length > 0) {
57
+ log(`🔁 [structural] touched ${touched.length} generated barrel(s) → ${generatedDir}`)
58
+ }
59
+
60
+ return { generatedDir, files: touched }
61
+ }