Skip to content

Commit 3ae4b02

Browse files
DavidJGrimsleydanstepanov
authored andcommitted
test: address windows harness review feedback
1 parent 123932d commit 3ae4b02

4 files changed

Lines changed: 109 additions & 85 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ package-lock.json
1313
cli/my-expo-app
1414
.playwright-mcp
1515
cli/my-*-app*
16+
cli/myTestProject-*

cli/__tests__/cli-integration.test.ts

Lines changed: 81 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Bun from 'bun';
22

33
import { version } from '../package.json';
44

5-
import { test, expect, afterEach } from 'bun:test';
5+
import { test, expect } from 'bun:test';
66
import * as path from 'node:path';
77
import * as fs from 'node:fs/promises';
88

@@ -51,7 +51,8 @@ const packageManagers = process.env.ALL_PACKAGE_MANAGERS
5151

5252
const skipSnapshots = process.env.SKIP_SNAPSHOTS === '1';
5353
const shouldSkipInstallsInTests = process.platform === 'win32';
54-
let currentProjectName = 'myTestProject';
54+
const retryableCleanupErrorCodes = new Set(['EBUSY', 'EPERM', 'ENOTEMPTY']);
55+
const snapshotIgnoredEntries = new Set(['bun.lock', 'bun.lockb', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock']);
5556

5657
test(`outputs version`, async () => {
5758
const output = await cli([`--version`]);
@@ -110,15 +111,13 @@ const popularCombinations = [
110111
...reactNavigationCombinations
111112
];
112113

113-
const getPathToProject = () => `./${currentProjectName}`;
114-
115-
const cleanupProject = async () => {
114+
const cleanupProject = async (pathToProject: string) => {
116115
for (let attempt = 0; attempt < 5; attempt++) {
117116
try {
118-
await fs.rm(getPathToProject(), { recursive: true, force: true });
117+
await fs.rm(pathToProject, { recursive: true, force: true });
119118
return;
120119
} catch (error) {
121-
if ((error as NodeJS.ErrnoException).code !== 'EBUSY' || attempt === 4) {
120+
if (!retryableCleanupErrorCodes.has((error as NodeJS.ErrnoException).code ?? '') || attempt === 4) {
122121
throw error;
123122
}
124123

@@ -127,30 +126,25 @@ const cleanupProject = async () => {
127126
}
128127
};
129128

130-
afterEach(async () => {
131-
await cleanupProject();
132-
});
133-
134-
const listProjectFiles = async (dir: string, displayPath: string): Promise<string[]> => {
129+
const listProjectFiles = async (dir: string, displayPath: string, pathToProject: string): Promise<string[]> => {
135130
const entries = await fs.readdir(dir, { withFileTypes: true });
136131
const files: string[] = [];
137132

138133
for (const entry of entries) {
139134
const childDisplayPath = `${displayPath}/${entry.name}`;
140135

141136
if (
142-
childDisplayPath.startsWith(`${getPathToProject()}/node_modules`) ||
143-
childDisplayPath.startsWith(`${getPathToProject()}/.git`) ||
144-
entry.name === 'bun.lock' ||
145-
entry.name === 'bun.lockb'
137+
childDisplayPath.startsWith(`${pathToProject}/node_modules`) ||
138+
childDisplayPath.startsWith(`${pathToProject}/.git`) ||
139+
snapshotIgnoredEntries.has(entry.name)
146140
) {
147141
continue;
148142
}
149143

150144
files.push(childDisplayPath);
151145

152146
if (entry.isDirectory()) {
153-
files.push(...(await listProjectFiles(path.join(dir, entry.name), childDisplayPath)));
147+
files.push(...(await listProjectFiles(path.join(dir, entry.name), childDisplayPath, pathToProject)));
154148
}
155149
}
156150

@@ -167,84 +161,88 @@ for (const packageManager of packageManagers) {
167161
: requestedFlags;
168162

169163
test(`generates a project with ${requestedFlags.join(' ')}`, async () => {
170-
currentProjectName = `myTestProject-${packageManager}-${index}`;
171-
const pathToProject = getPathToProject();
164+
const projectName = `myTestProject-${packageManager}-${index}`;
165+
const pathToProject = `./${projectName}`;
172166

173-
const output = await generateProject({
174-
projectName: currentProjectName,
175-
flags: effectiveFlags
176-
});
167+
try {
168+
const output = await generateProject({
169+
projectName,
170+
flags: effectiveFlags
171+
});
177172

178-
expect(output).toContain(packageManager);
173+
expect(output).toContain(packageManager);
179174

180-
if (!effectiveFlags.includes('--no-install')) {
181-
expect(output).toContain('Installing dependencies');
182-
}
175+
if (!effectiveFlags.includes('--no-install')) {
176+
expect(output).toContain('Installing dependencies');
177+
}
183178

184-
const pkgjson = await Bun.file(`${pathToProject}/package.json`).json();
185-
186-
const pkgJsonWithoutVersions = {
187-
...pkgjson,
188-
name: 'myTestProject',
189-
dependencies: Object.keys(pkgjson.dependencies).reduce((acc, key) => {
190-
return {
191-
...acc,
192-
[key]: ''
193-
};
194-
}, {}),
195-
devDependencies: Object.keys(pkgjson.devDependencies).reduce((acc, key) => {
196-
return {
197-
...acc,
198-
[key]: ''
199-
};
200-
}, {})
201-
};
202-
203-
if (!skipSnapshots) {
204-
expect(pkgJsonWithoutVersions).toMatchSnapshot(`${requestedFlags.join(', ')}-package-json`);
205-
}
179+
const pkgjson = await Bun.file(`${pathToProject}/package.json`).json();
180+
181+
const pkgJsonWithoutVersions = {
182+
...pkgjson,
183+
name: 'myTestProject',
184+
dependencies: Object.keys(pkgjson.dependencies).reduce((acc, key) => {
185+
return {
186+
...acc,
187+
[key]: ''
188+
};
189+
}, {}),
190+
devDependencies: Object.keys(pkgjson.devDependencies).reduce((acc, key) => {
191+
return {
192+
...acc,
193+
[key]: ''
194+
};
195+
}, {})
196+
};
197+
198+
if (!skipSnapshots) {
199+
expect(pkgJsonWithoutVersions).toMatchSnapshot(`${requestedFlags.join(', ')}-package-json`);
200+
}
206201

207-
const cesconfigText = await Bun.file(`${pathToProject}/cesconfig.jsonc`).text();
208-
// Strip single-line comments from JSONC
209-
const cleanedText = cesconfigText.replace(/^\s*\/\/.*$/gm, '');
210-
const cesconfig = JSON.parse(cleanedText);
211-
212-
const cesconfigWithoutOS = {
213-
...cesconfig,
214-
projectName: 'myTestProject',
215-
cesVersion: undefined,
216-
os: {},
217-
packageManager: { ...cesconfig.packageManager, version: undefined },
218-
flags: {
219-
...cesconfig.flags,
220-
noInstall: requestedFlags.includes('--no-install'),
221-
publish: false
202+
const cesconfigText = await Bun.file(`${pathToProject}/cesconfig.jsonc`).text();
203+
// Strip single-line comments from JSONC
204+
const cleanedText = cesconfigText.replace(/^\s*\/\/.*$/gm, '');
205+
const cesconfig = JSON.parse(cleanedText);
206+
207+
const cesconfigWithoutOS = {
208+
...cesconfig,
209+
projectName: 'myTestProject',
210+
cesVersion: undefined,
211+
os: {},
212+
packageManager: { ...cesconfig.packageManager, version: undefined },
213+
flags: {
214+
...cesconfig.flags,
215+
noInstall: requestedFlags.includes('--no-install'),
216+
publish: false
217+
}
218+
};
219+
220+
if (!skipSnapshots) {
221+
expect(cesconfigWithoutOS).toMatchSnapshot(`${requestedFlags.join(', ')}-ces-config-json`);
222222
}
223-
};
224223

225-
if (!skipSnapshots) {
226-
expect(cesconfigWithoutOS).toMatchSnapshot(`${requestedFlags.join(', ')}-ces-config-json`);
227-
}
224+
// sort the file list for consistent snapshotting
225+
const sortedFileList = [pathToProject, ...(await listProjectFiles(pathToProject, pathToProject, pathToProject))]
226+
.map((filePath) => filePath.replaceAll(projectName, 'myTestProject'))
227+
.toSorted((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }));
228228

229-
// sort the file list for consistent snapshotting
230-
const sortedFileList = [pathToProject, ...(await listProjectFiles(pathToProject, pathToProject))]
231-
.map((filePath) => filePath.replaceAll(currentProjectName, 'myTestProject'))
232-
.toSorted((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }));
229+
if (!skipSnapshots) {
230+
expect(sortedFileList).toMatchSnapshot(`${requestedFlags.join(', ')}-file-list`);
231+
}
233232

234-
if (!skipSnapshots) {
235-
expect(sortedFileList).toMatchSnapshot(`${requestedFlags.join(', ')}-file-list`);
236-
}
233+
// typecheck only works if we have packages installed
234+
if (!effectiveFlags.includes('--no-install')) {
235+
const { stderr, stdout, exitCode } = await Bun.$`cd ${projectName} && bun run tsc --noEmit`;
237236

238-
// typecheck only works if we have packages installed
239-
if (!effectiveFlags.includes('--no-install')) {
240-
const { stderr, stdout, exitCode } = await Bun.$`cd ${currentProjectName} && bun run tsc --noEmit`;
237+
if (exitCode !== 0) {
238+
console.warn('stdout', stdout.toString());
239+
console.warn('stderr', stderr.toString());
240+
}
241241

242-
if (exitCode !== 0) {
243-
console.warn('stdout', stdout.toString());
244-
console.warn('stderr', stderr.toString());
242+
expect(exitCode).toBe(0);
245243
}
246-
247-
expect(exitCode).toBe(0);
244+
} finally {
245+
await cleanupProject(pathToProject);
248246
}
249247
});
250248
}

packages/rn-new/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
],
1616
"scripts": {
1717
"build": "bun run clean && bun run compile",
18-
"clean": "node -e \"const fs=require('fs'); fs.rmSync('bin', { recursive: true, force: true });\"",
19-
"compile": "node -e \"const fs=require('fs'); const path=require('path'); const out=path.join('bin','rn-new.js'); const contents=\\`#!/usr/bin/env node\\n\\ntry {\\n require('create-expo-stack/bin/create-expo-stack.js');\\n} catch (error) {\\n console.error('Error: Could not find create-expo-stack package.');\\n console.error('Please ensure create-expo-stack is installed globally.');\\n process.exit(1);\\n}\\n\\`; fs.mkdirSync(path.dirname(out), { recursive: true }); fs.writeFileSync(out, contents); fs.chmodSync(out, 0o755);\"",
18+
"clean": "node ./scripts/build-bin.mjs clean",
19+
"compile": "node ./scripts/build-bin.mjs compile",
2020
"prepublishOnly": "bun run build"
2121
},
2222
"dependencies": {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
4+
const command = process.argv[2];
5+
const outputFile = path.join('bin', 'rn-new.js');
6+
const outputContents = `#!/usr/bin/env node
7+
8+
try {
9+
require('create-expo-stack/bin/create-expo-stack.js');
10+
} catch (error) {
11+
console.error('Error: Could not find create-expo-stack package.');
12+
console.error('Please ensure create-expo-stack is installed globally.');
13+
process.exit(1);
14+
}
15+
`;
16+
17+
if (command === 'clean') {
18+
fs.rmSync(path.dirname(outputFile), { recursive: true, force: true });
19+
} else if (command === 'compile') {
20+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
21+
fs.writeFileSync(outputFile, outputContents);
22+
fs.chmodSync(outputFile, 0o755);
23+
} else {
24+
throw new Error(`Unknown build-bin command: ${command}`);
25+
}

0 commit comments

Comments
 (0)