import { $ } from "bun";
import { patchInternals } from "bun:internal-for-testing";
import { describe, expect, test } from "bun:test";
import fs from "fs/promises";
import { tempDirWithFiles as __tempDirWithFiles } from "harness";
import { join as __join } from "node:path";
const { parse, apply, makeDiff } = patchInternals;

const makeDiffJs = async (aFolder: string, bFolder: string, cwd: string): Promise<string> => {
  const { stdout, stderr } =
    await $`git -c core.safecrlf=false diff --src-prefix=a/ --dst-prefix=b/ --ignore-cr-at-eol --irreversible-delete --full-index --no-index ${aFolder} ${bFolder}`
      .env(
        // https://github.com/pnpm/pnpm/blob/45f4262f0369cadf41cea3b823e8932eae157c4b/patching/plugin-commands-patching/src/patchCommit.ts#L117
        {
          ...process.env,
          // #region Predictable output
          // These variables aim to ignore the global git config so we get predictable output
          // https://git-scm.com/docs/git#Documentation/git.txt-codeGITCONFIGNOSYSTEMcode
          GIT_CONFIG_NOSYSTEM: "1",
          HOME: "",
          XDG_CONFIG_HOME: "",
          USERPROFILE: "",
        },
      )
      .quiet()
      .cwd(cwd)
      // For some reason git diff returns exit code 1 when it is not an error
      // So we must check that there is no stderr output instead of the exit code
      // to determine if the command was successful
      .throws(false);

  if (stderr.length > 0) throw new Error(stderr.toString());

  const patch = stdout.toString();

  return patch
    .replace(new RegExp(`(a|b)(${escapeStringRegexp(`/${removeTrailingAndLeadingSlash(aFolder)}/`)})`, "g"), "$1/")
    .replace(new RegExp(`(a|b)${escapeStringRegexp(`/${removeTrailingAndLeadingSlash(bFolder)}/`)}`, "g"), "$1/")
    .replace(new RegExp(escapeStringRegexp(`${aFolder}/`), "g"), "")
    .replace(new RegExp(escapeStringRegexp(`${bFolder}/`), "g"), "");
  // .replace(/\n\\ No newline at end of file\n$/, "\n");
};

const tempDirWithFiles: typeof __tempDirWithFiles =
  process.platform === "win32"
    ? (a, b) => __tempDirWithFiles(a.replaceAll("\\", "/"), b).replaceAll("\\", "/")
    : __tempDirWithFiles;
const join =
  process.platform === "win32"
    ? (...strings: string[]): string => __join(...strings.map(s => s.replaceAll("\\", "/"))).replaceAll("\\", "/")
    : __join;

// Recurse through a nested object, and return a copy where for any object where the only two
// properties are a numeric `capacity` and an array `items`, the capacity has been deleted. Meant to
// be used on serialized Zig structs that contain ArrayLists, because the allocation strategy of
// ArrayList can change and result in a different `capacity` without changing the interpretation of
// the ArrayList's value.
function removeCapacity(patch: any): any {
  if (Array.isArray(patch)) {
    return patch.map(removeCapacity);
  } else if (patch !== null && typeof patch == "object") {
    const keys = Object.keys(patch);
    keys.sort();
    if (keys.length == 2 && keys[0] == "capacity" && keys[1] == "items") {
      if (typeof patch.capacity == "number" && Array.isArray(patch.items)) {
        // this looks like an ArrayList, so delete the capacity because it is unstable
        return { items: patch.items.map(removeCapacity) };
      }
    }
    // ordinary object, so just apply to all the children
    const result = {};
    for (const k of keys) {
      result[k] = removeCapacity(patch[k]);
    }
    return result;
  }
  return patch;
}

