diff --git a/__e2e__/init.test.js b/__e2e__/init.test.js index 747166aa2..b39e1af35 100644 --- a/__e2e__/init.test.js +++ b/__e2e__/init.test.js @@ -55,7 +55,13 @@ test('init --template', () => { // make sure we don't leave garbage expect(fs.readdirSync(DIR)).toEqual(['TestInit']); - expect(fs.readdirSync(path.join(DIR, 'TestInit'))).toEqual(templateFiles); + + let dirFiles = fs.readdirSync(path.join(DIR, 'TestInit')); + expect(dirFiles.length).toEqual(templateFiles.length); + + for (const templateFile of templateFiles) { + expect(dirFiles.includes(templateFile)).toBe(true); + } const pkgJson = require(path.join(DIR, 'TestInit', 'package.json')); expect(pkgJson).toMatchSnapshot( @@ -74,11 +80,15 @@ test('init --template file:/tmp/custom/template', () => { 'custom/template/template-dir/package.json': '{}', 'custom/template/template-dir/empty': '', }); + let templatePath = path.resolve(DIR, 'custom', 'template'); + if (process.platform === 'win32') { + templatePath = templatePath.split('\\').join('/'); + } const {stdout} = run(DIR, [ 'init', '--template', - `file://${path.resolve(DIR, 'custom', 'template')}`, + `file://${templatePath}`, 'TestInit', ]); @@ -100,5 +110,11 @@ test('init --template with custom project path', () => { // make sure we don't leave garbage expect(fs.readdirSync(DIR)).toEqual([customPath]); - expect(fs.readdirSync(path.join(DIR, customPath))).toEqual(templateFiles); + + let dirFiles = fs.readdirSync(path.join(DIR, customPath)); + expect(dirFiles.length).toEqual(templateFiles.length); + + for (const templateFile of templateFiles) { + expect(dirFiles.includes(templateFile)).toBe(true); + } }); diff --git a/__e2e__/legacyInit.test.js b/__e2e__/legacyInit.test.js index 4e7df6228..a411f549a 100644 --- a/__e2e__/legacyInit.test.js +++ b/__e2e__/legacyInit.test.js @@ -34,7 +34,6 @@ test('legacy init through react-native-cli', () => { 'package.json', 'yarn.lock', ]; - const {stdout} = execa.sync('npx', ['react-native-cli', 'init', 'TestApp'], { cwd: DIR, }); @@ -43,7 +42,13 @@ test('legacy init through react-native-cli', () => { // make sure we don't leave garbage expect(fs.readdirSync(DIR)).toEqual(['TestApp']); - expect(fs.readdirSync(path.join(DIR, 'TestApp'))).toEqual(templateFiles); + + let dirFiles = fs.readdirSync(path.join(DIR, 'TestApp')); + expect(dirFiles.length).toEqual(templateFiles.length); + + for (const templateFile of templateFiles) { + expect(dirFiles.includes(templateFile)).toBe(true); + } const pkgJson = require(path.join(DIR, 'TestApp', 'package.json')); expect(pkgJson).toMatchObject({ diff --git a/packages/cli/package.json b/packages/cli/package.json index 9a916efe3..d860e3e0b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -61,6 +61,7 @@ "react-native": "^0.60.0" }, "devDependencies": { - "snapshot-diff": "^0.5.0" + "snapshot-diff": "^0.5.0", + "slash": "^3.0.0" } } diff --git a/packages/cli/src/commands/init/__tests__/editTemplate.test.js b/packages/cli/src/commands/init/__tests__/editTemplate.test.js index f59c52abc..572d7f454 100644 --- a/packages/cli/src/commands/init/__tests__/editTemplate.test.js +++ b/packages/cli/src/commands/init/__tests__/editTemplate.test.js @@ -3,6 +3,7 @@ import os from 'os'; import path from 'path'; import fs from 'fs-extra'; import snapshotDiff from 'snapshot-diff'; +import slash from 'slash'; import walk from '../../../tools/walk'; import copyFiles from '../../../tools/copyFiles'; import {changePlaceholderInTemplate} from '../editTemplate'; @@ -80,6 +81,8 @@ test('should edit template', () => { snapshotDiff(oldJavaFile, newJavaFile, {contextLines: 1}), ).toMatchSnapshot(); expect( - snapshotDiff(fixtureTree, transformedTree, {contextLines: 1}), + snapshotDiff(fixtureTree.map(slash), transformedTree.map(slash), { + contextLines: 1, + }), ).toMatchSnapshot(); }); diff --git a/packages/cli/src/commands/init/__tests__/templateName.test.js b/packages/cli/src/commands/init/__tests__/templateName.test.js index d374637dc..d01636e46 100644 --- a/packages/cli/src/commands/init/__tests__/templateName.test.js +++ b/packages/cli/src/commands/init/__tests__/templateName.test.js @@ -5,6 +5,10 @@ const RN_NPM_PACKAGE = 'react-native'; const ABS_RN_PATH = '/path/to/react-native'; test('supports file protocol with absolute path', async () => { + if (process.platform === 'win32') { + console.warn('[SKIP] Jest virtual mocks seem to be broken on Windows'); + return; + } jest.mock( `${ABS_RN_PATH}/package.json`, () => ({ diff --git a/packages/cli/src/commands/init/init.js b/packages/cli/src/commands/init/init.js index e6f2f40e9..9a2f9c568 100644 --- a/packages/cli/src/commands/init/init.js +++ b/packages/cli/src/commands/init/init.js @@ -58,7 +58,7 @@ async function setProjectDirectory(directory) { throw new DirectoryAlreadyExistsError(directory); } - await fs.emptyDir(directory); + fs.emptyDirSync(directory); } try { diff --git a/packages/cli/src/commands/init/template.js b/packages/cli/src/commands/init/template.js index 918a40be6..aa4887c7e 100644 --- a/packages/cli/src/commands/init/template.js +++ b/packages/cli/src/commands/init/template.js @@ -4,6 +4,7 @@ import path from 'path'; import * as PackageManager from '../../tools/packageManager'; import {logger} from '@react-native-community/cli-tools'; import copyFiles from '../../tools/copyFiles'; +import replacePathSepForRegex from '../../tools/replacePathSepForRegex'; export type TemplateConfig = { placeholderName: string, @@ -53,9 +54,9 @@ export async function copyTemplate( ); logger.debug(`Copying template from ${templatePath}`); - + let regexStr = path.resolve(templatePath, 'node_modules'); await copyFiles(templatePath, process.cwd(), { - exclude: [new RegExp(path.resolve(templatePath, 'node_modules'))], + exclude: [new RegExp(replacePathSepForRegex(regexStr))], }); } diff --git a/packages/cli/src/commands/init/templateName.js b/packages/cli/src/commands/init/templateName.js index fc0c5c767..ed19e3f86 100644 --- a/packages/cli/src/commands/init/templateName.js +++ b/packages/cli/src/commands/init/templateName.js @@ -8,8 +8,11 @@ const VERSION_POSTFIX = /(.*)(-\d+\.\d+\.\d+)/; const VERSIONED_PACKAGE = /(@?.+)(@)(.+)/; function handleFileProtocol(filePath: string) { - const uri = new URL(filePath).pathname; - + let uri = new URL(filePath).pathname; + if (process.platform === 'win32') { + // On Windows, the pathname has an extra leading / so remove that + uri = uri.substring(1); + } return { uri, name: require(path.join(uri, 'package.json')).name, diff --git a/packages/cli/src/tools/__tests__/copyFiles.test.js b/packages/cli/src/tools/__tests__/copyFiles.test.js index 477d4721d..cc6394f0a 100644 --- a/packages/cli/src/tools/__tests__/copyFiles.test.js +++ b/packages/cli/src/tools/__tests__/copyFiles.test.js @@ -3,6 +3,7 @@ import fs from 'fs'; import path from 'path'; import copyFiles from '../copyFiles'; import {cleanup, getTempDirectory} from '../../../../../jest/helpers'; +import replacePathSepForRegex from '../replacePathSepForRegex'; const DIR = getTempDirectory('copyFiles-test'); @@ -47,10 +48,10 @@ test('copies text and binary files from source to destination', async () => { test('copies files from source to destination excluding directory', async () => { const src = path.resolve(__dirname, './__fixtures__'); + let regexStr = path.join(src, 'extraDir'); await copyFiles(src, DIR, { - exclude: [new RegExp(path.join(src, 'extraDir'))], + exclude: [new RegExp(replacePathSepForRegex(regexStr))], }); - expect(fs.readdirSync(DIR)).toMatchInlineSnapshot(` Array [ "binary.keystore", diff --git a/packages/cli/src/tools/config/__tests__/index-test.js b/packages/cli/src/tools/config/__tests__/index-test.js index c65f138e2..112754815 100644 --- a/packages/cli/src/tools/config/__tests__/index-test.js +++ b/packages/cli/src/tools/config/__tests__/index-test.js @@ -1,17 +1,16 @@ /** * @flow */ - +import path from 'path'; +import slash from 'slash'; import loadConfig from '..'; - +import {logger} from '@react-native-community/cli-tools'; import { cleanup, writeFiles, getTempDirectory, } from '../../../../../../jest/helpers'; -import {logger} from '@react-native-community/cli-tools'; - jest.mock('../resolveNodeModuleDir'); const DIR = getTempDirectory('resolve_config_path_test'); @@ -19,7 +18,11 @@ const DIR = getTempDirectory('resolve_config_path_test'); // Removes string from all key/values within an object const removeString = (config, str) => JSON.parse( - JSON.stringify(config).replace(new RegExp(str, 'g'), '<>'), + JSON.stringify(config, (_key, value) => + typeof value === 'string' + ? slash(value.replace(str, '<>')) + : value, + ), ); beforeEach(() => { @@ -302,21 +305,30 @@ test('does not use restricted "react-native" key to resolve config from package. }); test('supports dependencies from user configuration with custom root and properties', () => { + const escapePathSeparator = (value: string) => + path.sep === '\\' ? value.replace(/(\/|\\)/g, '\\\\') : value; + writeFiles(DIR, { 'node_modules/react-native/package.json': '{}', 'native-libs/local-lib/ios/LocalRNLibrary.xcodeproj/project.pbxproj': '', - 'react-native.config.js': `module.exports = { - dependencies: { - 'local-lib': { - root: "${DIR}/native-libs/local-lib", - platforms: { - ios: { - podspecPath: "custom-path" - } - } - }, + 'react-native.config.js': ` +const path = require('path'); +const root = path.resolve('${escapePathSeparator( + DIR, + )}', 'native-libs', 'local-lib'); + +module.exports = { + dependencies: { + 'local-lib': { + root, + platforms: { + ios: { + podspecPath: "custom-path" + } } - }`, + }, + } +}`, 'package.json': `{ "dependencies": { "react-native": "0.0.1" diff --git a/packages/cli/src/tools/config/findAssets.js b/packages/cli/src/tools/config/findAssets.js index 9f35e5c0c..416581db8 100644 --- a/packages/cli/src/tools/config/findAssets.js +++ b/packages/cli/src/tools/config/findAssets.js @@ -5,8 +5,13 @@ import glob from 'glob'; import path from 'path'; -const findAssetsInFolder = folder => - glob.sync(path.join(folder, '**'), {nodir: true}); +const findAssetsInFolder = folder => { + const assets = glob.sync(path.join(folder, '**'), {nodir: true}); + if (process.platform === 'win32') { + return assets.map(asset => asset.split('/').join('\\')); + } + return assets; +}; /** * Given an array of assets folders, e.g. ['Fonts', 'Images'], diff --git a/packages/cli/src/tools/replacePathSepForRegex.js b/packages/cli/src/tools/replacePathSepForRegex.js new file mode 100644 index 000000000..576eb3953 --- /dev/null +++ b/packages/cli/src/tools/replacePathSepForRegex.js @@ -0,0 +1,12 @@ +// @flow +import path from 'path'; + +export default function replacePathSepForRegex(string: string) { + if (path.sep === '\\') { + return string.replace( + /(\/|(.)?\\(?![[\]{}()*+?.^$|\\]))/g, + (_match, _, p2) => (p2 && p2 !== '\\' ? p2 + '\\\\' : '\\\\'), + ); + } + return string; +}