import path from 'node:path'
import fs from 'fs-extra'
import fg from 'fast-glob'
import { normalizePath } from 'vite'
import { babelReplaceImportPathWithCertainFileName } from './replaceImportPath.mjs'
const { readJsonSync } = fs
function transform(content, filename) {
if (filename.endsWith('.js')) {
const result = babelReplaceImportPathWithCertainFileName(content, filename, console)
return result.code || content
}
return content
}
function onlyFiles(globString) {
return fg.sync(globString + '/**/*', { onlyFiles: true }).map((p) => normalizePath(p))
}
function replaceTailSlash(pathStr) {
return pathStr.replace(/\/$/, '')
}
export function copyfileToDynamicSrcMapper({ src, dest, transform, rename, folder, ...rest }) {
return {
...rest,
get src() {
return src || onlyFiles(folder)
},
dest,
transform,
rename
}
}
function extractPackageInfo(url, originCdnPrefix) {
let match = null
try {
const mergedRegex = new RegExp(
`^${originCdnPrefix}/?` +
`(?<packageName>(?:@[^/]+/)?[^/@]+)` +
`(?:@|/(?=.*/files))` +
`(?<versionDemand>[^/]+)` +
`(?:/files)?` +
`(?<filePathInPackage>.*?)$`
)
match = url.match(mergedRegex)
} catch (error) {
}
return match
}
export function getCdnPathNpmInfoForSingleFile(
url,
originCdnPrefix,
base,
dir,
transformIContent = false,
tempDir = 'bundle-deps'
) {
const baseSlash = base.endsWith('/') ? '' : '/'
const match = extractPackageInfo(url, originCdnPrefix)
if (!match) {
return null
}
const { packageName, versionDemand, filePathInPackage } = match.groups
let version = versionDemand
let isFolder = filePathInPackage.endsWith('/')
let src = replaceTailSlash(`node_modules/${packageName}${filePathInPackage}`)
const sourceExist = fs.existsSync(path.resolve(src))
let sourceExistExternal = false
if (sourceExist) {
const stat = fs.statSync(path.resolve(src))
if (stat.isDirectory()) {
isFolder = true
}
const content = readJsonSync(`node_modules/${packageName}/package.json`)
version = content.version
} else {
src = tempDir + '/' + src
sourceExistExternal = fs.existsSync(path.resolve(src))
if (sourceExistExternal) {
const packageJson = readJsonSync(`${tempDir}/node_modules/${packageName}/package.json`)
version = packageJson.version
}
}
const updateVersion = (version) => {
const destPackageDir = `${dir}/${packageName}@${version}`
const destFullPath = `${destPackageDir}${filePathInPackage}`
const destFullPathWithoutTailSlash = replaceTailSlash(destFullPath)
const dest = path.dirname(destFullPathWithoutTailSlash)
const rename = dest.startsWith(destPackageDir) ? null : path.basename(destFullPathWithoutTailSlash)
return {
version,
newUrl: `${base}${baseSlash}${destFullPath}`,
dest,
rename
}
}
return {
originUrl: url,
src,
packageName,
versionDemand,
filePathInPackage,
sourceExist,
sourceExistExternal,
...updateVersion(version),
updateVersion,
transform: transformIContent && !isFolder ? transform : null
}
}
export function getCdnPathNpmInfoForPackage(
url,
originCdnPrefix,
base,
dir,
transformIContent = false,
tempDir = 'bundle-deps'
) {
const baseSlash = base.endsWith('/') ? '' : '/'
const match = extractPackageInfo(url, originCdnPrefix)
if (!match) {
return null
}
const { packageName, versionDemand, filePathInPackage } = match.groups
let version = versionDemand
let src = `node_modules/${packageName}`
const sourceExist = fs.existsSync(path.resolve(src))
let sourceExistExternal = false
if (sourceExist) {
const content = readJsonSync(`${src}/package.json`)
version = content.version
} else {
src = tempDir + '/' + src
sourceExistExternal = fs.existsSync(path.resolve(src))
if (sourceExistExternal) {
const packageJson = readJsonSync(`${src}/package.json`)
version = packageJson.version
}
}
const updateVersion = (version) => {
const packageDir = `${dir}/${packageName}@${version}`
const packageDirBasename = path.basename(packageDir)
return {
version,
newUrl: `${base}${baseSlash}${dir}/${packageName}@${version}${filePathInPackage}`,
dest: path.dirname(packageDir),
rename: (_filename, _fileExtension, fullPath) => `${packageDirBasename}${fullPath.replace(src, '')}`
}
}
return {
folder: src,
originUrl: url,
src: sourceExist || sourceExistExternal ? onlyFiles(src) : null,
packageName,
versionDemand,
filePathInPackage,
sourceExist,
sourceExistExternal,
...updateVersion(version),
updateVersion,
transform: transformIContent ? transform : null
}
}
export function dedupeCopyFiles(files) {
return files.reduce((acc, cur) => {
if (
(cur.folder && !acc.some((item) => !!item.folder && item.folder === cur.folder && item.dest === cur.dest)) ||
(typeof cur.src === 'string' && !acc.some((item) => item.src === cur.src && item.dest === cur.dest))
) {
acc.push(cur)
}
return acc
}, [])
}
export function getPackageNeedToInstallAndFilesUsingSameVersion(files) {
const packageNeedToInstall = files
.filter((item) => !item.sourceExist)
.map(({ packageName, version, sourceExistExternal: exist }) => ({ packageName, version, exist }))
.reduce((acc, cur) => {
if (!acc.some(({ packageName }) => cur.packageName === packageName)) {
acc.push(cur)
}
return acc
}, [])
let newFiles = null
if (packageNeedToInstall.length) {
newFiles = files.map((file) => {
const samePackageDifferentVersion = packageNeedToInstall.find(
({ packageName, version }) => packageName === file.packageName && version !== file.version
)
if (samePackageDifferentVersion) {
return {
...file,
...file.updateVersion(samePackageDifferentVersion.version)
}
}
return file
})
}
return {
packages: packageNeedToInstall,
files: newFiles ?? files
}
}