From 5810f8150eb775663177a43266233abac19e9781 Mon Sep 17 00:00:00 2001 From: James Culveyhouse Date: Fri, 27 Oct 2023 15:54:16 -0500 Subject: [PATCH] C3: Relax empty directory check (#4226) * C3: Relax empty directory check * Adding tests * Add pattern matching to allowedExistingFile check --- .changeset/gentle-news-laugh.md | 5 ++ .../src/__tests__/common.test.ts | 21 +++++++- packages/create-cloudflare/src/common.ts | 54 +++++++++++++++++-- 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 .changeset/gentle-news-laugh.md diff --git a/.changeset/gentle-news-laugh.md b/.changeset/gentle-news-laugh.md new file mode 100644 index 000000000000..d26cea94336a --- /dev/null +++ b/.changeset/gentle-news-laugh.md @@ -0,0 +1,5 @@ +--- +"create-cloudflare": patch +--- + +Relax empty directory check. Directories containing certain common config files and/or files created by an ide will be exempt from the pre-flight check diff --git a/packages/create-cloudflare/src/__tests__/common.test.ts b/packages/create-cloudflare/src/__tests__/common.test.ts index a4fa08df4c03..d6015f50e82d 100644 --- a/packages/create-cloudflare/src/__tests__/common.test.ts +++ b/packages/create-cloudflare/src/__tests__/common.test.ts @@ -1,6 +1,6 @@ import * as command from "helpers/command"; import { describe, expect, test, vi } from "vitest"; -import { isGitConfigured } from "../common"; +import { isAllowedExistingFile, isGitConfigured } from "../common"; import { validateProjectDirectory } from "../common"; function promisify(value: T) { @@ -71,3 +71,22 @@ describe("validateProjectDirectory", () => { expect(validateProjectDirectory("f".repeat(59), args)).toBeUndefined(); }); }); + +describe("isAllowedExistingFile", () => { + const allowed = [ + "LICENSE", + "LICENSE.md", + "license", + ".npmignore", + ".git", + ".DS_Store", + ]; + test.each(allowed)("%s", (val) => { + expect(isAllowedExistingFile(val)).toBe(true); + }); + + const disallowed = ["foobar", "potato"]; + test.each(disallowed)("%s", (val) => { + expect(isAllowedExistingFile(val)).toBe(false); + }); +}); diff --git a/packages/create-cloudflare/src/common.ts b/packages/create-cloudflare/src/common.ts index 8a81a0097465..6ea658afd8ec 100644 --- a/packages/create-cloudflare/src/common.ts +++ b/packages/create-cloudflare/src/common.ts @@ -39,10 +39,13 @@ export const validateProjectDirectory = ( // Validate that the directory is non-existent or empty const path = resolve(relativePath); const existsAlready = existsSync(path); - const isEmpty = existsAlready && readdirSync(path).length === 0; // allow existing dirs _if empty_ to ensure c3 is non-destructive - if (existsAlready && !isEmpty) { - return `Directory \`${relativePath}\` already exists and is not empty. Please choose a new name.`; + if (existsAlready) { + for (const file of readdirSync(path)) { + if (!isAllowedExistingFile(file)) { + return `Directory \`${relativePath}\` already exists and contains files that might conflict. Please choose a new name.`; + } + } } // Ensure the name is valid per the pages schema @@ -67,6 +70,51 @@ export const validateProjectDirectory = ( } }; +export const isAllowedExistingFile = (file: string) => { + // C3 shouldn't prevent a user from using an existing directory if it + // only contains benign config and/or other files from the following set + const allowedExistingFiles = new Set([ + ".DS_Store", + ".git", + ".gitattributes", + ".gitignore", + ".gitlab-ci.yml", + ".hg", + ".hgcheck", + ".hgignore", + ".idea", + ".npmignore", + ".travis.yml", + ".vscode", + "Thumbs.db", + "docs", + "mkdocs.yml", + "npm-debug.log", + "yarn-debug.log", + "yarn-error.log", + "yarnrc.yml", + ".yarn", + ".gitkeep", + ]); + + if (allowedExistingFiles.has(file)) return true; + + const allowedExistingPatters = [ + /readme(\.md)?$/i, + /license(\.md)?$/i, + /\.iml$/, + /^npm-debug\.log/, + /^yarn-debug\.log/, + /^yarn-error\.log/, + ]; + + for (const regex of allowedExistingPatters) { + if (regex.test(file)) return true; + } + + return false; +}; + export const setupProjectDirectory = (args: C3Args) => { // Crash if the directory already exists const path = resolve(args.projectName);