diff --git a/packages/gatsby-cli/src/index.ts b/packages/gatsby-cli/src/index.ts index bddc929297c4f..3af5eea71459c 100755 --- a/packages/gatsby-cli/src/index.ts +++ b/packages/gatsby-cli/src/index.ts @@ -1,12 +1,14 @@ #!/usr/bin/env node import "@babel/polyfill" +import os from "os" import semver from "semver" import util from "util" import createCli from "./create-cli" import report from "./reporter" import pkg from "../package.json" import updateNotifier from "update-notifier" +import ensureWindowsDriveLetterIsUppercase from "./util/ensure-windows-drive-letter-is-uppercase" const useJsonLogger = process.argv.slice(2).some(arg => arg.includes(`json`)) @@ -14,6 +16,11 @@ if (useJsonLogger) { process.env.GATSBY_LOGGER = `json` } +// Ensure stable runs on Windows when started from different shells (i.e. c:\dir vs C:\dir) +if (os.platform() === `win32`) { + ensureWindowsDriveLetterIsUppercase() +} + // Check if update is available updateNotifier({ pkg }).notify({ isGlobal: true }) diff --git a/packages/gatsby-cli/src/util/ensure-windows-drive-letter-is-uppercase.js b/packages/gatsby-cli/src/util/ensure-windows-drive-letter-is-uppercase.js new file mode 100644 index 0000000000000..8f94bea8e3d48 --- /dev/null +++ b/packages/gatsby-cli/src/util/ensure-windows-drive-letter-is-uppercase.js @@ -0,0 +1,59 @@ +const { tmpdir } = require(`os`) +const report = require(`../reporter`) + +/** + * This function ensures that the current working directory on Windows + * always has an uppercase drive letter (i.e., C: vs. c:). + * + * Why? + * 1. Different utils like "true-case-path", "normalize-path", "slash" treat Windows + * drive letter differently. "true-case-path" will uppercase, others usually don't care. + * As a result path normalization produces different results depending on current cwd (c: vs. C:) + * which manifests in weird bugs that are very hard to debug. + * + * We can't control community plugins or site code, so everything should be working + * even with a different set of libraries. + * + * Related: https://github.com/Profiscience/true-case-path/issues/3 + * + * 2. Builds save some paths in a cache. If you run the first build from "c:" shell + * and then the next one from "C:" shell, you may get a bunch of webpack warnings + * because it expects module paths to be case-sensitive. + */ +module.exports = function ensureWindowsDriveLetterIsUppercase() { + const cwd = process.cwd() + const normalizedCwd = driveLetterToUpperCase(cwd) + + if (cwd !== normalizedCwd) { + try { + // When cwd is "c:\dir" then command "cd C:\dir" won't do anything + // You have to change the dir twice to actually change the casing of the path + process.chdir(tmpdir()) + process.chdir(normalizedCwd) + } catch { + // rollback + process.chdir(cwd) + } + + if (normalizedCwd !== process.cwd()) { + report.warn( + report.stripIndent(` + Your working directory has a lower case drive letter: + "${cwd}". + ---^ + For solid development experience, we recommend switching it to upper case: + cd "C:\\" + cd "${normalizedCwd}" + (Windows requires two directory switches to change the case of the drive letter) + `) + ) + } + } +} + +function driveLetterToUpperCase(path) { + const segments = path.split(`:\\`) + return segments.length > 1 + ? segments.shift().toUpperCase() + `:\\` + segments.join(`:\\`) + : path +}