import { FilterStats, MAX_FILE_REQUESTS } from "builder-util"
import { realpathSync } from "fs"
import { lstat, lstatSync, readdir } from "fs-extra"
import * as path from "path"
import asyncPool from "tiny-async-pool"
import { excludedNames, FileMatcher } from "../fileMatcher"
import { Packager } from "../packager"
import { FileCopyHelper } from "./AppFileWalker"
import { NodeModuleInfo } from "./packageDependencies"
import { resolveFunction } from "./resolve"
const excludedFiles = new Set(
[
".DS_Store",
"node_modules" ,
"CHANGELOG.md",
"ChangeLog",
"changelog.md",
"Changelog.md",
"Changelog",
"binding.gyp",
".npmignore",
"node_gyp_bins",
].concat(excludedNames.split(","))
)
const topLevelExcludedFiles = new Set([
"karma.conf.js",
".coveralls.yml",
"README.md",
"readme.markdown",
"README",
"readme.md",
"Readme.md",
"Readme",
"readme",
"test",
"tests",
"__tests__",
"powered-test",
"example",
"examples",
".bin",
])
export class NodeModuleCopyHelper extends FileCopyHelper {
constructor(matcher: FileMatcher, packager: Packager) {
super(matcher, matcher.isEmpty() ? null : matcher.createFilter(), packager)
}
async collectNodeModules(moduleInfo: NodeModuleInfo, nodeModuleExcludedExts: Array<string>, destination: string): Promise<Array<string>> {
const filter = this.filter
const metadata = this.metadata
const onNodeModuleFile = await resolveFunction(this.packager.appInfo.type, this.packager.config.onNodeModuleFile, "onNodeModuleFile")
const result: Array<string | undefined> = []
const queue: Array<string> = []
const emptyDirs: Set<string> = new Set()
const symlinkFiles: Map<string, number> = new Map()
const tmpPath = moduleInfo.dir
const moduleName = moduleInfo.name
queue.length = 1
const depPath = path.normalize(tmpPath)
queue[0] = depPath
while (queue.length > 0) {
const dirPath = queue.pop()!
const childNames = await readdir(dirPath)
childNames.sort()
const isTopLevel = dirPath === depPath
const dirs: Array<string> = []
const sortedFilePaths = await asyncPool(MAX_FILE_REQUESTS, childNames, async name => {
const filePath = path.join(dirPath, name)
const forceIncluded = onNodeModuleFile != null && !!onNodeModuleFile(filePath)
if (excludedFiles.has(name) || name.startsWith("._")) {
return null
}
const fileMatched = filter != null && filter(dirPath, lstatSync(dirPath))
if (!fileMatched || !forceIncluded || !!this.packager.config.disableDefaultIgnoredFiles) {
for (const ext of nodeModuleExcludedExts) {
if (name.endsWith(ext)) {
return null
}
}
if (isTopLevel && (topLevelExcludedFiles.has(name) || (moduleName === "libui-node" && (name === "build" || name === "docs" || name === "src")))) {
return null
}
if (dirPath.endsWith("build")) {
if (name === "gyp-mac-tool" || name === "Makefile" || name.endsWith(".mk") || name.endsWith(".gypi") || name.endsWith(".Makefile")) {
return null
}
} else if (dirPath.endsWith("Release") && (name === ".deps" || name === "obj.target")) {
return null
} else if (name === "src" && (dirPath.endsWith("keytar") || dirPath.endsWith("keytar-prebuild"))) {
return null
} else if (dirPath.endsWith("lzma-native") && (name === "build" || name === "deps")) {
return null
}
}
return lstat(filePath).then((stat: FilterStats) => {
stat.moduleName = moduleName
stat.moduleRootPath = destination
stat.moduleFullFilePath = path.join(destination, path.relative(depPath, filePath))
if (filter != null && !filter(filePath, stat)) {
return null
}
if (!stat.isDirectory()) {
metadata.set(filePath, stat)
}
const consumerResult = this.handleFile(filePath, dirPath, stat)
if (consumerResult == null) {
if (stat.isDirectory()) {
dirs.push(name)
return null
} else {
return filePath
}
} else {
return consumerResult.then(it => {
if ((it == null ? stat : it).isDirectory()) {
dirs.push(name)
return null
} else {
return filePath
}
})
}
})
})
let isEmpty = true
for (const child of sortedFilePaths) {
if (child != null) {
result.push(child)
if (this.metadata.get(child)?.isSymbolicLink()) {
symlinkFiles.set(child, result.length - 1)
}
isEmpty = false
}
}
if (isEmpty) {
emptyDirs.add(dirPath)
}
dirs.sort()
for (const child of dirs) {
queue.push(dirPath + path.sep + child)
}
}
for (const [file, index] of symlinkFiles) {
const resolvedPath = realpathSync(file)
if (emptyDirs.has(resolvedPath)) {
result[index] = undefined
}
}
return result.filter((it): it is string => it !== undefined)
}
}