mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
Co-authored-by: 190n <7763597+190n@users.noreply.github.com> Co-authored-by: Zack Radisic <56137411+zackradisic@users.noreply.github.com> Co-authored-by: pfg <pfg@pfg.pw> Co-authored-by: pfgithub <6010774+pfgithub@users.noreply.github.com> Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
911 lines
28 KiB
TypeScript
911 lines
28 KiB
TypeScript
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;
|
||
}
|