(source, target)
| 168 | } |
| 169 | |
| 170 | function syncTarget(source, target) { |
| 171 | const dir = path.resolve(ROOT_DIR, target.dir); |
| 172 | const report = { |
| 173 | route: target.route, |
| 174 | dir: path.relative(ROOT_DIR, dir), |
| 175 | added: [], |
| 176 | removed: [], |
| 177 | warnings: [], |
| 178 | }; |
| 179 | |
| 180 | if (!fs.existsSync(dir)) { |
| 181 | throw new Error(`Markdown directory not found: ${target.dir}`); |
| 182 | } |
| 183 | |
| 184 | const files = collectMarkdownSlugs(dir); |
| 185 | const blockRange = findRouteArray(source, target.route); |
| 186 | if (!blockRange) { |
| 187 | throw new Error(`Route not found in sidebar.ts: ${target.route}`); |
| 188 | } |
| 189 | |
| 190 | let block = source.slice(blockRange.start, blockRange.end + 1); |
| 191 | const refs = collectRefs(block); |
| 192 | const listedSlugs = new Set(refs.map((ref) => ref.slug)); |
| 193 | const fileSlugs = new Set(files.map((file) => file.slug)); |
| 194 | |
| 195 | const staleSlugs = [...listedSlugs].filter((slug) => !fileSlugs.has(slug)); |
| 196 | if (staleSlugs.length > 0) { |
| 197 | const removal = removeStaleStringEntries(block, staleSlugs); |
| 198 | block = removal.block; |
| 199 | report.removed = removal.removed; |
| 200 | |
| 201 | for (const slug of staleSlugs) { |
| 202 | if (!removal.removed.includes(slug)) { |
| 203 | report.warnings.push(`Stale ref "${slug}" is not a plain string entry; remove it manually if needed.`); |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | const refsAfterRemoval = collectRefs(block); |
| 209 | const listedAfterRemoval = new Set(refsAfterRemoval.map((ref) => ref.slug)); |
| 210 | const missingFiles = files.filter((file) => !listedAfterRemoval.has(file.slug)); |
| 211 | |
| 212 | if (missingFiles.length > 0) { |
| 213 | const insertion = appendMissingEntries(block, missingFiles, target.fallbackGroup); |
| 214 | block = insertion.block; |
| 215 | report.added = insertion.added; |
| 216 | report.warnings.push(...insertion.warnings); |
| 217 | } |
| 218 | |
| 219 | const nextSource = source.slice(0, blockRange.start) + block + source.slice(blockRange.end + 1); |
| 220 | return { source: nextSource, report }; |
| 221 | } |
| 222 | |
| 223 | function collectMarkdownSlugs(dir) { |
| 224 | return fs |
no test coverage detected