@nuasite/cms 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/utils.ts CHANGED
@@ -133,6 +133,19 @@ export function escapeReplacement(str: string): string {
133
133
  return str.replace(/\$/g, '$$$$')
134
134
  }
135
135
 
136
+ // ============================================================================
137
+ // Path Resolution
138
+ // ============================================================================
139
+
140
+ /**
141
+ * Resolve a source path to an absolute filesystem path.
142
+ * If the path is already absolute it is returned as-is; otherwise it is
143
+ * joined with the project root directory.
144
+ */
145
+ export function resolveSourcePath(sourcePath: string): string {
146
+ return path.isAbsolute(sourcePath) ? sourcePath : path.join(getProjectRoot(), sourcePath)
147
+ }
148
+
136
149
  // ============================================================================
137
150
  // Path Validation
138
151
  // ============================================================================
@@ -1,6 +1,7 @@
1
1
  import { watch } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import type { Plugin } from 'vite'
4
+ import { invalidateContentCache, notifyContentStoreUpdated, type ViteServerLike } from './content-invalidator'
4
5
  import { expectedDeletions } from './dev-middleware'
5
6
  import type { ManifestWriter } from './manifest-writer'
6
7
  import { markFileDirty } from './source-finder'
@@ -85,6 +86,11 @@ export function createVitePlugin(context: VitePluginContext): Plugin[] {
85
86
  // Without this, content collection edits update the data store on disk but the
86
87
  // browser never receives a full-reload because Vite's watcher never fires "change"
87
88
  // for that file. We use native fs.watch as a reliable fallback.
89
+ //
90
+ // Caveat: native fs.watch on Linux tracks the inode, not the path. Astro writes
91
+ // data-store.json via atomic rename (writeFile-tmp + rename), which replaces the
92
+ // inode and silently kills the existing watcher. We re-attach on every event to
93
+ // keep tracking the live file across atomic writes.
88
94
  const dataStoreWatchPlugin: Plugin = {
89
95
  name: 'cms-data-store-watch',
90
96
  configureServer(server) {
@@ -93,25 +99,32 @@ export function createVitePlugin(context: VitePluginContext): Plugin[] {
93
99
  const dataStorePath = join(root, '.astro', 'data-store.json')
94
100
  let fsWatcher: ReturnType<typeof watch> | undefined
95
101
  let debounce: ReturnType<typeof setTimeout> | undefined
102
+ let closed = false
96
103
 
97
104
  const invalidate = () => {
98
- // Replicate Astro's invalidateDataStore which never fires because
99
- // Vite's bundled chokidar 3.6.0 misses atomic-write changes.
100
- const ssr = server.environments.ssr
101
- const mod = ssr.moduleGraph.getModuleById('\0astro:data-store')
102
- if (mod) {
103
- ssr.moduleGraph.invalidateModule(mod, undefined, Date.now(), true)
104
- }
105
- ssr.hot.send('astro:content-changed', {})
106
- server.environments.client.hot.send({ type: 'full-reload', path: '*' })
105
+ invalidateContentCache(server as unknown as ViteServerLike)
106
+ // Wake any CMS API middleware call that is currently blocked
107
+ // waiting for the data store to reflect a just-written file.
108
+ // This keeps the invalidation on a single path (here) and lets
109
+ // the middleware respond only after the SSR module graph is fresh.
110
+ notifyContentStoreUpdated()
111
+ }
112
+
113
+ const onEvent = () => {
114
+ clearTimeout(debounce)
115
+ debounce = setTimeout(invalidate, 80)
116
+ // Re-attach: native fs.watch dies after the inode is replaced by an
117
+ // atomic rename. Close current and restart so subsequent writes are
118
+ // observed.
119
+ fsWatcher?.close()
120
+ fsWatcher = undefined
121
+ if (!closed) startWatching()
107
122
  }
108
123
 
109
124
  const startWatching = () => {
125
+ if (closed) return
110
126
  try {
111
- fsWatcher = watch(dataStorePath, () => {
112
- clearTimeout(debounce)
113
- debounce = setTimeout(invalidate, 80)
114
- })
127
+ fsWatcher = watch(dataStorePath, onEvent)
115
128
  } catch {
116
129
  // File doesn't exist yet — retry when it appears
117
130
  setTimeout(startWatching, 2000)
@@ -123,6 +136,7 @@ export function createVitePlugin(context: VitePluginContext): Plugin[] {
123
136
 
124
137
  const origClose = server.close.bind(server)
125
138
  server.close = async () => {
139
+ closed = true
126
140
  fsWatcher?.close()
127
141
  clearTimeout(debounce)
128
142
  return origClose()