diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67a18d1b2..f6c49f2a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,6 +159,30 @@ jobs: pod install --project-directory=macos ../scripts/xcodebuild.sh macos/Example.xcworkspace build working-directory: example + macos-template: + name: "macOS [template]" + strategy: + matrix: + template: [all, macos] + runs-on: macos-latest + if: "!contains(github.event.head_commit.message, '[skip ci]')" + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install + run: scripts/install-test-template.sh ${{ matrix.template }} + - name: Build + run: | + set -eo pipefail + yarn build:macos + if [[ ${{ matrix.template }} == macos ]]; then + pod install + ../scripts/xcodebuild.sh TemplateExample.xcworkspace build + else + pod install --project-directory=macos + ../scripts/xcodebuild.sh macos/TemplateExample.xcworkspace build + fi + working-directory: template-example release: needs: [ @@ -167,7 +191,8 @@ jobs: ios-template, android, android-template, - macos + macos, + macos-template, ] runs-on: macos-latest if: "!contains(github.event.head_commit.message, '[skip ci]')" diff --git a/.npmignore b/.npmignore index 3976c1591..f15b7710d 100644 --- a/.npmignore +++ b/.npmignore @@ -9,6 +9,8 @@ Brewfile* Gemfile* android/app/build/ -example/ +example/* +!example/metro.config.js +!example/react-native.config.js scripts/ test/ diff --git a/README.md b/README.md index c182e4ac4..aa94c8a55 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,22 @@ open -a "Android Studio" android # Bundle the JS first so CocoaPods can # find the assets. yarn build:ios -pod install +pod install --project-directory=ios + +# Finally, open the Xcode workspace. +open ios/Sample.xcworkspace +``` + +## macOS + +```bash +# Bundle the JS first so CocoaPods can +# find the assets. +yarn build:macos +pod install --project-directory=macos # Finally, open the Xcode workspace. -open Sample.xcworkspace +open macos/Sample.xcworkspace ``` # Developing React Native Test App diff --git a/ios/test_app.rb b/ios/test_app.rb index 39e340683..8b850033b 100644 --- a/ios/test_app.rb +++ b/ios/test_app.rb @@ -117,11 +117,15 @@ def use_test_app_internal!(target_platform) FileUtils.ln_sf(File.join(src_xcodeproj, 'xcshareddata'), dst_xcodeproj) # Link source files - %w[ReactTestApp ReactTestAppShared ReactTestAppTests ReactTestAppUITests].each do |file| + %w[ReactTestApp ReactTestAppTests ReactTestAppUITests].each do |file| source = File.expand_path(File.join(__dir__, '..', target_platform.to_s, file)) FileUtils.ln_sf(source, destination) end + # Shared code lives in `ios/ReactTestApp/` + source = File.expand_path(File.join(__dir__, 'ReactTestApp')) + FileUtils.ln_sf(source, File.join(destination, 'ReactTestAppShared')) + require_relative(autolink_script_path) platform :ios, '12.0' if target_platform == :ios diff --git a/plopfile.js b/plopfile.js index 35ffda7dd..51b378509 100644 --- a/plopfile.js +++ b/plopfile.js @@ -10,6 +10,25 @@ * }} InputData */ +/** + * Converts an object or value to a pretty JSON string. + * @param {{ [key: string]: unknown }} obj + * @return {string} + */ +function serialize(obj) { + return JSON.stringify(obj, undefined, 2) + "\n"; +} + +/** + * Sort the keys in specified object. + * @param {{ [key: string]: unknown }} obj + */ +function sortByKeys(obj) { + return Object.keys(obj) + .sort() + .reduce((sorted, key) => ({ ...sorted, [key]: obj[key] }), {}); +} + /** @type {(plop: import('plop').NodePlopAPI) => void} */ module.exports = (plop) => { plop.setGenerator("app", { @@ -31,6 +50,7 @@ module.exports = (plop) => { ], actions: (/** @type {InputData} */ { name, platforms }) => { const path = require("path"); + const exclusive = platforms !== "all"; const templateDir = path.dirname( require.resolve("react-native/template/package.json") ); @@ -48,21 +68,17 @@ module.exports = (plop) => { { type: "add", path: "app.json", - template: JSON.stringify( - { - name, - displayName: name, - components: [ - { - appKey: name, - displayName: name, - }, - ], - resources: ["dist/assets", "dist/main.jsbundle", "dist/res"], - }, - undefined, - 2 - ), + template: serialize({ + name, + displayName: name, + components: [ + { + appKey: name, + displayName: name, + }, + ], + resources: ["dist/assets", "dist/main.jsbundle", "dist/res"], + }), }, { type: "add", @@ -79,9 +95,7 @@ module.exports = (plop) => { { type: "add", path: "metro.config.js", - templateFile: require.resolve( - "react-native/template/metro.config.js" - ), + templateFile: path.join(__dirname, "example", "metro.config.js"), }, { type: "add", @@ -93,42 +107,67 @@ module.exports = (plop) => { version: testAppPackageVersion, } = require("./package.json"); const packageJson = JSON.parse(template); - const devDependencies = { - ...packageJson.devDependencies, - [testAppPackageName]: testAppPackageVersion, - mkdirp: "^1.0.0", - // TODO(tido64): Remove these when https://github.com/microsoft/react-native-test-app/pull/17 is merged - "@react-native-community/cli": "^4.3.0", - "@react-native-community/cli-platform-android": "^4.3.0", - "@react-native-community/cli-platform-ios": "^4.3.0", - "@react-native-community/eslint-config": "^0.0.5", - }; - return JSON.stringify( - { - ...packageJson, - name, - scripts: { - "build:android": - "mkdirp dist/res && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.jsbundle --assets-dest dist/res --reset-cache", - "build:ios": - "mkdirp dist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.jsbundle --assets-dest dist --reset-cache", - ...packageJson.scripts, - }, - devDependencies: Object.keys(devDependencies) - .sort() - .reduce( - (deps, key) => ({ ...deps, [key]: devDependencies[key] }), - {} - ), - }, - undefined, - 2 - ); + return serialize({ + ...packageJson, + name, + scripts: sortByKeys({ + ...(!exclusive || platforms === "android" + ? { + "build:android": + "mkdirp dist/res && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.jsbundle --assets-dest dist/res --reset-cache", + } + : undefined), + ...(!exclusive || platforms === "ios" + ? { + "build:ios": + "mkdirp dist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.jsbundle --assets-dest dist --reset-cache", + } + : undefined), + ...(!exclusive || platforms === "macos" + ? { + "build:macos": + "mkdirp dist && react-native bundle --entry-file index.js --platform macos --dev true --bundle-output dist/main.jsbundle --assets-dest dist --reset-cache --config=metro.config.macos.js", + "start:macos": + "react-native start --config=metro.config.macos.js", + } + : undefined), + ...(platforms !== "macos" + ? { start: "react-native start" } + : undefined), + ...packageJson.scripts, + }), + dependencies: sortByKeys({ + ...packageJson.dependencies, + ...(!exclusive || platforms === "macos" + ? { "react-native-macos": "0.60.0-microsoft.79" } + : undefined), + }), + devDependencies: sortByKeys({ + ...packageJson.devDependencies, + [testAppPackageName]: testAppPackageVersion, + mkdirp: "^1.0.0", + // TODO(tido64): Remove these when https://github.com/microsoft/react-native-test-app/pull/17 is merged + "@react-native-community/cli": "^4.3.0", + "@react-native-community/cli-platform-android": "^4.3.0", + "@react-native-community/cli-platform-ios": "^4.3.0", + "@react-native-community/eslint-config": "^0.0.5", + }), + }); }, }, ]; - const exclusive = platforms !== "all"; + if (!exclusive) { + actions.push({ + type: "add", + path: "react-native.config.js", + templateFile: path.join( + __dirname, + "example", + "react-native.config.js" + ), + }); + } if (!exclusive || platforms === "android") { const prefix = exclusive ? "" : "android/"; @@ -200,20 +239,69 @@ module.exports = (plop) => { "", ].join("\n"), }); + if (exclusive) { + actions.push({ + type: "add", + path: "react-native.config.js", + template: [ + "module.exports = {", + " project: {", + " ios: {", + ` project: "${prefix}ReactTestApp-Dummy.xcodeproj"`, + " }", + " }", + "};", + "", + ].join("\n"), + }); + } + } + + if (!exclusive || platforms === "macos") { + const prefix = exclusive ? "" : "macos/"; actions.push({ type: "add", - path: "react-native.config.js", + path: `${prefix}Podfile`, template: [ - "module.exports = {", - " project: {", - " ios: {", - ` project: "${prefix}ReactTestApp-Dummy.xcodeproj"`, - " }", - " }", - "};", + `require_relative '${ + exclusive ? "" : "../" + }node_modules/react-native-test-app/macos/test_app.rb'`, + "", + "workspace '{{name}}.xcworkspace'", + "", + "use_test_app!", "", ].join("\n"), }); + actions.push({ + type: "add", + path: "metro.config.macos.js", + templateFile: require.resolve( + "react-native-macos/local-cli/generator-macos/templates/metro.config.macos.js" + ), + }); + if (exclusive) { + actions.push({ + type: "add", + path: "react-native.config.js", + template: [ + 'if (process.argv.includes("--config=metro.config.macos.js")) {', + " module.exports = {", + ' reactNativePath: "node_modules/react-native-macos",', + " };", + "} else {", + " module.exports = {", + " project: {", + " ios: {", + ' project: "ReactTestApp-Dummy.xcodeproj",', + " },", + " },", + " };", + "}", + "", + ].join("\n"), + }); + } } return actions;