From bc3579879d053096a34037d82f198d4c2f912682 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 25 Apr 2024 00:54:40 +0200 Subject: [PATCH] fix(windows): embed app manifest directly in code (#1984) --- .github/workflows/build.yml | 7 +- .vscode/c_cpp_properties.json | 23 -- .../ReactAppTests.vcxproj} | 26 +- .../ReactAppTests.vcxproj.filters} | 21 +- example/windows/ReactAppTests/Tests.cpp | 18 ++ .../ReactTestAppTests/ManifestTests.cpp | 145 ---------- .../manifestTestFiles/simpleManifest.json | 10 - .../withComplexInitialProperties.json | 22 -- .../withMultipleComponents.json | 16 -- example/windows/ReactTestAppTests/pch.cpp | 6 - example/windows/ReactTestAppTests/pch.h | 13 - scripts/embed-manifest/cpp.mjs | 213 ++++++++++++++ scripts/embed-manifest/main.mjs | 8 +- scripts/generate-manifest.mjs | 22 +- test/embed-manifest/cpp.test.mjs | 266 ++++++++++++++++++ test/embed-manifest/fixtures.mjs | 136 +++++++++ test/embed-manifest/kotlin.test.mjs | 159 +---------- test/pack.test.mjs | 4 +- windows/Shared/EmbedManifest.targets | 11 + windows/Shared/JSValueWriterHelper.h | 18 +- windows/Shared/Manifest.cpp | 108 ------- windows/Shared/Manifest.h | 32 ++- windows/Shared/Session.h | 6 +- windows/Shared/ValidateManifest.targets | 11 - windows/UWP/MainPage.cpp | 65 ++--- windows/UWP/MainPage.h | 7 +- windows/UWP/ReactTestApp.vcxproj | 7 +- windows/UWP/ReactTestApp.vcxproj.filters | 1 - windows/UWP/packages.config | 1 - windows/Win32/Main.cpp | 10 +- windows/Win32/ReactApp.vcxproj | 6 +- windows/Win32/ReactApp.vcxproj.filters | 3 - windows/test-app.mjs | 5 - 33 files changed, 749 insertions(+), 657 deletions(-) delete mode 100644 .vscode/c_cpp_properties.json rename example/windows/{ReactTestAppTests/ReactTestAppTests.vcxproj => ReactAppTests/ReactAppTests.vcxproj} (83%) rename example/windows/{ReactTestAppTests/ReactTestAppTests.vcxproj.filters => ReactAppTests/ReactAppTests.vcxproj.filters} (58%) create mode 100644 example/windows/ReactAppTests/Tests.cpp delete mode 100644 example/windows/ReactTestAppTests/ManifestTests.cpp delete mode 100644 example/windows/ReactTestAppTests/manifestTestFiles/simpleManifest.json delete mode 100644 example/windows/ReactTestAppTests/manifestTestFiles/withComplexInitialProperties.json delete mode 100644 example/windows/ReactTestAppTests/manifestTestFiles/withMultipleComponents.json delete mode 100644 example/windows/ReactTestAppTests/pch.cpp delete mode 100644 example/windows/ReactTestAppTests/pch.h create mode 100644 scripts/embed-manifest/cpp.mjs create mode 100644 test/embed-manifest/cpp.test.mjs create mode 100644 test/embed-manifest/fixtures.mjs create mode 100644 windows/Shared/EmbedManifest.targets delete mode 100644 windows/Shared/Manifest.cpp delete mode 100644 windows/Shared/ValidateManifest.targets diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc1e2c7ac..2b6ed2e20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -130,6 +130,7 @@ jobs: - name: SwiftLint if: ${{ matrix.platform == 'macos' }} run: | + brew install swiftlint echo "::add-matcher::.github/swiftlint.json" swiftlint echo "::remove-matcher owner=swiftlint::" @@ -610,9 +611,9 @@ jobs: - name: Test if: ${{ steps.affected.outputs.windows != '' && matrix.platform == 'x64' }} run: | - ../../../scripts/MSBuild.ps1 -Configuration ${{ matrix.configuration }} -Platform ${{ matrix.platform }} -Target Build ReactTestAppTests.vcxproj - ../../../scripts/VSTest.ps1 ${{ matrix.platform }}\${{ matrix.configuration }}\ReactTestAppTests.dll - working-directory: example/windows/ReactTestAppTests + ../../../scripts/MSBuild.ps1 -Configuration ${{ matrix.configuration }} -Platform ${{ matrix.platform }} -Target Build ReactAppTests.vcxproj + ../../../scripts/VSTest.ps1 ${{ matrix.platform }}\${{ matrix.configuration }}\ReactAppTests.dll + working-directory: example/windows/ReactAppTests timeout-minutes: 60 windows-template: name: "Windows [template]" diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 84bb8a195..000000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "configurations": [ - { - "name": "Win32", - "includePath": [ - "${workspaceFolder}/example/node_modules/.generated/windows/ReactTestApp/Generated Files", - "${workspaceFolder}/example/node_modules/react-native-windows/build/x64/Debug/Microsoft.ReactNative/Generated Files", - "${workspaceFolder}/example/windows/packages/nlohmann.json.3.9.1/build/native/include" - ], - "defines": [ - "_DEBUG", - "UNICODE", - "_UNICODE" - ], - "windowsSdkVersion": "10.0.18362.0", - "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.27.29110/bin/Hostx64/x64/cl.exe", - "cStandard": "c11", - "cppStandard": "c++17", - "intelliSenseMode": "msvc-x64" - } - ], - "version": 4 -} diff --git a/example/windows/ReactTestAppTests/ReactTestAppTests.vcxproj b/example/windows/ReactAppTests/ReactAppTests.vcxproj similarity index 83% rename from example/windows/ReactTestAppTests/ReactTestAppTests.vcxproj rename to example/windows/ReactAppTests/ReactAppTests.vcxproj index 8b4ad854e..8a98c1bcc 100644 --- a/example/windows/ReactTestAppTests/ReactTestAppTests.vcxproj +++ b/example/windows/ReactAppTests/ReactAppTests.vcxproj @@ -4,7 +4,7 @@ 16.0 {D2B221C0-0781-4D20-8BF1-D88684662A5D} Win32Proj - ReactTestAppTests + ReactAppTests NativeUnitTestProject @@ -67,19 +67,16 @@ - Use Level4 true $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) true - pch.h - stdcpp17 + stdcpp20 true Windows $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(ReactAppProjectDir)\$(Platform)\$(Configuration) - %(AdditionalDependencies);Manifest.obj;pch.obj @@ -99,24 +96,7 @@ - - Create - - - - - - - - - true - - - true - - - true - + diff --git a/example/windows/ReactTestAppTests/ReactTestAppTests.vcxproj.filters b/example/windows/ReactAppTests/ReactAppTests.vcxproj.filters similarity index 58% rename from example/windows/ReactTestAppTests/ReactTestAppTests.vcxproj.filters rename to example/windows/ReactAppTests/ReactAppTests.vcxproj.filters index 5b29fb285..54b8aec73 100644 --- a/example/windows/ReactTestAppTests/ReactTestAppTests.vcxproj.filters +++ b/example/windows/ReactAppTests/ReactAppTests.vcxproj.filters @@ -15,27 +15,8 @@ - + Source Files - - Source Files - - - - - Header Files - - - - - Resource Files - - - Resource Files - - - Resource Files - diff --git a/example/windows/ReactAppTests/Tests.cpp b/example/windows/ReactAppTests/Tests.cpp new file mode 100644 index 000000000..e27809171 --- /dev/null +++ b/example/windows/ReactAppTests/Tests.cpp @@ -0,0 +1,18 @@ +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +// disable clang-format because it doesn't handle macros very well +// clang-format off +namespace ReactApp::Tests +{ + TEST_CLASS(Tests) + { + public: + TEST_METHOD(Test) + { + Assert::IsTrue(true); + } + }; +} // namespace ReactApp::Tests +// clang-format on diff --git a/example/windows/ReactTestAppTests/ManifestTests.cpp b/example/windows/ReactTestAppTests/ManifestTests.cpp deleted file mode 100644 index 5be114c9a..000000000 --- a/example/windows/ReactTestAppTests/ManifestTests.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "pch.h" - -#include -#include -#include -#include - -#include "Manifest.h" - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -using ReactTestApp::Component; -using ReactTestApp::Manifest; - -std::string readManifest(std::filesystem::path file) -{ - // Current working directory when running tests (with VSTest.Console.exe) - // changed between Visual Studio 16.8 and 16.9 and broke our pipelines. To - // prevent future build failures, we'll use the absolute path to this - // source file to build the path to the test fixtures. - // - // To ensure that `__FILE__` is a full path, we must also enable `/FC` in - // Properties > C/C++ > Advanced. - const auto p = std::filesystem::path(__FILE__).replace_filename("manifestTestFiles") / file; - - std::FILE *stream = nullptr; - fopen_s(&stream, p.u8string().c_str(), "rb"); - - std::string json; - std::fseek(stream, 0, SEEK_END); - json.resize(std::ftell(stream)); - - std::rewind(stream); - std::fread(json.data(), 1, json.size(), stream); - std::fclose(stream); - - return json; -} - -// disable clang-format because it doesn't handle macros very well -// clang-format off -namespace ReactTestAppTests -{ - TEST_CLASS(ManifestTests) - { - public: - TEST_METHOD(ParseManifestWithOneComponent) - { - auto json = readManifest("simpleManifest.json"); - auto result = ReactTestApp::GetManifest(json.c_str()); - if (!result.has_value()) { - Assert::Fail(L"Couldn't read manifest file"); - } - - auto &[manifest, checksum] = result.value(); - - Assert::AreEqual(manifest.name, {"Example"}); - Assert::AreEqual(manifest.displayName, {"Example"}); - Assert::IsTrue(manifest.components.has_value()); - - auto &components = manifest.components.value(); - Assert::AreEqual(components[0].appKey, {"Example"}); - Assert::AreEqual(components[0].displayName.value(), {"App"}); - Assert::IsFalse(components[0].initialProperties.has_value()); - } - - TEST_METHOD(ParseManifestWithMultipleComponents) - { - auto json = readManifest("withMultipleComponents.json"); - auto result = ReactTestApp::GetManifest(json.c_str()); - if (!result.has_value()) { - Assert::Fail(L"Couldn't read manifest file"); - } - - auto &[manifest, checksum] = result.value(); - - Assert::AreEqual(manifest.name, {"Example"}); - Assert::AreEqual(manifest.displayName, {"Example"}); - Assert::IsTrue(manifest.components.has_value()); - - auto &components = manifest.components.value(); - Assert::AreEqual(components.size(), {2}); - - Assert::AreEqual(components[0].appKey, {"0"}); - Assert::IsFalse(components[0].displayName.has_value()); - Assert::IsTrue(components[0].initialProperties.has_value()); - Assert::AreEqual(std::any_cast( - components[0].initialProperties.value()["key"]), - {"value"}); - - Assert::AreEqual(components[1].appKey, {"1"}); - Assert::AreEqual(components[1].displayName.value(), {"1"}); - Assert::IsFalse(components[1].initialProperties.has_value()); - } - - TEST_METHOD(ParseManifestWithComplexInitialProperties) - { - auto json = readManifest("withComplexInitialProperties.json"); - auto result = ReactTestApp::GetManifest(json.c_str()); - if (!result.has_value()) { - Assert::Fail(L"Couldn't read manifest file"); - } - - auto &[manifest, checksum] = result.value(); - - Assert::AreEqual(manifest.name, {"Name"}); - Assert::AreEqual(manifest.displayName, {"Display Name"}); - Assert::IsTrue(manifest.components.has_value()); - - auto &component = manifest.components.value()[0]; - Assert::AreEqual(component.appKey, {"AppKey"}); - Assert::IsFalse(component.displayName.has_value()); - Assert::IsTrue(component.initialProperties.has_value()); - - auto &initialProps = component.initialProperties.value(); - Assert::IsTrue(std::any_cast(initialProps["boolean"])); - Assert::AreEqual(std::any_cast(initialProps["number"]), {9000}); - Assert::AreEqual(std::any_cast(initialProps["string"]), {"string"}); - - auto const &array = std::any_cast>(initialProps["array"]); - Assert::IsTrue(array[0].type() == typeid(std::nullopt)); - Assert::IsTrue(std::any_cast(array[1])); - Assert::AreEqual(std::any_cast(array[2]), {9000}); - Assert::AreEqual(std::any_cast(array[3]), {"string"}); - Assert::IsTrue(std::any_cast>(array[4]).empty()); - Assert::IsTrue(std::any_cast>(array[5]).empty()); - - auto object = std::any_cast>(initialProps["object"]); - Assert::IsTrue(std::any_cast(object["boolean"])); - Assert::AreEqual(std::any_cast(object["number"]), {9000}); - Assert::AreEqual(std::any_cast(object["string"]), {"string"}); - - auto const &innerArray = std::any_cast>(object["array"]); - Assert::IsTrue(innerArray[0].type() == typeid(std::nullopt)); - Assert::IsTrue(std::any_cast(innerArray[1])); - Assert::AreEqual(std::any_cast(innerArray[2]), {9000}); - Assert::AreEqual(std::any_cast(innerArray[3]), {"string"}); - Assert::IsTrue(std::any_cast>(innerArray[4]).empty()); - Assert::IsTrue(std::any_cast>(innerArray[5]).empty()); - - Assert::IsTrue(object["object"].type() == typeid(std::nullopt)); - } - }; -} // namespace ReactTestAppTests -// clang-format on diff --git a/example/windows/ReactTestAppTests/manifestTestFiles/simpleManifest.json b/example/windows/ReactTestAppTests/manifestTestFiles/simpleManifest.json deleted file mode 100644 index b158a73be..000000000 --- a/example/windows/ReactTestAppTests/manifestTestFiles/simpleManifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "Example", - "displayName": "Example", - "components": [ - { - "appKey": "Example", - "displayName": "App" - } - ] -} diff --git a/example/windows/ReactTestAppTests/manifestTestFiles/withComplexInitialProperties.json b/example/windows/ReactTestAppTests/manifestTestFiles/withComplexInitialProperties.json deleted file mode 100644 index daa5cbc83..000000000 --- a/example/windows/ReactTestAppTests/manifestTestFiles/withComplexInitialProperties.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "Name", - "displayName": "Display Name", - "components": [ - { - "appKey": "AppKey", - "initialProperties": { - "boolean": true, - "number": 9000, - "string": "string", - "array": [null, true, 9000, "string", [], {}], - "object": { - "boolean": true, - "number": 9000, - "string": "string", - "array": [null, true, 9000, "string", [], {}], - "object": null - } - } - } - ] -} diff --git a/example/windows/ReactTestAppTests/manifestTestFiles/withMultipleComponents.json b/example/windows/ReactTestAppTests/manifestTestFiles/withMultipleComponents.json deleted file mode 100644 index 20e83bf66..000000000 --- a/example/windows/ReactTestAppTests/manifestTestFiles/withMultipleComponents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "Example", - "displayName": "Example", - "components": [ - { - "appKey": "0", - "initialProperties": { - "key": "value" - } - }, - { - "appKey": "1", - "displayName": "1" - } - ] -} diff --git a/example/windows/ReactTestAppTests/pch.cpp b/example/windows/ReactTestAppTests/pch.cpp deleted file mode 100644 index 6a7dfc882..000000000 --- a/example/windows/ReactTestAppTests/pch.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// pch.cpp: source file corresponding to the pre-compiled header - -#include "pch.h" - -// When you are using pre-compiled headers, this source file is necessary for compilation to -// succeed. diff --git a/example/windows/ReactTestAppTests/pch.h b/example/windows/ReactTestAppTests/pch.h deleted file mode 100644 index 75f619469..000000000 --- a/example/windows/ReactTestAppTests/pch.h +++ /dev/null @@ -1,13 +0,0 @@ -// pch.h: This is a precompiled header file. -// Files listed below are compiled only once, improving build performance for future builds. -// This also affects IntelliSense performance, including code completion and many code browsing -// features. However, files listed here are ALL re-compiled if any one of them is updated between -// builds. Do not add files here that you will be updating frequently as this negates the -// performance advantage. - -#ifndef PCH_H -#define PCH_H - -// add headers that you want to pre-compile here - -#endif // PCH_H diff --git a/scripts/embed-manifest/cpp.mjs b/scripts/embed-manifest/cpp.mjs new file mode 100644 index 000000000..223869b34 --- /dev/null +++ b/scripts/embed-manifest/cpp.mjs @@ -0,0 +1,213 @@ +// @ts-check +import * as nodefs from "node:fs"; +import * as path from "node:path"; +import { findFile, isMain } from "../helpers.js"; +import { main } from "./main.mjs"; + +const INDENT = " "; + +/** + * @param {string} message + */ +export function warn(message) { + console.warn("app.json:", message); +} + +/** + * @param {number} i + * @returns {string} + */ +function num(i) { + const value = i.toString(); + return value.includes(".") ? value : "INT64_C(" + value + ")"; +} + +/** + * @param {unknown} s + * @returns {string} + */ +function str(s, literal = "") { + return typeof s === "string" ? '"' + s + `"${literal}` : "std::nullopt"; +} + +/** + * @param {unknown[]} items + * @param {number} level + * @returns {string} + */ +function array(items, level) { + if (items.length === 0) { + return "std::vector{}"; + } + + const innerIndent = INDENT.repeat(level + 1); + + const lines = []; + for (const value of items) { + switch (typeof value) { + case "boolean": + lines.push(innerIndent + value.toString()); + break; + case "number": + lines.push(innerIndent + num(value)); + break; + case "string": + lines.push(innerIndent + str(value, "sv")); + break; + case "object": + if (Array.isArray(value)) { + lines.push(innerIndent + array(value, level + 1)); + } else if (value) { + lines.push(innerIndent + object(value, level + 1)); + } else { + lines.push(innerIndent + "nullptr"); + } + break; + default: + warn(`Unexpected JSON type while parsing: ${value}`); + break; + } + } + lines.push(INDENT.repeat(level) + "}"); + return "std::vector{\n" + lines.join(",\n"); +} + +/** + * @param {unknown} props + * @param {number} level + * @returns {string} + */ +function object(props, level) { + if (typeof props !== "object" || !props) { + return "std::nullopt"; + } + + const entries = Object.entries(props); + if (entries.length === 0) { + return "JSONObject{}"; + } + + const innerIndent = INDENT.repeat(level + 1); + + const lines = ["JSONObject{"]; + for (const [key, value] of entries) { + switch (typeof value) { + case "boolean": + lines.push(`${innerIndent}{${str(key)}, ${value}},`); + break; + case "number": + lines.push(`${innerIndent}{${str(key)}, ${num(value)}},`); + break; + case "string": + lines.push(`${innerIndent}{${str(key)}, ${str(value, "sv")}},`); + break; + case "object": + if (Array.isArray(value)) { + lines.push( + `${innerIndent}{`, + `${innerIndent}${INDENT}${str(key)},`, + `${innerIndent}${INDENT}${array(value, level + 2)}`, + `${innerIndent}},` + ); + } else if (value) { + lines.push( + `${innerIndent}{`, + `${innerIndent}${INDENT}${str(key)},`, + `${innerIndent}${INDENT}${object(value, level + 2)}`, + `${innerIndent}},` + ); + } else { + lines.push(`${innerIndent}{${str(key)}, nullptr},`); + } + break; + default: + warn(`Unexpected JSON type while parsing '${key}': ${value}`); + break; + } + } + lines.push(INDENT.repeat(level) + "}"); + return lines.join("\n"); +} + +/** + * @param {unknown} components + * @param {number} level + * @returns {string} + */ +function components(components, level) { + if (!Array.isArray(components) || components.length === 0) { + return "std::make_optional>({})"; + } + + const outerIndent = INDENT.repeat(level + 1); + const innerIndent = INDENT.repeat(level + 2); + + const lines = ["std::make_optional>({"]; + for (const c of components) { + lines.push(outerIndent + "Component{"); + lines.push(innerIndent + str(c.appKey) + ","); + lines.push(innerIndent + str(c.displayName ?? c.appKey) + ","); + lines.push(innerIndent + object(c.initialProperties, level + 2) + ","); + lines.push(innerIndent + str(c.presentationStyle) + ","); + lines.push(innerIndent + str(c.slug)); + lines.push(outerIndent + "},"); + } + lines.push(INDENT.repeat(level) + "})"); + return lines.join("\n"); +} + +/** + * @param {Record} json + * @param {string} checksum + * @returns {string} + */ +export function generate(json, checksum, fs = nodefs) { + const nodeModulesPath = findFile("node_modules", process.cwd(), fs); + if (!nodeModulesPath) { + console.error( + "Failed to find 'node_modules' — make sure you've installed npm dependencies" + ); + return ""; + } + + const code = [ + "// clang-format off", + '#include "Manifest.h"', + "", + "#include ", + "", + "using ReactApp::Component;", + "using ReactApp::JSONObject;", + "using ReactApp::Manifest;", + "", + "Manifest ReactApp::GetManifest()", + "{", + " using namespace std::literals::string_view_literals;", + "", + " return Manifest{", + " " + str(json.name) + ",", + " " + str(json.displayName ?? json.name) + ",", + " " + str(json.version) + ",", + " " + str(json.bundleRoot) + ",", + " " + str(json.singleApp) + ",", + " " + components(json.components, 2), + " };", + "}", + "", + "std::string_view ReactApp::GetManifestChecksum()", + "{", + ` return "${checksum}";`, + "}", + "", + ].join("\n"); + + const dest = path.join(nodeModulesPath, ".generated", "Manifest.g.cpp"); + fs.promises + .mkdir(path.dirname(dest), { recursive: true, mode: 0o755 }) + .then(() => fs.promises.writeFile(dest, code)); + return "app.json -> " + dest; +} + +if (isMain(import.meta.url)) { + process.exitCode = main(generate); +} diff --git a/scripts/embed-manifest/main.mjs b/scripts/embed-manifest/main.mjs index 1fbf6dbb3..df32fb510 100644 --- a/scripts/embed-manifest/main.mjs +++ b/scripts/embed-manifest/main.mjs @@ -5,7 +5,7 @@ import { findFile } from "../helpers.js"; import { validateManifest } from "../validate-manifest.js"; /** - * @param {(json: Record, checksum: string) => string} generate + * @param {(json: Record, checksum: string, fs?: typeof nodefs) => string} generate * @param {string} projectRoot * @returns {number} */ @@ -19,7 +19,11 @@ export function main(generate, projectRoot = process.cwd(), fs = nodefs) { const checksum = createHash("sha256") .update(JSON.stringify(manifest)) .digest("hex"); - const provider = generate(manifest, checksum); + const provider = generate(manifest, checksum, fs); + if (!provider) { + return 1; + } + console.log(provider); return 0; } diff --git a/scripts/generate-manifest.mjs b/scripts/generate-manifest.mjs index c96d5e2ad..952ca649a 100755 --- a/scripts/generate-manifest.mjs +++ b/scripts/generate-manifest.mjs @@ -43,17 +43,22 @@ function getLanguage(output) { "#include ", "#include ", "#include ", - "#include ", - "#include ", + "#include ", "#include ", "", - "namespace ReactTestApp", + "namespace ReactApp", "{", + // Note that we can only use `std::string_view` here because we + // embed the app manifest directly in the binary and can make + // lifetime guarantees for strings. + " using JSONObject = std::map;", + "", ].join("\n"), footer: [ - " std::optional> GetManifest(const char *const json = nullptr);", + " Manifest GetManifest();", + " std::string_view GetManifestChecksum();", "", - "} // namespace ReactTestApp", + "} // namespace ReactApp", "", ].join("\n"), }, @@ -62,11 +67,14 @@ function getLanguage(output) { return `${nullable(propType, required)} ${name};`; }, objectProperty: (name, required) => { - const propType = `std::map`; + const propType = "JSONObject"; return `${nullable(propType, required)} ${name};`; }, stringProperty: (name, required) => { - return `${nullable("std::string", required)} ${name};`; + // Note that we can only use `std::string_view` here because we embed + // the app manifest directly in the binary and can make lifetime + // guarantees for strings. + return `${nullable("std::string_view", required)} ${name};`; }, structBegin: (name) => `struct ${typename(name)} {`, structEnd: `};`, diff --git a/test/embed-manifest/cpp.test.mjs b/test/embed-manifest/cpp.test.mjs new file mode 100644 index 000000000..630ce337b --- /dev/null +++ b/test/embed-manifest/cpp.test.mjs @@ -0,0 +1,266 @@ +// @ts-check +import { equal } from "node:assert/strict"; +import * as fs from "node:fs"; +import { describe, it } from "node:test"; +import { generate as generateActual } from "../../scripts/embed-manifest/cpp.mjs"; +import * as fixtures from "./fixtures.mjs"; + +describe("embed manifest (C++)", () => { + /** @type {(json: Record) => Promise} */ + const generate = (json) => + new Promise((resolve) => { + generateActual(json, "0", { + ...fs, + existsSync: () => true, + promises: { + ...fs.promises, + mkdir: () => Promise.resolve(undefined), + writeFile: (_, data) => { + resolve(data.toString()); + return Promise.resolve(); + }, + }, + }); + }); + + it("generates all properties", async () => { + equal( + await generate(fixtures.simple), + `// clang-format off +#include "Manifest.h" + +#include + +using ReactApp::Component; +using ReactApp::JSONObject; +using ReactApp::Manifest; + +Manifest ReactApp::GetManifest() +{ + using namespace std::literals::string_view_literals; + + return Manifest{ + "Example", + "Template", + "1.0", + "main", + "single", + std::make_optional>({ + Component{ + "Example", + "Example", + std::nullopt, + std::nullopt, + std::nullopt + }, + Component{ + "Example", + "Template", + JSONObject{}, + "modal", + "single" + }, + }) + }; +} + +std::string_view ReactApp::GetManifestChecksum() +{ + return "0"; +} +` + ); + }); + + it("handles missing properties", async () => { + equal( + await generate(fixtures.minimum), + `// clang-format off +#include "Manifest.h" + +#include + +using ReactApp::Component; +using ReactApp::JSONObject; +using ReactApp::Manifest; + +Manifest ReactApp::GetManifest() +{ + using namespace std::literals::string_view_literals; + + return Manifest{ + "Example", + "Example", + std::nullopt, + std::nullopt, + std::nullopt, + std::make_optional>({}) + }; +} + +std::string_view ReactApp::GetManifestChecksum() +{ + return "0"; +} +` + ); + }); + + it("handles valid JSON data types", async () => { + equal( + await generate(fixtures.extended), + `// clang-format off +#include "Manifest.h" + +#include + +using ReactApp::Component; +using ReactApp::JSONObject; +using ReactApp::Manifest; + +Manifest ReactApp::GetManifest() +{ + using namespace std::literals::string_view_literals; + + return Manifest{ + "Example", + "Example", + std::nullopt, + std::nullopt, + std::nullopt, + std::make_optional>({ + Component{ + "Example", + "Example", + JSONObject{ + {"boolean", true}, + {"double", 1.1}, + {"int", INT64_C(1)}, + {"null", nullptr}, + {"string", "string"sv}, + { + "array", + std::vector{ + true, + 1.1, + INT64_C(1), + nullptr, + "string"sv, + std::vector{ + true, + 1.1, + INT64_C(1), + nullptr, + "string"sv, + std::vector{}, + JSONObject{ + {"boolean", true}, + {"double", 1.1}, + {"int", INT64_C(1)}, + {"null", nullptr}, + {"string", "string"sv}, + }, + }, + JSONObject{ + {"boolean", true}, + {"double", 1.1}, + {"int", INT64_C(1)}, + {"null", nullptr}, + {"string", "string"sv}, + }, + } + }, + { + "object", + JSONObject{ + {"boolean", true}, + {"double", 1.1}, + {"int", INT64_C(1)}, + {"null", nullptr}, + {"string", "string"sv}, + { + "array", + std::vector{ + true, + 1.1, + INT64_C(1), + nullptr, + "string"sv, + std::vector{ + true, + 1.1, + INT64_C(1), + nullptr, + "string"sv, + std::vector{}, + JSONObject{ + {"boolean", true}, + {"double", 1.1}, + {"int", INT64_C(1)}, + {"null", nullptr}, + {"string", "string"sv}, + }, + }, + JSONObject{}, + } + }, + { + "object", + JSONObject{ + {"boolean", true}, + {"double", 1.1}, + {"int", INT64_C(1)}, + {"null", nullptr}, + {"string", "string"sv}, + { + "array", + std::vector{ + true, + 1.1, + INT64_C(1), + nullptr, + "string"sv, + std::vector{ + true, + 1.1, + INT64_C(1), + nullptr, + "string"sv, + std::vector{}, + JSONObject{ + {"boolean", true}, + {"double", 1.1}, + {"int", INT64_C(1)}, + {"null", nullptr}, + {"string", "string"sv}, + }, + }, + JSONObject{ + {"boolean", true}, + {"double", 1.1}, + {"int", INT64_C(1)}, + {"null", nullptr}, + {"string", "string"sv}, + }, + } + }, + } + }, + } + }, + }, + std::nullopt, + std::nullopt + }, + }) + }; +} + +std::string_view ReactApp::GetManifestChecksum() +{ + return "0"; +} +` + ); + }); +}); diff --git a/test/embed-manifest/fixtures.mjs b/test/embed-manifest/fixtures.mjs new file mode 100644 index 000000000..6fd1c77b9 --- /dev/null +++ b/test/embed-manifest/fixtures.mjs @@ -0,0 +1,136 @@ +export const simple = { + $schema: + "https://raw.githubusercontent.com/microsoft/react-native-test-app/trunk/schema.json", + name: "Example", + displayName: "Template", + version: "1.0", + bundleRoot: "main", + singleApp: "single", + components: [ + { + appKey: "Example", + }, + { + appKey: "Example", + displayName: "Template", + initialProperties: {}, + presentationStyle: "modal", + slug: "single", + }, + ], + resources: ["dist/res", "dist/main.jsbundle"], +}; + +export const minimum = { name: "Example" }; + +export const extended = { + name: "Example", + components: [ + { + appKey: "Example", + initialProperties: { + boolean: true, + double: 1.1, + int: 1, + null: null, + string: "string", + array: [ + true, + 1.1, + 1, + null, + "string", + [ + true, + 1.1, + 1, + null, + "string", + [], + { + boolean: true, + double: 1.1, + int: 1, + null: null, + string: "string", + }, + ], + { + boolean: true, + double: 1.1, + int: 1, + null: null, + string: "string", + }, + ], + object: { + boolean: true, + double: 1.1, + int: 1, + null: null, + string: "string", + array: [ + true, + 1.1, + 1, + null, + "string", + [ + true, + 1.1, + 1, + null, + "string", + [], + { + boolean: true, + double: 1.1, + int: 1, + null: null, + string: "string", + }, + ], + {}, + ], + object: { + boolean: true, + double: 1.1, + int: 1, + null: null, + string: "string", + array: [ + true, + 1.1, + 1, + null, + "string", + [ + true, + 1.1, + 1, + null, + "string", + [], + { + boolean: true, + double: 1.1, + int: 1, + null: null, + string: "string", + }, + ], + { + boolean: true, + double: 1.1, + int: 1, + null: null, + string: "string", + }, + ], + }, + }, + }, + }, + ], + resources: ["dist/res", "dist/main.jsbundle"], +}; diff --git a/test/embed-manifest/kotlin.test.mjs b/test/embed-manifest/kotlin.test.mjs index 52ca11ae6..81f645b85 100644 --- a/test/embed-manifest/kotlin.test.mjs +++ b/test/embed-manifest/kotlin.test.mjs @@ -2,33 +2,15 @@ import { equal } from "node:assert/strict"; import { describe, it } from "node:test"; import { generate as generateActual } from "../../scripts/embed-manifest/kotlin.mjs"; +import * as fixtures from "./fixtures.mjs"; describe("embed manifest (Kotlin)", () => { /** @type {(json: Record) => string} */ const generate = (json) => generateActual(json, "0"); it("generates all properties", () => { - const code = generate({ - $schema: - "https://raw.githubusercontent.com/microsoft/react-native-test-app/trunk/schema.json", - name: "Example", - displayName: "Template", - version: "1.0", - bundleRoot: "main", - singleApp: "single", - components: [ - { - appKey: "Example", - displayName: "Template", - initialProperties: {}, - presentationStyle: "modal", - slug: "single", - }, - ], - resources: ["dist/res", "dist/main.jsbundle"], - }); equal( - code, + generate(fixtures.simple), `package com.microsoft.reacttestapp.manifest import android.os.Bundle @@ -47,6 +29,13 @@ class ManifestProvider { "main", "single", arrayListOf( + Component( + "Example", + "Example", + null, + null, + null + ), Component( "Example", "Template", @@ -64,9 +53,8 @@ class ManifestProvider { }); it("handles missing properties", () => { - const code = generate({ name: "Example" }); equal( - code, + generate(fixtures.minimum), `package com.microsoft.reacttestapp.manifest import android.os.Bundle @@ -94,125 +82,8 @@ class ManifestProvider { }); it("handles valid JSON data types", () => { - const code = generate({ - name: "Example", - components: [ - { - appKey: "Example", - initialProperties: { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - array: [ - true, - 1.1, - 1, - null, - "string", - [ - true, - 1.1, - 1, - null, - "string", - [], - { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - }, - ], - { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - }, - ], - object: { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - array: [ - true, - 1.1, - 1, - null, - "string", - [ - true, - 1.1, - 1, - null, - "string", - [], - { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - }, - ], - { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - }, - ], - object: { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - array: [ - true, - 1.1, - 1, - null, - "string", - [ - true, - 1.1, - 1, - null, - "string", - [], - { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - }, - ], - { - boolean: true, - double: 1.1, - int: 1, - null: null, - string: "string", - }, - ], - }, - }, - }, - }, - ], - resources: ["dist/res", "dist/main.jsbundle"], - }); equal( - code, + generate(fixtures.extended), `package com.microsoft.reacttestapp.manifest import android.os.Bundle @@ -303,13 +174,7 @@ class ManifestProvider { putString("string", "string") } ), - Bundle().apply { - putBoolean("boolean", true) - putDouble("double", 1.1) - putInt("int", 1) - putString("null", null) - putString("string", "string") - } + Bundle() ) ) putBundle( diff --git a/test/pack.test.mjs b/test/pack.test.mjs index 2a6c0fde5..c8e9d9186 100644 --- a/test/pack.test.mjs +++ b/test/pack.test.mjs @@ -187,6 +187,7 @@ describe("npm pack", () => { "scripts/config-plugins/types.ts", "scripts/configure-projects.js", "scripts/configure.mjs", + "scripts/embed-manifest/cpp.mjs", "scripts/embed-manifest/kotlin.mjs", "scripts/embed-manifest/main.mjs", "scripts/helpers.js", @@ -220,13 +221,12 @@ describe("npm pack", () => { "visionos/ReactTestAppUITests/ReactTestAppUITests.swift", "visionos/test_app.rb", "windows/ExperimentalFeatures.props", + "windows/Shared/EmbedManifest.targets", "windows/Shared/JSValueWriterHelper.h", - "windows/Shared/Manifest.cpp", "windows/Shared/Manifest.h", "windows/Shared/ReactInstance.cpp", "windows/Shared/ReactInstance.h", "windows/Shared/Session.h", - "windows/Shared/ValidateManifest.targets", "windows/UWP/App.cpp", "windows/UWP/App.h", "windows/UWP/App.idl", diff --git a/windows/Shared/EmbedManifest.targets b/windows/Shared/EmbedManifest.targets new file mode 100644 index 000000000..2fd24013e --- /dev/null +++ b/windows/Shared/EmbedManifest.targets @@ -0,0 +1,11 @@ + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-test-app\package.json'))\node_modules\react-native-test-app\scripts\embed-manifest\cpp.mjs + $([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'app.json')) + $(MSBuildProjectDirectory)\..\..\Manifest.g.cpp + + + + + diff --git a/windows/Shared/JSValueWriterHelper.h b/windows/Shared/JSValueWriterHelper.h index 3ccbaa2af..4eafa2578 100644 --- a/windows/Shared/JSValueWriterHelper.h +++ b/windows/Shared/JSValueWriterHelper.h @@ -2,12 +2,14 @@ #include #include -#include +#include #include #include #include +#include "Manifest.h" + namespace ReactApp { void JSValueWriterWriteValue(winrt::Microsoft::ReactNative::IJSValueWriter const &writer, @@ -17,29 +19,25 @@ namespace ReactApp writer.WriteBoolean(std::any_cast(value)); } else if (value.type() == typeid(std::int64_t)) { writer.WriteInt64(std::any_cast(value)); - } else if (value.type() == typeid(std::uint64_t)) { - writer.WriteInt64(std::any_cast(value)); } else if (value.type() == typeid(double)) { writer.WriteDouble(std::any_cast(value)); - } else if (value.type() == typeid(std::nullopt)) { - writer.WriteNull(); - } else if (value.type() == typeid(std::string)) { - writer.WriteString(winrt::to_hstring(std::any_cast(value))); + } else if (value.type() == typeid(std::string_view)) { + writer.WriteString(winrt::to_hstring(std::any_cast(value))); } else if (value.type() == typeid(std::vector)) { writer.WriteArrayBegin(); for (auto &&entry : std::any_cast>(value)) { JSValueWriterWriteValue(writer, entry); } writer.WriteArrayEnd(); - } else if (value.type() == typeid(std::map)) { + } else if (value.type() == typeid(JSONObject)) { writer.WriteObjectBegin(); - for (auto &[key, val] : std::any_cast>(value)) { + for (auto &[key, val] : std::any_cast(value)) { writer.WritePropertyName(winrt::to_hstring(key)); JSValueWriterWriteValue(writer, val); } writer.WriteObjectEnd(); } else { - assert(false); + writer.WriteNull(); } } } // namespace ReactApp diff --git a/windows/Shared/Manifest.cpp b/windows/Shared/Manifest.cpp deleted file mode 100644 index 14f84957f..000000000 --- a/windows/Shared/Manifest.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "pch.h" - -#include "Manifest.h" - -#include - -#include "app.json.h" - -using nlohmann::detail::value_t; - -namespace -{ - template - std::optional get_optional(const nlohmann::json &j, const std::string &key) - { - auto element = j.find(key); - if (element != j.end()) { - return element->get(); - } else { - return std::nullopt; - } - } - - std::any getAny(const nlohmann::json &j) - { - switch (j.type()) { - case value_t::null: - return std::nullopt; - case value_t::boolean: - return j.get(); - case value_t::number_integer: - return j.get(); - case value_t::number_unsigned: - return j.get(); - case value_t::number_float: - return j.get(); - case value_t::string: - return j.get(); - case value_t::object: { - std::map map; - for (auto &&e : j.items()) { - map.insert(std::make_pair(e.key(), getAny(e.value()))); - } - return map; - } - case value_t::array: { - std::vector array; - for (auto &&e : j.items()) { - array.push_back(getAny(e.value())); - } - return array; - } - // value_t::discarded. This case should never be hit since the check for malformed json - // is done previously in GetManifest() - default: - assert(false); - return std::nullopt; - } - } - - std::optional> parseInitialProps(const nlohmann::json &j) - { - auto element = j.find("initialProperties"); - if (element != j.end()) { - std::map map; - for (auto &&property : element->items()) { - map.insert(std::make_pair(property.key(), getAny(property.value()))); - } - return map; - } else { - return std::nullopt; - } - } - -} // namespace - -namespace ReactTestApp -{ - void from_json(const nlohmann::json &j, Component &c) - { - c.appKey = j.at("appKey"); - c.displayName = get_optional(j, "displayName"); - c.initialProperties = parseInitialProps(j); - c.presentationStyle = get_optional(j, "presentationStyle"); - c.slug = get_optional(j, "slug"); - } - - void from_json(const nlohmann::json &j, Manifest &m) - { - m.name = j.at("name"); - m.displayName = j.at("displayName"); - m.bundleRoot = get_optional(j, "bundleRoot"); - m.singleApp = get_optional(j, "singleApp"); - m.components = get_optional>(j, "components") - .value_or(std::vector{}); - } - - std::optional> GetManifest(const char *const json) - { - auto manifest = json == nullptr ? ReactTestApp_AppManifest : json; - auto j = nlohmann::json::parse(manifest, nullptr, false); - if (j.is_discarded()) { - return std::nullopt; - } - - return std::make_tuple(j.get(), std::string{ReactTestApp_AppManifestChecksum}); - } -} // namespace ReactTestApp diff --git a/windows/Shared/Manifest.h b/windows/Shared/Manifest.h index 092952a6b..568749634 100644 --- a/windows/Shared/Manifest.h +++ b/windows/Shared/Manifest.h @@ -6,29 +6,31 @@ #include #include #include -#include -#include +#include #include -namespace ReactTestApp +namespace ReactApp { + using JSONObject = std::map; + struct Component { - std::string appKey; - std::optional displayName; - std::optional> initialProperties; - std::optional presentationStyle; - std::optional slug; + std::string_view appKey; + std::optional displayName; + std::optional initialProperties; + std::optional presentationStyle; + std::optional slug; }; struct Manifest { - std::string name; - std::string displayName; - std::optional version; - std::optional bundleRoot; - std::optional singleApp; + std::string_view name; + std::string_view displayName; + std::optional version; + std::optional bundleRoot; + std::optional singleApp; std::optional> components; }; - std::optional> GetManifest(const char *const json = nullptr); + Manifest GetManifest(); + std::string_view GetManifestChecksum(); -} // namespace ReactTestApp +} // namespace ReactApp diff --git a/windows/Shared/Session.h b/windows/Shared/Session.h index 095c15a77..ab15d5d92 100644 --- a/windows/Shared/Session.h +++ b/windows/Shared/Session.h @@ -39,7 +39,7 @@ namespace ReactTestApp return winrt::to_string(value); } - static void ManifestChecksum(std::string const &value) + static void ManifestChecksum(std::string_view value) { auto v = PropertyValue::CreateString(winrt::to_hstring(value)); LocalSettings().Insert(kChecksum, v); @@ -58,7 +58,7 @@ namespace ReactTestApp LocalSettings().Insert(kRememberLastComponentEnabled, value); } - static std::optional GetLastOpenedComponent(std::string const &manifestChecksum) + static std::optional GetLastOpenedComponent(std::string_view manifestChecksum) { if (!ShouldRememberLastComponent() || manifestChecksum != ManifestChecksum()) { return std::nullopt; @@ -67,7 +67,7 @@ namespace ReactTestApp return LastComponentIndex(); } - static void StoreComponent(int index, std::string const &manifestChecksum) + static void StoreComponent(int index, std::string_view manifestChecksum) { LastComponentIndex(index); ManifestChecksum(manifestChecksum); diff --git a/windows/Shared/ValidateManifest.targets b/windows/Shared/ValidateManifest.targets deleted file mode 100644 index 5125c873c..000000000 --- a/windows/Shared/ValidateManifest.targets +++ /dev/null @@ -1,11 +0,0 @@ - - - - $([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'app.json')) - $(MSBuildProjectDirectory)\..\..\app.json.h - node -e "require('react-native-test-app/scripts/validate-manifest').validate('file')" - - - - - diff --git a/windows/UWP/MainPage.cpp b/windows/UWP/MainPage.cpp index ed743f437..fa20b6c85 100644 --- a/windows/UWP/MainPage.cpp +++ b/windows/UWP/MainPage.cpp @@ -12,9 +12,10 @@ #include "JSValueWriterHelper.h" #include "MainPage.g.cpp" +#include "Manifest.g.cpp" #include "Session.h" -using ReactTestApp::Component; +using ReactApp::Component; using ReactTestApp::JSBundleSource; using ReactTestApp::ReactInstance; using ReactTestApp::Session; @@ -123,34 +124,26 @@ MainPage::MainPage() InitializeComponent(); InitializeTitleBar(); - auto result = ::ReactTestApp::GetManifest(); - if (result.has_value()) { - auto &[manifest, checksum] = result.value(); - - manifestChecksum_ = std::move(checksum); - - AppTitle().Text(to_hstring(manifest.displayName)); - reactInstance_.BundleRoot(manifest.bundleRoot.has_value() - ? std::make_optional(to_hstring(manifest.bundleRoot.value())) - : std::nullopt); - - if constexpr (kSingleAppMode) { - assert(manifest.singleApp.has_value() || - !"`ENABLE_SINGLE_APP_MODE` shouldn't have been true"); - assert(manifest.components.has_value() || !"At least one component must be declared"); - - for (auto &component : *manifest.components) { - if (component.slug == *manifest.singleApp) { - InitializeReactRootView(reactInstance_.ReactHost(), ReactRootView(), component); - break; - } + auto manifest = ::ReactApp::GetManifest(); + AppTitle().Text(to_hstring(manifest.displayName)); + reactInstance_.BundleRoot(manifest.bundleRoot.has_value() + ? std::make_optional(to_hstring(manifest.bundleRoot.value())) + : std::nullopt); + + if constexpr (kSingleAppMode) { + assert(manifest.singleApp.has_value() || + !"`ENABLE_SINGLE_APP_MODE` shouldn't have been true"); + assert(manifest.components.has_value() || !"At least one component must be declared"); + + for (auto &component : *manifest.components) { + if (component.slug == *manifest.singleApp) { + InitializeReactRootView(reactInstance_.ReactHost(), ReactRootView(), component); + break; } } - - InitializeReactMenu(std::move(manifest)); - } else { - InitializeReactMenu(std::nullopt); } + + InitializeReactMenu(std::move(manifest)); } IAsyncAction MainPage::LoadFromDevServer(IInspectable const &, RoutedEventArgs) @@ -308,24 +301,15 @@ void MainPage::InitializeDebugMenu() } } -void MainPage::InitializeReactMenu(std::optional<::ReactTestApp::Manifest> manifest) +void MainPage::InitializeReactMenu(::ReactApp::Manifest manifest) { if constexpr (kDebug || !kSingleAppMode) { AppMenuBar().Visibility(Visibility::Visible); RememberLastComponentMenuItem().IsChecked(Session::ShouldRememberLastComponent()); - auto menuItems = ReactMenuBarItem().Items(); - if (!manifest.has_value()) { - MenuFlyoutItem newMenuItem; - newMenuItem.Text(L"Couldn't parse 'app.json'"); - newMenuItem.IsEnabled(false); - menuItems.Append(newMenuItem); - return; - } - if constexpr (!kSingleAppMode) { - auto &components = manifest->components; + auto &components = manifest.components; if (!components.has_value() || components->empty()) { reactInstance_.SetComponentsRegisteredDelegate( [this](std::vector const &appKeys) { @@ -388,8 +372,9 @@ void MainPage::OnComponentsRegistered(std::vector components) } else { // If only one component is present, load it right away. Otherwise, // check whether we can reopen a component from previous session. - auto index = - components.size() == 1 ? 0 : Session::GetLastOpenedComponent(manifestChecksum_); + auto index = components.size() == 1 + ? 0 + : Session::GetLastOpenedComponent(::ReactApp::GetManifestChecksum()); if (index.has_value()) { Loaded([this, component = components[index.value()]](IInspectable const &, RoutedEventArgs const &) { @@ -417,7 +402,7 @@ void MainPage::OnComponentsRegistered(std::vector components) newMenuItem.Click( [this, component = std::move(component), i](IInspectable const &, RoutedEventArgs) { LoadReactComponent(component); - Session::StoreComponent(i, manifestChecksum_); + Session::StoreComponent(i, ::ReactApp::GetManifestChecksum()); }); // Add keyboard accelerator for first nine (1-9) components diff --git a/windows/UWP/MainPage.h b/windows/UWP/MainPage.h index 89ea087be..85e922f47 100644 --- a/windows/UWP/MainPage.h +++ b/windows/UWP/MainPage.h @@ -54,18 +54,17 @@ namespace winrt::ReactTestApp::implementation using Base = MainPageT; ::ReactTestApp::ReactInstance reactInstance_; - std::string manifestChecksum_; void InitializeDebugMenu(); - void InitializeReactMenu(std::optional<::ReactTestApp::Manifest>); + void InitializeReactMenu(::ReactApp::Manifest); void InitializeTitleBar(); bool IsPresenting(); bool LoadJSBundleFrom(::ReactTestApp::JSBundleSource); - void LoadReactComponent(::ReactTestApp::Component const &); + void LoadReactComponent(::ReactApp::Component const &); - void OnComponentsRegistered(std::vector<::ReactTestApp::Component>); + void OnComponentsRegistered(std::vector<::ReactApp::Component>); void OnCoreTitleBarLayoutMetricsChanged( Windows::ApplicationModel::Core::CoreApplicationViewTitleBar const &, diff --git a/windows/UWP/ReactTestApp.vcxproj b/windows/UWP/ReactTestApp.vcxproj index eed6b4088..6094df9a4 100644 --- a/windows/UWP/ReactTestApp.vcxproj +++ b/windows/UWP/ReactTestApp.vcxproj @@ -167,7 +167,6 @@ $(ReactAppUniversalDir)\MainPage.xaml - @@ -184,12 +183,9 @@ - - - - + @@ -208,7 +204,6 @@ - diff --git a/windows/UWP/ReactTestApp.vcxproj.filters b/windows/UWP/ReactTestApp.vcxproj.filters index b63201820..b35f5defb 100644 --- a/windows/UWP/ReactTestApp.vcxproj.filters +++ b/windows/UWP/ReactTestApp.vcxproj.filters @@ -16,7 +16,6 @@ - diff --git a/windows/UWP/packages.config b/windows/UWP/packages.config index 8e945056d..9973c1908 100644 --- a/windows/UWP/packages.config +++ b/windows/UWP/packages.config @@ -5,6 +5,5 @@ - diff --git a/windows/Win32/Main.cpp b/windows/Win32/Main.cpp index 247e8136c..e3f193e9b 100644 --- a/windows/Win32/Main.cpp +++ b/windows/Win32/Main.cpp @@ -3,7 +3,7 @@ #include "Main.h" #include "JSValueWriterHelper.h" -#include "Manifest.h" +#include "Manifest.g.cpp" #include "ReactInstance.h" namespace winrt @@ -55,12 +55,12 @@ namespace rootView.Size(size); } - winrt::ReactViewOptions MakeReactViewOptions(ReactTestApp::Component const &component) + winrt::ReactViewOptions MakeReactViewOptions(ReactApp::Component const &component) { winrt::ReactViewOptions viewOptions; viewOptions.ComponentName(winrt::to_hstring(component.appKey)); - auto initialProps = component.initialProperties.value_or(std::map{}); + auto initialProps = component.initialProperties.value_or({}); initialProps["concurrentRoot"] = true; viewOptions.InitialProps( [initialProps = std::move(initialProps)](winrt::IJSValueWriter const &writer) { @@ -81,9 +81,7 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE /* instance */, PSTR /* commandLine */, int /* showCmd */) { - auto result = ::ReactTestApp::GetManifest(); - assert(result.has_value() && "Failed to parse app manifest"); - auto &[manifest, checksum] = *result; + auto manifest = ::ReactApp::GetManifest(); assert(manifest.components.has_value() && (*manifest.components).size() > 0 && "At least one component must be declared"); diff --git a/windows/Win32/ReactApp.vcxproj b/windows/Win32/ReactApp.vcxproj index 987846793..e94db6960 100644 --- a/windows/Win32/ReactApp.vcxproj +++ b/windows/Win32/ReactApp.vcxproj @@ -116,7 +116,6 @@ - @@ -138,13 +137,10 @@ - + - - - This project references targets in your node_modules\react-native-windows folder. The missing file is {0}. diff --git a/windows/Win32/ReactApp.vcxproj.filters b/windows/Win32/ReactApp.vcxproj.filters index 0b1aee467..9293f02ae 100644 --- a/windows/Win32/ReactApp.vcxproj.filters +++ b/windows/Win32/ReactApp.vcxproj.filters @@ -47,9 +47,6 @@ Source Files - - Source Files - Source Files diff --git a/windows/test-app.mjs b/windows/test-app.mjs index 9d4bc27ae..aa2f63e47 100755 --- a/windows/test-app.mjs +++ b/windows/test-app.mjs @@ -14,7 +14,6 @@ import { writeTextFile, } from "../scripts/helpers.js"; import { parseArgs } from "../scripts/parseargs.mjs"; -import { validate } from "../scripts/validate-manifest.js"; import { loadReactNativeConfig, projectInfo } from "./project.mjs"; import { configureForUWP } from "./uwp.mjs"; import { configureForWin32 } from "./win32.mjs"; @@ -144,10 +143,6 @@ export function generateSolution(destPath, options, fs = nodefs) { return "Could not find 'react-native-windows'"; } - if (validate("file", destPath) !== 0) { - return "App manifest validation failed!"; - } - const info = projectInfo(options, rnWindowsPath, destPath, fs); const { projDir, projectFileName, projectFiles, solutionTemplatePath } = info.useFabric