fix(windows): support for custom Package.appxmanifest (#302)

This commit is contained in:
Tommy Nguyen 2021-04-13 12:12:29 +02:00 коммит произвёл GitHub
Родитель 4929c4805d
Коммит 76286ee50a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
17 изменённых файлов: 173 добавлений и 106 удалений

Просмотреть файл

@ -79,6 +79,7 @@
"@types/mustache": "^4.0.0",
"@types/node": "^12.0.0",
"@types/prompts": "^2.0.0",
"@types/rimraf": "^3.0.0",
"eslint": "^7.10.0",
"eslint-plugin-jest": "^24.0.0",
"eslint-plugin-prettier": "^3.1.4",

Просмотреть файл

@ -605,7 +605,6 @@ function isDestructive(packagePath, { files, oldFiles }) {
*/
function removeAllFiles(files, destination) {
/** @type {(p: string, cb: (error?: Error) => void) => void} */
// @ts-ignore
const rimraf = require("rimraf");
const rethrow = (/** @type {Error | undefined} */ error) => {

Просмотреть файл

@ -9,7 +9,7 @@
jest.mock("fs");
describe("getAppName()", () => {
const { mockFiles } = require("./mockFiles");
const { mockFiles } = require("../mockFiles");
const { getAppName } = require("../../scripts/configure");
const consoleSpy = jest.spyOn(global.console, "warn");

Просмотреть файл

@ -9,7 +9,7 @@
jest.mock("fs");
describe("isDestructive()", () => {
const { mockFiles } = require("./mockFiles");
const { mockFiles } = require("../mockFiles");
const { isDestructive } = require("../../scripts/configure");
/**

Просмотреть файл

@ -11,7 +11,7 @@ jest.mock("fs");
const fs = require("fs");
describe("removeAllFiles()", () => {
const { mockFiles } = require("./mockFiles");
const { mockFiles } = require("../mockFiles");
const { removeAllFiles } = require("../../scripts/configure");
beforeEach(() => {

Просмотреть файл

@ -9,7 +9,7 @@
jest.mock("fs");
describe("updatePackageManifest()", () => {
const { mockFiles } = require("./mockFiles");
const { mockFiles } = require("../mockFiles");
const { updatePackageManifest } = require("../../scripts/configure");
afterEach(() => mockFiles());

Просмотреть файл

@ -12,7 +12,7 @@ const fs = require("fs");
const path = require("path");
describe("writeAllFiles()", () => {
const { mockFiles } = require("./mockFiles");
const { mockFiles } = require("../mockFiles");
const { writeAllFiles } = require("../../scripts/configure");
afterEach(() => {

Просмотреть файл

@ -19,7 +19,10 @@ function mockFiles(...files) {
// @ts-ignore `__setMockFiles`
fs.__setMockFiles(
Object.fromEntries(
files.map(([filename, content]) => [filename, JSON.stringify(content)])
files.map(([filename, content]) => [
filename,
typeof content === "string" ? content : JSON.stringify(content),
])
)
);
}

Просмотреть файл

@ -9,19 +9,19 @@
jest.mock("fs");
describe("copyAndReplace", () => {
const { mockFiles } = require("../mockFiles");
const { copyAndReplace } = require("../../windows/test-app");
// @ts-ignore `__setMockFiles`
afterEach(() => require("fs").__setMockFiles({}));
afterEach(() => mockFiles());
test("replaces text files only", () => {
mockFiles(
["ReactTestApp_TemporaryKey.pfx", "binary"],
["ReactTestApp.png", "binary"],
["ReactTestApp.sln", "binary"]
);
const fs = require("fs");
// @ts-ignore `__setMockFiles`
fs.__setMockFiles({
"ReactTestApp_TemporaryKey.pfx": "binary",
"ReactTestApp.png": "binary",
"ReactTestApp.sln": "binary",
});
const replacements = { binary: "text" };

Просмотреть файл

@ -10,6 +10,7 @@ jest.mock("fs");
describe("generateSolution", () => {
const path = require("path");
const { mockFiles } = require("../mockFiles");
const { generateSolution } = require("../../windows/test-app");
const cwd = process.cwd();
@ -24,8 +25,7 @@ describe("generateSolution", () => {
});
afterEach(() => {
// @ts-ignore `__setMockFiles`
require("fs").__setMockFiles({});
mockFiles();
process.chdir(cwd);
});
@ -42,10 +42,7 @@ describe("generateSolution", () => {
});
test("exits if 'react-native-windows' folder cannot be found", () => {
// @ts-ignore `__setMockFiles`
require("fs").__setMockFiles({
[path.resolve("", "node_modules")]: "directory",
});
mockFiles([path.resolve("", "node_modules"), "directory"]);
expect(generateSolution("test", options)).toBe(
"Could not find 'react-native-windows'"
@ -53,11 +50,10 @@ describe("generateSolution", () => {
});
test("exits if 'react-native-test-app' folder cannot be found", () => {
// @ts-ignore `__setMockFiles`
require("fs").__setMockFiles({
[path.resolve("", "node_modules")]: "directory",
[path.resolve("", "node_modules", "react-native-windows")]: "directory",
});
mockFiles(
[path.resolve("", "node_modules"), "directory"],
[path.resolve("", "node_modules", "react-native-windows"), "directory"]
);
expect(generateSolution("test", options)).toBe(
"Could not find 'react-native-test-app'"

Просмотреть файл

@ -10,43 +10,74 @@ jest.mock("fs");
describe("getBundleResources", () => {
const path = require("path");
const { mockFiles } = require("../mockFiles");
const { getBundleResources } = require("../../windows/test-app");
// @ts-ignore `__setMockFiles`
afterEach(() => require("fs").__setMockFiles({}));
afterEach(() => mockFiles());
test("returns app name and bundle resources", () => {
const assets = path.join("dist", "assets");
const bundle = path.join("dist", "main.bundle");
// @ts-ignore `__setMockFiles`
require("fs").__setMockFiles({
"app.json": JSON.stringify({
name: "Example",
resources: [assets, bundle],
}),
[assets]: "directory",
[bundle]: "text",
});
const [appName, bundleDirContent, bundleFileContent] = getBundleResources(
"app.json",
path.resolve("")
mockFiles(
[
"app.json",
{
name: "Example",
resources: [assets, bundle],
},
],
[assets, "directory"],
[bundle, "text"]
);
const {
appName,
appxManifest,
bundleDirContent,
bundleFileContent,
} = getBundleResources("app.json", path.resolve(""));
expect(appName).toBe("Example");
expect(appxManifest).toBe("windows/Package.appxmanifest");
expect(bundleDirContent).toBe(`${assets}\\**\\*;`);
expect(bundleFileContent).toBe(`${bundle};`);
});
test("returns package manifest", () => {
mockFiles([
"app.json",
{
windows: {
appxManifest: "windows/Example/Package.appxmanifest",
},
},
]);
const {
appName,
appxManifest,
bundleDirContent,
bundleFileContent,
} = getBundleResources("app.json", path.resolve(""));
expect(appName).toBe("ReactTestApp");
expect(appxManifest).toBe("windows/Example/Package.appxmanifest");
expect(bundleDirContent).toBe("");
expect(bundleFileContent).toBe("");
});
test("handles missing manifest", () => {
const warnSpy = jest.spyOn(global.console, "warn").mockImplementation();
const [appName, bundleDirContent, bundleFileContent] = getBundleResources(
"",
""
);
const {
appName,
appxManifest,
bundleDirContent,
bundleFileContent,
} = getBundleResources("", "");
expect(appName).toBe("ReactTestApp");
expect(appxManifest).toBe("windows/Package.appxmanifest");
expect(bundleDirContent).toBeFalsy();
expect(bundleFileContent).toBeFalsy();
@ -56,19 +87,19 @@ describe("getBundleResources", () => {
});
test("handles invalid manifest", () => {
// @ts-ignore `__setMockFiles`
require("fs").__setMockFiles({
"app.json": "-",
});
mockFiles(["app.json", "-"]);
const warnSpy = jest.spyOn(global.console, "warn").mockImplementation();
const [appName, bundleDirContent, bundleFileContent] = getBundleResources(
"app.json",
path.resolve("")
);
const {
appName,
appxManifest,
bundleDirContent,
bundleFileContent,
} = getBundleResources("app.json", path.resolve(""));
expect(appName).toBe("ReactTestApp");
expect(appxManifest).toBe("windows/Package.appxmanifest");
expect(bundleDirContent).toBeFalsy();
expect(bundleFileContent).toBeFalsy();

Просмотреть файл

@ -9,10 +9,10 @@
jest.mock("fs");
describe("parseResources", () => {
const { mockFiles } = require("../mockFiles");
const { parseResources } = require("../../windows/test-app");
// @ts-ignore `__setMockFiles`
afterEach(() => require("fs").__setMockFiles({}));
afterEach(() => mockFiles());
test("returns empty strings for no resources", () => {
expect(parseResources(undefined, "", "")).toEqual(["", ""]);
@ -22,11 +22,7 @@ describe("parseResources", () => {
});
test("returns references to existing assets", () => {
// @ts-ignore `__setMockFiles`
require("fs").__setMockFiles({
"dist/assets": "directory",
"dist/main.jsbundle": "text",
});
mockFiles(["dist/assets", "directory"], ["dist/main.jsbundle", "text"]);
expect(
parseResources(

Просмотреть файл

@ -9,8 +9,6 @@
#include "App.h"
#include "MainPage.h"
using winrt::ReactTestApp::implementation::App;
using winrt::Windows::ApplicationModel::SuspendingEventArgs;
using winrt::Windows::ApplicationModel::Activation::ApplicationExecutionState;
@ -73,12 +71,10 @@ void App::OnLaunched(LaunchActivatedEventArgs const &e)
// Ensure the current window is active
Window::Current().Activate();
}
} else {
if (!e.PrelaunchActivated()) {
NavigateToFirstPage(rootFrame, e);
// Ensure the current window is active
Window::Current().Activate();
}
} else if (!e.PrelaunchActivated()) {
NavigateToFirstPage(rootFrame, e);
// Ensure the current window is active
Window::Current().Activate();
}
}
@ -110,6 +106,6 @@ void App::NavigateToFirstPage(Frame &rootFrame, LaunchActivatedEventArgs const &
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(xaml_typename<ReactTestApp::MainPage>(), box_value(e.Arguments()));
rootFrame.Navigate(xaml_typename<MainPage>(), box_value(e.Arguments()));
}
}

Просмотреть файл

@ -166,7 +166,10 @@
</Page>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="$(ReactTestAppDir)\Package.appxmanifest">
<AppxManifest Include="$(SolutionDir)$(ReactTestAppPackageManifest)" Condition="Exists('$(SolutionDir)$(ReactTestAppPackageManifest)')">
<SubType>Designer</SubType>
</AppxManifest>
<AppxManifest Include="$(ReactTestAppDir)\Package.appxmanifest" Condition="!Exists('$(SolutionDir)$(ReactTestAppPackageManifest)')">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>

Просмотреть файл

@ -36,6 +36,7 @@
</Text>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="$(SolutionDir)$(ReactTestAppPackageManifest)" />
<AppxManifest Include="$(ReactTestAppDir)\Package.appxmanifest" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -19,6 +19,8 @@ const templateView = {
// Binary files in React Native Test App Windows project
const binaryExtensions = [".png", ".pfx"];
const textFileReadOptions = { encoding: "utf-8" };
const textFileWriteOptions = { encoding: "utf-8", mode: 0o644 };
/**
* Finds nearest relative path to a file or directory from current path.
@ -58,7 +60,7 @@ function findUserProjects(projectDir, projects = []) {
findUserProjects(fullPath, projects);
}
} else if (fullPath.endsWith(".vcxproj")) {
const vcxproj = fs.readFileSync(fullPath, { encoding: "utf8" });
const vcxproj = fs.readFileSync(fullPath, textFileReadOptions);
const guidMatch = vcxproj.match(
/<ProjectGuid>({[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}})<\/ProjectGuid>/
);
@ -130,6 +132,16 @@ function replaceContent(content, replacements) {
);
}
/**
* Rethrows specified error.
* @param {Error | null} error
*/
function rethrow(error) {
if (error) {
throw error;
}
}
/**
* Returns a solution entry for specified project.
* @param {{ path: string; name: string; guid: string; }} project
@ -169,11 +181,11 @@ function copyAndReplace(srcPath, destPath, replacements = {}) {
return fs.writeFile(
destPath,
replaceContent(
fs.readFileSync(srcPath, { encoding: "utf8" }),
fs.readFileSync(srcPath, textFileReadOptions),
replacements
),
{
encoding: "utf8",
encoding: "utf-8",
mode: fs.statSync(srcPath).mode,
},
throwOnError
@ -185,22 +197,36 @@ function copyAndReplace(srcPath, destPath, replacements = {}) {
* Reads manifest file and and resolves paths to bundle resources.
* @param {string | null} manifestFilePath Path to the closest manifest file.
* @param {string} projectFilesDestPath Resolved paths will be relative to this path.
* @return {[string, string, string]} Application name and paths to directories and files to include
* @return {{
* appName: string;
* appxManifest: string;
* bundleDirContent: string;
* bundleFileContent: string;
* }} Application name, and paths to directories and files to include.
*/
function getBundleResources(manifestFilePath, projectFilesDestPath) {
// Default value if manifest or 'name' field doesn't exist
// Default value if manifest or 'name' field don't exist.
const defaultName = "ReactTestApp";
// Default `Package.appxmanifest` path. The project will automatically use our
// fallback if there is no file at this path.
const defaultAppxManifest = "windows/Package.appxmanifest";
if (manifestFilePath) {
try {
const content = fs.readFileSync(manifestFilePath, { encoding: "utf8" });
const { name, resources } = JSON.parse(content);
const content = fs.readFileSync(manifestFilePath, textFileReadOptions);
const { name, resources, windows } = JSON.parse(content);
const [bundleDirContent, bundleFileContent] = parseResources(
resources,
path.dirname(manifestFilePath),
projectFilesDestPath
);
return [name || defaultName, bundleDirContent, bundleFileContent];
return {
appName: name || defaultName,
appxManifest: (windows && windows.appxManifest) || defaultAppxManifest,
bundleDirContent,
bundleFileContent,
};
} catch (e) {
console.warn(`Could not parse 'app.json':\n${e.message}`);
}
@ -208,7 +234,12 @@ function getBundleResources(manifestFilePath, projectFilesDestPath) {
console.warn("Could not find 'app.json' file.");
}
return [defaultName, "", ""];
return {
appName: defaultName,
appxManifest: defaultAppxManifest,
bundleDirContent: "",
bundleFileContent: "",
};
}
/**
@ -218,9 +249,7 @@ function getBundleResources(manifestFilePath, projectFilesDestPath) {
*/
function getPackageVersion(packagePath) {
const { version } = JSON.parse(
fs.readFileSync(path.join(packagePath, "package.json"), {
encoding: "utf8",
})
fs.readFileSync(path.join(packagePath, "package.json"), textFileReadOptions)
);
return version;
}
@ -287,10 +316,12 @@ function generateSolution(destPath, { autolink, useNuGet }) {
fs.mkdirSync(destPath, { recursive: true });
const manifestFilePath = findNearest("app.json");
const [appName, bundleDirContent, bundleFileContent] = getBundleResources(
manifestFilePath,
projectFilesDestPath
);
const {
appName,
appxManifest,
bundleDirContent,
bundleFileContent,
} = getBundleResources(manifestFilePath, projectFilesDestPath);
const rnWindowsVersion = getPackageVersion(rnWindowsPath);
@ -312,6 +343,9 @@ function generateSolution(destPath, { autolink, useNuGet }) {
)};`,
"\\$\\(BundleDirContentPaths\\)": bundleDirContent,
"\\$\\(BundleFileContentPaths\\)": bundleFileContent,
"\\$\\(ReactTestAppPackageManifest\\)": path.normalize(
path.relative(destPath, path.resolve(appxManifest))
),
};
const copyTasks = [
@ -387,7 +421,7 @@ function generateSolution(destPath, { autolink, useNuGet }) {
const solutionTask = fs.writeFile(
path.join(destPath, `${appName}.sln`),
mustache
.render(fs.readFileSync(solutionTemplatePath, { encoding: "utf8" }), {
.render(fs.readFileSync(solutionTemplatePath, textFileReadOptions), {
...templateView,
useExperimentalNuget: useNuGet,
})
@ -406,15 +440,8 @@ function generateSolution(destPath, { autolink, useNuGet }) {
/EndProject\r?\nGlobal/,
["EndProject", additionalProjectEntries, "Global"].join(os.EOL)
),
{
encoding: "utf8",
mode: 0o644,
},
(error) => {
if (error) {
throw error;
}
}
textFileWriteOptions,
rethrow
);
if (useNuGet) {
const nugetConfigPath =
@ -445,18 +472,11 @@ function generateSolution(destPath, { autolink, useNuGet }) {
fs.writeFile(
path.join(destPath, "NuGet.Config"),
mustache.render(
fs.readFileSync(nugetConfigPath, { encoding: "utf8" }),
fs.readFileSync(nugetConfigPath, textFileReadOptions),
{}
),
{
encoding: "utf8",
mode: 0o644,
},
(error) => {
if (error) {
throw error;
}
}
textFileWriteOptions,
rethrow
);
}
}

Просмотреть файл

@ -1536,6 +1536,14 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/glob@*":
version "7.1.3"
resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
dependencies:
"@types/minimatch" "*"
"@types/node" "*"
"@types/graceful-fs@^4.1.2":
version "4.1.4"
resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz#4ff9f641a7c6d1a3508ff88bc3141b152772e753"
@ -1583,6 +1591,11 @@
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/minimatch@*":
version "3.0.4"
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21"
integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==
"@types/minimist@^1.2.0":
version "1.2.1"
resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
@ -1630,6 +1643,14 @@
resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
"@types/rimraf@^3.0.0":
version "3.0.0"
resolved "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f"
integrity sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ==
dependencies:
"@types/glob" "*"
"@types/node" "*"
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"