@intlayer/engine 9.5.11 → 9.5.12

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 (83) hide show
  1. package/dist/assets/installSkills/skills/bundle-optimization.md +123 -0
  2. package/dist/assets/installSkills/skills/compat.md +0 -4
  3. package/dist/assets/installSkills/skills/content.md +8 -2
  4. package/dist/assets/installSkills/skills/next-js.md +2 -2
  5. package/dist/assets/installSkills/skills/usage.md +1 -1
  6. package/dist/cjs/build.cjs +5 -0
  7. package/dist/cjs/cli.cjs +1 -0
  8. package/dist/cjs/init/index.cjs +2 -12
  9. package/dist/cjs/init/index.cjs.map +1 -1
  10. package/dist/cjs/init/utils/backendPackages.cjs +15 -0
  11. package/dist/cjs/init/utils/backendPackages.cjs.map +1 -0
  12. package/dist/cjs/init/utils/index.cjs +2 -3
  13. package/dist/cjs/installSkills/index.cjs +2 -0
  14. package/dist/cjs/installSkills/index.cjs.map +1 -1
  15. package/dist/cjs/loadDictionaries/loadDictionaries.cjs +9 -8
  16. package/dist/cjs/loadDictionaries/loadDictionaries.cjs.map +1 -1
  17. package/dist/cjs/loadDictionaries/loadRemoteDictionaries.cjs +3 -5
  18. package/dist/cjs/loadDictionaries/loadRemoteDictionaries.cjs.map +1 -1
  19. package/dist/cjs/loadDictionaries/remoteDictionaryClient.cjs +18 -0
  20. package/dist/cjs/loadDictionaries/remoteDictionaryClient.cjs.map +1 -0
  21. package/dist/cjs/prepareIntlayerServer.cjs +132 -0
  22. package/dist/cjs/prepareIntlayerServer.cjs.map +1 -0
  23. package/dist/cjs/utils/getProcessChainCommands.cjs +117 -0
  24. package/dist/cjs/utils/getProcessChainCommands.cjs.map +1 -0
  25. package/dist/cjs/utils/index.cjs +2 -0
  26. package/dist/cjs/utils/startContentWatcher.cjs +99 -0
  27. package/dist/cjs/utils/startContentWatcher.cjs.map +1 -0
  28. package/dist/cjs/watcher.cjs +2 -2
  29. package/dist/cjs/watcher.cjs.map +1 -1
  30. package/dist/cjs/writeJsonIfChanged.cjs.map +1 -1
  31. package/dist/esm/build.mjs +2 -1
  32. package/dist/esm/cli.mjs +2 -2
  33. package/dist/esm/init/index.mjs +1 -11
  34. package/dist/esm/init/index.mjs.map +1 -1
  35. package/dist/esm/init/utils/backendPackages.mjs +14 -0
  36. package/dist/esm/init/utils/backendPackages.mjs.map +1 -0
  37. package/dist/esm/init/utils/index.mjs +2 -2
  38. package/dist/esm/installSkills/index.mjs +2 -0
  39. package/dist/esm/installSkills/index.mjs.map +1 -1
  40. package/dist/esm/loadDictionaries/loadDictionaries.mjs +9 -8
  41. package/dist/esm/loadDictionaries/loadDictionaries.mjs.map +1 -1
  42. package/dist/esm/loadDictionaries/loadRemoteDictionaries.mjs +2 -4
  43. package/dist/esm/loadDictionaries/loadRemoteDictionaries.mjs.map +1 -1
  44. package/dist/esm/loadDictionaries/remoteDictionaryClient.mjs +16 -0
  45. package/dist/esm/loadDictionaries/remoteDictionaryClient.mjs.map +1 -0
  46. package/dist/esm/prepareIntlayerServer.mjs +128 -0
  47. package/dist/esm/prepareIntlayerServer.mjs.map +1 -0
  48. package/dist/esm/utils/getProcessChainCommands.mjs +115 -0
  49. package/dist/esm/utils/getProcessChainCommands.mjs.map +1 -0
  50. package/dist/esm/utils/index.mjs +2 -1
  51. package/dist/esm/utils/startContentWatcher.mjs +98 -0
  52. package/dist/esm/utils/startContentWatcher.mjs.map +1 -0
  53. package/dist/esm/watcher.mjs +1 -1
  54. package/dist/esm/watcher.mjs.map +1 -1
  55. package/dist/esm/writeJsonIfChanged.mjs +1 -1
  56. package/dist/esm/writeJsonIfChanged.mjs.map +1 -1
  57. package/dist/types/build.d.ts +2 -1
  58. package/dist/types/cli.d.ts +2 -2
  59. package/dist/types/init/index.d.ts +1 -1
  60. package/dist/types/init/index.d.ts.map +1 -1
  61. package/dist/types/init/utils/backendPackages.d.ts +5 -0
  62. package/dist/types/init/utils/backendPackages.d.ts.map +1 -0
  63. package/dist/types/init/utils/index.d.ts +2 -2
  64. package/dist/types/installSkills/index.d.ts +1 -0
  65. package/dist/types/installSkills/index.d.ts.map +1 -1
  66. package/dist/types/loadDictionaries/loadRemoteDictionaries.d.ts.map +1 -1
  67. package/dist/types/loadDictionaries/remoteDictionaryClient.d.ts +14 -0
  68. package/dist/types/loadDictionaries/remoteDictionaryClient.d.ts.map +1 -0
  69. package/dist/types/prepareIntlayerServer.d.ts +67 -0
  70. package/dist/types/prepareIntlayerServer.d.ts.map +1 -0
  71. package/dist/types/utils/getProcessChainCommands.d.ts +35 -0
  72. package/dist/types/utils/getProcessChainCommands.d.ts.map +1 -0
  73. package/dist/types/utils/index.d.ts +2 -1
  74. package/dist/types/utils/startContentWatcher.d.ts +40 -0
  75. package/dist/types/utils/startContentWatcher.d.ts.map +1 -0
  76. package/dist/types/watcher.d.ts.map +1 -1
  77. package/package.json +7 -7
  78. package/dist/cjs/init/utils/devScript.cjs +0 -57
  79. package/dist/cjs/init/utils/devScript.cjs.map +0 -1
  80. package/dist/esm/init/utils/devScript.mjs +0 -55
  81. package/dist/esm/init/utils/devScript.mjs.map +0 -1
  82. package/dist/types/init/utils/devScript.d.ts +0 -48
  83. package/dist/types/init/utils/devScript.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"loadRemoteDictionaries.mjs","names":[],"sources":["../../../src/loadDictionaries/loadRemoteDictionaries.ts"],"sourcesContent":["import { createIntlayerCMS } from '@intlayer/api';\nimport { dictionaryEndpoint } from '@intlayer/api/dictionary';\n// @ts-ignore @intlayer/backend is not build yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { getRemoteDictionaries } from '@intlayer/dictionaries-entry/remote';\nimport type {\n Dictionary,\n DictionaryId,\n DictionaryKey,\n} from '@intlayer/types/dictionary';\nimport { fetchDistantDictionaries } from '../fetchDistantDictionaries';\nimport { sortAlphabetically } from '../utils/sortAlphabetically';\nimport type { DictionariesStatus } from './loadDictionaries';\n\nexport const formatDistantDictionaries = (\n dictionaries: (DictionaryAPI | Dictionary)[]\n): Dictionary[] =>\n dictionaries.map((dict) => ({\n ...dict,\n localId: `${dict.key}::remote::${dict.id}`,\n location: 'remote' as const,\n }));\n\nexport const loadRemoteDictionaries = async (\n configuration = getConfiguration(),\n onStatusUpdate?: (status: DictionariesStatus[]) => void,\n options?: {\n onStartRemoteCheck?: () => void;\n onStopRemoteCheck?: () => void;\n onError?: (error: Error) => void;\n }\n): Promise<Dictionary[]> => {\n const { editor } = configuration;\n const remoteDictionariesRecord = getRemoteDictionaries(configuration);\n\n const hasRemoteDictionaries = Boolean(editor.clientId && editor.clientSecret);\n\n if (!hasRemoteDictionaries) return [];\n\n try {\n options?.onStartRemoteCheck?.();\n\n const dictionary = dictionaryEndpoint(createIntlayerCMS(configuration));\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await dictionary.getDictionariesUpdateTimestamp();\n\n const distantDictionaryUpdateTimeStamp: Record<\n DictionaryId,\n { key: DictionaryKey; updatedAt: number }\n > | null = getDictionariesKeysResult.data;\n\n if (!distantDictionaryUpdateTimeStamp) {\n throw new Error('No distant dictionaries found');\n }\n\n const dictionariesIdToFetch = Object.entries(\n distantDictionaryUpdateTimeStamp\n ).filter(([dictionaryId, data]) => {\n // If remote doesn't provide updatedAt, fetch to be safe\n if (!data.updatedAt) return true;\n\n // If no local cache exists, fetch\n const local: Dictionary | undefined = remoteDictionariesRecord[\n data.key\n ]?.find((dictionary) => dictionary.id === dictionaryId);\n if (!local) return true;\n\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n\n // If local timestamp missing or older than remote, fetch\n if (typeof localUpdatedAt !== 'number') return true;\n\n return data.updatedAt > localUpdatedAt;\n });\n\n const flatRemoteDictionariesRecord: DictionaryAPI[] = Object.values(\n remoteDictionariesRecord\n ).flat();\n\n const cachedDictionaries: Dictionary[] =\n flatRemoteDictionariesRecord.filter((dictionary) => {\n const remoteUpdatedAt = dictionary.id\n ? distantDictionaryUpdateTimeStamp[dictionary.id]?.updatedAt\n : undefined;\n\n const localUpdatedAtRaw = dictionary.updatedAt;\n\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n\n // Consider as cached/imported when local exists and is up-to-date or newer\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n });\n\n // Report cached as already imported\n if (cachedDictionaries.length > 0) {\n onStatusUpdate?.(\n cachedDictionaries.map((dictionary) => ({\n dictionaryKey: dictionary.key,\n type: 'remote',\n status: 'imported',\n }))\n );\n }\n\n // Several qualified dictionaries can share a key, so keys are deduplicated:\n // one request per key already returns all of its siblings.\n const orderedDistantDictionaryKeys = [\n ...new Set(dictionariesIdToFetch.map(([, data]) => data.key)),\n ].sort(sortAlphabetically);\n\n // Report pending for keys to be fetched so totals are visible immediately\n if (orderedDistantDictionaryKeys.length > 0) {\n onStatusUpdate?.(\n orderedDistantDictionaryKeys.map((key) => ({\n dictionaryKey: key,\n type: 'remote',\n status: 'pending',\n }))\n );\n }\n\n const distantDictionariesData = await fetchDistantDictionaries(\n {\n dictionaryKeys: orderedDistantDictionaryKeys,\n },\n onStatusUpdate\n );\n\n const distantDictionaries: Dictionary[] = formatDistantDictionaries(\n distantDictionariesData\n );\n\n // Fetching by key also returns the up-to-date siblings of a stale\n // dictionary, so drop the cached copies the remote just sent back.\n const fetchedDictionaryIds = new Set(\n distantDictionaries.map((dictionary) => dictionary.id)\n );\n\n const notRefetchedCachedDictionaries = cachedDictionaries.filter(\n (dictionary) => !fetchedDictionaryIds.has(dictionary.id)\n );\n\n return [...notRefetchedCachedDictionaries, ...distantDictionaries];\n } catch (error) {\n options?.onError?.(error as Error);\n return [];\n } finally {\n options?.onStopRemoteCheck?.();\n }\n};\n"],"mappings":";;;;;;;;AAeA,MAAa,6BACX,iBAEA,aAAa,KAAK,UAAU;CAC1B,GAAG;CACH,SAAS,GAAG,KAAK,IAAI,YAAY,KAAK;CACtC,UAAU;AACZ,EAAE;AAEJ,MAAa,yBAAyB,OACpC,gBAAgB,iBAAiB,GACjC,gBACA,YAK0B;CAC1B,MAAM,EAAE,WAAW;CACnB,MAAM,2BAA2B,sBAAsB,aAAa;CAIpE,IAAI,CAF0B,QAAQ,OAAO,YAAY,OAAO,YAEvC,GAAG,OAAO,CAAC;CAEpC,IAAI;EACF,SAAS,qBAAqB;EAQ9B,MAAM,oCAGK,MATQ,mBAAmB,kBAAkB,aAAa,CAIpD,CAAC,CAAC,+BAA+B,EAKd,CAAC;EAErC,IAAI,CAAC,kCACH,MAAM,IAAI,MAAM,+BAA+B;EAGjD,MAAM,wBAAwB,OAAO,QACnC,gCACF,CAAC,CAAC,QAAQ,CAAC,cAAc,UAAU;GAEjC,IAAI,CAAC,KAAK,WAAW,OAAO;GAG5B,MAAM,QAAgC,yBACpC,KAAK,IACN,EAAE,MAAM,eAAe,WAAW,OAAO,YAAY;GACtD,IAAI,CAAC,OAAO,OAAO;GAEnB,MAAM,oBAAqB,OAAe;GAK1C,MAAM,iBACJ,OAAO,sBAAsB,WACzB,oBACA,oBACE,IAAI,KAAK,iBAAiB,CAAC,CAAC,QAAQ,IACpC;GAGR,IAAI,OAAO,mBAAmB,UAAU,OAAO;GAE/C,OAAO,KAAK,YAAY;EAC1B,CAAC;EAMD,MAAM,qBAJgD,OAAO,OAC3D,wBACF,CAAC,CAAC,KAG2B,CAAC,CAAC,QAAQ,eAAe;GAClD,MAAM,kBAAkB,WAAW,KAC/B,iCAAiC,WAAW,GAAG,EAAE,YACjD;GAEJ,MAAM,oBAAoB,WAAW;GAErC,MAAM,iBACJ,OAAO,sBAAsB,WACzB,oBACA,oBACE,IAAI,KAAK,iBAAiB,CAAC,CAAC,QAAQ,IACpC;GAGR,OACE,OAAO,mBAAmB,YAC1B,OAAO,oBAAoB,YAC3B,kBAAkB;EAEtB,CAAC;EAGH,IAAI,mBAAmB,SAAS,GAC9B,iBACE,mBAAmB,KAAK,gBAAgB;GACtC,eAAe,WAAW;GAC1B,MAAM;GACN,QAAQ;EACV,EAAE,CACJ;EAKF,MAAM,+BAA+B,CACnC,GAAG,IAAI,IAAI,sBAAsB,KAAK,GAAG,UAAU,KAAK,GAAG,CAAC,CAC9D,CAAC,CAAC,KAAK,kBAAkB;EAGzB,IAAI,6BAA6B,SAAS,GACxC,iBACE,6BAA6B,KAAK,SAAS;GACzC,eAAe;GACf,MAAM;GACN,QAAQ;EACV,EAAE,CACJ;EAGF,MAAM,0BAA0B,MAAM,yBACpC,EACE,gBAAgB,6BAClB,GACA,cACF;EAEA,MAAM,sBAAoC,0BACxC,uBACF;EAIA,MAAM,uBAAuB,IAAI,IAC/B,oBAAoB,KAAK,eAAe,WAAW,EAAE,CACvD;EAMA,OAAO,CAAC,GAJ+B,mBAAmB,QACvD,eAAe,CAAC,qBAAqB,IAAI,WAAW,EAAE,CAGjB,GAAG,GAAG,mBAAmB;CACnE,SAAS,OAAO;EACd,SAAS,UAAU,KAAc;EACjC,OAAO,CAAC;CACV,UAAU;EACR,SAAS,oBAAoB;CAC/B;AACF"}
