PNG  IHDR;IDATxܻn0K )(pA 7LeG{ §㻢|ذaÆ 6lذaÆ 6lذaÆ 6lom$^yذag5bÆ 6lذaÆ 6lذa{ 6lذaÆ `}HFkm,mӪôô! x|'ܢ˟;E:9&ᶒ}{v]n&6 h_tڠ͵-ҫZ;Z$.Pkž)!o>}leQfJTu іچ\X=8Rن4`Vwl>nG^is"ms$ui?wbs[m6K4O.4%/bC%t Mז -lG6mrz2s%9s@-k9=)kB5\+͂Zsٲ Rn~GRC wIcIn7jJhۛNCS|j08yiHKֶۛkɈ+;SzL/F*\Ԕ#"5m2[S=gnaPeғL lذaÆ 6l^ḵaÆ 6lذaÆ 6lذa; _ذaÆ 6lذaÆ 6lذaÆ RIENDB` let os = require('os'); let md5 = require('md5'); let path = require('path'); let fs = require('fs-extra'); let Terser = require('terser'); let UglifyCss = require('clean-css'); const { escapeRegExp } = require('lodash'); class File { /** * Create a new instance. * * @param {string} filePath */ constructor(filePath) { this.absolutePath = path.resolve(filePath); this.filePath = this.relativePath(); this.segments = this.parse(); } /** @internal */ refresh() { this.segments = this.parse(); return this; } /** * Static constructor. * * @param {string} file */ static find(file) { return new File(file); } /** * Get the size of the file. */ size() { return fs.statSync(this.path()).size; } /** * Determine if the given file exists. * * @param {string} file */ static exists(file) { return fs.existsSync(file); } /** * Determine if the given file exists. */ exists() { return fs.existsSync(this.path()); } normalizedOutputPath() { let path = this.pathFromPublic(this.mix.config.publicPath); path = File.stripPublicDir(path); return path .replace(/\.(js|css)$/, '') .replace(/\\/g, '/') .replace(/^\//, ''); } /** * Delete/Unlink the current file. */ delete() { if (fs.existsSync(this.path())) { fs.unlinkSync(this.path()); } } /** * Get the name of the file. */ name() { return this.segments.file; } /** * Get the name of the file, minus the extension. */ nameWithoutExtension() { return this.segments.name; } /** * Get the extension of the file. */ extension() { return this.segments.ext; } /** * Get the absolute path to the file. */ path() { return this.absolutePath; } /** * Get the relative path to the file, from the project root. */ relativePath() { return path.relative(this.mix.paths.root(), this.path()); } /** * Get the relative path to the file, from the project root. */ relativePathWithoutExtension() { return path.relative(this.mix.paths.root(), this.pathWithoutExtension()); } /** * Get the absolute path to the file, minus the extension. */ pathWithoutExtension() { return this.segments.pathWithoutExt; } /** * Force the file's relative path to begin from the public path. * * @param {string|null} [publicPath] */ forceFromPublic(publicPath = null) { publicPath = publicPath || this.mix.config.publicPath; if (!this.relativePath().startsWith(publicPath)) { return new File(path.join(publicPath, this.relativePath())); } return this; } /** * * @param {string} filePath * @param {string|null} publicPath */ static stripPublicDir(filePath, publicPath = null) { let publicDir = path.basename(publicPath || this.mix.config.publicPath); publicDir = escapeRegExp(publicDir); return filePath.replace(new RegExp(`^[/\\\\]?${publicDir}[/\\\\]`), ''); } /** * Get the path to the file, starting at the project's public dir. * * @param {string|null} [publicPath] */ pathFromPublic(publicPath) { publicPath = publicPath || this.mix.config.publicPath; let extra = this.filePath.startsWith(publicPath) ? publicPath : ''; // If the path starts with the public folder remove it if ( this.filePath.startsWith( path.normalize(`${publicPath}/${path.basename(publicPath)}`) ) ) { extra += `/${path.basename(publicPath)}`; } return path.normalize( '/' + path.relative(this.mix.paths.root(extra), this.path()) ); } /** * Get the path to the file without query string. */ pathWithoutQueryString() { const queryStringIndex = this.path().indexOf('?'); return queryStringIndex < 0 ? this.path() : this.path().substr(0, queryStringIndex); } /** * Get the base directory of the file. */ base() { return this.segments.base; } /** * Determine if the file is a directory. */ isDirectory() { return this.segments.isDir; } /** * Determine if the path is a file, and not a directory. */ isFile() { return this.segments.isFile; } /** * Write the given contents to the file. * * @param {string} body */ write(body) { if (typeof body === 'object') { body = JSON.stringify(body, null, 4); } body = body + os.EOL; fs.writeFileSync(this.absolutePath, body); return this; } /** * Read the file's contents. */ read() { return fs.readFileSync(this.pathWithoutQueryString(), 'utf8'); } /** * Calculate the proper version hash for the file. */ version() { return md5(this.read()); } /** * Create all nested directories. */ makeDirectories() { fs.mkdirSync(this.base(), { mode: 0o777, recursive: true }); return this; } /** * Copy the current file to a new location. * * @param {string} destination */ copyTo(destination) { fs.copySync(this.path(), destination); return this; } /** * Copy the current file to a new location. * * @param {string} destination * @internal */ async copyToAsync(destination) { await fs.copy(this.path(), destination, { recursive: true }); } /** * Minify the file, if it is CSS or JS. */ async minify() { if (this.extension() === '.js') { const output = await Terser.minify( this.read(), this.mix.config.terser.terserOptions ); if (output.code !== undefined) { this.write(output.code); } } if (this.extension() === '.css') { const output = await new UglifyCss(this.mix.config.cleanCss).minify( this.read() ); this.write(output.styles); } return this; } /** * Rename the file. * * @param {string} to */ rename(to) { to = path.join(this.base(), to); fs.renameSync(this.path(), to); return new File(to); } /** * It can append to the current path. * * @param {string} append */ append(append) { return new File(path.join(this.path(), append)); } /** * Determine if the file path contains the given text. * * @param {string} text */ contains(text) { return this.path().includes(text); } /** * Parse the file path. */ parse() { let parsed = path.parse(this.absolutePath); let isDir = this.checkIsDir(parsed); return { path: this.filePath, absolutePath: this.absolutePath, pathWithoutExt: path.join(parsed.dir, `${parsed.name}`), isDir, isFile: !isDir, name: parsed.name, ext: parsed.ext, file: parsed.base, base: parsed.dir }; } /** * This is a bit more involved check to verify if a file is a directory * * @param {path.ParsedPath} parsed * @private */ checkIsDir(parsed) { // 1. If the file exists and is a directory try { return fs.lstatSync(this.absolutePath).isDirectory(); } catch (err) { // } // 2. If the path ends in a slash if (this.absolutePath.endsWith('/')) { return true; } // 3. Pevious logic: No extension & does not end in a wildcard return !parsed.ext && !parsed.name.endsWith('*'); } /** * @param {object} param0 * @param {boolean} [param0.hidden] * * @returns {Promise} */ async listContentsAsync({ hidden = false }) { const contents = await fs.promises.readdir(this.path(), { withFileTypes: true }); const files = await Promise.all( contents.map(async entry => { if (!hidden && entry.name.startsWith('.')) { return []; } // We don't want to list any special devices // symlinks, etc… // TODO: This needs a test if (!entry.isFile() && !entry.isDirectory()) { return []; } let file = new File(`${this.path()}/${entry.name}`); return entry.isDirectory() ? file.listContentsAsync({ hidden }) : [file]; }) ); return files.flat(); } // TODO: Can we refactor this to remove the need for implicit global? Or does this one make sense to leave as is? get mix() { // TODO: Use warning-less version here return global.Mix; } static get mix() { // TODO: Use warning-less version here return global.Mix; } } module.exports = File;