Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): throw error if build folder already exists on initial clean #9112

Merged
merged 13 commits into from Jun 30, 2023

Conversation

thedevwonder
Copy link
Contributor

@thedevwonder thedevwonder commented Jun 29, 2023

Pre-flight checklist

  • I have read the Contributing Guidelines on pull requests.
  • If this is a code change: I have written unit tests and/or added dogfooding pages to fully verify the new behavior.
  • If this is a new API or substantial change: the PR has an accompanying issue (closes #0000) and the maintainers have approved on my working plan.

Motivation

Fix #9110

Test Plan

local tests

Test links

Deploy preview: https://deploy-preview-9112--docusaurus-2.netlify.app/

Related issues/PRs

@netlify
Copy link

netlify bot commented Jun 29, 2023

[V2]

Built without sensitive environment variables

Name Link
🔨 Latest commit d86ac5c
🔍 Latest deploy log https://app.netlify.com/sites/docusaurus-2/deploys/649ef212e20d2500082f0b9c
😎 Deploy Preview https://deploy-preview-9112--docusaurus-2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@github-actions
Copy link

github-actions bot commented Jun 29, 2023

⚡️ Lighthouse report for the deploy preview of this PR

URL Performance Accessibility Best Practices SEO PWA Report
/ 🟠 81 🟢 97 🟢 92 🟢 100 🟠 89 Report
/docs/installation 🟠 75 🟢 100 🟢 92 🟢 100 🟠 89 Report

@@ -30,6 +30,7 @@
// More context: https://github.com/facebook/docusaurus/pull/1839

import path from 'path';
import * as fs from 'fs';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use fs-extra and await fs.pathExists(); you can find other examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Josh-Cena I'm pushing a commit that use await pathExists instead of sync func

@Josh-Cena
Copy link
Collaborator

@thedevwonder Using the sync version is fine here, since we'd better leave the method sync. It shouldn't be too much of a problem.

@Josh-Cena Josh-Cena changed the title fix(plugins): throw error if build folder already exists on initial c… fix(plugins): throw error if build folder already exists on initial clean Jun 30, 2023
@thedevwonder
Copy link
Contributor Author

@thedevwonder Using the sync version is fine here, since we'd better leave the method sync. It shouldn't be too much of a problem.

sorry I didn't see the notification and already did it. Should I revert back to sync version?

@Josh-Cena Josh-Cena changed the title fix(plugins): throw error if build folder already exists on initial clean fix(core): throw error if build folder already exists on initial clean Jun 30, 2023
if (this.initialClean) {
return;
}

if (await fs.pathExists(this.outputPath)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outputPath is supposed to exist—this method is used to clean the output. What's problematic is if it's not a directory. You also need to test if it's a file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I will add a validation for file as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Josh-Cena I have a confusion here, pathExistsSync will still return true when build is a file. Then why do we need an additional validation?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three cases here:

  1. outputPath doesn't exist -> Always good; removeFiles does nothing
  2. outputPath is a directory -> This is why we have this function; we want to clean its content and prepare for the next build output
  3. outputPath is a file -> This is what Build error The cwd option must be a path to a directory when a file called build exists #9110 is reporting. In this case delSync fails because cwd is a file, not a directory. Logically, we can't clear anything from this file, so we fail.

We don't want to touch the behavior in cases 1 and 2, only give a better error in 3.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I misunderstood earlier. We do not need to change the behaviour when this.outputPath is a directory. So the check that I've put should only validate files and not directories.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Josh-Cena Reading the original issue, I came across another observation. the error message should occur in case-insensitive filesystems as well. To check if there are no existing files with same name as build but different case, I created a private validator that returns boolean. checks for both files and directories with duplicate name. This will solve:

  • case where root dir may have diff variations of same file such as BUILD, build, buiLD.
  • diff variations of same directories.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested the code in local env.

Comment on lines 119 to 130
private checkIfFileNameIsDuplicate(): boolean {
const dir = path.dirname(this.outputPath);
const baseName = path.basename(this.outputPath).toLowerCase();
// eslint-disable-next-line no-restricted-properties
const duplicateFiles = fs
.readdirSync(dir)
.filter((fileName) => fileName.toLowerCase() === baseName);
if (duplicateFiles.length > 0) {
return true;
}
return false;
}
Copy link
Collaborator

@slorber slorber Jun 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be enough

if (
      (await fs.pathExists(outputPath)) &&
      (await fs.stat(outputPath)).isFile()
    ) {
   throw new Error("...");
}

On:

  • case insensitive FS: this should throw for files like BUILD or build
  • case-sensitive FS: if a file named BUILD exists, I think it's allowed to create a folder named build? 🤔

I only have access to a case insensitive file-system. Can someone test my assumption on case sensitive file-system?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slorber, I think yours is better because it doesn't break the abstraction of whether the FS is case-sensitive or not :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be enough

if (
      (await fs.pathExists(outputPath)) &&
      (await fs.stat(outputPath)).isFile()
    ) {
   throw new Error("...");
}

On:

  • case insensitive FS: this should throw for files like BUILD or build
  • case-sensitive FS: if a file named BUILD exists, I think it's allowed to create a folder named build? 🤔

I only have access to a case insensitive file-system. Can someone test my assumption on case sensitive file-system?

I used gitpod.io to test this usecase. It is a case-sensitive system. I changed the code because I thought someone using case-sensitive FS may create a BUILD file and someone not using this FS may find this file throwing an error during the build

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this, any suggestions? @slorber @Josh-Cena

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine. Running the same project on different systems can often run into inconsistencies, like some imports that work on case-insensitive systems suddenly fail in case-sensitive ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Josh-Cena I see, keeping it simple then!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Josh-Cena @slorber I have used the sync version of pathExists and stat functions and fixed the error message. Let me know if this looks good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have also tested the code in my local env. Works fine:)

@@ -152,6 +166,12 @@ export default class CleanWebpackPlugin {
return;
}

if (this.checkIfFileNameIsDuplicate()) {
throw new Error(
`Output directory ${this.outputPath} already exists. Docusaurus needs this directory to save the build output. Either remove the directory or choose a different build directory via '--out-dir'.`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's a directory the role of this plugin is to delete. As @Josh-Cena said the problem is only if a file already exists, so this error message is not good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slorber this makes sense, I'll do the change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes done

Copy link
Collaborator

@slorber slorber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM.

Unfortunate that we need to use sync IO but not a big deal.

@thedevwonder
Copy link
Contributor Author

thedevwonder commented Jun 30, 2023

Thanks, LGTM.

Unfortunate that we need to use sync IO but not a big deal.

@slorber is there some way we can check the performance diff in introducing sync I/O functions here? Just curious. Probably build time could be one parameter

@thedevwonder
Copy link
Contributor Author

Thanks, LGTM.
Unfortunate that we need to use sync IO but not a big deal.

@slorber is there some way we can check the performance diff in introducing sync I/O functions here? Just curious. Probably build time could be one parameter

oh I see, build performance is literally one of the checks here, my bad

@slorber
Copy link
Collaborator

slorber commented Jun 30, 2023

That's fine, this CI fails for other reasons 👍

@slorber slorber added the pr: bug fix This PR fixes a bug in a past release. label Jun 30, 2023
@slorber slorber merged commit 8ea1945 into facebook:main Jun 30, 2023
29 of 31 checks passed
@slorber slorber added the to backport This PR is planned to be backported to a stable version of Docusaurus label Jun 30, 2023
This was referenced Oct 19, 2023
@slorber slorber removed the to backport This PR is planned to be backported to a stable version of Docusaurus label Nov 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Signed Facebook CLA pr: bug fix This PR fixes a bug in a past release.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Build error The cwd option must be a path to a directory when a file called build exists
4 participants