153 lines
3.9 KiB
JavaScript
153 lines
3.9 KiB
JavaScript
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
|
|
export const syncExternalIndex = async ({
|
|
projectRoot,
|
|
outputDir,
|
|
externalIndexFile,
|
|
syncOptions,
|
|
}) => {
|
|
if (!externalIndexFile) {
|
|
throw new Error('externalIndexFile is required for sync')
|
|
}
|
|
|
|
const generatedIndexPath = path.join(outputDir, 'index.js')
|
|
const [generatedContent, targetContent] = await Promise.all([
|
|
fs.readFile(generatedIndexPath, 'utf8'),
|
|
readFileIfExists(externalIndexFile),
|
|
])
|
|
const nextContent = buildTargetFileContent({
|
|
targetContent,
|
|
generatedContent,
|
|
generatedIndexPath,
|
|
externalIndexFile,
|
|
exportFrom: syncOptions.exportFrom,
|
|
includeGeneratedIndexSnapshot: syncOptions.includeGeneratedIndexSnapshot,
|
|
snapshotTitle: syncOptions.snapshotTitle,
|
|
blockStart: syncOptions.blockStart,
|
|
blockEnd: syncOptions.blockEnd,
|
|
projectRoot,
|
|
})
|
|
|
|
if (normalizeLineEndings(nextContent) === normalizeLineEndings(targetContent)) {
|
|
console.log(`no changes: ${toProjectRelativePath(projectRoot, externalIndexFile)}`)
|
|
return
|
|
}
|
|
|
|
await fs.mkdir(path.dirname(externalIndexFile), { recursive: true })
|
|
await fs.writeFile(externalIndexFile, nextContent, 'utf8')
|
|
console.log(`synced: ${toProjectRelativePath(projectRoot, externalIndexFile)}`)
|
|
}
|
|
|
|
const buildTargetFileContent = ({
|
|
targetContent,
|
|
generatedContent,
|
|
generatedIndexPath,
|
|
externalIndexFile,
|
|
exportFrom,
|
|
includeGeneratedIndexSnapshot,
|
|
snapshotTitle,
|
|
blockStart,
|
|
blockEnd,
|
|
projectRoot,
|
|
}) => {
|
|
const lineEnding = targetContent.includes('\r\n') ? '\r\n' : '\n'
|
|
const normalizedTargetContent = normalizeLineEndings(targetContent)
|
|
const managedBlock = buildManagedBlock({
|
|
generatedContent,
|
|
generatedIndexPath,
|
|
externalIndexFile,
|
|
exportFrom,
|
|
includeGeneratedIndexSnapshot,
|
|
snapshotTitle,
|
|
blockStart,
|
|
blockEnd,
|
|
projectRoot,
|
|
})
|
|
const blockPattern = new RegExp(
|
|
`${escapeRegExp(blockStart)}[\\s\\S]*?${escapeRegExp(blockEnd)}`,
|
|
'm',
|
|
)
|
|
const trimmedTargetContent = normalizedTargetContent.trimEnd()
|
|
let nextContent = managedBlock
|
|
|
|
if (trimmedTargetContent) {
|
|
nextContent = blockPattern.test(normalizedTargetContent)
|
|
? trimmedTargetContent.replace(blockPattern, managedBlock)
|
|
: `${trimmedTargetContent}\n\n${managedBlock}`
|
|
}
|
|
|
|
return `${nextContent}\n`.replace(/\n/g, lineEnding)
|
|
}
|
|
|
|
const buildManagedBlock = ({
|
|
generatedContent,
|
|
generatedIndexPath,
|
|
externalIndexFile,
|
|
exportFrom,
|
|
includeGeneratedIndexSnapshot,
|
|
snapshotTitle,
|
|
blockStart,
|
|
blockEnd,
|
|
projectRoot,
|
|
}) => {
|
|
const lines = [
|
|
blockStart,
|
|
`// Synced from '${toProjectRelativePath(projectRoot, generatedIndexPath)}'. Do not edit manually.`,
|
|
]
|
|
|
|
if (includeGeneratedIndexSnapshot) {
|
|
lines.push(snapshotTitle, ...buildCommentLines(generatedContent))
|
|
}
|
|
|
|
lines.push(
|
|
`export * from '${exportFrom || buildExportFrom(externalIndexFile, path.dirname(generatedIndexPath))}'`,
|
|
)
|
|
lines.push(blockEnd)
|
|
|
|
return lines.join('\n')
|
|
}
|
|
|
|
const buildExportFrom = (externalIndexFile, generatedDir) => {
|
|
let relativeImportPath = path
|
|
.relative(path.dirname(externalIndexFile), generatedDir)
|
|
.replace(/\\/g, '/')
|
|
|
|
if (!relativeImportPath.startsWith('.')) {
|
|
relativeImportPath = `./${relativeImportPath}`
|
|
}
|
|
|
|
return relativeImportPath
|
|
}
|
|
|
|
const buildCommentLines = (content) => {
|
|
return normalizeLineEndings(content)
|
|
.trimEnd()
|
|
.split('\n')
|
|
.map((line) => (line ? `// ${line}` : '//'))
|
|
}
|
|
|
|
const readFileIfExists = async (targetPath) => {
|
|
try {
|
|
return await fs.readFile(targetPath, 'utf8')
|
|
} catch (error) {
|
|
if (error?.code === 'ENOENT') {
|
|
return ''
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
const normalizeLineEndings = (content) => {
|
|
return content.replace(/\r\n/g, '\n')
|
|
}
|
|
|
|
const escapeRegExp = (value) => {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
}
|
|
|
|
const toProjectRelativePath = (projectRoot, targetPath) => {
|
|
return path.relative(projectRoot, targetPath).replace(/\\/g, '/')
|
|
}
|