Repository: microsoft/react-native-test-app (ts)

Issue:
bug: ENOENT: no such file or directory, copyfile 'node_modules/react-native-test-app/example/.gitignore' -> 'example/.gitignore'
### What happened?

running `init-test-app` crashes due to behavior in npm. i'm no [expert on the matter](https://github.com/npm/npm/issues/1862) so take this solution with grain of salt. adding a `.npmignore` file will preserve the `.gitignore` file in the example folder. steps to reproduce are simple enough. i'm using node 18.9.1 with npm v8.19.1. run `npm install react-native-test-app` and you will see that under node_modules/react-native-test-app/example there is a .npmignore file but not the .gitignore file as expected. if you download the tarball from npm you will see the .gitignore as expected. simplest solution is to just add an empty .npmignore file to the example folder to prevent this behavior 🤷‍♂️ 

### Version

2.0.3

### What platforms are you seeing this issue on?

- [ ] Android
- [ ] iOS
- [X] macOS
- [ ] Windows

### System Information

```true
not relevant
```


### Steps to Reproduce

listed in description above ↑ 

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

Pull request:
fix: workaround for npm ignoring `.gitignore`
### Description

`npm install` seems to be renaming `.gitignore` files to `.npmignore`. This breaks our templates. As a workaround, we'll rename the files ourselves, from `.gitignore` to `_gitignore`, to avoid any further attempts to mutate the package.

Resolves #1228.

### Platforms affected

- [ ] Android
- [ ] iOS
- [ ] macOS
- [ ] Windows

### Test plan

```
npm pack
tar tf react-native-test-app-0.0.1-dev.tgz | grep gitignore
```

Verify that `.gitignore` files have been renamed to `_gitignore`, i.e.:

```
% tar tf react-native-test-app-0.0.1-dev.tgz | grep ignore
package/example/_gitignore
package/example/windows/_gitignore
```

Patch:
diff --git a/package.json b/package.json
index 4486235..45206e9 100644
--- a/package.json
+++ b/package.json
@@ -27,9 +27,9 @@
     "/android/app/src/!(test)",
     "/android/support/src",
     "/common",
-    "/example/.gitignore",
+    "/example/_gitignore",
     "/example/android/gradle*",
-    "/example/windows/.gitignore",
+    "/example/windows/_gitignore",
     "/example/{metro,react-native}.config.js",
     "/ios",
     "/macos",
@@ -64,6 +64,8 @@
     "lint:kt": "ktlint --android --relative 'android/app/src/**/*.kt'",
     "lint:rb": "bundle exec rubocop",
     "lint:swift": "swiftlint",
+    "prepack": "node scripts/pack.mjs pre",
+    "postpack": "node scripts/pack.mjs post",
     "set-react-version": "node scripts/set-react-version.mjs",
     "test:js": "jest",
     "test:rb": "bundle exec ruby -Ilib:test -e \"Dir.glob('test/test_*.rb').each { |file| require_relative file }\""
