123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.normalizeLineEndings = exports.BasePortableFakeFS = exports.FakeFS = void 0;
- const os_1 = require("os");
- const copyPromise_1 = require("./algorithms/copyPromise");
- const path_1 = require("./path");
- class FakeFS {
- constructor(pathUtils) {
- this.pathUtils = pathUtils;
- }
- async *genTraversePromise(init, { stableSort = false } = {}) {
- const stack = [init];
- while (stack.length > 0) {
- const p = stack.shift();
- const entry = await this.lstatPromise(p);
- if (entry.isDirectory()) {
- const entries = await this.readdirPromise(p);
- if (stableSort) {
- for (const entry of entries.sort()) {
- stack.push(this.pathUtils.join(p, entry));
- }
- }
- else {
- throw new Error(`Not supported`);
- }
- }
- else {
- yield p;
- }
- }
- }
- async removePromise(p, { recursive = true, maxRetries = 5 } = {}) {
- let stat;
- try {
- stat = await this.lstatPromise(p);
- }
- catch (error) {
- if (error.code === `ENOENT`) {
- return;
- }
- else {
- throw error;
- }
- }
- if (stat.isDirectory()) {
- if (recursive) {
- const entries = await this.readdirPromise(p);
- await Promise.all(entries.map(entry => {
- return this.removePromise(this.pathUtils.resolve(p, entry));
- }));
- }
- // 5 gives 1s worth of retries at worst
- for (let t = 0; t <= maxRetries; t++) {
- try {
- await this.rmdirPromise(p);
- break;
- }
- catch (error) {
- if (error.code !== `EBUSY` && error.code !== `ENOTEMPTY`) {
- throw error;
- }
- else if (t < maxRetries) {
- await new Promise(resolve => setTimeout(resolve, t * 100));
- }
- }
- }
- }
- else {
- await this.unlinkPromise(p);
- }
- }
- removeSync(p, { recursive = true } = {}) {
- let stat;
- try {
- stat = this.lstatSync(p);
- }
- catch (error) {
- if (error.code === `ENOENT`) {
- return;
- }
- else {
- throw error;
- }
- }
- if (stat.isDirectory()) {
- if (recursive)
- for (const entry of this.readdirSync(p))
- this.removeSync(this.pathUtils.resolve(p, entry));
- this.rmdirSync(p);
- }
- else {
- this.unlinkSync(p);
- }
- }
- async mkdirpPromise(p, { chmod, utimes } = {}) {
- p = this.resolve(p);
- if (p === this.pathUtils.dirname(p))
- return undefined;
- const parts = p.split(this.pathUtils.sep);
- let createdDirectory;
- for (let u = 2; u <= parts.length; ++u) {
- const subPath = parts.slice(0, u).join(this.pathUtils.sep);
- if (!this.existsSync(subPath)) {
- try {
- await this.mkdirPromise(subPath);
- }
- catch (error) {
- if (error.code === `EEXIST`) {
- continue;
- }
- else {
- throw error;
- }
- }
- createdDirectory !== null && createdDirectory !== void 0 ? createdDirectory : (createdDirectory = subPath);
- if (chmod != null)
- await this.chmodPromise(subPath, chmod);
- if (utimes != null) {
- await this.utimesPromise(subPath, utimes[0], utimes[1]);
- }
- else {
- const parentStat = await this.statPromise(this.pathUtils.dirname(subPath));
- await this.utimesPromise(subPath, parentStat.atime, parentStat.mtime);
- }
- }
- }
- return createdDirectory;
- }
- mkdirpSync(p, { chmod, utimes } = {}) {
- p = this.resolve(p);
- if (p === this.pathUtils.dirname(p))
- return undefined;
- const parts = p.split(this.pathUtils.sep);
- let createdDirectory;
- for (let u = 2; u <= parts.length; ++u) {
- const subPath = parts.slice(0, u).join(this.pathUtils.sep);
- if (!this.existsSync(subPath)) {
- try {
- this.mkdirSync(subPath);
- }
- catch (error) {
- if (error.code === `EEXIST`) {
- continue;
- }
- else {
- throw error;
- }
- }
- createdDirectory !== null && createdDirectory !== void 0 ? createdDirectory : (createdDirectory = subPath);
- if (chmod != null)
- this.chmodSync(subPath, chmod);
- if (utimes != null) {
- this.utimesSync(subPath, utimes[0], utimes[1]);
- }
- else {
- const parentStat = this.statSync(this.pathUtils.dirname(subPath));
- this.utimesSync(subPath, parentStat.atime, parentStat.mtime);
- }
- }
- }
- return createdDirectory;
- }
- async copyPromise(destination, source, { baseFs = this, overwrite = true, stableSort = false, stableTime = false, linkStrategy = null } = {}) {
- return await (0, copyPromise_1.copyPromise)(this, destination, baseFs, source, { overwrite, stableSort, stableTime, linkStrategy });
- }
- copySync(destination, source, { baseFs = this, overwrite = true } = {}) {
- const stat = baseFs.lstatSync(source);
- const exists = this.existsSync(destination);
- if (stat.isDirectory()) {
- this.mkdirpSync(destination);
- const directoryListing = baseFs.readdirSync(source);
- for (const entry of directoryListing) {
- this.copySync(this.pathUtils.join(destination, entry), baseFs.pathUtils.join(source, entry), { baseFs, overwrite });
- }
- }
- else if (stat.isFile()) {
- if (!exists || overwrite) {
- if (exists)
- this.removeSync(destination);
- const content = baseFs.readFileSync(source);
- this.writeFileSync(destination, content);
- }
- }
- else if (stat.isSymbolicLink()) {
- if (!exists || overwrite) {
- if (exists)
- this.removeSync(destination);
- const target = baseFs.readlinkSync(source);
- this.symlinkSync((0, path_1.convertPath)(this.pathUtils, target), destination);
- }
- }
- else {
- throw new Error(`Unsupported file type (file: ${source}, mode: 0o${stat.mode.toString(8).padStart(6, `0`)})`);
- }
- const mode = stat.mode & 0o777;
- this.chmodSync(destination, mode);
- }
- async changeFilePromise(p, content, opts = {}) {
- if (Buffer.isBuffer(content)) {
- return this.changeFileBufferPromise(p, content, opts);
- }
- else {
- return this.changeFileTextPromise(p, content, opts);
- }
- }
- async changeFileBufferPromise(p, content, { mode } = {}) {
- let current = Buffer.alloc(0);
- try {
- current = await this.readFilePromise(p);
- }
- catch (error) {
- // ignore errors, no big deal
- }
- if (Buffer.compare(current, content) === 0)
- return;
- await this.writeFilePromise(p, content, { mode });
- }
- async changeFileTextPromise(p, content, { automaticNewlines, mode } = {}) {
- let current = ``;
- try {
- current = await this.readFilePromise(p, `utf8`);
- }
- catch (error) {
- // ignore errors, no big deal
- }
- const normalizedContent = automaticNewlines
- ? normalizeLineEndings(current, content)
- : content;
- if (current === normalizedContent)
- return;
- await this.writeFilePromise(p, normalizedContent, { mode });
- }
- changeFileSync(p, content, opts = {}) {
- if (Buffer.isBuffer(content)) {
- return this.changeFileBufferSync(p, content, opts);
- }
- else {
- return this.changeFileTextSync(p, content, opts);
- }
- }
- changeFileBufferSync(p, content, { mode } = {}) {
- let current = Buffer.alloc(0);
- try {
- current = this.readFileSync(p);
- }
- catch (error) {
- // ignore errors, no big deal
- }
- if (Buffer.compare(current, content) === 0)
- return;
- this.writeFileSync(p, content, { mode });
- }
- changeFileTextSync(p, content, { automaticNewlines = false, mode } = {}) {
- let current = ``;
- try {
- current = this.readFileSync(p, `utf8`);
- }
- catch (error) {
- // ignore errors, no big deal
- }
- const normalizedContent = automaticNewlines
- ? normalizeLineEndings(current, content)
- : content;
- if (current === normalizedContent)
- return;
- this.writeFileSync(p, normalizedContent, { mode });
- }
- async movePromise(fromP, toP) {
- try {
- await this.renamePromise(fromP, toP);
- }
- catch (error) {
- if (error.code === `EXDEV`) {
- await this.copyPromise(toP, fromP);
- await this.removePromise(fromP);
- }
- else {
- throw error;
- }
- }
- }
- moveSync(fromP, toP) {
- try {
- this.renameSync(fromP, toP);
- }
- catch (error) {
- if (error.code === `EXDEV`) {
- this.copySync(toP, fromP);
- this.removeSync(fromP);
- }
- else {
- throw error;
- }
- }
- }
- async lockPromise(affectedPath, callback) {
- const lockPath = `${affectedPath}.flock`;
- const interval = 1000 / 60;
- const startTime = Date.now();
- let fd = null;
- // Even when we detect that a lock file exists, we still look inside to see
- // whether the pid that created it is still alive. It's not foolproof
- // (there are false positive), but there are no false negative and that's
- // all that matters in 99% of the cases.
- const isAlive = async () => {
- let pid;
- try {
- ([pid] = await this.readJsonPromise(lockPath));
- }
- catch (error) {
- // If we can't read the file repeatedly, we assume the process was
- // aborted before even writing finishing writing the payload.
- return Date.now() - startTime < 500;
- }
- try {
- // "As a special case, a signal of 0 can be used to test for the
- // existence of a process" - so we check whether it's alive.
- process.kill(pid, 0);
- return true;
- }
- catch (error) {
- return false;
- }
- };
- while (fd === null) {
- try {
- fd = await this.openPromise(lockPath, `wx`);
- }
- catch (error) {
- if (error.code === `EEXIST`) {
- if (!await isAlive()) {
- try {
- await this.unlinkPromise(lockPath);
- continue;
- }
- catch (error) {
- // No big deal if we can't remove it. Just fallback to wait for
- // it to be eventually released by its owner.
- }
- }
- if (Date.now() - startTime < 60 * 1000) {
- await new Promise(resolve => setTimeout(resolve, interval));
- }
- else {
- throw new Error(`Couldn't acquire a lock in a reasonable time (via ${lockPath})`);
- }
- }
- else {
- throw error;
- }
- }
- }
- await this.writePromise(fd, JSON.stringify([process.pid]));
- try {
- return await callback();
- }
- finally {
- try {
- // closePromise needs to come before unlinkPromise otherwise another process can attempt
- // to get the file handle after the unlink but before close resuling in
- // EPERM: operation not permitted, open
- await this.closePromise(fd);
- await this.unlinkPromise(lockPath);
- }
- catch (error) {
- // noop
- }
- }
- }
- async readJsonPromise(p) {
- const content = await this.readFilePromise(p, `utf8`);
- try {
- return JSON.parse(content);
- }
- catch (error) {
- error.message += ` (in ${p})`;
- throw error;
- }
- }
- readJsonSync(p) {
- const content = this.readFileSync(p, `utf8`);
- try {
- return JSON.parse(content);
- }
- catch (error) {
- error.message += ` (in ${p})`;
- throw error;
- }
- }
- async writeJsonPromise(p, data) {
- return await this.writeFilePromise(p, `${JSON.stringify(data, null, 2)}\n`);
- }
- writeJsonSync(p, data) {
- return this.writeFileSync(p, `${JSON.stringify(data, null, 2)}\n`);
- }
- async preserveTimePromise(p, cb) {
- const stat = await this.lstatPromise(p);
- const result = await cb();
- if (typeof result !== `undefined`)
- p = result;
- if (this.lutimesPromise) {
- await this.lutimesPromise(p, stat.atime, stat.mtime);
- }
- else if (!stat.isSymbolicLink()) {
- await this.utimesPromise(p, stat.atime, stat.mtime);
- }
- }
- async preserveTimeSync(p, cb) {
- const stat = this.lstatSync(p);
- const result = cb();
- if (typeof result !== `undefined`)
- p = result;
- if (this.lutimesSync) {
- this.lutimesSync(p, stat.atime, stat.mtime);
- }
- else if (!stat.isSymbolicLink()) {
- this.utimesSync(p, stat.atime, stat.mtime);
- }
- }
- }
- exports.FakeFS = FakeFS;
- class BasePortableFakeFS extends FakeFS {
- constructor() {
- super(path_1.ppath);
- }
- }
- exports.BasePortableFakeFS = BasePortableFakeFS;
- function getEndOfLine(content) {
- const matches = content.match(/\r?\n/g);
- if (matches === null)
- return os_1.EOL;
- const crlf = matches.filter(nl => nl === `\r\n`).length;
- const lf = matches.length - crlf;
- return crlf > lf ? `\r\n` : `\n`;
- }
- function normalizeLineEndings(originalContent, newContent) {
- return newContent.replace(/\r?\n/g, getEndOfLine(originalContent));
- }
- exports.normalizeLineEndings = normalizeLineEndings;
|