describe("apply", () => {
  test("edgecase", async () => {
    const newcontents = "module.exports = x => x % 420 === 0;";
    const tempdir2 = tempDirWithFiles("patch-test2", {
      ".bun/install/cache/is-even@1.0.0": {
        "index.js": "module.exports = x => x % 2 === 0;",
      },
    });
    const tempdir = tempDirWithFiles("patch-test", {
      a: {},
      ["node_modules/is-even"]: {
        "index.js": newcontents,
      },
    });

    const patchfile = await makeDiff(
      `${tempdir2}/.bun/install/cache/is-even@1.0.0`,
      `${tempdir}/node_modules/is-even`,
      tempdir,
    );

    await apply(patchfile, `${tempdir}/node_modules/is-even`);
    expect(await fs.readFile(`${tempdir}/node_modules/is-even/index.js`).then(b => b.toString())).toBe(newcontents);
  });

  test("empty", async () => {
    const tempdir = tempDirWithFiles("patch-test", {
      a: {},
      b: {},
    });

    const afolder = join(tempdir, "a");
    const bfolder = join(tempdir, "b");

    const patchfile = await makeDiff(afolder, bfolder, tempdir);
    expect(patchfile).toBe("");

    await apply(patchfile, afolder);

    expect(await fs.readdir(afolder)).toEqual([]);
  });

  describe("deletion", () => {
    test("simple", async () => {
      const files = {
        "a/hey.txt": "hello!",
        "a/byebye.txt": "goodbye :(",
        "b/hey.txt": "hello!",
      };
      const tempdir = tempDirWithFiles("patch-test", files);

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      console.log("makeDiff args", afolder, bfolder);
      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      console.log("PATCHFILE", patchfile);
      console.log("afolder", afolder);
      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "hey.txt")}`.cwd(tempdir).text()).toBe(files["b/hey.txt"]);
      expect(
        await $`if ls -d ${join(afolder, "byebye.txt")}; then echo oops; else echo okay!; fi;`.cwd(tempdir).text(),
      ).toBe("okay!\n");
    });
  });

  describe("creation", () => {
    test("simple", async () => {
      const files = {
        "a": {},
        "b/newfile.txt": "hey im new here!",
      };
      const tempdir = tempDirWithFiles("patch-test", files);

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "newfile.txt")}`.cwd(tempdir).text()).toBe(files["b/newfile.txt"]);
    });

    test("multi-line", async () => {
      const files = {
        "a": {},
        "b/newfile.txt": "hey im new here!\nhello",
      };
      const tempdir = tempDirWithFiles("patch-test", files);

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "newfile.txt")}`.cwd(tempdir).text()).toBe(files["b/newfile.txt"]);
    });
  });

  describe("rename", () => {
    test("files", async () => {
      const files = {
        "a/hey.txt": "hello!",
        "b/heynow.txt": "hello!",
      };
      const tempdir = tempDirWithFiles("patch-test", files);

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "heynow.txt")}`.cwd(tempdir).text()).toBe(files["b/heynow.txt"]);
      expect(
        await $`if ls -d ${join(afolder, "hey.txt")}; then echo oops; else echo okay!; fi;`.cwd(tempdir).text(),
      ).toBe("okay!\n");
    });

    test("folders", async () => {
      const files = {
        "a/foo/hey.txt": "hello!",
        "a/foo/hi.txt": "hello!",
        "a/foo/lmao.txt": "lmao!",
        "b/foo": {},
        "b/bar/hey.txt": "hello!",
        "b/bar/hi.txt": "hello!",
        "b/bar/lmao.txt": "lmao!",
      };
      const tempdir = tempDirWithFiles("patch-test", files);

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      // Should we remove the folder if it's empty? Technically running `git apply` does this
      // But git does not track empty directories so it's not really a problem
      // expect(
      //   await $`if ls -d ${join(afolder, "foo")}; then echo should not exist!; else echo okay!; fi;`
      //     .cwd(tempdir)
      //     .text(),
      // ).toBe("okay!\n");

      expect(await $`cat ${join(afolder, "bar", "hey.txt")}`.cwd(tempdir).text()).toBe(files["b/bar/hey.txt"]);
      expect(await $`cat ${join(afolder, "bar", "hi.txt")}`.cwd(tempdir).text()).toBe(files["b/bar/hi.txt"]);
      expect(await $`cat ${join(afolder, "bar", "lmao.txt")}`.cwd(tempdir).text()).toBe(files["b/bar/lmao.txt"]);
      expect(
        await $`ls ${join(afolder, "bar")}`
          .cwd(tempdir)
          .text()
          .then((out: string) =>
            out
              .split("\n")
              .filter(x => x !== "")
              .sort(),
          ),
      ).toEqual(["hey.txt", "hi.txt", "lmao.txt"].sort());
    });
  });

  describe("mode change", () => {
    // chmod doesn't do anything on windows so skiip
    test.if(process.platform !== "win32")("simple", async () => {
      const files = {
        "a/hi.txt": "hello!",
        "b/hi.txt": "hi!",
      };

      const tempdir = tempDirWithFiles("patch-test", files);

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      await fs.chmod(join(bfolder, "hi.txt"), 0o755);

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "hi.txt")}`.cwd(tempdir).text()).toBe(files["b/hi.txt"]);
      const stat = await fs.stat(join(afolder, "hi.txt"));
      expect((stat.mode & parseInt("777", 8)).toString(8)).toBe("755");
    });
  });

  describe("patch", () => {
    test("simple insertion", async () => {
      const afile = `hello!\n`;
      const bfile = `hello!\nwassup?\n`;

      const tempdir = tempDirWithFiles("patch-test", {
        "a/hello.txt": afile,
        "b/hello.txt": bfile,
      });

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
    });

    test("simple deletion", async () => {
      const afile = `hello!\nwassup?\n`;
      const bfile = `hello!\n`;

      const tempdir = tempDirWithFiles("patch-test", {
        "a/hello.txt": afile,
        "b/hello.txt": bfile,
      });

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
    });

    test("multi insertion", async () => {
      const afile = `hello!\n`;
      const bfile = `lol\nhello!\nwassup?\n`;

      const tempdir = tempDirWithFiles("patch-test", {
        "a/hello.txt": afile,
        "b/hello.txt": bfile,
      });

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
    });

    test("multi deletion", async () => {
      const afile = `hello!\nwassup?\nlmao\n`;
      const bfile = `wassup?\n`;

      const tempdir = tempDirWithFiles("patch-test", {
        "a/hello.txt": afile,
        "b/hello.txt": bfile,
      });

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
    });

    test("multi-hunk insertion", async () => {
      const afile = `0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20`;
      const bfile = `0
0.5 hi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
19.5 lol hi
20`;

      const tempdir = tempDirWithFiles("patch-test", {
        "a/hello.txt": afile,
        "b/hello.txt": bfile,
      });

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
    });

    test("multi-hunk deletion", async () => {
      const bfile = `0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20`;
      const afile = `0
0.5 hi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
19.5 lol hi
20`;

      const tempdir = tempDirWithFiles("patch-test", {
        "a/hello.txt": afile,
        "b/hello.txt": bfile,
      });

      const afolder = join(tempdir, "a");
      const bfolder = join(tempdir, "b");

      const patchfile = await makeDiff(afolder, bfolder, tempdir);

      await apply(patchfile, afolder);

      expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
    });
  });

  describe("No newline at end of file", () => {
    // TODO: simple, multiline, multiple hunks
  });
});

describe("parse", () => {
  test("works for a simple case", () => {
    expect(removeCapacity(JSON.parse(parse(patch)))).toEqual({
      "parts": {
        "items": [
          {
            "file_patch": {
              "path": "banana.ts",
              "hunks": {
                "items": [
                  {
                    "header": { "original": { "start": 1, "len": 5 }, "patched": { "start": 1, "len": 5 } },
                    "parts": {
                      "items": [
                        {
                          "type": "context",
                          "lines": { "items": ["this", "is", ""] },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "deletion",
                          "lines": { "items": ["a"] },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "insertion",
                          "lines": { "items": [""] },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "context",
                          "lines": { "items": ["file"] },
                          "no_newline_at_end_of_file": false,
                        },
                      ],
                    },
                  },
                ],
              },
              "before_hash": "2de83dd",
              "after_hash": "842652c",
            },
          },
        ],
      },
    });
  });

  test("fails when the patch file has invalid headers", () => {
    expect(() => parse(invalidHeaders1)).toThrow();
    expect(() => parse(invalidHeaders2)).toThrow();
    expect(() => parse(invalidHeaders3)).toThrow();
    expect(() => parse(invalidHeaders4)).toThrow();
    expect(() => parse(invalidHeaders5)).toThrow();
  });

  test("is OK when blank lines are accidentally created", () => {
    expect(parse(accidentalBlankLine)).toEqual(parse(patch));
  });

  test(`can handle files with CRLF line breaks`, () => {
    expect(removeCapacity(JSON.parse(parse(crlfLineBreaks)))).toEqual({
      "parts": {
        "items": [
          {
            "file_creation": {
              "path": "banana.ts",
              "mode": "non_executable",
              "hunk": {
                "header": { "original": { "start": 1, "len": 0 }, "patched": { "start": 1, "len": 1 } },
                "parts": {
                  "items": [
                    {
                      "type": "insertion",
                      "lines": { "items": ["this is a new file\r"] },
                      "no_newline_at_end_of_file": false,
                    },
                  ],
                },
              },
              "hash": "3e1267f",
            },
          },
        ],
      },
    });
  });

  test("works", () => {
    expect(removeCapacity(JSON.parse(parse(modeChangeAndModifyAndRename)))).toEqual({
      "parts": {
        "items": [
          { "file_rename": { "from_path": "numbers.txt", "to_path": "banana.txt" } },
          { "file_mode_change": { "path": "banana.txt", "old_mode": "non_executable", "new_mode": "executable" } },
          {
            "file_patch": {
              "path": "banana.txt",
              "hunks": {
                "items": [
                  {
                    "header": { "original": { "start": 1, "len": 4 }, "patched": { "start": 1, "len": 4 } },
                    "parts": {
                      "items": [
                        {
                          "type": "deletion",
                          "lines": { "items": ["one"] },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "insertion",
                          "lines": { "items": ["ne"] },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "context",
                          "lines": { "items": ["", "two", ""] },
                          "no_newline_at_end_of_file": false,
                        },
                      ],
                    },
                  },
                ],
              },
              "before_hash": "fbf1785",
              "after_hash": "92d2c5f",
            },
          },
        ],
      },
    });
  });

  test("parses old-style patches", () => {
    expect(removeCapacity(JSON.parse(parse(oldStylePatch)))).toEqual({
      "parts": {
        "items": [
          {
            "file_patch": {
              "path": "node_modules/graphql/utilities/assertValidName.js",
              "hunks": {
                "items": [
                  {
                    "header": { "original": { "start": 41, "len": 10 }, "patched": { "start": 41, "len": 11 } },
                    "parts": {
                      "items": [
                        {
                          "type": "context",
                          "lines": {
                            "items": [
                              " */",
                              "function isValidNameError(name, node) {",
                              "  !(typeof name === 'string') ? (0, _invariant2.default)(0, 'Expected string') : void 0;",
                            ],
                          },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "deletion",
                          "lines": {
                            "items": [
                              "  if (name.length > 1 && name[0] === '_' && name[1] === '_') {",
                              "    return new _GraphQLError.GraphQLError('Name \"' + name + '\" must not begin with \"__\", which is reserved by ' + 'GraphQL introspection.', node);",
                              "  }",
                            ],
                          },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "insertion",
                          "lines": {
                            "items": [
                              "  // if (name.length > 1 && name[0] === '_' && name[1] === '_') {",
                              "  //   return new _GraphQLError.GraphQLError('Name \"' + name + '\" must not begin with \"__\", which is reserved by ' + 'GraphQL introspection.', node);",
                              "  // }",
                            ],
                          },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "context",
                          "lines": {
                            "items": [
                              "  if (!NAME_RX.test(name)) {",
                              "    return new _GraphQLError.GraphQLError('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"' + name + '\" does not.', node);",
                              "  }",
                            ],
                          },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "insertion",
                          "lines": { "items": [""] },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "context",
                          "lines": { "items": ["}"] },
                          "no_newline_at_end_of_file": true,
                        },
                      ],
                    },
                  },
                ],
              },
              "before_hash": null,
              "after_hash": null,
            },
          },
          {
            "file_patch": {
              "path": "node_modules/graphql/utilities/assertValidName.mjs",
              "hunks": {
                "items": [
                  {
                    "header": { "original": { "start": 29, "len": 9 }, "patched": { "start": 29, "len": 9 } },
                    "parts": {
                      "items": [
                        {
                          "type": "context",
                          "lines": {
                            "items": [
                              " */",
                              "export function isValidNameError(name, node) {",
                              "  !(typeof name === 'string') ? invariant(0, 'Expected string') : void 0;",
                            ],
                          },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "deletion",
                          "lines": {
                            "items": [
                              "  if (name.length > 1 && name[0] === '_' && name[1] === '_') {",
                              "    return new GraphQLError('Name \"' + name + '\" must not begin with \"__\", which is reserved by ' + 'GraphQL introspection.', node);",
                              "  }",
                            ],
                          },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "insertion",
                          "lines": {
                            "items": [
                              "  // if (name.length > 1 && name[0] === '_' && name[1] === '_') {",
                              "  //   return new GraphQLError('Name \"' + name + '\" must not begin with \"__\", which is reserved by ' + 'GraphQL introspection.', node);",
                              "  // }",
                            ],
                          },
                          "no_newline_at_end_of_file": false,
                        },
                        {
                          "type": "context",
                          "lines": {
                            "items": [
                              "  if (!NAME_RX.test(name)) {",
                              "    return new GraphQLError('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"' + name + '\" does not.', node);",
                              "  }",
                            ],
                          },
                          "no_newline_at_end_of_file": false,
                        },
                      ],
                    },
                  },
                ],
              },
              "before_hash": null,
              "after_hash": null,
            },
          },
        ],
      },
    });
  });
});

const patch = `diff --git a/banana.ts b/banana.ts\nindex 2de83dd..842652c 100644\n--- a/banana.ts\n+++ b/banana.ts\n@@ -1,5 +1,5 @@\n this\n is\n \n-a\n+\n file\n`;

const invalidHeaders1 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,5 +1,4 @@
 this
 is

-a
+
 file
`;

const invalidHeaders2 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,4 +1,5 @@
 this
 is

-a
+
 file
`;

const invalidHeaders3 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,0 +1,5 @@
 this
 is

-a
+
 file
`;
const invalidHeaders4 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,5 +1,0 @@
 this
 is

-a
+
 file
`;

const invalidHeaders5 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,5 +1,5@@
 this
 is

-a
+
 file
`;

const accidentalBlankLine = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,5 +1,5 @@
 this
 is

-a
+
 file
`;

const crlfLineBreaks = /* diff */ `diff --git a/banana.ts b/banana.ts
new file mode 100644
index 0000000..3e1267f
--- /dev/null
+++ b/banana.ts
@@ -0,0 +1 @@
+this is a new file
`.replace(/\n/g, "\r\n");

const modeChangeAndModifyAndRename = /* diff */ `diff --git a/numbers.txt b/banana.txt
old mode 100644
new mode 100755
similarity index 96%
rename from numbers.txt
rename to banana.txt
index fbf1785..92d2c5f
--- a/numbers.txt
+++ b/banana.txt
@@ -1,4 +1,4 @@
-one
+ne

 two

`;

const oldStylePatch = /* diff */ `patch-package
--- a/node_modules/graphql/utilities/assertValidName.js
+++ b/node_modules/graphql/utilities/assertValidName.js
@@ -41,10 +41,11 @@ function assertValidName(name) {
  */
 function isValidNameError(name, node) {
   !(typeof name === 'string') ? (0, _invariant2.default)(0, 'Expected string') : void 0;
-  if (name.length > 1 && name[0] === '_' && name[1] === '_') {
-    return new _GraphQLError.GraphQLError('Name "' + name + '" must not begin with "__", which is reserved by ' + 'GraphQL introspection.', node);
-  }
+  // if (name.length > 1 && name[0] === '_' && name[1] === '_') {
+  //   return new _GraphQLError.GraphQLError('Name "' + name + '" must not begin with "__", which is reserved by ' + 'GraphQL introspection.', node);
+  // }
   if (!NAME_RX.test(name)) {
     return new _GraphQLError.GraphQLError('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "' + name + '" does not.', node);
   }
+
 }
\\ No newline at end of file
--- a/node_modules/graphql/utilities/assertValidName.mjs
+++ b/node_modules/graphql/utilities/assertValidName.mjs
@@ -29,9 +29,9 @@ export function assertValidName(name) {
  */
 export function isValidNameError(name, node) {
   !(typeof name === 'string') ? invariant(0, 'Expected string') : void 0;
-  if (name.length > 1 && name[0] === '_' && name[1] === '_') {
-    return new GraphQLError('Name "' + name + '" must not begin with "__", which is reserved by ' + 'GraphQL introspection.', node);
-  }
+  // if (name.length > 1 && name[0] === '_' && name[1] === '_') {
+  //   return new GraphQLError('Name "' + name + '" must not begin with "__", which is reserved by ' + 'GraphQL introspection.', node);
+  // }
   if (!NAME_RX.test(name)) {
     return new GraphQLError('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "' + name + '" does not.', node);
   }
`;
function escapeStringRegexp(string: string) {
  if (typeof string !== "string") {
    throw new TypeError("Expected a string");
  }

  // Escape characters with special meaning either inside or outside character sets.
  // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
  return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
}

function removeTrailingAndLeadingSlash(p: string): string {
  if (p[0] === "/" || p.endsWith("/")) {
    return p.replace(/^\/|\/$/g, "");
  }
  return p;
}