diff --git a/scripts/configure.js b/scripts/configure.js
index 550e49a..14cb321 100755
--- a/scripts/configure.js
+++ b/scripts/configure.js
@@ -408,6 +408,9 @@ const getConfig = (() => {
       typeof configuration === "undefined" ||
       "JEST_WORKER_ID" in process.env // skip caching when testing
     ) {
+      // Git ignore files are only renamed when published. `GIT_IGNORE_FILE`
+      // should therefore only be set during testing.
+      const gitignore = process.env["GIT_IGNORE_FILE"] || "_gitignore";
       const { name, templatePath, testAppPath, flatten, init } = params;
       const projectPathFlag = flatten ? " --project-path ." : "";
       const testAppRelPath = projectRelativePath(params);
@@ -421,7 +424,7 @@ const getConfig = (() => {
         common: {
           files: {
             ".gitignore": {
-              source: path.join(testAppPath, "example", ".gitignore"),
+              source: path.join(testAppPath, "example", gitignore),
             },
             ".watchmanconfig": {
               source: path.join(templateDir, "_watchmanconfig"),
@@ -631,12 +634,7 @@ const getConfig = (() => {
         windows: {
           files: {
             ".gitignore": {
-              source: path.join(
-                testAppPath,
-                "example",
-                "windows",
-                ".gitignore"
-              ),
+              source: path.join(testAppPath, "example", "windows", gitignore),
             },
           },
           oldFiles: [
diff --git a/scripts/pack.mjs b/scripts/pack.mjs
new file mode 100644
index 0000000..f092fbf
--- /dev/null
+++ b/scripts/pack.mjs
@@ -0,0 +1,41 @@
+// @ts-check
+import * as fs from "fs";
+import * as path from "path";
+
+const files = ["example/.gitignore", "example/windows/.gitignore"];
+
+/**
+ * Renames `.dotfile` to `_dotfile`.
+ * @param {string} p
+ * @returns {string}
+ */
+function renameDotFile(p) {
+  return path.join(path.dirname(p), "_" + path.basename(p).substring(1));
+}
+
+/**
+ * `npm install` seems to be renaming `.gitignore` files to `.npmignore`.
+ * This breaks our templates (see
+ * https://github.com/microsoft/react-native-test-app/issues/1228). As a
+ * workaround, we'll rename the files ourselves, from `.gitignore` to
+ * `_gitignore`, to avoid any further attempts to mutate the package.
+ */
+const { [2]: script } = process.argv;
+switch (script) {
+  case "pre": {
+    for (const f of files) {
+      fs.renameSync(f, renameDotFile(f));
+    }
+    break;
+  }
+
+  case "post": {
+    for (const f of files) {
+      fs.renameSync(renameDotFile(f), f);
+    }
+    break;
+  }
+
+  default:
+    throw new Error(`No such script: ${script}`);
+}


Test patch:
diff --git a/scripts/install-test-template.sh b/scripts/install-test-template.sh
index 20805ec..2fd3633 100755
--- a/scripts/install-test-template.sh
+++ b/scripts/install-test-template.sh
@@ -35,7 +35,7 @@ done
 npm pack
 
 yarn
-npx react-native init-test-app --destination template-example --name TemplateExample --platform "$platform"
+GIT_IGNORE_FILE=".gitignore" npx react-native init-test-app --destination template-example --name TemplateExample --platform "$platform"
 
 pushd template-example 1> /dev/null
 
diff --git a/test/android-test-app/test-app-util.test.js b/test/android-test-app/test-app-util.test.js
index 9a723bd..2a550ae 100644
--- a/test/android-test-app/test-app-util.test.js
+++ b/test/android-test-app/test-app-util.test.js
@@ -43,7 +43,12 @@ describe("test-app-util", () => {
     return Number(major) * 10000 + Number(minor) * 100 + Number(patch);
   }
 
+  beforeAll(() => {
+    process.env["GIT_IGNORE_FILE"] = ".gitignore";
+  });
+
   afterAll(() => {
+    delete process.env["GIT_IGNORE_FILE"];
     removeProject(defaultTestProject);
   });
 
diff --git a/test/configure/__snapshots__/gatherConfig.test.js.snap b/test/configure/__snapshots__/gatherConfig.test.js.snap
index 45bbf47..d185a91 100644
--- a/test/configure/__snapshots__/gatherConfig.test.js.snap
+++ b/test/configure/__snapshots__/gatherConfig.test.js.snap
@@ -5,7 +5,7 @@ Object {
   "dependencies": Object {},
   "files": Object {
     ".gitignore": Object {
-      "source": "example/.gitignore",
+      "source": "example/_gitignore",
     },
     ".watchmanconfig": Object {
       "source": "node_modules/react-native/template/_watchmanconfig",
@@ -64,7 +64,7 @@ Object {
   "dependencies": Object {},
   "files": Object {
     ".gitignore": Object {
-      "source": "example/.gitignore",
+      "source": "example/_gitignore",
     },
     ".watchmanconfig": Object {
       "source": "node_modules/react-native/template/_watchmanconfig",
@@ -195,7 +195,7 @@ Object {
   "dependencies": Object {},
   "files": Object {
     ".gitignore": Object {
-      "source": "example/.gitignore",
+      "source": "example/_gitignore",
     },
     ".watchmanconfig": Object {
       "source": "node_modules/react-native/template/_watchmanconfig",
@@ -204,7 +204,7 @@ Object {
       "source": "node_modules/react-native/template/babel.config.js",
     },
     "common/.gitignore": Object {
-      "source": "example/.gitignore",
+      "source": "example/_gitignore",
     },
     "common/.watchmanconfig": Object {
       "source": "node_modules/react-native/template/_watchmanconfig",
@@ -293,7 +293,7 @@ Object {
   "dependencies": Object {},
   "files": Object {
     ".gitignore": Object {
-      "source": "example/.gitignore",
+      "source": "example/_gitignore",
     },
     ".watchmanconfig": Object {
       "source": "node_modules/react-native/template/_watchmanconfig",
@@ -366,7 +366,7 @@ Object {
   },
   "files": Object {
     ".gitignore": Object {
-      "source": "example/.gitignore",
+      "source": "example/_gitignore",
     },
     ".watchmanconfig": Object {
       "source": "node_modules/react-native/template/_watchmanconfig",
@@ -482,7 +482,7 @@ module.exports = {
 };
 ",
     "windows/.gitignore": Object {
-      "source": "example/windows/.gitignore",
+      "source": "example/windows/_gitignore",
     },
   },
   "oldFiles": Array [
@@ -517,7 +517,7 @@ Object {
   "dependencies": Object {},
   "files": Object {
     ".gitignore": Object {
-      "source": "example/.gitignore",
+      "source": "example/_gitignore",
     },
     ".watchmanconfig": Object {
       "source": "node_modules/react-native/template/_watchmanconfig",