1
+ {"version":3,"file":"loadRemoteDictionaries.mjs","names":[],"sources":["../../../src/loadDictionaries/loadRemoteDictionaries.ts"],"sourcesContent":["// @ts-ignore @intlayer/backend is not build yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { getRemoteDictionaries } from '@intlayer/dictionaries-entry/remote';\nimport type {\n Dictionary,\n DictionaryId,\n DictionaryKey,\n} from '@intlayer/types/dictionary';\nimport { sortAlphabetically } from '../utils/sortAlphabetically';\nimport type { DictionariesStatus } from './loadDictionaries';\n\nexport const formatDistantDictionaries = (\n dictionaries: (DictionaryAPI | Dictionary)[]\n): Dictionary[] =>\n dictionaries.map((dict) => ({\n ...dict,\n localId: `${dict.key}::remote::${dict.id}`,\n location: 'remote' as const,\n }));\n\nexport const loadRemoteDictionaries = async (\n configuration = getConfiguration(),\n onStatusUpdate?: (status: DictionariesStatus[]) => void,\n options?: {\n onStartRemoteCheck?: () => void;\n onStopRemoteCheck?: () => void;\n onError?: (error: Error) => void;\n }\n): Promise<Dictionary[]> => {\n const { editor } = configuration;\n const remoteDictionariesRecord = getRemoteDictionaries(configuration);\n\n const hasRemoteDictionaries = Boolean(editor.clientId && editor.clientSecret);\n\n if (!hasRemoteDictionaries) return [];\n\n try {\n options?.onStartRemoteCheck?.();\n\n // Lazy: the CMS client is only needed when remote credentials are set\n const { createRemoteDictionaryClient, fetchDistantDictionaries } =\n await import('./remoteDictionaryClient');\n\n const dictionary = createRemoteDictionaryClient(configuration);\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await dictionary.getDictionariesUpdateTimestamp();\n\n const distantDictionaryUpdateTimeStamp: Record<\n DictionaryId,\n { key: DictionaryKey; updatedAt: number }\n > | null = getDictionariesKeysResult.data;\n\n if (!distantDictionaryUpdateTimeStamp) {\n throw new Error('No distant dictionaries found');\n }\n\n const dictionariesIdToFetch = Object.entries(\n distantDictionaryUpdateTimeStamp\n ).filter(([dictionaryId, data]) => {\n // If remote doesn't provide updatedAt, fetch to be safe\n if (!data.updatedAt) return true;\n\n // If no local cache exists, fetch\n const local: Dictionary | undefined = remoteDictionariesRecord[\n data.key\n ]?.find((dictionary) => dictionary.id === dictionaryId);\n if (!local) return true;\n\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n\n // If local timestamp missing or older than remote, fetch\n if (typeof localUpdatedAt !== 'number') return true;\n\n return data.updatedAt > localUpdatedAt;\n });\n\n const flatRemoteDictionariesRecord: DictionaryAPI[] = Object.values(\n remoteDictionariesRecord\n ).flat();\n\n const cachedDictionaries: Dictionary[] =\n flatRemoteDictionariesRecord.filter((dictionary) => {\n const remoteUpdatedAt = dictionary.id\n ? distantDictionaryUpdateTimeStamp[dictionary.id]?.updatedAt\n : undefined;\n\n const localUpdatedAtRaw = dictionary.updatedAt;\n\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n\n // Consider as cached/imported when local exists and is up-to-date or newer\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n });\n\n // Report cached as already imported\n if (cachedDictionaries.length > 0) {\n onStatusUpdate?.(\n cachedDictionaries.map((dictionary) => ({\n dictionaryKey: dictionary.key,\n type: 'remote',\n status: 'imported',\n }))\n );\n }\n\n // Several qualified dictionaries can share a key, so keys are deduplicated:\n // one request per key already returns all of its siblings.\n const orderedDistantDictionaryKeys = [\n ...new Set(dictionariesIdToFetch.map(([, data]) => data.key)),\n ].sort(sortAlphabetically);\n\n // Report pending for keys to be fetched so totals are visible immediately\n if (orderedDistantDictionaryKeys.length > 0) {\n onStatusUpdate?.(\n orderedDistantDictionaryKeys.map((key) => ({\n dictionaryKey: key,\n type: 'remote',\n status: 'pending',\n }))\n );\n }\n\n const distantDictionariesData = await fetchDistantDictionaries(\n {\n dictionaryKeys: orderedDistantDictionaryKeys,\n },\n onStatusUpdate\n );\n\n const distantDictionaries: Dictionary[] = formatDistantDictionaries(\n distantDictionariesData\n );\n\n // Fetching by key also returns the up-to-date siblings of a stale\n // dictionary, so drop the cached copies the remote just sent back.\n const fetchedDictionaryIds = new Set(\n distantDictionaries.map((dictionary) => dictionary.id)\n );\n\n const notRefetchedCachedDictionaries = cachedDictionaries.filter(\n (dictionary) => !fetchedDictionaryIds.has(dictionary.id)\n );\n\n return [...notRefetchedCachedDictionaries, ...distantDictionaries];\n } catch (error) {\n options?.onError?.(error as Error);\n return [];\n } finally {\n options?.onStopRemoteCheck?.();\n }\n};\n"],"mappings":";;;;;AAYA,MAAa,6BACX,iBAEA,aAAa,KAAK,UAAU;CAC1B,GAAG;CACH,SAAS,GAAG,KAAK,IAAI,YAAY,KAAK;CACtC,UAAU;AACZ,EAAE;AAEJ,MAAa,yBAAyB,OACpC,gBAAgB,iBAAiB,GACjC,gBACA,YAK0B;CAC1B,MAAM,EAAE,WAAW;CACnB,MAAM,2BAA2B,sBAAsB,aAAa;CAIpE,IAAI,CAF0B,QAAQ,OAAO,YAAY,OAAO,YAEvC,GAAG,OAAO,CAAC;CAEpC,IAAI;EACF,SAAS,qBAAqB;EAG9B,MAAM,EAAE,8BAA8B,6BACpC,MAAM,OAAO;EAQf,MAAM,oCAGK,MATQ,6BAA6B,aAI/B,CAAC,CAAC,+BAA+B,EAKd,CAAC;EAErC,IAAI,CAAC,kCACH,MAAM,IAAI,MAAM,+BAA+B;EAGjD,MAAM,wBAAwB,OAAO,QACnC,gCACF,CAAC,CAAC,QAAQ,CAAC,cAAc,UAAU;GAEjC,IAAI,CAAC,KAAK,WAAW,OAAO;GAG5B,MAAM,QAAgC,yBACpC,KAAK,IACN,EAAE,MAAM,eAAe,WAAW,OAAO,YAAY;GACtD,IAAI,CAAC,OAAO,OAAO;GAEnB,MAAM,oBAAqB,OAAe;GAK1C,MAAM,iBACJ,OAAO,sBAAsB,WACzB,oBACA,oBACE,IAAI,KAAK,iBAAiB,CAAC,CAAC,QAAQ,IACpC;GAGR,IAAI,OAAO,mBAAmB,UAAU,OAAO;GAE/C,OAAO,KAAK,YAAY;EAC1B,CAAC;EAMD,MAAM,qBAJgD,OAAO,OAC3D,wBACF,CAAC,CAAC,KAG2B,CAAC,CAAC,QAAQ,eAAe;GAClD,MAAM,kBAAkB,WAAW,KAC/B,iCAAiC,WAAW,GAAG,EAAE,YACjD;GAEJ,MAAM,oBAAoB,WAAW;GAErC,MAAM,iBACJ,OAAO,sBAAsB,WACzB,oBACA,oBACE,IAAI,KAAK,iBAAiB,CAAC,CAAC,QAAQ,IACpC;GAGR,OACE,OAAO,mBAAmB,YAC1B,OAAO,oBAAoB,YAC3B,kBAAkB;EAEtB,CAAC;EAGH,IAAI,mBAAmB,SAAS,GAC9B,iBACE,mBAAmB,KAAK,gBAAgB;GACtC,eAAe,WAAW;GAC1B,MAAM;GACN,QAAQ;EACV,EAAE,CACJ;EAKF,MAAM,+BAA+B,CACnC,GAAG,IAAI,IAAI,sBAAsB,KAAK,GAAG,UAAU,KAAK,GAAG,CAAC,CAC9D,CAAC,CAAC,KAAK,kBAAkB;EAGzB,IAAI,6BAA6B,SAAS,GACxC,iBACE,6BAA6B,KAAK,SAAS;GACzC,eAAe;GACf,MAAM;GACN,QAAQ;EACV,EAAE,CACJ;EAGF,MAAM,0BAA0B,MAAM,yBACpC,EACE,gBAAgB,6BAClB,GACA,cACF;EAEA,MAAM,sBAAoC,0BACxC,uBACF;EAIA,MAAM,uBAAuB,IAAI,IAC/B,oBAAoB,KAAK,eAAe,WAAW,EAAE,CACvD;EAMA,OAAO,CAAC,GAJ+B,mBAAmB,QACvD,eAAe,CAAC,qBAAqB,IAAI,WAAW,EAAE,CAGjB,GAAG,GAAG,mBAAmB;CACnE,SAAS,OAAO;EACd,SAAS,UAAU,KAAc;EACjC,OAAO,CAAC;CACV,UAAU;EACR,SAAS,oBAAoB;CAC/B;AACF"}
@@ -0,0 +1,16 @@
1
+ import { fetchDistantDictionaries } from "../fetchDistantDictionaries.mjs";
2
+ import { createIntlayerCMS } from "@intlayer/api";
3
+ import { dictionaryEndpoint } from "@intlayer/api/dictionary";
4
+
5
+ //#region src/loadDictionaries/remoteDictionaryClient.ts
6
+ /**
7
+ * Creates the CMS dictionary client.
8
+ *
9
+ * Kept in its own module so `loadRemoteDictionaries` can import it lazily:
10
+ * `@intlayer/api` is only loaded when remote credentials are configured.
11
+ */
12
+ const createRemoteDictionaryClient = (configuration) => dictionaryEndpoint(createIntlayerCMS(configuration));
13
+
14
+ //#endregion
15
+ export { createRemoteDictionaryClient, fetchDistantDictionaries };
16
+ //# sourceMappingURL=remoteDictionaryClient.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteDictionaryClient.mjs","names":[],"sources":["../../../src/loadDictionaries/remoteDictionaryClient.ts"],"sourcesContent":["import { createIntlayerCMS } from '@intlayer/api';\nimport { dictionaryEndpoint } from '@intlayer/api/dictionary';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\nexport { fetchDistantDictionaries } from '../fetchDistantDictionaries';\n\n/**\n * Creates the CMS dictionary client.\n *\n * Kept in its own module so `loadRemoteDictionaries` can import it lazily:\n * `@intlayer/api` is only loaded when remote credentials are configured.\n */\nexport const createRemoteDictionaryClient = (\n configuration: IntlayerConfig\n): ReturnType<typeof dictionaryEndpoint> =>\n dictionaryEndpoint(createIntlayerCMS(configuration));\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,gCACX,kBAEA,mBAAmB,kBAAkB,aAAa,CAAC"}
@@ -0,0 +1,128 @@
1
+ import { startContentWatcher } from "./utils/startContentWatcher.mjs";
2
+ import { prepareIntlayer } from "./prepareIntlayer.mjs";
3
+ import { getProcessChainCommands } from "./utils/getProcessChainCommands.mjs";
4
+ import { getAppLogger } from "@intlayer/config/logger";
5
+
6
+ //#region src/prepareIntlayerServer.ts
7
+ /**
8
+ * Explicit override, for any setup the detection below does not recognise:
9
+ * `INTLAYER_WATCH=true` forces the content watcher on, `INTLAYER_WATCH=false`
10
+ * forces it off.
11
+ */
12
+ const INTLAYER_WATCH_ENV_VAR = "INTLAYER_WATCH";
13
+ /** Runtime flags that restart or reload the process on file changes. */
14
+ const WATCH_MODE_EXEC_ARGUMENTS = [
15
+ "--watch",
16
+ "--watch-path",
17
+ "--hot"
18
+ ];
19
+ /**
20
+ * Environment variables set on the child process by a restarting watcher:
21
+ * `node --watch` and `ts-node-dev`.
22
+ */
23
+ const WATCH_MODE_ENV_VARS = ["WATCH_REPORT_DEPENDENCIES", "TS_NODE_DEV"];
24
+ /**
25
+ * Package script names that start a dev server (`dev`, `start:dev`,
26
+ * `dev:api`, `watch`…), matched on the `npm_lifecycle_event` that npm, pnpm,
27
+ * yarn and bun set for `<pm> run <script>`.
28
+ */
29
+ const DEV_SCRIPT_NAME_PATTERN = /\b(dev|develop|development|watch)\b/;
30
+ /**
31
+ * Command lines of the tools that restart a server on file changes, matched on
32
+ * this process and the ones above it. Needed because most of them leave no
33
+ * trace in the child itself.
34
+ *
35
+ * - Dedicated restarters, matched on their executable name (`nodemon`,
36
+ * `watchexec`, `entr`…).
37
+ * - `tsx watch`, whose watch mode is a subcommand.
38
+ * - Any runtime or CLI given `--watch` / `--hot`: `deno run --watch` (which
39
+ * restarts in-process), `nest start --watch`, `tsup --watch --onSuccess`…
40
+ * - The `-w` shorthand of build tools that run the server on each rebuild
41
+ * (`tsdown -w --on-success "node dist/index.mjs"`).
42
+ */
43
+ const DEV_WATCHER_COMMAND_PATTERNS = [
44
+ /(^|[\s/])(nodemon|ts-node-dev|tsnd|node-dev|tsc-watch|supervisor|watchexec|entr|chokidar|onchange)(\.c?m?js)?(\s|$)/,
45
+ /(^|[\s/])tsx(\/dist\/cli\.m?js)?\s+watch(\s|$)/,
46
+ /\s--(watch|watch-path|hot)(=|\s|$)/,
47
+ /(^|[\s/])(tsdown|tsup|tsc|rollup|rolldown)(\/\S*)?(\s+\S+)*\s+-w(\s|$)/
48
+ ];
49
+ /**
50
+ * Whether a command line is a watcher that restarts its child on changes.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * getIsDevWatcherCommand('node /app/node_modules/.bin/nodemon src/index.ts'); // → true
55
+ * getIsDevWatcherCommand('node dist/index.js'); // → false
56
+ * ```
57
+ */
58
+ const getIsDevWatcherCommand = (command) => DEV_WATCHER_COMMAND_PATTERNS.some((pattern) => pattern.test(command));
59
+ /**
60
+ * Whether this process runs under a watcher that restarts or reloads it
61
+ * (`bun --watch`/`--hot`, `node --watch`, `ts-node-dev`).
62
+ */
63
+ const getIsWatchModeProcess = () => process.execArgv.some((argument) => WATCH_MODE_EXEC_ARGUMENTS.some((flag) => argument === flag || argument.startsWith(`${flag}=`))) || WATCH_MODE_ENV_VARS.some((envVar) => Boolean(process.env[envVar]));
64
+ /**
65
+ * Whether a server process runs in development, and so should watch the
66
+ * content declarations.
67
+ *
68
+ * `INTLAYER_WATCH=true|false` decides first, then `NODE_ENV` when set. An unset
69
+ * `NODE_ENV` is not a sign of development (`bun dist/index.mjs` rarely sets
70
+ * it), so the process must then show a positive signal, cheapest first:
71
+ * 1. a runtime watch flag or marker (`bun --watch`/`--hot`, `node --watch`,
72
+ * `ts-node-dev`);
73
+ * 2. a `dev`-like package script;
74
+ * 3. a restarting watcher in its own command line or its ancestors'
75
+ * (`deno run --watch`, `nodemon`, `tsx watch`…), for those started
76
+ * directly rather than through a script.
77
+ */
78
+ const getIsServerDevelopment = async () => {
79
+ const watchOverride = process.env[INTLAYER_WATCH_ENV_VAR]?.toLowerCase();
80
+ if (watchOverride === "true" || watchOverride === "1") return true;
81
+ if (watchOverride === "false" || watchOverride === "0") return false;
82
+ const nodeEnv = process.env.NODE_ENV;
83
+ if (nodeEnv) return nodeEnv === "development";
84
+ if (getIsWatchModeProcess()) return true;
85
+ if (DEV_SCRIPT_NAME_PATTERN.test(process.env["npm_lifecycle_event"] ?? "")) return true;
86
+ return (await getProcessChainCommands()).some(getIsDevWatcherCommand);
87
+ };
88
+ /**
89
+ * The dev check of this process, computed once: its answer cannot change for
90
+ * the life of the process, and Fastify, Elysia or Remix may register the
91
+ * integration more than once.
92
+ */
93
+ let serverDevelopmentPromise;
94
+ const getIsServerDevelopmentOnce = () => {
95
+ serverDevelopmentPromise ??= getIsServerDevelopment();
96
+ return serverDevelopmentPromise;
97
+ };
98
+ /**
99
+ * Prepares the dictionaries for a server integration (Express, Fastify, Hono…)
100
+ * and, in development, keeps them in sync with the content declarations.
101
+ *
102
+ * Replaces wrapping the dev server in `intlayer watch --with`: the watcher runs
103
+ * inside the server, deduplicated through the project's watcher lock, so a
104
+ * server restarted by `tsx watch` / `node --watch` hands it over to its
105
+ * successor, and a remaining `intlayer watch` makes it stand down.
106
+ *
107
+ * Fire-and-forget: the server must not wait on the build to start listening.
108
+ *
109
+ * @param configuration - The resolved Intlayer configuration.
110
+ * @param options - How the integration identifies itself, and whether to watch.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * prepareIntlayerServer(getConfiguration(), { label: 'express-intlayer' });
115
+ * ```
116
+ */
117
+ const prepareIntlayerServer = (configuration, { label, watch }) => {
118
+ prepareIntlayer(configuration).then(async () => {
119
+ if (!configuration.content.watch) return;
120
+ if (watch ?? await getIsServerDevelopmentOnce()) startContentWatcher(configuration, { label });
121
+ }).catch((error) => {
122
+ getAppLogger(configuration)(["Failed to prepare Intlayer dictionaries:", error], { level: "error" });
123
+ });
124
+ };
125
+
126
+ //#endregion
127
+ export { INTLAYER_WATCH_ENV_VAR, getIsDevWatcherCommand, getIsServerDevelopment, prepareIntlayerServer };
128
+ //# sourceMappingURL=prepareIntlayerServer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepareIntlayerServer.mjs","names":[],"sources":["../../src/prepareIntlayerServer.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { prepareIntlayer } from './prepareIntlayer';\nimport { getProcessChainCommands } from './utils/getProcessChainCommands';\nimport { startContentWatcher } from './utils/startContentWatcher';\n\n/**\n * Explicit override, for any setup the detection below does not recognise:\n * `INTLAYER_WATCH=true` forces the content watcher on, `INTLAYER_WATCH=false`\n * forces it off.\n */\nexport const INTLAYER_WATCH_ENV_VAR = 'INTLAYER_WATCH';\n\n/** Runtime flags that restart or reload the process on file changes. */\nconst WATCH_MODE_EXEC_ARGUMENTS = ['--watch', '--watch-path', '--hot'];\n\n/**\n * Environment variables set on the child process by a restarting watcher:\n * `node --watch` and `ts-node-dev`.\n */\nconst WATCH_MODE_ENV_VARS = ['WATCH_REPORT_DEPENDENCIES', 'TS_NODE_DEV'];\n\n/**\n * Package script names that start a dev server (`dev`, `start:dev`,\n * `dev:api`, `watch`…), matched on the `npm_lifecycle_event` that npm, pnpm,\n * yarn and bun set for `<pm> run <script>`.\n */\nconst DEV_SCRIPT_NAME_PATTERN = /\\b(dev|develop|development|watch)\\b/;\n\n/**\n * Command lines of the tools that restart a server on file changes, matched on\n * this process and the ones above it. Needed because most of them leave no\n * trace in the child itself.\n *\n * - Dedicated restarters, matched on their executable name (`nodemon`,\n * `watchexec`, `entr`…).\n * - `tsx watch`, whose watch mode is a subcommand.\n * - Any runtime or CLI given `--watch` / `--hot`: `deno run --watch` (which\n * restarts in-process), `nest start --watch`, `tsup --watch --onSuccess`…\n * - The `-w` shorthand of build tools that run the server on each rebuild\n * (`tsdown -w --on-success \"node dist/index.mjs\"`).\n */\nconst DEV_WATCHER_COMMAND_PATTERNS = [\n /(^|[\\s/])(nodemon|ts-node-dev|tsnd|node-dev|tsc-watch|supervisor|watchexec|entr|chokidar|onchange)(\\.c?m?js)?(\\s|$)/,\n /(^|[\\s/])tsx(\\/dist\\/cli\\.m?js)?\\s+watch(\\s|$)/,\n /\\s--(watch|watch-path|hot)(=|\\s|$)/,\n // `-w` alone is too common a flag (`grep -w`), so it only counts after a\n // build tool whose `-w` means watch (`tsdown -w --on-success …`, `tsup -w`).\n /(^|[\\s/])(tsdown|tsup|tsc|rollup|rolldown)(\\/\\S*)?(\\s+\\S+)*\\s+-w(\\s|$)/,\n];\n\n/**\n * Whether a command line is a watcher that restarts its child on changes.\n *\n * @example\n * ```ts\n * getIsDevWatcherCommand('node /app/node_modules/.bin/nodemon src/index.ts'); // → true\n * getIsDevWatcherCommand('node dist/index.js'); // → false\n * ```\n */\nexport const getIsDevWatcherCommand = (command: string): boolean =>\n DEV_WATCHER_COMMAND_PATTERNS.some((pattern) => pattern.test(command));\n\n/**\n * Whether this process runs under a watcher that restarts or reloads it\n * (`bun --watch`/`--hot`, `node --watch`, `ts-node-dev`).\n */\nconst getIsWatchModeProcess = (): boolean =>\n process.execArgv.some((argument) =>\n WATCH_MODE_EXEC_ARGUMENTS.some(\n (flag) => argument === flag || argument.startsWith(`${flag}=`)\n )\n ) || WATCH_MODE_ENV_VARS.some((envVar) => Boolean(process.env[envVar]));\n\n/**\n * Whether a server process runs in development, and so should watch the\n * content declarations.\n *\n * `INTLAYER_WATCH=true|false` decides first, then `NODE_ENV` when set. An unset\n * `NODE_ENV` is not a sign of development (`bun dist/index.mjs` rarely sets\n * it), so the process must then show a positive signal, cheapest first:\n * 1. a runtime watch flag or marker (`bun --watch`/`--hot`, `node --watch`,\n * `ts-node-dev`);\n * 2. a `dev`-like package script;\n * 3. a restarting watcher in its own command line or its ancestors'\n * (`deno run --watch`, `nodemon`, `tsx watch`…), for those started\n * directly rather than through a script.\n */\nexport const getIsServerDevelopment = async (): Promise<boolean> => {\n const watchOverride = process.env[INTLAYER_WATCH_ENV_VAR]?.toLowerCase();\n\n if (watchOverride === 'true' || watchOverride === '1') return true;\n if (watchOverride === 'false' || watchOverride === '0') return false;\n\n const nodeEnv = process.env['NODE_ENV'];\n\n if (nodeEnv) return nodeEnv === 'development';\n\n if (getIsWatchModeProcess()) return true;\n\n if (DEV_SCRIPT_NAME_PATTERN.test(process.env['npm_lifecycle_event'] ?? '')) {\n return true;\n }\n\n const processChainCommands = await getProcessChainCommands();\n\n return processChainCommands.some(getIsDevWatcherCommand);\n};\n\n/**\n * The dev check of this process, computed once: its answer cannot change for\n * the life of the process, and Fastify, Elysia or Remix may register the\n * integration more than once.\n */\nlet serverDevelopmentPromise: Promise<boolean> | undefined;\n\nconst getIsServerDevelopmentOnce = (): Promise<boolean> => {\n serverDevelopmentPromise ??= getIsServerDevelopment();\n\n return serverDevelopmentPromise;\n};\n\nexport type PrepareIntlayerServerOptions = {\n /**\n * How the integration names itself in the watcher lock and in messages, e.g.\n * `express-intlayer`.\n */\n label: string;\n /**\n * Whether to watch the content declarations after preparing them.\n * Defaults to {@link getIsServerDevelopment}.\n */\n watch?: boolean;\n};\n\n/**\n * Prepares the dictionaries for a server integration (Express, Fastify, Hono…)\n * and, in development, keeps them in sync with the content declarations.\n *\n * Replaces wrapping the dev server in `intlayer watch --with`: the watcher runs\n * inside the server, deduplicated through the project's watcher lock, so a\n * server restarted by `tsx watch` / `node --watch` hands it over to its\n * successor, and a remaining `intlayer watch` makes it stand down.\n *\n * Fire-and-forget: the server must not wait on the build to start listening.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param options - How the integration identifies itself, and whether to watch.\n *\n * @example\n * ```ts\n * prepareIntlayerServer(getConfiguration(), { label: 'express-intlayer' });\n * ```\n */\nexport const prepareIntlayerServer = (\n configuration: IntlayerConfig,\n { label, watch }: PrepareIntlayerServerOptions\n): void => {\n prepareIntlayer(configuration)\n .then(async () => {\n if (!configuration.content.watch) return;\n\n const shouldWatch = watch ?? (await getIsServerDevelopmentOnce());\n\n if (shouldWatch) startContentWatcher(configuration, { label });\n })\n .catch((error: unknown) => {\n getAppLogger(configuration)(\n ['Failed to prepare Intlayer dictionaries:', error],\n { level: 'error' }\n );\n });\n};\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,yBAAyB;;AAGtC,MAAM,4BAA4B;CAAC;CAAW;CAAgB;AAAO;;;;;AAMrE,MAAM,sBAAsB,CAAC,6BAA6B,aAAa;;;;;;AAOvE,MAAM,0BAA0B;;;;;;;;;;;;;;AAehC,MAAM,+BAA+B;CACnC;CACA;CACA;CAGA;AACF;;;;;;;;;;AAWA,MAAa,0BAA0B,YACrC,6BAA6B,MAAM,YAAY,QAAQ,KAAK,OAAO,CAAC;;;;;AAMtE,MAAM,8BACJ,QAAQ,SAAS,MAAM,aACrB,0BAA0B,MACvB,SAAS,aAAa,QAAQ,SAAS,WAAW,GAAG,KAAK,EAAE,CAC/D,CACF,KAAK,oBAAoB,MAAM,WAAW,QAAQ,QAAQ,IAAI,OAAO,CAAC;;;;;;;;;;;;;;;AAgBxE,MAAa,yBAAyB,YAA8B;CAClE,MAAM,gBAAgB,QAAQ,IAAI,uBAAuB,EAAE,YAAY;CAEvE,IAAI,kBAAkB,UAAU,kBAAkB,KAAK,OAAO;CAC9D,IAAI,kBAAkB,WAAW,kBAAkB,KAAK,OAAO;CAE/D,MAAM;CAEN,IAAI,SAAS,OAAO,YAAY;CAEhC,IAAI,sBAAsB,GAAG,OAAO;CAEpC,IAAI,wBAAwB,KAAK,QAAQ,IAAI,0BAA0B,EAAE,GACvE,OAAO;CAKT,QAAO,MAF4B,wBAAwB,EAEhC,CAAC,KAAK,sBAAsB;AACzD;;;;;;AAOA,IAAI;AAEJ,MAAM,mCAAqD;CACzD,6BAA6B,uBAAuB;CAEpD,OAAO;AACT;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,yBACX,eACA,EAAE,OAAO,YACA;CACT,gBAAgB,aAAa,CAAC,CAC3B,KAAK,YAAY;EAChB,IAAI,CAAC,cAAc,QAAQ,OAAO;EAIlC,IAFoB,SAAU,MAAM,2BAA2B,GAE9C,oBAAoB,eAAe,EAAE,MAAM,CAAC;CAC/D,CAAC,CAAC,CACD,OAAO,UAAmB;EACzB,aAAa,aAAa,CAAC,CACzB,CAAC,4CAA4C,KAAK,GAClD,EAAE,OAAO,QAAQ,CACnB;CACF,CAAC;AACL"}
@@ -0,0 +1,115 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+
5
+ //#region src/utils/getProcessChainCommands.ts
6
+ /** How long listing the processes may take before giving up. */
7
+ const PROCESS_LIST_TIMEOUT_MS = 2e3;
8
+ /**
9
+ * Parses the output of `ps -A -o pid=,ppid=,command=`.
10
+ *
11
+ * @param output - One `<pid> <ppid> <command>` row per line.
12
+ * @returns The processes, keyed by PID.
13
+ */
14
+ const parseProcessTable = (output) => {
15
+ const processes = /* @__PURE__ */ new Map();
16
+ for (const line of output.split("\n")) {
17
+ const match = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
18
+ if (!match) continue;
19
+ processes.set(Number(match[1]), {
20
+ parentPid: Number(match[2]),
21
+ command: match[3].trim()
22
+ });
23
+ }
24
+ return processes;
25
+ };
26
+ /** Reads one process from `/proc` (Linux): no subprocess needed. */
27
+ const readLinuxProcess = async (pid) => {
28
+ try {
29
+ const stat = await readFile(`/proc/${pid}/stat`, "utf8");
30
+ const [, parentPidField] = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
31
+ const commandLine = await readFile(`/proc/${pid}/cmdline`, "utf8");
32
+ return {
33
+ parentPid: Number(parentPidField),
34
+ command: commandLine.split("\0").join(" ").trim()
35
+ };
36
+ } catch {
37
+ return;
38
+ }
39
+ };
40
+ /** Lists every process through one `ps` call (macOS, BSD). */
41
+ const readProcessTable = async () => {
42
+ try {
43
+ const { stdout } = await promisify(execFile)("ps", [
44
+ "-A",
45
+ "-o",
46
+ "pid=,ppid=,command="
47
+ ], {
48
+ timeout: PROCESS_LIST_TIMEOUT_MS,
49
+ maxBuffer: 16777216
50
+ });
51
+ return parseProcessTable(stdout);
52
+ } catch {
53
+ return /* @__PURE__ */ new Map();
54
+ }
55
+ };
56
+ /**
57
+ * Whether reading the process list is allowed without side effects.
58
+ *
59
+ * Under Deno, a missing permission makes an interactive process stop and
60
+ * prompt the user, so the list is only read when access is already granted.
61
+ * Node and Bun have no permission model on these calls.
62
+ */
63
+ const getCanReadProcessList = () => {
64
+ const permissions = globalThis.Deno?.permissions;
65
+ if (!permissions) return true;
66
+ try {
67
+ const descriptor = process.platform === "linux" ? {
68
+ name: "read",
69
+ path: "/proc"
70
+ } : {
71
+ name: "run",
72
+ command: "ps"
73
+ };
74
+ return permissions.querySync?.(descriptor).state === "granted";
75
+ } catch {
76
+ return false;
77
+ }
78
+ };
79
+ /**
80
+ * Command lines of this process and of the processes above it, closest first.
81
+ *
82
+ * This process is included because some runtimes restart the script inside
83
+ * the same process, leaving their watch flag only on their own command line
84
+ * (`deno run --watch`).
85
+ *
86
+ * Best effort: resolves to an empty list on Windows, or when the process list
87
+ * cannot be read (sandbox, missing `ps`, Deno without the permission), and
88
+ * never throws.
89
+ *
90
+ * @param maxDepth - How many processes to read at most, this one included.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * await getProcessChainCommands();
95
+ * // → ['node src/index.js', 'node …/nodemon src/index.js', 'npm run dev']
96
+ * ```
97
+ */
98
+ const getProcessChainCommands = async (maxDepth = 6) => {
99
+ if (process.platform === "win32") return [];
100
+ if (!getCanReadProcessList()) return [];
101
+ const getProcess = process.platform === "linux" ? readLinuxProcess : await readProcessTable().then((processes) => async (pid) => processes.get(pid));
102
+ const commands = [];
103
+ let pid = process.pid;
104
+ while (pid > 1 && commands.length < maxDepth) {
105
+ const entry = await getProcess(pid);
106
+ if (!entry) break;
107
+ commands.push(entry.command);
108
+ pid = entry.parentPid;
109
+ }
110
+ return commands;
111
+ };
112
+
113
+ //#endregion
114
+ export { getProcessChainCommands, parseProcessTable };
115
+ //# sourceMappingURL=getProcessChainCommands.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getProcessChainCommands.mjs","names":[],"sources":["../../../src/utils/getProcessChainCommands.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { promisify } from 'node:util';\n\n/** Process as listed by the OS: its parent and the command line it runs. */\nexport type ProcessEntry = {\n parentPid: number;\n command: string;\n};\n\n/** How long listing the processes may take before giving up. */\nconst PROCESS_LIST_TIMEOUT_MS = 2000;\n\n/**\n * Parses the output of `ps -A -o pid=,ppid=,command=`.\n *\n * @param output - One `<pid> <ppid> <command>` row per line.\n * @returns The processes, keyed by PID.\n */\nexport const parseProcessTable = (\n output: string\n): Map<number, ProcessEntry> => {\n const processes = new Map<number, ProcessEntry>();\n\n for (const line of output.split('\\n')) {\n const match = /^\\s*(\\d+)\\s+(\\d+)\\s+(.*)$/.exec(line);\n\n if (!match) continue;\n\n processes.set(Number(match[1]), {\n parentPid: Number(match[2]),\n command: match[3]!.trim(),\n });\n }\n\n return processes;\n};\n\n/** Reads one process from `/proc` (Linux): no subprocess needed. */\nconst readLinuxProcess = async (\n pid: number\n): Promise<ProcessEntry | undefined> => {\n try {\n const stat = await readFile(`/proc/${pid}/stat`, 'utf8');\n // The command name is wrapped in parentheses and may contain spaces, so the\n // fields are read after the last `)`: `<state> <ppid> …`.\n const [, parentPidField] = stat.slice(stat.lastIndexOf(')') + 2).split(' ');\n const commandLine = await readFile(`/proc/${pid}/cmdline`, 'utf8');\n\n return {\n parentPid: Number(parentPidField),\n command: commandLine.split('\\0').join(' ').trim(),\n };\n } catch {\n return undefined;\n }\n};\n\n/** Lists every process through one `ps` call (macOS, BSD). */\nconst readProcessTable = async (): Promise<Map<number, ProcessEntry>> => {\n try {\n const { stdout } = await promisify(execFile)(\n 'ps',\n ['-A', '-o', 'pid=,ppid=,command='],\n { timeout: PROCESS_LIST_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024 }\n );\n\n return parseProcessTable(stdout);\n } catch {\n return new Map();\n }\n};\n\ntype DenoPermissionState = 'granted' | 'prompt' | 'denied';\n\ntype DenoPermissions = {\n querySync?: (descriptor: {\n name: 'run' | 'read';\n command?: string;\n path?: string;\n }) => { state: DenoPermissionState };\n};\n\n/**\n * Whether reading the process list is allowed without side effects.\n *\n * Under Deno, a missing permission makes an interactive process stop and\n * prompt the user, so the list is only read when access is already granted.\n * Node and Bun have no permission model on these calls.\n */\nconst getCanReadProcessList = (): boolean => {\n const permissions = (\n globalThis as { Deno?: { permissions?: DenoPermissions } }\n ).Deno?.permissions;\n\n if (!permissions) return true;\n\n try {\n const descriptor =\n process.platform === 'linux'\n ? ({ name: 'read', path: '/proc' } as const)\n : ({ name: 'run', command: 'ps' } as const);\n\n return permissions.querySync?.(descriptor).state === 'granted';\n } catch {\n return false;\n }\n};\n\n/**\n * Command lines of this process and of the processes above it, closest first.\n *\n * This process is included because some runtimes restart the script inside\n * the same process, leaving their watch flag only on their own command line\n * (`deno run --watch`).\n *\n * Best effort: resolves to an empty list on Windows, or when the process list\n * cannot be read (sandbox, missing `ps`, Deno without the permission), and\n * never throws.\n *\n * @param maxDepth - How many processes to read at most, this one included.\n *\n * @example\n * ```ts\n * await getProcessChainCommands();\n * // → ['node src/index.js', 'node …/nodemon src/index.js', 'npm run dev']\n * ```\n */\nexport const getProcessChainCommands = async (\n maxDepth = 6\n): Promise<string[]> => {\n if (process.platform === 'win32') return [];\n if (!getCanReadProcessList()) return [];\n\n const getProcess: (pid: number) => Promise<ProcessEntry | undefined> =\n process.platform === 'linux'\n ? readLinuxProcess\n : await readProcessTable().then(\n (processes) => async (pid: number) => processes.get(pid)\n );\n\n const commands: string[] = [];\n let pid = process.pid;\n\n // PID 1 is init/launchd: nothing above it is a dev tool.\n while (pid > 1 && commands.length < maxDepth) {\n const entry = await getProcess(pid);\n\n if (!entry) break;\n\n commands.push(entry.command);\n pid = entry.parentPid;\n }\n\n return commands;\n};\n"],"mappings":";;;;;;AAWA,MAAM,0BAA0B;;;;;;;AAQhC,MAAa,qBACX,WAC8B;CAC9B,MAAM,4BAAY,IAAI,IAA0B;CAEhD,KAAK,MAAM,QAAQ,OAAO,MAAM,IAAI,GAAG;EACrC,MAAM,QAAQ,4BAA4B,KAAK,IAAI;EAEnD,IAAI,CAAC,OAAO;EAEZ,UAAU,IAAI,OAAO,MAAM,EAAE,GAAG;GAC9B,WAAW,OAAO,MAAM,EAAE;GAC1B,SAAS,MAAM,EAAE,CAAE,KAAK;EAC1B,CAAC;CACH;CAEA,OAAO;AACT;;AAGA,MAAM,mBAAmB,OACvB,QACsC;CACtC,IAAI;EACF,MAAM,OAAO,MAAM,SAAS,SAAS,IAAI,QAAQ,MAAM;EAGvD,MAAM,GAAG,kBAAkB,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG;EAC1E,MAAM,cAAc,MAAM,SAAS,SAAS,IAAI,WAAW,MAAM;EAEjE,OAAO;GACL,WAAW,OAAO,cAAc;GAChC,SAAS,YAAY,MAAM,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;EAClD;CACF,QAAQ;EACN;CACF;AACF;;AAGA,MAAM,mBAAmB,YAAgD;CACvE,IAAI;EACF,MAAM,EAAE,WAAW,MAAM,UAAU,QAAQ,CAAC,CAC1C,MACA;GAAC;GAAM;GAAM;EAAqB,GAClC;GAAE,SAAS;GAAyB,WAAW;EAAiB,CAClE;EAEA,OAAO,kBAAkB,MAAM;CACjC,QAAQ;EACN,uBAAO,IAAI,IAAI;CACjB;AACF;;;;;;;;AAmBA,MAAM,8BAAuC;CAC3C,MAAM,cACJ,WACA,MAAM;CAER,IAAI,CAAC,aAAa,OAAO;CAEzB,IAAI;EACF,MAAM,aACJ,QAAQ,aAAa,UAChB;GAAE,MAAM;GAAQ,MAAM;EAAQ,IAC9B;GAAE,MAAM;GAAO,SAAS;EAAK;EAEpC,OAAO,YAAY,YAAY,UAAU,CAAC,CAAC,UAAU;CACvD,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,0BAA0B,OACrC,WAAW,MACW;CACtB,IAAI,QAAQ,aAAa,SAAS,OAAO,CAAC;CAC1C,IAAI,CAAC,sBAAsB,GAAG,OAAO,CAAC;CAEtC,MAAM,aACJ,QAAQ,aAAa,UACjB,mBACA,MAAM,iBAAiB,CAAC,CAAC,MACtB,cAAc,OAAO,QAAgB,UAAU,IAAI,GAAG,CACzD;CAEN,MAAM,WAAqB,CAAC;CAC5B,IAAI,MAAM,QAAQ;CAGlB,OAAO,MAAM,KAAK,SAAS,SAAS,UAAU;EAC5C,MAAM,QAAQ,MAAM,WAAW,GAAG;EAElC,IAAI,CAAC,OAAO;EAEZ,SAAS,KAAK,MAAM,OAAO;EAC3B,MAAM,MAAM;CACd;CAEA,OAAO;AACT"}
@@ -20,7 +20,8 @@ import { resolveRelativePath } from "./resolveRelativePath.mjs";
20
20
  import { runOnce } from "./runOnce.mjs";
21
21
  import { runParallel } from "./runParallel/index.mjs";
22
22
  import { sortAlphabetically } from "./sortAlphabetically.mjs";
23
+ import { startContentWatcher } from "./startContentWatcher.mjs";
23
24
  import { verifyIdenticObjectFormat } from "./verifyIdenticObjectFormat.mjs";
24
25
  import { withFileLock } from "./withFileLock.mjs";
25
26
 
26
- export { CLI_CONTENT_WATCHER_ENV_VAR, Queue, acquireContentWatcherLock, assembleJSON, autoDecorateContent, buildComponentFilesList, chunkArray, chunkJSON, claimCliContentWatcher, excludeObjectFormat, formatLocale, formatPath, getChunk, getCliContentWatcherLabel, getContentExtension, getContentWatcherLockFilePath, getContentWatcherOwner, getExtensionFromFormat, getFormatFromExtension, getGlobalLimiter, getPathHash, getTaskLimiter, mergeChunks, pLimit, parallelize, parallelizeGlobal, parseContentDeclarationFileName, reconstructFromSingleChunk, reduceObjectFormat, reportRedundantContentWatcher, resolveObjectPromises, resolveRelativePath, runOnce, runParallel, sortAlphabetically, splitTextByLines, verifyIdenticObjectFormat, withFileLock };
27
+ export { CLI_CONTENT_WATCHER_ENV_VAR, Queue, acquireContentWatcherLock, assembleJSON, autoDecorateContent, buildComponentFilesList, chunkArray, chunkJSON, claimCliContentWatcher, excludeObjectFormat, formatLocale, formatPath, getChunk, getCliContentWatcherLabel, getContentExtension, getContentWatcherLockFilePath, getContentWatcherOwner, getExtensionFromFormat, getFormatFromExtension, getGlobalLimiter, getPathHash, getTaskLimiter, mergeChunks, pLimit, parallelize, parallelizeGlobal, parseContentDeclarationFileName, reconstructFromSingleChunk, reduceObjectFormat, reportRedundantContentWatcher, resolveObjectPromises, resolveRelativePath, runOnce, runParallel, sortAlphabetically, splitTextByLines, startContentWatcher, verifyIdenticObjectFormat, withFileLock };
@@ -0,0 +1,98 @@
1
+ import { acquireContentWatcherLock, getCliContentWatcherLabel, getContentWatcherOwner, reportRedundantContentWatcher } from "./contentWatcherLock.mjs";
2
+ import { getAppLogger } from "@intlayer/config/logger";
3
+
4
+ //#region src/utils/startContentWatcher.ts
5
+ /**
6
+ * How often a process that lost the ownership race checks whether the owner is
7
+ * gone, so it can take over once that process stops (e.g. a short-lived config
8
+ * process, or a second dev server).
9
+ */
10
+ const TAKEOVER_POLL_INTERVAL_MS = 5e3;
11
+ /**
12
+ * Per-process state, kept on `globalThis` rather than in module scope: a process
13
+ * can load both the CJS and ESM builds of this package, and hot-reloading
14
+ * servers (`bun --hot`, Vite SSR) re-evaluate modules without restarting — each
15
+ * copy must still see the watcher the first one started.
16
+ */
17
+ const getContentWatcherState = () => {
18
+ const stateKey = Symbol.for("intlayer.contentWatcher");
19
+ const globalScope = globalThis;
20
+ globalScope[stateKey] ??= {
21
+ isWatching: false,
22
+ isClaimInFlight: false
23
+ };
24
+ return globalScope[stateKey];
25
+ };
26
+ const claimAndWatch = async (configuration, options) => {
27
+ const state = getContentWatcherState();
28
+ if (!await acquireContentWatcherLock(configuration, {
29
+ source: "bundler",
30
+ label: options.label
31
+ })) {
32
+ const owner = await getContentWatcherOwner(configuration);
33
+ if (owner?.source === "cli") {
34
+ reportRedundantContentWatcher(configuration, {
35
+ cliLabel: owner.label,
36
+ bundlerLabel: options.label
37
+ });
38
+ return;
39
+ }
40
+ setTimeout(() => {
41
+ startContentWatcher(configuration, options);
42
+ }, TAKEOVER_POLL_INTERVAL_MS).unref?.();
43
+ return;
44
+ }
45
+ state.isWatching = true;
46
+ try {
47
+ const { watch } = await import("../watcher.mjs");
48
+ await watch({ configuration });
49
+ } catch (error) {
50
+ state.isWatching = false;
51
+ getAppLogger(configuration)(["Failed to watch Intlayer content declarations:", error], { level: "error" });
52
+ }
53
+ };
54
+ /**
55
+ * Starts the content declaration watcher from an integration (bundler plugin or
56
+ * server middleware), making sure only one watcher rebuilds a project's
57
+ * dictionaries.
58
+ *
59
+ * - Idempotent within a process, across module copies and hot reloads.
60
+ * - Stands down, with an explanation, when `intlayer watch` already covers the
61
+ * project, whether it spawned this process (`--with`) or runs elsewhere.
62
+ * - Otherwise the first process to take the project lock watches; the others
63
+ * retry in the background to take over once it stops.
64
+ *
65
+ * Not awaitable on purpose: a dev server must not wait on the watcher to serve.
66
+ *
67
+ * @param configuration - The resolved Intlayer configuration.
68
+ * @param options - How the integration identifies itself.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * configureServer() {
73
+ * startContentWatcher(configuration, { label: 'vite-intlayer' });
74
+ * }
75
+ * ```
76
+ */
77
+ const startContentWatcher = (configuration, options) => {
78
+ const state = getContentWatcherState();
79
+ if (state.isWatching || state.isClaimInFlight) return;
80
+ if (!configuration.content.watch) return;
81
+ if (options.getShouldSkip?.()) return;
82
+ const cliWatcherLabel = getCliContentWatcherLabel();
83
+ if (cliWatcherLabel) {
84
+ reportRedundantContentWatcher(configuration, {
85
+ cliLabel: cliWatcherLabel,
86
+ bundlerLabel: options.label
87
+ });
88
+ return;
89
+ }
90
+ state.isClaimInFlight = true;
91
+ claimAndWatch(configuration, options).finally(() => {
92
+ state.isClaimInFlight = false;
93
+ });
94
+ };
95
+
96
+ //#endregion
97
+ export { startContentWatcher };
98
+ //# sourceMappingURL=startContentWatcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startContentWatcher.mjs","names":[],"sources":["../../../src/utils/startContentWatcher.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport {\n acquireContentWatcherLock,\n getCliContentWatcherLabel,\n getContentWatcherOwner,\n reportRedundantContentWatcher,\n} from './contentWatcherLock';\n\n/**\n * How often a process that lost the ownership race checks whether the owner is\n * gone, so it can take over once that process stops (e.g. a short-lived config\n * process, or a second dev server).\n */\nconst TAKEOVER_POLL_INTERVAL_MS = 5000;\n\nexport type StartContentWatcherOptions = {\n /**\n * How the integration names itself in the lock and in messages, e.g.\n * `vite-intlayer`.\n */\n label: string;\n /**\n * Whether this process must never own the watcher, re-checked on every\n * attempt (e.g. a helper process that outlives the dev server).\n */\n getShouldSkip?: () => boolean;\n};\n\ntype ContentWatcherState = {\n /** Set once the watcher is running in this process. */\n isWatching: boolean;\n /**\n * Set while an ownership attempt is in flight: taking the lock is\n * asynchronous, and integrations re-enter on config reloads or server\n * restarts.\n */\n isClaimInFlight: boolean;\n};\n\n/**\n * Per-process state, kept on `globalThis` rather than in module scope: a process\n * can load both the CJS and ESM builds of this package, and hot-reloading\n * servers (`bun --hot`, Vite SSR) re-evaluate modules without restarting — each\n * copy must still see the watcher the first one started.\n */\nconst getContentWatcherState = (): ContentWatcherState => {\n const stateKey = Symbol.for('intlayer.contentWatcher');\n const globalScope = globalThis as typeof globalThis & {\n [key: symbol]: ContentWatcherState | undefined;\n };\n\n globalScope[stateKey] ??= { isWatching: false, isClaimInFlight: false };\n\n return globalScope[stateKey];\n};\n\nconst claimAndWatch = async (\n configuration: IntlayerConfig,\n options: StartContentWatcherOptions\n): Promise<void> => {\n const state = getContentWatcherState();\n\n const hasAcquiredLock = await acquireContentWatcherLock(configuration, {\n source: 'bundler',\n label: options.label,\n });\n\n if (!hasAcquiredLock) {\n const owner = await getContentWatcherOwner(configuration);\n\n // A CLI watcher started from another terminal is just as redundant as one\n // wrapping this command, and worth the same explanation.\n if (owner?.source === 'cli') {\n reportRedundantContentWatcher(configuration, {\n cliLabel: owner.label,\n bundlerLabel: options.label,\n });\n return;\n }\n\n // Another integration owns the watcher: retry in the background so this\n // process picks it up if that one stops first.\n const retryTimer = setTimeout(() => {\n startContentWatcher(configuration, options);\n }, TAKEOVER_POLL_INTERVAL_MS);\n\n // Unreferenced so a process whose only remaining work is this retry can\n // still exit.\n retryTimer.unref?.();\n\n return;\n }\n\n state.isWatching = true;\n\n try {\n // Imported lazily: the watcher pulls in `@parcel/watcher`, a native module\n // with no business being loaded during a production build.\n const { watch } = await import('../watcher');\n\n await watch({ configuration });\n } catch (error) {\n state.isWatching = false;\n\n getAppLogger(configuration)(\n ['Failed to watch Intlayer content declarations:', error],\n { level: 'error' }\n );\n }\n};\n\n/**\n * Starts the content declaration watcher from an integration (bundler plugin or\n * server middleware), making sure only one watcher rebuilds a project's\n * dictionaries.\n *\n * - Idempotent within a process, across module copies and hot reloads.\n * - Stands down, with an explanation, when `intlayer watch` already covers the\n * project, whether it spawned this process (`--with`) or runs elsewhere.\n * - Otherwise the first process to take the project lock watches; the others\n * retry in the background to take over once it stops.\n *\n * Not awaitable on purpose: a dev server must not wait on the watcher to serve.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param options - How the integration identifies itself.\n *\n * @example\n * ```ts\n * configureServer() {\n * startContentWatcher(configuration, { label: 'vite-intlayer' });\n * }\n * ```\n */\nexport const startContentWatcher = (\n configuration: IntlayerConfig,\n options: StartContentWatcherOptions\n): void => {\n const state = getContentWatcherState();\n\n if (state.isWatching || state.isClaimInFlight) return;\n if (!configuration.content.watch) return;\n if (options.getShouldSkip?.()) return;\n\n // Spawned by `intlayer watch --with`: the CLI watcher covers the project, and\n // this is known from the environment alone — no need to touch the lock.\n const cliWatcherLabel = getCliContentWatcherLabel();\n\n if (cliWatcherLabel) {\n reportRedundantContentWatcher(configuration, {\n cliLabel: cliWatcherLabel,\n bundlerLabel: options.label,\n });\n return;\n }\n\n state.isClaimInFlight = true;\n\n claimAndWatch(configuration, options).finally(() => {\n state.isClaimInFlight = false;\n });\n};\n"],"mappings":";;;;;;;;;AAcA,MAAM,4BAA4B;;;;;;;AAgClC,MAAM,+BAAoD;CACxD,MAAM,WAAW,OAAO,IAAI,yBAAyB;CACrD,MAAM,cAAc;CAIpB,YAAY,cAAc;EAAE,YAAY;EAAO,iBAAiB;CAAM;CAEtE,OAAO,YAAY;AACrB;AAEA,MAAM,gBAAgB,OACpB,eACA,YACkB;CAClB,MAAM,QAAQ,uBAAuB;CAOrC,IAAI,CAAC,MALyB,0BAA0B,eAAe;EACrE,QAAQ;EACR,OAAO,QAAQ;CACjB,CAAC,GAEqB;EACpB,MAAM,QAAQ,MAAM,uBAAuB,aAAa;EAIxD,IAAI,OAAO,WAAW,OAAO;GAC3B,8BAA8B,eAAe;IAC3C,UAAU,MAAM;IAChB,cAAc,QAAQ;GACxB,CAAC;GACD;EACF;EAUA,AANmB,iBAAiB;GAClC,oBAAoB,eAAe,OAAO;EAC5C,GAAG,yBAIM,CAAC,CAAC,QAAQ;EAEnB;CACF;CAEA,MAAM,aAAa;CAEnB,IAAI;EAGF,MAAM,EAAE,UAAU,MAAM,OAAO;EAE/B,MAAM,MAAM,EAAE,cAAc,CAAC;CAC/B,SAAS,OAAO;EACd,MAAM,aAAa;EAEnB,aAAa,aAAa,CAAC,CACzB,CAAC,kDAAkD,KAAK,GACxD,EAAE,OAAO,QAAQ,CACnB;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,uBACX,eACA,YACS;CACT,MAAM,QAAQ,uBAAuB;CAErC,IAAI,MAAM,cAAc,MAAM,iBAAiB;CAC/C,IAAI,CAAC,cAAc,QAAQ,OAAO;CAClC,IAAI,QAAQ,gBAAgB,GAAG;CAI/B,MAAM,kBAAkB,0BAA0B;CAElD,IAAI,iBAAiB;EACnB,8BAA8B,eAAe;GAC3C,UAAU;GACV,cAAc,QAAQ;EACxB,CAAC;EACD;CACF;CAEA,MAAM,kBAAkB;CAExB,cAAc,eAAe,OAAO,CAAC,CAAC,cAAc;EAClD,MAAM,kBAAkB;CAC1B,CAAC;AACH"}
@@ -2,7 +2,6 @@ import { formatPath } from "./utils/formatter.mjs";
2
2
  import { getFormatFromExtension } from "./utils/getFormatFromExtension.mjs";
3
3
  import { parseContentDeclarationFileName } from "./utils/parseContentDeclarationFileName.mjs";
4
4
  import { prepareIntlayer } from "./prepareIntlayer.mjs";
5
- import { writeContentDeclaration } from "./writeContentDeclaration/writeContentDeclaration.mjs";
6
5
  import { handleContentDeclarationFileChange } from "./handleContentDeclarationFileChange.mjs";
7
6
  import { handleAdditionalContentDeclarationFile } from "./handleAdditionalContentDeclarationFile.mjs";
8
7
  import { handleContentDeclarationFileMoved } from "./handleContentDeclarationFileMoved.mjs";
@@ -171,6 +170,7 @@ const watch = async (options) => {
171
170
  const { key, locale } = parseContentDeclarationFileName(filePath, configuration);
172
171
  const isMarkdown = getFormatFromExtension(extname(filePath)) === "md";
173
172
  const declaredLocale = locale ?? (isMarkdown ? configuration.internationalization.defaultLocale : void 0);
173
+ const { writeContentDeclaration } = await import("./writeContentDeclaration/index.mjs");
174
174
  await writeContentDeclaration({
175
175
  key,
176
176
  ...declaredLocale && { locale: declaredLocale },
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.mjs","names":["fsWatch"],"sources":["../../src/watcher.ts"],"sourcesContent":["import { existsSync, watch as fsWatch } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname, extname, resolve } from 'node:path';\nimport * as ANSIColor from '@intlayer/config/colors';\nimport { transpileTSToCJS } from '@intlayer/config/file';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n getConfigurationAndFilePath,\n} from '@intlayer/config/node';\nimport {\n clearAllCache,\n clearDiskCacheMemory,\n clearModuleCache,\n normalizePath,\n} from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { handleAdditionalContentDeclarationFile } from './handleAdditionalContentDeclarationFile';\nimport { handleContentDeclarationFileChange } from './handleContentDeclarationFileChange';\nimport { handleContentDeclarationFileMoved } from './handleContentDeclarationFileMoved';\nimport { handleUnlinkedContentDeclarationFile } from './handleUnlinkedContentDeclarationFile';\nimport { prepareIntlayer } from './prepareIntlayer';\nimport {\n formatPath,\n getFormatFromExtension,\n parseContentDeclarationFileName,\n} from './utils';\nimport { writeContentDeclaration } from './writeContentDeclaration';\n\n// Map to track files that were recently unlinked: oldPath -> { timer, timestamp }\nconst pendingUnlinks = new Map<\n string,\n { timer: NodeJS.Timeout; oldPath: string }\n>();\n\n// Array-based sequential task queue — no Promise chain accumulation, no race conditions\nconst taskQueue: (() => Promise<void>)[] = [];\nlet isProcessing = false;\n\nconst processQueue = async () => {\n if (isProcessing) return;\n isProcessing = true;\n while (taskQueue.length > 0) {\n const task = taskQueue.shift()!;\n try {\n await task();\n } catch (error) {\n console.error(error);\n }\n }\n isProcessing = false;\n};\n\nconst processEvent = (task: () => Promise<void>) => {\n taskQueue.push(task);\n processQueue();\n};\n\ntype WatchOptions = {\n configuration?: IntlayerConfig;\n configOptions?: GetConfigurationOptions;\n skipPrepare?: boolean;\n persistent?: boolean;\n};\n\n// awaitWriteFinish equivalent: debounce per path until the file is stable\nconst STABILITY_THRESHOLD = 1000;\n\nconst createStabilityDebounce = () => {\n const pending = new Map<string, NodeJS.Timeout>();\n return (path: string, handler: () => void) => {\n const existing = pending.get(path);\n if (existing) clearTimeout(existing);\n pending.set(\n path,\n setTimeout(() => {\n pending.delete(path);\n handler();\n }, STABILITY_THRESHOLD)\n );\n };\n};\n\n// Initialize @parcel/watcher (non-persistent until subscribed)\nexport const watch = async (options?: WatchOptions) => {\n const { subscribe } = await import('@parcel/watcher');\n\n const configResult = getConfigurationAndFilePath(options?.configOptions);\n const configurationFilePath = configResult.configurationFilePath;\n let configuration: IntlayerConfig =\n options?.configuration ?? configResult.configuration;\n const appLogger = getAppLogger(configuration);\n\n const {\n watch: isWatchMode,\n fileExtensions,\n contentDir,\n excludedPath,\n } = configuration.content;\n\n if (!configuration.content.watch) return;\n\n appLogger('Watching Intlayer content declarations');\n\n if (configuration.build.optimize === true) {\n appLogger(\n [\n `Build optimization is forced to ${colorize('true', ANSIColor.GREY)}, but watching is enabled too.`,\n 'It may lead to dev mode performance degradation as well as import errors.',\n 'Its recommended to keep the',\n colorize('`build.optimized`', ANSIColor.BLUE),\n 'option',\n colorize('undefined', ANSIColor.GREY),\n 'to get the best dev mode experience',\n ],\n {\n level: 'warn',\n }\n );\n }\n\n // Strip glob markers from excludedPath entries to get plain segments (e.g. 'node_modules')\n const excludedSegments = excludedPath.map((segment) =>\n segment.replace(/^\\*\\*\\//, '').replace(/\\/\\*\\*$/, '')\n );\n\n const normalizedConfigPath = configurationFilePath\n ? normalizePath(configurationFilePath)\n : null;\n\n const { mainDir, baseDir } = configuration.system;\n const normalizedMainDir = normalizePath(mainDir);\n const normalizedIntlayerDir = normalizePath(dirname(mainDir));\n\n const subscriptions: { unsubscribe: () => Promise<void> }[] = [];\n\n const scheduleStable = createStabilityDebounce();\n\n // ── mainDir watcher (depth 0) ──────────────────────────────────────────────\n // Detects broken or missing entry-point files inside .intlayer/main\n if (existsSync(mainDir)) {\n const mainDirSub = await subscribe(normalizedMainDir, (err, events) => {\n if (err || isProcessing) return;\n\n for (const event of events) {\n const eventPath = normalizePath(event.path);\n // depth-0 filter: only files directly inside mainDir\n const rel = eventPath.slice(normalizedMainDir.length + 1);\n if (!rel || rel.includes('/')) continue;\n // Temp files written by the bundler (write-then-rename) are build-internal;\n // their deletion must not trigger a clean rebuild.\n if (rel.endsWith('.tmp')) continue;\n\n if (event.type === 'update') {\n processEvent(async () => {\n try {\n // Validate that the regenerated entry point — and the relative\n // dictionary graph it imports — still resolves, by bundling it\n // in-memory with esbuild (via the shared, freshness-checked\n // transpile cache). This replaces a cache-busting dynamic\n // `import()`, which leaked a permanent record into Node's ESM\n // registry on every rebuild since that registry is never evicted.\n const entryCode = await readFile(event.path, 'utf-8');\n await transpileTSToCJS(entryCode, event.path);\n } catch {\n appLogger(\n `Entry point ${basename(event.path)} failed to load, running clean rebuild...`,\n { level: 'warn' }\n );\n await prepareIntlayer(configuration, {\n clean: true,\n forceRun: true,\n });\n }\n });\n } else if (event.type === 'delete') {\n processEvent(async () => {\n appLogger(\n [\n 'Entry point',\n formatPath(basename(event.path)),\n 'was removed, running clean rebuild...',\n ],\n { level: 'warn' }\n );\n await prepareIntlayer(configuration, {\n clean: true,\n forceRun: true,\n });\n });\n }\n }\n });\n subscriptions.push(mainDirSub);\n }\n\n // ── baseDir watcher (depth 0) — detect .intlayer directory removal ─────────\n // Native fs.watch is non-recursive and ideal for single-directory depth-0 detection.\n const intlayerDirName = basename(normalizedIntlayerDir);\n const fsDirWatcher = fsWatch(\n baseDir,\n { persistent: isWatchMode },\n (eventType, filename) => {\n if (isProcessing || !filename) return;\n if (filename !== intlayerDirName) return;\n\n const fullPath = normalizePath(resolve(baseDir, filename));\n if (fullPath !== normalizedIntlayerDir) return;\n\n if (eventType === 'rename' && !existsSync(normalizedIntlayerDir)) {\n appLogger([\n formatPath('.intlayer'),\n 'directory removed, running clean rebuild...',\n ]);\n processEvent(() =>\n prepareIntlayer(configuration, { clean: true, forceRun: true })\n );\n }\n }\n );\n subscriptions.push({\n unsubscribe: async () => {\n fsDirWatcher.close();\n },\n });\n\n // ── main content watcher ───────────────────────────────────────────────────\n // Ignore patterns for @parcel/watcher (micromatch globs)\n const ignorePatterns = excludedSegments.map((s) => `**/${s}/**`);\n\n // Strip trailing slashes so directory matching stays exact. A user-supplied\n const contentDirs = contentDir\n .map((dir) => normalizePath(dir).replace(/\\/+$/, ''))\n .filter(existsSync);\n\n // Collect unique directories to subscribe to (dirs only, not file paths)\n const dirsToWatch = new Set<string>(contentDirs);\n if (normalizedConfigPath) {\n dirsToWatch.add(normalizePath(dirname(normalizedConfigPath)));\n }\n\n const contentHandler = (\n err: Error | null,\n events: Array<{ type: string; path: string }>\n ) => {\n if (err) {\n appLogger(`Watcher error: ${err}`, { level: 'error' });\n appLogger('Restarting watcher');\n prepareIntlayer(configuration);\n return;\n }\n\n for (const event of events) {\n const filePath = event.path;\n const path = normalizePath(filePath);\n\n const isConfigFile =\n normalizedConfigPath && path === normalizedConfigPath;\n\n if (!isConfigFile) {\n // Must originate from a watched content directory\n const isInContentDir = contentDirs.some(\n (d) => path.startsWith(`${d}/`) || path === d\n );\n if (!isInContentDir) continue;\n\n if (excludedSegments.some((segment) => path.includes(`/${segment}`)))\n continue;\n\n if (!fileExtensions.some((extension) => path.endsWith(extension)))\n continue;\n }\n\n if (event.type === 'create') {\n const fileName = basename(filePath);\n\n // Move detection must happen synchronously before any debounce\n let isMove = false;\n let matchedOldPath: string | undefined;\n\n for (const [oldPath] of pendingUnlinks) {\n if (basename(oldPath) === fileName) {\n matchedOldPath = oldPath;\n break;\n }\n }\n\n if (!matchedOldPath && pendingUnlinks.size === 1) {\n matchedOldPath = pendingUnlinks.keys().next().value;\n }\n\n if (matchedOldPath) {\n const pending = pendingUnlinks.get(matchedOldPath);\n if (pending) {\n clearTimeout(pending.timer);\n pendingUnlinks.delete(matchedOldPath);\n }\n isMove = true;\n appLogger(`File moved from ${matchedOldPath} to ${filePath}`);\n }\n\n if (isMove && matchedOldPath) {\n processEvent(async () => {\n await handleContentDeclarationFileMoved(\n matchedOldPath!,\n filePath,\n configuration\n );\n });\n } else {\n // Debounce: wait for write to finish before reading the file\n scheduleStable(path, () => {\n processEvent(async () => {\n const fileContent = await readFile(filePath, 'utf-8');\n const isEmpty = fileContent === '';\n\n if (isEmpty) {\n const { key, locale } = parseContentDeclarationFileName(\n filePath,\n configuration\n );\n\n // A markdown declaration carries a single locale body, so it\n // must always state which locale it holds.\n const isMarkdown =\n getFormatFromExtension(extname(filePath)) === 'md';\n const declaredLocale =\n locale ??\n (isMarkdown\n ? configuration.internationalization.defaultLocale\n : undefined);\n\n await writeContentDeclaration(\n {\n key,\n ...(declaredLocale && { locale: declaredLocale }),\n content: {},\n filePath,\n },\n configuration\n );\n }\n\n await handleAdditionalContentDeclarationFile(\n filePath,\n configuration\n );\n });\n });\n }\n } else if (event.type === 'update') {\n scheduleStable(path, () => {\n processEvent(async () => {\n if (isConfigFile) {\n appLogger('Configuration file changed, repreparing Intlayer');\n\n clearModuleCache(filePath);\n clearAllCache();\n\n const { configuration: newConfiguration } =\n getConfigurationAndFilePath(options?.configOptions);\n\n configuration = options?.configuration ?? newConfiguration;\n\n await prepareIntlayer(configuration, { clean: false });\n } else {\n // Clear module cache for the changed file to avoid stale require() results\n clearModuleCache(filePath);\n // Evict in-memory caches so loadContentDeclaration picks up fresh content\n clearAllCache();\n clearDiskCacheMemory();\n await handleContentDeclarationFileChange(filePath, configuration);\n }\n });\n });\n } else if (event.type === 'delete') {\n // Delay unlink processing to see if an 'add' event occurs (indicating a move)\n const timer = setTimeout(async () => {\n // If timer fires, the file was genuinely removed\n pendingUnlinks.delete(filePath);\n processEvent(async () =>\n handleUnlinkedContentDeclarationFile(filePath, configuration)\n );\n }, 200); // 200ms window to catch the 'create' event\n\n pendingUnlinks.set(filePath, { timer, oldPath: filePath });\n }\n }\n };\n\n for (const dir of dirsToWatch) {\n const sub = await subscribe(dir, contentHandler, {\n ignore: ignorePatterns,\n });\n\n subscriptions.push(sub);\n }\n\n return subscriptions;\n};\n\nexport const buildAndWatchIntlayer = async (options?: WatchOptions) => {\n const { skipPrepare, ...rest } = options ?? {};\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n\n if (!skipPrepare) {\n await prepareIntlayer(configuration, { forceRun: true });\n }\n\n // Only enter watch mode when the caller explicitly opts in via `persistent`.\n // `configuration.content.watch` is the dev-mode signal consumed by bundler\n // plugins (e.g. vite-intlayer's `configureServer`); it must not coerce\n // `intlayer build` (which passes `persistent: false`) into a persistent\n // watcher, since that prevents the build command from ever exiting.\n if (options?.persistent) {\n await watch({ ...rest, configuration });\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"watcher.mjs","names":["fsWatch"],"sources":["../../src/watcher.ts"],"sourcesContent":["import { existsSync, watch as fsWatch } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname, extname, resolve } from 'node:path';\nimport * as ANSIColor from '@intlayer/config/colors';\nimport { transpileTSToCJS } from '@intlayer/config/file';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n getConfigurationAndFilePath,\n} from '@intlayer/config/node';\nimport {\n clearAllCache,\n clearDiskCacheMemory,\n clearModuleCache,\n normalizePath,\n} from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { handleAdditionalContentDeclarationFile } from './handleAdditionalContentDeclarationFile';\nimport { handleContentDeclarationFileChange } from './handleContentDeclarationFileChange';\nimport { handleContentDeclarationFileMoved } from './handleContentDeclarationFileMoved';\nimport { handleUnlinkedContentDeclarationFile } from './handleUnlinkedContentDeclarationFile';\nimport { prepareIntlayer } from './prepareIntlayer';\nimport {\n formatPath,\n getFormatFromExtension,\n parseContentDeclarationFileName,\n} from './utils';\n\n// Map to track files that were recently unlinked: oldPath -> { timer, timestamp }\nconst pendingUnlinks = new Map<\n string,\n { timer: NodeJS.Timeout; oldPath: string }\n>();\n\n// Array-based sequential task queue — no Promise chain accumulation, no race conditions\nconst taskQueue: (() => Promise<void>)[] = [];\nlet isProcessing = false;\n\nconst processQueue = async () => {\n if (isProcessing) return;\n isProcessing = true;\n while (taskQueue.length > 0) {\n const task = taskQueue.shift()!;\n try {\n await task();\n } catch (error) {\n console.error(error);\n }\n }\n isProcessing = false;\n};\n\nconst processEvent = (task: () => Promise<void>) => {\n taskQueue.push(task);\n processQueue();\n};\n\ntype WatchOptions = {\n configuration?: IntlayerConfig;\n configOptions?: GetConfigurationOptions;\n skipPrepare?: boolean;\n persistent?: boolean;\n};\n\n// awaitWriteFinish equivalent: debounce per path until the file is stable\nconst STABILITY_THRESHOLD = 1000;\n\nconst createStabilityDebounce = () => {\n const pending = new Map<string, NodeJS.Timeout>();\n return (path: string, handler: () => void) => {\n const existing = pending.get(path);\n if (existing) clearTimeout(existing);\n pending.set(\n path,\n setTimeout(() => {\n pending.delete(path);\n handler();\n }, STABILITY_THRESHOLD)\n );\n };\n};\n\n// Initialize @parcel/watcher (non-persistent until subscribed)\nexport const watch = async (options?: WatchOptions) => {\n const { subscribe } = await import('@parcel/watcher');\n\n const configResult = getConfigurationAndFilePath(options?.configOptions);\n const configurationFilePath = configResult.configurationFilePath;\n let configuration: IntlayerConfig =\n options?.configuration ?? configResult.configuration;\n const appLogger = getAppLogger(configuration);\n\n const {\n watch: isWatchMode,\n fileExtensions,\n contentDir,\n excludedPath,\n } = configuration.content;\n\n if (!configuration.content.watch) return;\n\n appLogger('Watching Intlayer content declarations');\n\n if (configuration.build.optimize === true) {\n appLogger(\n [\n `Build optimization is forced to ${colorize('true', ANSIColor.GREY)}, but watching is enabled too.`,\n 'It may lead to dev mode performance degradation as well as import errors.',\n 'Its recommended to keep the',\n colorize('`build.optimized`', ANSIColor.BLUE),\n 'option',\n colorize('undefined', ANSIColor.GREY),\n 'to get the best dev mode experience',\n ],\n {\n level: 'warn',\n }\n );\n }\n\n // Strip glob markers from excludedPath entries to get plain segments (e.g. 'node_modules')\n const excludedSegments = excludedPath.map((segment) =>\n segment.replace(/^\\*\\*\\//, '').replace(/\\/\\*\\*$/, '')\n );\n\n const normalizedConfigPath = configurationFilePath\n ? normalizePath(configurationFilePath)\n : null;\n\n const { mainDir, baseDir } = configuration.system;\n const normalizedMainDir = normalizePath(mainDir);\n const normalizedIntlayerDir = normalizePath(dirname(mainDir));\n\n const subscriptions: { unsubscribe: () => Promise<void> }[] = [];\n\n const scheduleStable = createStabilityDebounce();\n\n // ── mainDir watcher (depth 0) ──────────────────────────────────────────────\n // Detects broken or missing entry-point files inside .intlayer/main\n if (existsSync(mainDir)) {\n const mainDirSub = await subscribe(normalizedMainDir, (err, events) => {\n if (err || isProcessing) return;\n\n for (const event of events) {\n const eventPath = normalizePath(event.path);\n // depth-0 filter: only files directly inside mainDir\n const rel = eventPath.slice(normalizedMainDir.length + 1);\n if (!rel || rel.includes('/')) continue;\n // Temp files written by the bundler (write-then-rename) are build-internal;\n // their deletion must not trigger a clean rebuild.\n if (rel.endsWith('.tmp')) continue;\n\n if (event.type === 'update') {\n processEvent(async () => {\n try {\n // Validate that the regenerated entry point — and the relative\n // dictionary graph it imports — still resolves, by bundling it\n // in-memory with esbuild (via the shared, freshness-checked\n // transpile cache). This replaces a cache-busting dynamic\n // `import()`, which leaked a permanent record into Node's ESM\n // registry on every rebuild since that registry is never evicted.\n const entryCode = await readFile(event.path, 'utf-8');\n await transpileTSToCJS(entryCode, event.path);\n } catch {\n appLogger(\n `Entry point ${basename(event.path)} failed to load, running clean rebuild...`,\n { level: 'warn' }\n );\n await prepareIntlayer(configuration, {\n clean: true,\n forceRun: true,\n });\n }\n });\n } else if (event.type === 'delete') {\n processEvent(async () => {\n appLogger(\n [\n 'Entry point',\n formatPath(basename(event.path)),\n 'was removed, running clean rebuild...',\n ],\n { level: 'warn' }\n );\n await prepareIntlayer(configuration, {\n clean: true,\n forceRun: true,\n });\n });\n }\n }\n });\n subscriptions.push(mainDirSub);\n }\n\n // ── baseDir watcher (depth 0) — detect .intlayer directory removal ─────────\n // Native fs.watch is non-recursive and ideal for single-directory depth-0 detection.\n const intlayerDirName = basename(normalizedIntlayerDir);\n const fsDirWatcher = fsWatch(\n baseDir,\n { persistent: isWatchMode },\n (eventType, filename) => {\n if (isProcessing || !filename) return;\n if (filename !== intlayerDirName) return;\n\n const fullPath = normalizePath(resolve(baseDir, filename));\n if (fullPath !== normalizedIntlayerDir) return;\n\n if (eventType === 'rename' && !existsSync(normalizedIntlayerDir)) {\n appLogger([\n formatPath('.intlayer'),\n 'directory removed, running clean rebuild...',\n ]);\n processEvent(() =>\n prepareIntlayer(configuration, { clean: true, forceRun: true })\n );\n }\n }\n );\n subscriptions.push({\n unsubscribe: async () => {\n fsDirWatcher.close();\n },\n });\n\n // ── main content watcher ───────────────────────────────────────────────────\n // Ignore patterns for @parcel/watcher (micromatch globs)\n const ignorePatterns = excludedSegments.map((s) => `**/${s}/**`);\n\n // Strip trailing slashes so directory matching stays exact. A user-supplied\n const contentDirs = contentDir\n .map((dir) => normalizePath(dir).replace(/\\/+$/, ''))\n .filter(existsSync);\n\n // Collect unique directories to subscribe to (dirs only, not file paths)\n const dirsToWatch = new Set<string>(contentDirs);\n if (normalizedConfigPath) {\n dirsToWatch.add(normalizePath(dirname(normalizedConfigPath)));\n }\n\n const contentHandler = (\n err: Error | null,\n events: Array<{ type: string; path: string }>\n ) => {\n if (err) {\n appLogger(`Watcher error: ${err}`, { level: 'error' });\n appLogger('Restarting watcher');\n prepareIntlayer(configuration);\n return;\n }\n\n for (const event of events) {\n const filePath = event.path;\n const path = normalizePath(filePath);\n\n const isConfigFile =\n normalizedConfigPath && path === normalizedConfigPath;\n\n if (!isConfigFile) {\n // Must originate from a watched content directory\n const isInContentDir = contentDirs.some(\n (d) => path.startsWith(`${d}/`) || path === d\n );\n if (!isInContentDir) continue;\n\n if (excludedSegments.some((segment) => path.includes(`/${segment}`)))\n continue;\n\n if (!fileExtensions.some((extension) => path.endsWith(extension)))\n continue;\n }\n\n if (event.type === 'create') {\n const fileName = basename(filePath);\n\n // Move detection must happen synchronously before any debounce\n let isMove = false;\n let matchedOldPath: string | undefined;\n\n for (const [oldPath] of pendingUnlinks) {\n if (basename(oldPath) === fileName) {\n matchedOldPath = oldPath;\n break;\n }\n }\n\n if (!matchedOldPath && pendingUnlinks.size === 1) {\n matchedOldPath = pendingUnlinks.keys().next().value;\n }\n\n if (matchedOldPath) {\n const pending = pendingUnlinks.get(matchedOldPath);\n if (pending) {\n clearTimeout(pending.timer);\n pendingUnlinks.delete(matchedOldPath);\n }\n isMove = true;\n appLogger(`File moved from ${matchedOldPath} to ${filePath}`);\n }\n\n if (isMove && matchedOldPath) {\n processEvent(async () => {\n await handleContentDeclarationFileMoved(\n matchedOldPath!,\n filePath,\n configuration\n );\n });\n } else {\n // Debounce: wait for write to finish before reading the file\n scheduleStable(path, () => {\n processEvent(async () => {\n const fileContent = await readFile(filePath, 'utf-8');\n const isEmpty = fileContent === '';\n\n if (isEmpty) {\n const { key, locale } = parseContentDeclarationFileName(\n filePath,\n configuration\n );\n\n // A markdown declaration carries a single locale body, so it\n // must always state which locale it holds.\n const isMarkdown =\n getFormatFromExtension(extname(filePath)) === 'md';\n const declaredLocale =\n locale ??\n (isMarkdown\n ? configuration.internationalization.defaultLocale\n : undefined);\n\n // Lazy: pulls recast/ast-types, only needed for new empty files\n const { writeContentDeclaration } = await import(\n './writeContentDeclaration'\n );\n\n await writeContentDeclaration(\n {\n key,\n ...(declaredLocale && { locale: declaredLocale }),\n content: {},\n filePath,\n },\n configuration\n );\n }\n\n await handleAdditionalContentDeclarationFile(\n filePath,\n configuration\n );\n });\n });\n }\n } else if (event.type === 'update') {\n scheduleStable(path, () => {\n processEvent(async () => {\n if (isConfigFile) {\n appLogger('Configuration file changed, repreparing Intlayer');\n\n clearModuleCache(filePath);\n clearAllCache();\n\n const { configuration: newConfiguration } =\n getConfigurationAndFilePath(options?.configOptions);\n\n configuration = options?.configuration ?? newConfiguration;\n\n await prepareIntlayer(configuration, { clean: false });\n } else {\n // Clear module cache for the changed file to avoid stale require() results\n clearModuleCache(filePath);\n // Evict in-memory caches so loadContentDeclaration picks up fresh content\n clearAllCache();\n clearDiskCacheMemory();\n await handleContentDeclarationFileChange(filePath, configuration);\n }\n });\n });\n } else if (event.type === 'delete') {\n // Delay unlink processing to see if an 'add' event occurs (indicating a move)\n const timer = setTimeout(async () => {\n // If timer fires, the file was genuinely removed\n pendingUnlinks.delete(filePath);\n processEvent(async () =>\n handleUnlinkedContentDeclarationFile(filePath, configuration)\n );\n }, 200); // 200ms window to catch the 'create' event\n\n pendingUnlinks.set(filePath, { timer, oldPath: filePath });\n }\n }\n };\n\n for (const dir of dirsToWatch) {\n const sub = await subscribe(dir, contentHandler, {\n ignore: ignorePatterns,\n });\n\n subscriptions.push(sub);\n }\n\n return subscriptions;\n};\n\nexport const buildAndWatchIntlayer = async (options?: WatchOptions) => {\n const { skipPrepare, ...rest } = options ?? {};\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n\n if (!skipPrepare) {\n await prepareIntlayer(configuration, { forceRun: true });\n }\n\n // Only enter watch mode when the caller explicitly opts in via `persistent`.\n // `configuration.content.watch` is the dev-mode signal consumed by bundler\n // plugins (e.g. vite-intlayer's `configureServer`); it must not coerce\n // `intlayer build` (which passes `persistent: false`) into a persistent\n // watcher, since that prevents the build command from ever exiting.\n if (options?.persistent) {\n await watch({ ...rest, configuration });\n }\n};\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { writeFileIfChanged } from "./writeFileIfChanged.mjs";
2
2
 
3
3
  //#region src/writeJsonIfChanged.ts
4
- const writeJsonIfChanged = async (path, data, { pretty = true } = {}) => {
4
+ const writeJsonIfChanged = async (path, data, { pretty = process.env.NODE_ENV === "development" } = {}) => {
5
5
  const space = pretty ? 2 : void 0;
6
6
  let json = "{}";
7
7
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"writeJsonIfChanged.mjs","names":[],"sources":["../../src/writeJsonIfChanged.ts"],"sourcesContent":["import { writeFileIfChanged } from './writeFileIfChanged';\n\nexport const writeJsonIfChanged = async <T>(\n path: string,\n data: T,\n { pretty = process.env.NODE_ENV === 'development' } = {}\n): Promise<boolean> => {\n const space = pretty ? 2 : undefined;\n\n let json = '{}';\n\n try {\n json = JSON.stringify(data, null, space);\n } catch (error) {\n console.error(`Error while parsing data to JSON for ${path}:`, error);\n }\n\n return await writeFileIfChanged(path, json);\n};\n"],"mappings":";;;AAEA,MAAa,qBAAqB,OAChC,MACA,MACA,EAAE,SAAS,SAA2C,CAAC,MAClC;CACrB,MAAM,QAAQ,SAAS,IAAI;CAE3B,IAAI,OAAO;CAEX,IAAI;EACF,OAAO,KAAK,UAAU,MAAM,MAAM,KAAK;CACzC,SAAS,OAAO;EACd,QAAQ,MAAM,wCAAwC,KAAK,IAAI,KAAK;CACtE;CAEA,OAAO,MAAM,mBAAmB,MAAM,IAAI;AAC5C"}
1
+ {"version":3,"file":"writeJsonIfChanged.mjs","names":[],"sources":["../../src/writeJsonIfChanged.ts"],"sourcesContent":["import { writeFileIfChanged } from './writeFileIfChanged';\n\nexport const writeJsonIfChanged = async <T>(\n path: string,\n data: T,\n { pretty = process.env.NODE_ENV === 'development' } = {}\n): Promise<boolean> => {\n const space = pretty ? 2 : undefined;\n\n let json = '{}';\n\n try {\n json = JSON.stringify(data, null, space);\n } catch (error) {\n console.error(`Error while parsing data to JSON for ${path}:`, error);\n }\n\n return await writeFileIfChanged(path, json);\n};\n"],"mappings":";;;AAEA,MAAa,qBAAqB,OAChC,MACA,MACA,EAAE,kCAAkC,kBAAkB,CAAC,MAClC;CACrB,MAAM,QAAQ,SAAS,IAAI;CAE3B,IAAI,OAAO;CAEX,IAAI;EACF,OAAO,KAAK,UAAU,MAAM,MAAM,KAAK;CACzC,SAAS,OAAO;EACd,QAAQ,MAAM,wCAAwC,KAAK,IAAI,KAAK;CACtE;CAEA,OAAO,MAAM,mBAAmB,MAAM,IAAI;AAC5C"}
@@ -20,6 +20,7 @@ import { loadLocalDictionaries } from "./loadDictionaries/loadLocalDictionaries.
20
20
  import { formatDistantDictionaries, loadRemoteDictionaries } from "./loadDictionaries/loadRemoteDictionaries.js";
21
21
  import "./loadDictionaries/index.js";
22
22
  import { prepareIntlayer } from "./prepareIntlayer.js";
23
+ import { INTLAYER_WATCH_ENV_VAR, PrepareIntlayerServerOptions, getIsDevWatcherCommand, getIsServerDevelopment, prepareIntlayerServer } from "./prepareIntlayerServer.js";
23
24
  import { readDictionariesFromDisk } from "./utils/readDictionariesFromDisk.js";
24
25
  import { isCachedConfigurationUpToDate, writeConfiguration } from "./writeConfiguration/index.js";
25
26
  import { detectExportedComponentName } from "./writeContentDeclaration/detectExportedComponentName.js";
@@ -28,4 +29,4 @@ import { transformJSFile } from "./writeContentDeclaration/transformJSFile.js";
28
29
  import { writeContentDeclaration } from "./writeContentDeclaration/writeContentDeclaration.js";
29
30
  import { writeJSFile } from "./writeContentDeclaration/writeJSFile.js";
30
31
  import "./writeContentDeclaration/index.js";
31
- export { BuildDictionariesOptions, CreateDictionaryEntryPointOptions, DictionariesStatus, DictionaryStatus, buildDictionary, cleanOutputDir, createDictionaryEntryPoint, createModuleAugmentation, createTypes, detectExportedComponentName, ensureIntlayerBundle, formatDictionaries, formatDictionariesOutput, formatDictionary, formatDictionaryOutput, formatDistantDictionaries, formatDomains, formatLocalDictionaries, formatRewriteRules, generateDictionaryListContent, generateTypeScriptType, getBuiltDictionariesPath, getBuiltDynamicDictionariesPath, getBuiltFetchDictionariesPath, getBuiltRemoteDictionariesPath, getBuiltUnmergedDictionariesPath, getTypeName, isCachedConfigurationUpToDate, loadContentDeclaration, loadContentDeclarations, loadDictionaries, loadLocalDictionaries, loadRemoteDictionaries, prepareIntlayer, processContentDeclaration, readDictionariesFromDisk, transformJSFile, writeConfiguration, writeContentDeclaration, writeJSFile };
32
+ export { BuildDictionariesOptions, CreateDictionaryEntryPointOptions, DictionariesStatus, DictionaryStatus, INTLAYER_WATCH_ENV_VAR, PrepareIntlayerServerOptions, buildDictionary, cleanOutputDir, createDictionaryEntryPoint, createModuleAugmentation, createTypes, detectExportedComponentName, ensureIntlayerBundle, formatDictionaries, formatDictionariesOutput, formatDictionary, formatDictionaryOutput, formatDistantDictionaries, formatDomains, formatLocalDictionaries, formatRewriteRules, generateDictionaryListContent, generateTypeScriptType, getBuiltDictionariesPath, getBuiltDynamicDictionariesPath, getBuiltFetchDictionariesPath, getBuiltRemoteDictionariesPath, getBuiltUnmergedDictionariesPath, getIsDevWatcherCommand, getIsServerDevelopment, getTypeName, isCachedConfigurationUpToDate, loadContentDeclaration, loadContentDeclarations, loadDictionaries, loadLocalDictionaries, loadRemoteDictionaries, prepareIntlayer, prepareIntlayerServer, processContentDeclaration, readDictionariesFromDisk, transformJSFile, writeConfiguration, writeContentDeclaration, writeJSFile };