@react-native-windows/cli
Advanced tools
Comparing version 0.0.0-canary.57 to 0.0.0-canary.58
@@ -5,3 +5,44 @@ { | ||
{ | ||
"date": "Wed, 10 Feb 2021 05:06:04 GMT", | ||
"date": "Thu, 11 Feb 2021 05:06:13 GMT", | ||
"tag": "@react-native-windows/cli_v0.0.0-canary.58", | ||
"version": "0.0.0-canary.58", | ||
"comments": { | ||
"prerelease": [ | ||
{ | ||
"comment": "Refactor autolinking code", | ||
"author": "asklar@microsoft.com", | ||
"commit": "3a875c2bf27354dc0b5cf96a291ab9105bf3a774", | ||
"package": "@react-native-windows/cli" | ||
}, | ||
{ | ||
"comment": "Rename \"BuildFlags.props\" to \"ExperimentalFeatures.props\"", | ||
"author": "ngerlem@microsoft.com", | ||
"commit": "ab41a9b88db59cba0eae1ff7a59c1b550cc8c2bf", | ||
"package": "@react-native-windows/cli" | ||
}, | ||
{ | ||
"comment": "Better dependency warning on run-windows build error", | ||
"author": "ngerlem@microsoft.com", | ||
"commit": "cf6a97e48f5a8ae59fdb29ef39e71800394a73dc", | ||
"package": "@react-native-windows/cli" | ||
} | ||
], | ||
"patch": [ | ||
{ | ||
"comment": "Bump @rnw-scripts/just-task to v2.1.0", | ||
"author": "ngerlem@microsoft.com", | ||
"commit": "ab41a9b88db59cba0eae1ff7a59c1b550cc8c2bf", | ||
"package": "@react-native-windows/cli" | ||
}, | ||
{ | ||
"comment": "Bump @rnw-scripts/jest-unittest-config to v1.2.0", | ||
"author": "ngerlem@microsoft.com", | ||
"commit": "ab41a9b88db59cba0eae1ff7a59c1b550cc8c2bf", | ||
"package": "@react-native-windows/cli" | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"date": "Wed, 10 Feb 2021 05:06:37 GMT", | ||
"tag": "@react-native-windows/cli_v0.0.0-canary.57", | ||
@@ -8,0 +49,0 @@ "version": "0.0.0-canary.57", |
# Change Log - @react-native-windows/cli | ||
This log was last generated on Wed, 10 Feb 2021 05:06:04 GMT and should not be manually modified. | ||
This log was last generated on Thu, 11 Feb 2021 05:06:13 GMT and should not be manually modified. | ||
<!-- Start content --> | ||
## 0.0.0-canary.58 | ||
Thu, 11 Feb 2021 05:06:13 GMT | ||
### Patches | ||
- Bump @rnw-scripts/just-task to v2.1.0 (ngerlem@microsoft.com) | ||
- Bump @rnw-scripts/jest-unittest-config to v1.2.0 (ngerlem@microsoft.com) | ||
### Changes | ||
- Refactor autolinking code (asklar@microsoft.com) | ||
- Rename "BuildFlags.props" to "ExperimentalFeatures.props" (ngerlem@microsoft.com) | ||
- Better dependency warning on run-windows build error (ngerlem@microsoft.com) | ||
## 0.0.0-canary.57 | ||
Wed, 10 Feb 2021 05:06:04 GMT | ||
Wed, 10 Feb 2021 05:06:37 GMT | ||
@@ -11,0 +26,0 @@ ### Changes |
@@ -140,3 +140,3 @@ "use strict"; | ||
else { | ||
// No react-native.config, try to heurestically find any projects | ||
// No react-native.config, try to heuristically find any projects | ||
const foundProjects = configUtils.findDependencyProjectFiles(sourceDir); | ||
@@ -143,0 +143,0 @@ for (const foundProject of foundProjects) { |
@@ -18,3 +18,3 @@ /** | ||
project: Project; | ||
useWinUI3: boolean; | ||
useWinUI3?: boolean; | ||
} | ||
@@ -21,0 +21,0 @@ /** |
@@ -36,3 +36,2 @@ "use strict"; | ||
sourceDir: path.relative(folder, sourceDir), | ||
useWinUI3: false, | ||
}; | ||
@@ -39,0 +38,0 @@ let validProject = false; |
@@ -82,7 +82,10 @@ "use strict"; | ||
const userConfig = rnc.dependency.platforms.windows; | ||
const expectedConfig = rnc.expectedConfig; | ||
if (expectedConfig !== null) { | ||
expectedConfig.folder = folder; | ||
if (name === 'BlankLib') { | ||
expect(dependencyConfig_1.dependencyConfigWindows(folder, userConfig)).toMatchSnapshot(); | ||
} | ||
expect(dependencyConfig_1.dependencyConfigWindows(folder, userConfig)).toEqual(expectedConfig); | ||
else { | ||
expect(dependencyConfig_1.dependencyConfigWindows(folder, userConfig)).toMatchSnapshot({ | ||
folder: expect.stringContaining(name), | ||
}); | ||
} | ||
}); | ||
@@ -92,3 +95,2 @@ // Tests the result of ignoring the windows project config in react-native.config.js | ||
const folder = path.resolve('src/e2etest/projects/', name); | ||
const rnc = require(path.join(folder, 'react-native.config.js')); | ||
if (setup !== undefined) { | ||
@@ -98,8 +100,11 @@ await setup(folder); | ||
const userConfig = {}; | ||
const expectedConfig = rnc.expectedConfigIgnoringOverride; | ||
if (expectedConfig !== null) { | ||
expectedConfig.folder = folder; | ||
if (name === 'BlankLib') { | ||
expect(dependencyConfig_1.dependencyConfigWindows(folder, userConfig)).toMatchSnapshot(); | ||
} | ||
expect(dependencyConfig_1.dependencyConfigWindows(folder, userConfig)).toEqual(expectedConfig); | ||
else { | ||
expect(dependencyConfig_1.dependencyConfigWindows(folder, userConfig)).toMatchSnapshot({ | ||
folder: expect.stringContaining(name), | ||
}); | ||
} | ||
}); | ||
//# sourceMappingURL=dependencyConfig.test.js.map |
@@ -13,10 +13,3 @@ "use strict"; | ||
const autolink_1 = require("../runWindows/utils/autolink"); | ||
const templateRoot = path.resolve('../../../vnext/template'); | ||
const testProjectGuid = '{416476D5-974A-4EE2-8145-4E331297247E}'; | ||
async function tryMkdir(dir) { | ||
try { | ||
await fs.promises.mkdir(dir, { recursive: true }); | ||
} | ||
catch (err) { } | ||
} | ||
const projectConfig_utils_1 = require("./projectConfig.utils"); | ||
function project(name, setup) { | ||
@@ -31,3 +24,3 @@ return [name, setup]; | ||
const windowsDir = path.join(folder, 'windows'); | ||
await tryMkdir(windowsDir); | ||
await projectConfig_utils_1.tryMkdir(windowsDir); | ||
}), | ||
@@ -37,3 +30,3 @@ // New C++ project based on the template | ||
const windowsDir = path.join(folder, 'windows'); | ||
await tryMkdir(windowsDir); | ||
await projectConfig_utils_1.tryMkdir(windowsDir); | ||
const replacements = { | ||
@@ -43,9 +36,9 @@ name: 'SimpleCppApp', | ||
useMustache: true, | ||
projectGuidUpper: testProjectGuid, | ||
projectGuidLower: testProjectGuid.toLowerCase(), | ||
projectGuidUpper: projectConfig_utils_1.testProjectGuid, | ||
projectGuidLower: projectConfig_utils_1.testProjectGuid.toLowerCase(), | ||
}; | ||
await generator_common_1.copyAndReplace(path.join(templateRoot, 'cpp-app/proj/MyApp.sln'), path.join(windowsDir, 'SimpleCppApp.sln'), replacements, null); | ||
await generator_common_1.copyAndReplace(path.join(projectConfig_utils_1.templateRoot, 'cpp-app/proj/MyApp.sln'), path.join(windowsDir, 'SimpleCppApp.sln'), replacements, null); | ||
const projDir = path.join(windowsDir, 'SimpleCppApp'); | ||
await tryMkdir(projDir); | ||
await generator_common_1.copyAndReplace(path.join(templateRoot, 'cpp-app/proj/MyApp.vcxproj'), path.join(projDir, 'SimpleCppApp.vcxproj'), replacements, null); | ||
await projectConfig_utils_1.tryMkdir(projDir); | ||
await generator_common_1.copyAndReplace(path.join(projectConfig_utils_1.templateRoot, 'cpp-app/proj/MyApp.vcxproj'), path.join(projDir, 'SimpleCppApp.vcxproj'), replacements, null); | ||
}), | ||
@@ -55,3 +48,3 @@ // New C# project based on the template | ||
const windowsDir = path.join(folder, 'windows'); | ||
await tryMkdir(windowsDir); | ||
await projectConfig_utils_1.tryMkdir(windowsDir); | ||
const replacements = { | ||
@@ -61,48 +54,12 @@ name: 'SimpleCSharpApp', | ||
useMustache: true, | ||
projectGuidUpper: testProjectGuid, | ||
projectGuidLower: testProjectGuid.toLowerCase(), | ||
projectGuidUpper: projectConfig_utils_1.testProjectGuid, | ||
projectGuidLower: projectConfig_utils_1.testProjectGuid.toLowerCase(), | ||
}; | ||
await generator_common_1.copyAndReplace(path.join(templateRoot, 'cs-app/proj/MyApp.sln'), path.join(windowsDir, 'SimpleCSharpApp.sln'), replacements, null); | ||
await generator_common_1.copyAndReplace(path.join(projectConfig_utils_1.templateRoot, 'cs-app/proj/MyApp.sln'), path.join(windowsDir, 'SimpleCSharpApp.sln'), replacements, null); | ||
const projDir = path.join(windowsDir, 'SimpleCSharpApp'); | ||
await tryMkdir(projDir); | ||
await generator_common_1.copyAndReplace(path.join(templateRoot, 'cs-app/proj/MyApp.csproj'), path.join(projDir, 'SimpleCSharpApp.csproj'), replacements, null); | ||
await projectConfig_utils_1.tryMkdir(projDir); | ||
await generator_common_1.copyAndReplace(path.join(projectConfig_utils_1.templateRoot, 'cs-app/proj/MyApp.csproj'), path.join(projDir, 'SimpleCSharpApp.csproj'), replacements, null); | ||
}), | ||
project('WithWinUI3', async (folder) => { | ||
const windowsDir = path.join(folder, 'windows'); | ||
await tryMkdir(windowsDir); | ||
const replacements = { | ||
name: 'WithWinUI3', | ||
namespace: 'WithWinUI3', | ||
useMustache: true, | ||
projectGuidUpper: testProjectGuid, | ||
projectGuidLower: testProjectGuid.toLowerCase(), | ||
useWinUI3: false, | ||
useHermes: false, | ||
packagesConfigCppNugetPackages: [ | ||
{ | ||
id: 'Microsoft.ReactNative.Cxx', | ||
version: '1.0.0', | ||
hasProps: false, | ||
hasTargets: true, | ||
}, | ||
{ | ||
id: 'Microsoft.UI.Xaml', | ||
version: '2.3.4.5', | ||
hasProps: false, | ||
hasTargets: false, | ||
}, | ||
{ | ||
id: 'Microsoft.WinUI', | ||
version: '3.2.1.0', | ||
hasProps: false, | ||
hasTargets: false, | ||
}, | ||
], | ||
}; | ||
await generator_common_1.copyAndReplace(path.join(templateRoot, 'cpp-app/proj/MyApp.sln'), path.join(windowsDir, 'WithWinUI3.sln'), replacements, null); | ||
const projDir = path.join(windowsDir, 'WithWinUI3'); | ||
await tryMkdir(projDir); | ||
await generator_common_1.copyAndReplace(path.join(templateRoot, 'cpp-app/proj/MyApp.vcxproj'), path.join(projDir, 'WithWinUI3.vcxproj'), replacements, null); | ||
await generator_common_1.copyAndReplace(path.join(templateRoot, 'cpp-app/proj/packages.config'), path.join(projDir, 'packages.config'), replacements, null); | ||
await generator_common_1.copyAndReplace(path.join(templateRoot, 'shared-app/proj/BuildFlags.props'), path.join(windowsDir, 'BuildFlags.props'), replacements, null); | ||
await projectConfig_utils_1.ensureWinUI3Project(folder); | ||
}), | ||
@@ -117,4 +74,3 @@ ]; | ||
const userConfig = null; | ||
const expectedConfig = null; | ||
expect(projectConfig_1.projectConfigWindows(folder, userConfig)).toBe(expectedConfig); | ||
expect(projectConfig_1.projectConfigWindows(folder, userConfig)).toBeNull(); | ||
}); | ||
@@ -129,7 +85,10 @@ // Tests the result given a windows project config in react-native.config.js | ||
const userConfig = rnc.project.windows; | ||
const expectedConfig = rnc.expectedConfig; | ||
if (expectedConfig !== null) { | ||
expectedConfig.folder = folder; | ||
if (name === 'BlankApp') { | ||
expect(projectConfig_1.projectConfigWindows(folder, userConfig)).toMatchSnapshot(); | ||
} | ||
expect(projectConfig_1.projectConfigWindows(folder, userConfig)).toEqual(expectedConfig); | ||
else { | ||
expect(projectConfig_1.projectConfigWindows(folder, userConfig)).toMatchSnapshot({ | ||
folder: expect.stringContaining(name), | ||
}); | ||
} | ||
}); | ||
@@ -139,3 +98,2 @@ // Tests the result of ignoring the windows project config in react-native.config.js | ||
const folder = path.resolve('src/e2etest/projects/', name); | ||
const rnc = require(path.join(folder, 'react-native.config.js')); | ||
if (setup !== undefined) { | ||
@@ -145,19 +103,26 @@ await setup(folder); | ||
const userConfig = {}; | ||
const expectedConfig = rnc.expectedConfigIgnoringOverride; | ||
if (expectedConfig !== null) { | ||
expectedConfig.folder = folder; | ||
if (name === 'BlankApp') { | ||
expect(projectConfig_1.projectConfigWindows(folder, userConfig)).toMatchSnapshot(); | ||
} | ||
expect(projectConfig_1.projectConfigWindows(folder, userConfig)).toEqual(expectedConfig); | ||
else { | ||
expect(projectConfig_1.projectConfigWindows(folder, userConfig)).toMatchSnapshot({ | ||
folder: expect.stringContaining(name), | ||
}); | ||
} | ||
}); | ||
test('useWinUI3=true in react-native.config.js, useWinUI3=false in BuildFlags.props', async (done) => { | ||
test('useWinUI3=true in react-native.config.js, useWinUI3=false in ExperimentalFeatures.props', async (done) => { | ||
const folder = path.resolve('src/e2etest/projects/WithWinUI3'); | ||
const rnc = require(path.join(folder, 'react-native.config.js')); | ||
const config = projectConfig_1.projectConfigWindows(folder, rnc.project.windows); | ||
expect(autolink_1.ensureXAMLDialect(config, false)).toBeTruthy(); | ||
const al = new autolink_1.AutolinkWindows({ windows: config }, {}, { | ||
check: false, | ||
logging: false, | ||
}); | ||
expect(al.ensureXAMLDialect()).toBeTruthy(); | ||
const packagesConfig = (await fs.promises.readFile(path.join(folder, 'windows/WithWinUI3/packages.config'))).toString(); | ||
const buildFlags = (await fs.promises.readFile(path.join(folder, 'windows/BuildFlags.props'))).toString(); | ||
expect(packagesConfig.replace(/\r/g, '')).toEqual(rnc.expectedPackagesConfig); | ||
expect(buildFlags.replace(/\r/g, '')).toEqual(rnc.expectedBuildFlags); | ||
const experimentalFeatures = (await fs.promises.readFile(path.join(folder, 'windows/ExperimentalFeatures.props'))).toString(); | ||
expect(packagesConfig.replace(/\r/g, '')).toMatchSnapshot(); | ||
expect(experimentalFeatures.replace(/\r/g, '')).toMatchSnapshot(); | ||
done(); | ||
}); | ||
//# sourceMappingURL=projectConfig.test.js.map |
@@ -305,6 +305,6 @@ "use strict"; | ||
} | ||
if (fs.existsSync(path.join(sharedPath, projDir, 'BuildFlags.props'))) { | ||
if (fs.existsSync(path.join(sharedPath, projDir, 'ExperimentalFeatures.props'))) { | ||
sharedProjMappings.push({ | ||
from: path.join(sharedPath, projDir, 'BuildFlags.props'), | ||
to: path.join(windowsDir, 'BuildFlags.props'), | ||
from: path.join(sharedPath, projDir, 'ExperimentalFeatures.props'), | ||
to: path.join(windowsDir, 'ExperimentalFeatures.props'), | ||
}); | ||
@@ -311,0 +311,0 @@ } |
@@ -105,3 +105,3 @@ "use strict"; | ||
const rnwDependenciesPath = path.join(path.dirname(rnwPkgJsonPath), 'scripts/rnw-dependencies.ps1'); | ||
commandWithProgress_1.newError(`Please install the necessary dependencies by running ${rnwDependenciesPath} from an elevated PowerShell prompt.\nFor more information, go to http://aka.ms/rnw-deps`); | ||
commandWithProgress_1.newError(`It is possible your installation is missing required software dependencies. Dependencies can be automatically installed by running ${rnwDependenciesPath} from an elevated PowerShell prompt.\nFor more information, go to http://aka.ms/rnw-deps`); | ||
} | ||
@@ -108,0 +108,0 @@ return setExitProcessWithError(e, options.logging); |
@@ -6,5 +6,77 @@ /** | ||
*/ | ||
import { Command } from '@react-native-community/cli-types'; | ||
import { Command, Dependency, ProjectConfig } from '@react-native-community/cli-types'; | ||
import { WindowsProjectConfig } from '../../config/projectConfig'; | ||
export declare function ensureXAMLDialect(windowsProjectConfig: WindowsProjectConfig, checkMode: boolean): boolean; | ||
import { Ora } from 'ora'; | ||
export declare class AutolinkWindows { | ||
readonly projectConfig: ProjectConfig; | ||
readonly dependenciesConfig: { | ||
[key: string]: Dependency; | ||
}; | ||
readonly options: AutoLinkOptions; | ||
private changesNecessary; | ||
protected windowsAppConfig: WindowsProjectConfig; | ||
areChangesNeeded(): boolean; | ||
private getWindowsConfig; | ||
private getSolutionFile; | ||
constructor(projectConfig: ProjectConfig, dependenciesConfig: { | ||
[key: string]: Dependency; | ||
}, options: AutoLinkOptions); | ||
run(spinner: Ora): void; | ||
/** | ||
* Handles the --proj command-line option by consuming its value into the windowsAppConfig | ||
*/ | ||
fixUpForProjOption(): void; | ||
/** | ||
* Handles the --sln command-line option by consuming its value into the windowsAppConfig | ||
*/ | ||
fixUpForSlnOption(): void; | ||
/** Validates the all of the required app (solution) properties are present and valid */ | ||
validateRequiredAppProperties(): void; | ||
/** @return the full path to the project file (.vcxproj or .csproj) */ | ||
private getProjectFile; | ||
/** Validates that all of the required app _project_ properties are present and valid */ | ||
validateRequiredProjectProperties(): void; | ||
private generateCppAutolinking; | ||
getCppReplacements(): { | ||
cppPackageProviders: string; | ||
cppIncludes: string; | ||
}; | ||
private generateCSAutolinking; | ||
getCsReplacements(): { | ||
csUsingNamespaces: string; | ||
csReactPackageProviders: string; | ||
}; | ||
/** Cache of dependencies */ | ||
private readonly windowsDependencies; | ||
private getWindowsDependencies; | ||
/** | ||
* Updates the target file with the expected contents if it's different. | ||
* @param filePath Path to the target file to update. | ||
* @param expectedContents The expected contents of the file. | ||
* @return Whether any changes were necessary. | ||
*/ | ||
protected updateFile(filePath: string, expectedContents: string): boolean; | ||
private generateAutolinkTargets; | ||
private generateAutolinkProps; | ||
private getCSModules; | ||
private updateSolution; | ||
protected getExperimentalFeaturesPropsXml(): { | ||
path: string; | ||
content: Document; | ||
} | undefined; | ||
ensureXAMLDialect(): boolean; | ||
protected getPackagesConfigXml(): { | ||
path: string; | ||
content: Document; | ||
} | undefined; | ||
private updatePackagesConfigXAMLDialect; | ||
private updatePackagesConfig; | ||
} | ||
interface AutoLinkOptions { | ||
logging: boolean; | ||
check: boolean; | ||
sln?: string; | ||
proj?: string; | ||
} | ||
export declare const autoLinkCommand: Command; | ||
export {}; |
@@ -8,3 +8,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.autoLinkCommand = exports.ensureXAMLDialect = void 0; | ||
exports.autoLinkCommand = exports.AutolinkWindows = void 0; | ||
// Types in this file are inaccurate compared to usage in terms of falsiness. | ||
@@ -25,93 +25,85 @@ // We should try to rewrite some of this to do automated schema validation to | ||
const formatter = require('xml-formatter'); | ||
/** | ||
* Locates the react-native-windows directory | ||
* @param config project configuration | ||
*/ | ||
function resolveRnwRoot(projectConfig) { | ||
const rnwPackage = path.dirname(require.resolve('react-native-windows/package.json', { | ||
paths: [projectConfig.folder], | ||
})); | ||
return rnwPackage; | ||
} | ||
/** | ||
* Locates the react-native-windows directory containing template files | ||
* @param config project configuration | ||
*/ | ||
function resolveTemplateRoot(projectConfig) { | ||
const rnwPackage = resolveRnwRoot(projectConfig); | ||
return path.join(rnwPackage, 'template'); | ||
} | ||
/** | ||
* Logs the given message if verbose is True. | ||
* @param message The message to log. | ||
* @param verbose Whether or not verbose logging is enabled. | ||
*/ | ||
function verboseMessage(message, verbose) { | ||
if (verbose) { | ||
console.log(message); | ||
} | ||
} | ||
/** | ||
* Updates the target file with the expected contents if it's different. | ||
* @param filePath Path to the target file to update. | ||
* @param expectedContents The expected contents of the file. | ||
* @param verbose If true, enable verbose logging. | ||
* @param checkMode It true, don't make any changes. | ||
* @return Whether any changes were necessary. | ||
*/ | ||
function updateFile(filePath, expectedContents, verbose, checkMode) { | ||
const fileName = chalk.bold(path.basename(filePath)); | ||
verboseMessage(`Reading ${fileName}...`, verbose); | ||
const actualContents = fs.existsSync(filePath) | ||
? fs.readFileSync(filePath).toString() | ||
: ''; | ||
const contentsChanged = expectedContents !== actualContents; | ||
if (contentsChanged) { | ||
verboseMessage(chalk.yellow(`${fileName} needs to be updated.`), verbose); | ||
if (!checkMode) { | ||
verboseMessage(`Writing ${fileName}...`, verbose); | ||
fs.writeFileSync(filePath, expectedContents, { | ||
encoding: 'utf8', | ||
flag: 'w', | ||
}); | ||
class AutolinkWindows { | ||
constructor(projectConfig, dependenciesConfig, options) { | ||
this.projectConfig = projectConfig; | ||
this.dependenciesConfig = dependenciesConfig; | ||
this.options = options; | ||
/** Cache of dependencies */ | ||
this.windowsDependencies = {}; | ||
this.changesNecessary = false; | ||
if (!('windows' in this.projectConfig) || | ||
this.projectConfig.windows === null) { | ||
throw new telemetry_1.CodedError('NoWindowsConfig', 'Windows auto-link only supported on Windows app projects'); | ||
} | ||
this.windowsAppConfig = projectConfig.windows; | ||
} | ||
else { | ||
verboseMessage(`No changes to ${fileName}.`, verbose); | ||
areChangesNeeded() { | ||
return this.changesNecessary; | ||
} | ||
return contentsChanged; | ||
} | ||
/** | ||
* Performs auto-linking for RNW native modules and apps. | ||
* @param args Unprocessed args passed from react-native CLI. | ||
* @param config Config passed from react-native CLI. | ||
* @param options Options passed from react-native CLI. | ||
*/ | ||
// Disabling lint warnings due to high existing cyclomatic complexity | ||
// eslint-disable-next-line complexity | ||
async function updateAutoLink(args, config, options) { | ||
const startTime = perf_hooks_1.performance.now(); | ||
const verbose = options.logging; | ||
const checkMode = options.check; | ||
let changesNecessary = false; | ||
const spinner = commandWithProgress_1.newSpinner(checkMode ? 'Checking auto-linked files...' : 'Auto-linking...'); | ||
verboseMessage('', verbose); | ||
try { | ||
getWindowsConfig() { | ||
return this.windowsAppConfig; | ||
} | ||
getSolutionFile() { | ||
return path.join(this.getWindowsConfig().folder, this.getWindowsConfig().sourceDir, this.getWindowsConfig().solutionFile); | ||
} | ||
run(spinner) { | ||
const verbose = this.options.logging; | ||
verboseMessage('', verbose); | ||
verboseMessage('Parsing project...', verbose); | ||
const projectConfig = config.project; | ||
if (!('windows' in projectConfig) || projectConfig.windows === null) { | ||
throw new telemetry_1.CodedError('NoWindowsConfig', 'Windows auto-link only supported on Windows app projects'); | ||
const rnwRoot = resolveRnwRoot(this.windowsAppConfig); | ||
const templateRoot = resolveTemplateRoot(this.windowsAppConfig); | ||
this.fixUpForSlnOption(); | ||
this.fixUpForProjOption(); | ||
verboseMessage('Found Windows app project, config:', verbose); | ||
verboseMessage(this.windowsAppConfig, verbose); | ||
this.validateRequiredAppProperties(); | ||
const solutionFile = this.getSolutionFile(); | ||
const windowsAppProjectConfig = this.windowsAppConfig.project; | ||
this.validateRequiredProjectProperties(); | ||
const projectFile = this.getProjectFile(); | ||
const projectDir = path.dirname(projectFile); | ||
const projectLang = windowsAppProjectConfig.projectLang; | ||
verboseMessage('Parsing dependencies...', verbose); | ||
this.changesNecessary = this.ensureXAMLDialect() || this.changesNecessary; | ||
// Generating cs/cpp files for app code consumption | ||
if (projectLang === 'cs') { | ||
this.changesNecessary = | ||
this.generateCSAutolinking(templateRoot, projectLang, projectDir) || | ||
this.changesNecessary; | ||
} | ||
const windowsAppConfig = projectConfig.windows; | ||
const rnwRoot = resolveRnwRoot(windowsAppConfig); | ||
const templateRoot = resolveTemplateRoot(windowsAppConfig); | ||
if (options.sln) { | ||
const slnFile = path.join(windowsAppConfig.folder, options.sln); | ||
windowsAppConfig.solutionFile = path.relative(path.join(windowsAppConfig.folder, windowsAppConfig.sourceDir), slnFile); | ||
else if (projectLang === 'cpp') { | ||
this.changesNecessary = | ||
this.generateCppAutolinking(templateRoot, projectLang, projectDir) || | ||
this.changesNecessary; | ||
} | ||
if (options.proj) { | ||
const projFile = path.join(windowsAppConfig.folder, options.proj); | ||
// Generating props for app project consumption | ||
let propertiesForProps = ''; | ||
let csModuleNames = []; | ||
if (projectLang === 'cpp') { | ||
csModuleNames = this.getCSModules(); | ||
if (csModuleNames.length > 0) { | ||
propertiesForProps += `\n <!-- Set due to dependency on C# module(s): ${csModuleNames.join()} -->`; | ||
propertiesForProps += `\n <ConsumeCSharpModules Condition="'$(ConsumeCSharpModules)'==''">true</ConsumeCSharpModules>`; | ||
} | ||
} | ||
this.changesNecessary = | ||
this.generateAutolinkProps(templateRoot, projectDir, propertiesForProps) || this.changesNecessary; | ||
// Generating targets for app project consumption | ||
this.changesNecessary = | ||
this.generateAutolinkTargets(projectDir, templateRoot) || | ||
this.changesNecessary; | ||
// Generating project entries for solution | ||
this.changesNecessary = | ||
this.updateSolution(rnwRoot, solutionFile) || this.changesNecessary; | ||
spinner.succeed(); | ||
} | ||
/** | ||
* Handles the --proj command-line option by consuming its value into the windowsAppConfig | ||
*/ | ||
fixUpForProjOption() { | ||
if (this.options.proj) { | ||
const projFile = path.join(this.windowsAppConfig.folder, this.options.proj); | ||
const projectContents = configUtils.readProjectFile(projFile); | ||
windowsAppConfig.project = { | ||
projectFile: path.relative(path.join(windowsAppConfig.folder, windowsAppConfig.sourceDir), projFile), | ||
this.windowsAppConfig.project = { | ||
projectFile: path.relative(path.join(this.windowsAppConfig.folder, this.windowsAppConfig.sourceDir), projFile), | ||
projectName: configUtils.getProjectName(projectContents), | ||
@@ -122,4 +114,14 @@ projectLang: configUtils.getProjectLanguage(projFile), | ||
} | ||
verboseMessage('Found Windows app project, config:', verbose); | ||
verboseMessage(windowsAppConfig, verbose); | ||
} | ||
/** | ||
* Handles the --sln command-line option by consuming its value into the windowsAppConfig | ||
*/ | ||
fixUpForSlnOption() { | ||
if (this.options.sln) { | ||
const slnFile = path.join(this.windowsAppConfig.folder, this.options.sln); | ||
this.windowsAppConfig.solutionFile = path.relative(path.join(this.windowsAppConfig.folder, this.windowsAppConfig.sourceDir), slnFile); | ||
} | ||
} | ||
/** Validates the all of the required app (solution) properties are present and valid */ | ||
validateRequiredAppProperties() { | ||
const alwaysRequired = [ | ||
@@ -132,12 +134,20 @@ 'folder', | ||
alwaysRequired.forEach(item => { | ||
if (!(item in windowsAppConfig) || windowsAppConfig[item] === null) { | ||
if (!(item in this.windowsAppConfig) || | ||
this.windowsAppConfig[item] === null) { | ||
throw new telemetry_1.CodedError('IncompleteConfig', `${item} is required but not specified by react-native config`, { item: item }); | ||
} | ||
else if (typeof windowsAppConfig[item] === 'string' && | ||
windowsAppConfig[item].startsWith('Error: ')) { | ||
throw new telemetry_1.CodedError('InvalidConfig', `${item} invalid. ${windowsAppConfig[item]}`, { item: item }); | ||
else if (typeof this.windowsAppConfig[item] === 'string' && | ||
this.windowsAppConfig[item].startsWith('Error: ')) { | ||
throw new telemetry_1.CodedError('InvalidConfig', `${item} invalid. ${this.windowsAppConfig[item]}`, { item: item }); | ||
} | ||
}); | ||
const solutionFile = path.join(windowsAppConfig.folder, windowsAppConfig.sourceDir, windowsAppConfig.solutionFile); | ||
const windowsAppProjectConfig = windowsAppConfig.project; | ||
} | ||
/** @return the full path to the project file (.vcxproj or .csproj) */ | ||
getProjectFile() { | ||
const windowsAppConfig = this.getWindowsConfig(); | ||
return path.join(windowsAppConfig.folder, windowsAppConfig.sourceDir, windowsAppConfig.project.projectFile); | ||
} | ||
/** Validates that all of the required app _project_ properties are present and valid */ | ||
validateRequiredProjectProperties() { | ||
const windowsAppProjectConfig = this.windowsAppConfig.project; | ||
const projectRequired = [ | ||
@@ -159,136 +169,145 @@ 'projectFile', | ||
}); | ||
const projectFile = path.join(windowsAppConfig.folder, windowsAppConfig.sourceDir, windowsAppConfig.project.projectFile); | ||
const projectDir = path.dirname(projectFile); | ||
const projectLang = windowsAppConfig.project.projectLang; | ||
verboseMessage('Parsing dependencies...', verbose); | ||
const dependenciesConfig = config.dependencies; | ||
const windowsDependencies = {}; | ||
for (const dependencyName of Object.keys(dependenciesConfig)) { | ||
const windowsDependency = dependenciesConfig[dependencyName].platforms.windows; | ||
if (windowsDependency) { | ||
verboseMessage(`${chalk.bold(dependencyName)} has Windows implementation, config:`, verbose); | ||
verboseMessage(windowsDependency, verbose); | ||
let dependencyIsValid = true; | ||
dependencyIsValid = !!(dependencyIsValid && | ||
'sourceDir' in windowsDependency && | ||
windowsDependency.sourceDir && | ||
!windowsDependency.sourceDir.startsWith('Error: ')); | ||
if ('projects' in windowsDependency && | ||
Array.isArray(windowsDependency.projects)) { | ||
windowsDependency.projects.forEach(project => { | ||
const itemsToCheck = [ | ||
'projectFile', | ||
'directDependency', | ||
]; | ||
itemsToCheck.forEach(item => { | ||
dependencyIsValid = !!(dependencyIsValid && | ||
item in project && | ||
project[item] && | ||
!project[item].toString().startsWith('Error: ')); | ||
}); | ||
} | ||
generateCppAutolinking(templateRoot, projectLang, projectDir) { | ||
const { cppPackageProviders, cppIncludes } = this.getCppReplacements(); | ||
const cppFileName = 'AutolinkedNativeModules.g.cpp'; | ||
const srcCppFile = path.join(templateRoot, `${projectLang}-app`, 'src', cppFileName); | ||
const destCppFile = path.join(projectDir, cppFileName); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(destCppFile))}...`, this.options.logging); | ||
const cppContents = generatorCommon.resolveContents(srcCppFile, { | ||
useMustache: true, | ||
autolinkCppIncludes: cppIncludes, | ||
autolinkCppPackageProviders: cppPackageProviders, | ||
}); | ||
return this.updateFile(destCppFile, cppContents); | ||
} | ||
getCppReplacements() { | ||
let cppIncludes = ''; | ||
let cppPackageProviders = ''; | ||
const windowsDependencies = this.getWindowsDependencies(); | ||
for (const dependencyName of Object.keys(windowsDependencies)) { | ||
windowsDependencies[dependencyName].projects.forEach(project => { | ||
if (project.directDependency) { | ||
cppIncludes += `\n\n// Includes from ${dependencyName}`; | ||
project.cppHeaders.forEach(header => { | ||
cppIncludes += `\n#include <${header}>`; | ||
}); | ||
cppPackageProviders += `\n // IReactPackageProviders from ${dependencyName}`; | ||
project.cppPackageProviders.forEach(packageProvider => { | ||
cppPackageProviders += `\n packageProviders.Append(winrt::${packageProvider}());`; | ||
}); | ||
} | ||
if (dependencyIsValid) { | ||
verboseMessage(`Adding ${chalk.bold(dependencyName)}.`, verbose); | ||
windowsDependencies[dependencyName] = windowsDependency; | ||
}); | ||
} | ||
if (cppPackageProviders === '') { | ||
// There are no windows dependencies, this would result in warning. C4100: 'packageProviders': unreferenced formal parameter. | ||
// therefore add a usage. | ||
cppPackageProviders = '\n UNREFERENCED_PARAMETER(packageProviders);'; // CODESYNC: vnext\local-cli\generator-windows\index.js | ||
} | ||
return { cppPackageProviders, cppIncludes }; | ||
} | ||
generateCSAutolinking(templateRoot, projectLang, projectDir) { | ||
const { csUsingNamespaces, csReactPackageProviders, } = this.getCsReplacements(); | ||
const csFileName = 'AutolinkedNativeModules.g.cs'; | ||
const srcCsFile = path.join(templateRoot, `${projectLang}-app`, 'src', csFileName); | ||
const destCsFile = path.join(projectDir, csFileName); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(destCsFile))}...`, this.options.logging); | ||
const csContents = generatorCommon.resolveContents(srcCsFile, { | ||
useMustache: true, | ||
autolinkCsUsingNamespaces: csUsingNamespaces, | ||
autolinkCsReactPackageProviders: csReactPackageProviders, | ||
}); | ||
return this.updateFile(destCsFile, csContents); | ||
} | ||
getCsReplacements() { | ||
let csUsingNamespaces = ''; | ||
let csReactPackageProviders = ''; | ||
const windowsDependencies = this.getWindowsDependencies(); | ||
for (const dependencyName of Object.keys(windowsDependencies)) { | ||
windowsDependencies[dependencyName].projects.forEach(project => { | ||
if (project.directDependency) { | ||
csUsingNamespaces += `\n\n// Namespaces from ${dependencyName}`; | ||
project.csNamespaces.forEach(namespace => { | ||
csUsingNamespaces += `\nusing ${namespace};`; | ||
}); | ||
csReactPackageProviders += `\n // IReactPackageProviders from ${dependencyName}`; | ||
project.csPackageProviders.forEach(packageProvider => { | ||
csReactPackageProviders += `\n packageProviders.Add(new ${packageProvider}());`; | ||
}); | ||
} | ||
} | ||
} | ||
changesNecessary = | ||
ensureXAMLDialect(windowsAppConfig, checkMode) || changesNecessary; | ||
// Generating cs/cpp files for app code consumption | ||
if (projectLang === 'cs') { | ||
let csUsingNamespaces = ''; | ||
let csReactPackageProviders = ''; | ||
for (const dependencyName of Object.keys(windowsDependencies)) { | ||
windowsDependencies[dependencyName].projects.forEach(project => { | ||
if (project.directDependency) { | ||
csUsingNamespaces += `\n\n// Namespaces from ${dependencyName}`; | ||
project.csNamespaces.forEach(namespace => { | ||
csUsingNamespaces += `\nusing ${namespace};`; | ||
}); | ||
csReactPackageProviders += `\n // IReactPackageProviders from ${dependencyName}`; | ||
project.csPackageProviders.forEach(packageProvider => { | ||
csReactPackageProviders += `\n packageProviders.Add(new ${packageProvider}());`; | ||
}); | ||
} | ||
}); | ||
} | ||
const csFileName = 'AutolinkedNativeModules.g.cs'; | ||
const srcCsFile = path.join(templateRoot, `${projectLang}-app`, 'src', csFileName); | ||
const destCsFile = path.join(projectDir, csFileName); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(destCsFile))}...`, verbose); | ||
const csContents = generatorCommon.resolveContents(srcCsFile, { | ||
useMustache: true, | ||
autolinkCsUsingNamespaces: csUsingNamespaces, | ||
autolinkCsReactPackageProviders: csReactPackageProviders, | ||
}); | ||
changesNecessary = | ||
updateFile(destCsFile, csContents, verbose, checkMode) || | ||
changesNecessary; | ||
} | ||
else if (projectLang === 'cpp') { | ||
let cppIncludes = ''; | ||
let cppPackageProviders = ''; | ||
for (const dependencyName of Object.keys(windowsDependencies)) { | ||
windowsDependencies[dependencyName].projects.forEach(project => { | ||
if (project.directDependency) { | ||
cppIncludes += `\n\n// Includes from ${dependencyName}`; | ||
project.cppHeaders.forEach(header => { | ||
cppIncludes += `\n#include <${header}>`; | ||
return { csUsingNamespaces, csReactPackageProviders }; | ||
} | ||
getWindowsDependencies() { | ||
if (Object.keys(this.windowsDependencies).length === 0) { | ||
for (const dependencyName of Object.keys(this.dependenciesConfig)) { | ||
const windowsDependency = this | ||
.dependenciesConfig[dependencyName].platforms.windows; | ||
if (windowsDependency) { | ||
verboseMessage(`${chalk.bold(dependencyName)} has Windows implementation, config:`, this.options.logging); | ||
verboseMessage(windowsDependency, this.options.logging); | ||
let dependencyIsValid = true; | ||
dependencyIsValid = !!(dependencyIsValid && | ||
'sourceDir' in windowsDependency && | ||
windowsDependency.sourceDir && | ||
!windowsDependency.sourceDir.startsWith('Error: ')); | ||
if ('projects' in windowsDependency && | ||
Array.isArray(windowsDependency.projects)) { | ||
windowsDependency.projects.forEach(project => { | ||
const itemsToCheck = [ | ||
'projectFile', | ||
'directDependency', | ||
]; | ||
itemsToCheck.forEach(item => { | ||
dependencyIsValid = !!(dependencyIsValid && | ||
item in project && | ||
project[item] && | ||
!project[item].toString().startsWith('Error: ')); | ||
}); | ||
}); | ||
cppPackageProviders += `\n // IReactPackageProviders from ${dependencyName}`; | ||
project.cppPackageProviders.forEach(packageProvider => { | ||
cppPackageProviders += `\n packageProviders.Append(winrt::${packageProvider}());`; | ||
}); | ||
} | ||
}); | ||
if (dependencyIsValid) { | ||
verboseMessage(`Adding ${chalk.bold(dependencyName)}.`, this.options.logging); | ||
this.windowsDependencies[dependencyName] = windowsDependency; | ||
} | ||
else { | ||
verboseMessage(`Invalid dependency configuration for dependency ${dependencyName}`, this.options.logging); | ||
} | ||
} | ||
} | ||
if (cppPackageProviders === '') { | ||
// There are no windows dependencies, this would result in warning. C4100: 'packageProviders': unreferenced formal parameter. | ||
// therefore add a usage. | ||
cppPackageProviders = '\n UNREFERENCED_PARAMETER(packageProviders);'; // CODESYNC: vnext\local-cli\generator-windows\index.js | ||
} | ||
const cppFileName = 'AutolinkedNativeModules.g.cpp'; | ||
const srcCppFile = path.join(templateRoot, `${projectLang}-app`, 'src', cppFileName); | ||
const destCppFile = path.join(projectDir, cppFileName); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(destCppFile))}...`, verbose); | ||
const cppContents = generatorCommon.resolveContents(srcCppFile, { | ||
useMustache: true, | ||
autolinkCppIncludes: cppIncludes, | ||
autolinkCppPackageProviders: cppPackageProviders, | ||
}); | ||
changesNecessary = | ||
updateFile(destCppFile, cppContents, verbose, checkMode) || | ||
changesNecessary; | ||
} | ||
// Generating props for app project consumption | ||
let propertiesForProps = ''; | ||
const csModuleNames = []; | ||
if (projectLang === 'cpp') { | ||
for (const dependencyName of Object.keys(windowsDependencies)) { | ||
windowsDependencies[dependencyName].projects.forEach(project => { | ||
if (project.directDependency && project.projectLang === 'cs') { | ||
csModuleNames.push(project.projectName); | ||
} | ||
return this.windowsDependencies; | ||
} | ||
/** | ||
* Updates the target file with the expected contents if it's different. | ||
* @param filePath Path to the target file to update. | ||
* @param expectedContents The expected contents of the file. | ||
* @return Whether any changes were necessary. | ||
*/ | ||
updateFile(filePath, expectedContents) { | ||
const fileName = chalk.bold(path.basename(filePath)); | ||
verboseMessage(`Reading ${fileName}...`, this.options.logging); | ||
const actualContents = fs.existsSync(filePath) | ||
? fs.readFileSync(filePath).toString() | ||
: ''; | ||
const contentsChanged = expectedContents !== actualContents; | ||
if (contentsChanged) { | ||
verboseMessage(chalk.yellow(`${fileName} needs to be updated.`), this.options.logging); | ||
if (!this.options.check) { | ||
verboseMessage(`Writing ${fileName}...`, this.options.logging); | ||
fs.writeFileSync(filePath, expectedContents, { | ||
encoding: 'utf8', | ||
flag: 'w', | ||
}); | ||
} | ||
if (csModuleNames.length > 0) { | ||
propertiesForProps += `\n <!-- Set due to dependency on C# module(s): ${csModuleNames.join()} -->`; | ||
propertiesForProps += `\n <ConsumeCSharpModules Condition="'$(ConsumeCSharpModules)'==''">true</ConsumeCSharpModules>`; | ||
} | ||
} | ||
const propsFileName = 'AutolinkedNativeModules.g.props'; | ||
const srcPropsFile = path.join(templateRoot, `shared-app`, 'src', propsFileName); | ||
const destPropsFile = path.join(projectDir, propsFileName); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(destPropsFile))}...`, verbose); | ||
const propsContents = generatorCommon.resolveContents(srcPropsFile, { | ||
useMustache: true, | ||
autolinkPropertiesForProps: propertiesForProps, | ||
}); | ||
changesNecessary = | ||
updateFile(destPropsFile, propsContents, verbose, checkMode) || | ||
changesNecessary; | ||
// Generating targets for app project consumption | ||
else { | ||
verboseMessage(`No changes to ${fileName}.`, this.options.logging); | ||
} | ||
return contentsChanged; | ||
} | ||
generateAutolinkTargets(projectDir, templateRoot) { | ||
let projectReferencesForTargets = ''; | ||
const windowsDependencies = this.getWindowsDependencies(); | ||
for (const dependencyName of Object.keys(windowsDependencies)) { | ||
@@ -301,4 +320,4 @@ windowsDependencies[dependencyName].projects.forEach(project => { | ||
projectReferencesForTargets += `\n <ProjectReference Include="$(ProjectDir)${relDependencyProjectFile}"> | ||
<Project>${project.projectGuid}</Project> | ||
</ProjectReference>`; | ||
<Project>${project.projectGuid}</Project> | ||
</ProjectReference>`; | ||
} | ||
@@ -310,3 +329,3 @@ }); | ||
const destTargetFile = path.join(projectDir, targetFileName); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(destTargetFile))}...`, verbose); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(destTargetFile))}...`, this.options.logging); | ||
const targetContents = generatorCommon.resolveContents(srcTargetFile, { | ||
@@ -316,7 +335,30 @@ useMustache: true, | ||
}); | ||
changesNecessary = | ||
updateFile(destTargetFile, targetContents, verbose, checkMode) || | ||
changesNecessary; | ||
// Generating project entries for solution | ||
return this.updateFile(destTargetFile, targetContents); | ||
} | ||
generateAutolinkProps(templateRoot, projectDir, propertiesForProps) { | ||
const propsFileName = 'AutolinkedNativeModules.g.props'; | ||
const srcPropsFile = path.join(templateRoot, `shared-app`, 'src', propsFileName); | ||
const destPropsFile = path.join(projectDir, propsFileName); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(destPropsFile))}...`, this.options.logging); | ||
const propsContents = generatorCommon.resolveContents(srcPropsFile, { | ||
useMustache: true, | ||
autolinkPropertiesForProps: propertiesForProps, | ||
}); | ||
return this.updateFile(destPropsFile, propsContents); | ||
} | ||
getCSModules() { | ||
const csModuleNames = []; | ||
const windowsDependencies = this.getWindowsDependencies(); | ||
for (const dependencyName of Object.keys(windowsDependencies)) { | ||
windowsDependencies[dependencyName].projects.forEach(project => { | ||
if (project.directDependency && project.projectLang === 'cs') { | ||
csModuleNames.push(project.projectName); | ||
} | ||
}); | ||
} | ||
return csModuleNames; | ||
} | ||
updateSolution(rnwRoot, solutionFile) { | ||
const projectsForSolution = []; | ||
const windowsDependencies = this.getWindowsDependencies(); | ||
for (const dependencyName of Object.keys(windowsDependencies)) { | ||
@@ -334,2 +376,3 @@ // Process dependency projects | ||
} | ||
const csModuleNames = this.getCSModules(); | ||
if (csModuleNames.length > 0) { | ||
@@ -351,76 +394,88 @@ // Add managed projects | ||
} | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(solutionFile))} changes...`, verbose); | ||
verboseMessage(`Calculating ${chalk.bold(path.basename(solutionFile))} changes...`, this.options.logging); | ||
let changesNecessary = false; | ||
projectsForSolution.forEach(project => { | ||
const contentsChanged = vstools.addProjectToSolution(solutionFile, project, verbose, checkMode); | ||
const contentsChanged = vstools.addProjectToSolution(solutionFile, project, this.options.logging, this.options.check); | ||
changesNecessary = changesNecessary || contentsChanged; | ||
}); | ||
spinner.succeed(); | ||
const endTime = perf_hooks_1.performance.now(); | ||
if (!changesNecessary) { | ||
console.log(`${chalk.green('Success:')} No auto-linking changes necessary. (${Math.round(endTime - startTime)}ms)`); | ||
return changesNecessary; | ||
} | ||
getExperimentalFeaturesPropsXml() { | ||
const experimentalFeaturesProps = path.join(path.dirname(this.getSolutionFile()), 'ExperimentalFeatures.props'); | ||
if (fs.existsSync(experimentalFeaturesProps)) { | ||
const experimentalFeaturesContents = configUtils.readProjectFile(experimentalFeaturesProps); | ||
return { | ||
path: experimentalFeaturesProps, | ||
content: experimentalFeaturesContents, | ||
}; | ||
} | ||
else if (checkMode) { | ||
console.log(`${chalk.yellow('Warning:')} Auto-linking changes were necessary but ${chalk.bold('--check')} specified. Run ${chalk.bold("'npx react-native autolink-windows'")} to apply the changes. (${Math.round(endTime - startTime)}ms)`); | ||
throw new telemetry_1.CodedError('NeedAutolinking', 'Auto-linking changes were necessary but --check was specified'); | ||
return undefined; | ||
} | ||
ensureXAMLDialect() { | ||
var _a, _b; | ||
let changesNeeded = false; | ||
const useWinUI3FromConfig = this.getWindowsConfig().useWinUI3; | ||
const experimentalFeatures = this.getExperimentalFeaturesPropsXml(); | ||
if (experimentalFeatures) { | ||
const useWinUI3FromExperimentalFeatures = ((_a = configUtils | ||
.tryFindPropertyValue(experimentalFeatures.content, 'UseWinUI3')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'true'; | ||
// use the UseWinUI3 value in react-native.config.js, or if not present, the value from ExperimentalFeatures.props | ||
changesNeeded = this.updatePackagesConfigXAMLDialect(useWinUI3FromConfig !== undefined | ||
? useWinUI3FromConfig | ||
: useWinUI3FromExperimentalFeatures); | ||
if (useWinUI3FromConfig !== undefined) { | ||
// Make sure ExperimentalFeatures.props matches the value that comes from react-native.config.js | ||
const node = experimentalFeatures.content.getElementsByTagName('UseWinUI3'); | ||
const newValue = useWinUI3FromConfig ? 'true' : 'false'; | ||
changesNeeded = ((_b = node.item(0)) === null || _b === void 0 ? void 0 : _b.textContent) !== newValue || changesNeeded; | ||
if (!this.options.check && changesNeeded) { | ||
node.item(0).textContent = newValue; | ||
const experimentalFeaturesOutput = new xmldom_1.XMLSerializer().serializeToString(experimentalFeatures.content); | ||
this.updateFile(experimentalFeatures.path, experimentalFeaturesOutput); | ||
} | ||
} | ||
} | ||
else { | ||
console.log(`${chalk.green('Success:')} Auto-linking changes completed. (${Math.round(endTime - startTime)}ms)`); | ||
return changesNeeded; | ||
} | ||
getPackagesConfigXml() { | ||
const projectFile = this.getProjectFile(); | ||
const packagesConfig = path.join(path.dirname(projectFile), 'packages.config'); | ||
if (fs.existsSync(packagesConfig)) { | ||
return { | ||
path: packagesConfig, | ||
content: configUtils.readProjectFile(packagesConfig), | ||
}; | ||
} | ||
return undefined; | ||
} | ||
catch (e) { | ||
spinner.fail(); | ||
const endTime = perf_hooks_1.performance.now(); | ||
console.log(`${chalk.red('Error:')} ${e.toString()}. (${Math.round(endTime - startTime)}ms)`); | ||
throw e; | ||
} | ||
} | ||
function ensureXAMLDialect(windowsProjectConfig, checkMode) { | ||
var _a, _b; | ||
const slnFile = path.join(windowsProjectConfig.folder, windowsProjectConfig.sourceDir, windowsProjectConfig.solutionFile); | ||
const buildFlagsProps = path.join(path.dirname(slnFile), 'BuildFlags.props'); | ||
let changesNeeded = false; | ||
if (fs.existsSync(buildFlagsProps)) { | ||
const buildFlagsContents = configUtils.readProjectFile(buildFlagsProps); | ||
const useWinUI3FromConfig = windowsProjectConfig.useWinUI3; | ||
const useWinUI3FromBuildFlags = ((_a = configUtils | ||
.tryFindPropertyValue(buildFlagsContents, 'UseWinUI3')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'true'; | ||
// use the UseWinUI3 value in react-native.config.js, or if not present, the value from BuildFlags.props | ||
changesNeeded = updatePackagesConfigXAMLDialect(useWinUI3FromConfig !== undefined | ||
? useWinUI3FromConfig | ||
: useWinUI3FromBuildFlags, windowsProjectConfig, checkMode); | ||
if (useWinUI3FromConfig !== undefined) { | ||
// Make sure BuildFlags matches the value that comes from react-native.config.js | ||
const node = buildFlagsContents.getElementsByTagName('UseWinUI3'); | ||
const newValue = useWinUI3FromConfig ? 'true' : 'false'; | ||
changesNeeded = changesNeeded || ((_b = node.item(0)) === null || _b === void 0 ? void 0 : _b.textContent) !== newValue; | ||
if (!checkMode && changesNeeded) { | ||
node.item(0).textContent = newValue; | ||
const buildFlagsOutput = new xmldom_1.XMLSerializer().serializeToString(buildFlagsContents); | ||
fs.writeFileSync(buildFlagsProps, buildFlagsOutput); | ||
updatePackagesConfigXAMLDialect(useWinUI3) { | ||
let changed = false; | ||
const packagesConfig = this.getPackagesConfigXml(); | ||
if (packagesConfig) { | ||
// if we don't have a packages.config, then this is a C# project, in which case we use <PackageReference> and dynamically pick the right XAML package. | ||
const project = this.getWindowsConfig(); | ||
const winUIPropsPath = path.join(resolveRnwRoot(project), 'PropertySheets/WinUI.props'); | ||
const winuiPropsContents = configUtils.readProjectFile(winUIPropsPath); | ||
const winui2xVersion = configUtils.tryFindPropertyValue(winuiPropsContents, 'WinUI2xVersion'); | ||
const winui3Version = configUtils.tryFindPropertyValue(winuiPropsContents, 'WinUI3Version'); | ||
const dialects = [ | ||
{ id: 'Microsoft.WinUI', version: winui3Version }, | ||
{ id: 'Microsoft.UI.Xaml', version: winui2xVersion }, | ||
]; | ||
const keepPkg = useWinUI3 ? dialects[0] : dialects[1]; | ||
const removePkg = useWinUI3 ? dialects[1] : dialects[0]; | ||
changed = this.updatePackagesConfig(packagesConfig, [removePkg], [keepPkg]); | ||
if (!this.options.check && changed) { | ||
const serializer = new xmldom_1.XMLSerializer(); | ||
const output = serializer.serializeToString(packagesConfig.content); | ||
const formattedXml = formatter(output, { indentation: ' ' }); | ||
this.updateFile(packagesConfig.path, formattedXml); | ||
} | ||
} | ||
return changed; | ||
} | ||
return changesNeeded; | ||
} | ||
exports.ensureXAMLDialect = ensureXAMLDialect; | ||
function updatePackagesConfigXAMLDialect(useWinUI3, project, checkMode) { | ||
const projectFile = path.join(project.folder, project.sourceDir, project.project.projectFile); | ||
const packagesConfig = path.join(path.dirname(projectFile), 'packages.config'); | ||
let changed = false; | ||
if (fs.existsSync(packagesConfig)) { | ||
// if we don't have a packages.config, then this is a C# project, in which case we use <PackageReference> and dynamically pick the right XAML package. | ||
const packagesConfigContents = configUtils.readProjectFile(packagesConfig); | ||
const packageElements = packagesConfigContents.documentElement.getElementsByTagName('package'); | ||
const winUIPropsPath = path.join(resolveRnwRoot(project), 'PropertySheets/WinUI.props'); | ||
const winuiPropsContents = configUtils.readProjectFile(winUIPropsPath); | ||
const winui2xVersion = configUtils.tryFindPropertyValue(winuiPropsContents, 'WinUI2xVersion'); | ||
const winui3Version = configUtils.tryFindPropertyValue(winuiPropsContents, 'WinUI3Version'); | ||
const dialects = [ | ||
{ id: 'Microsoft.WinUI', version: winui3Version }, | ||
{ id: 'Microsoft.UI.Xaml', version: winui2xVersion }, | ||
]; | ||
const keepPkg = useWinUI3 ? dialects[0] : dialects[1]; | ||
const removePkg = useWinUI3 ? dialects[1] : dialects[0]; | ||
updatePackagesConfig(packagesConfig, removePkgs, keepPkgs) { | ||
let changed = false; | ||
const packageElements = packagesConfig.content.documentElement.getElementsByTagName('package'); | ||
const nodesToRemove = []; | ||
let needsToAddKeepPkg = true; | ||
for (let i = 0; i < packageElements.length; i++) { | ||
@@ -430,16 +485,17 @@ const packageElement = packageElements.item(i); | ||
const id = idAttr.value; | ||
if (id === removePkg.id) { | ||
const keepPkg = keepPkgs.find(pkg => pkg.id === id); | ||
if (removePkgs.find(pkg => pkg.id === id)) { | ||
nodesToRemove.push(packageElement); | ||
changed = true; | ||
} | ||
else if (id === keepPkg.id) { | ||
else if (keepPkg) { | ||
changed = | ||
changed || packageElement.getAttribute('version') !== keepPkg.version; | ||
changed || keepPkg.version !== packageElement.getAttribute('version'); | ||
packageElement.setAttribute('version', keepPkg.version); | ||
needsToAddKeepPkg = false; | ||
keepPkgs = keepPkgs.filter(pkg => pkg.id !== keepPkg.id); | ||
} | ||
} | ||
nodesToRemove.forEach(pkg => packagesConfigContents.documentElement.removeChild(pkg)); | ||
if (needsToAddKeepPkg) { | ||
const newPkg = packagesConfigContents.createElement('package'); | ||
nodesToRemove.forEach(pkg => packagesConfig.content.documentElement.removeChild(pkg)); | ||
keepPkgs.forEach(keepPkg => { | ||
const newPkg = packagesConfig.content.createElement('package'); | ||
Object.entries(keepPkg).forEach(([attr, value]) => { | ||
@@ -449,13 +505,67 @@ newPkg.setAttribute(attr, value); | ||
newPkg.setAttribute('targetFramework', 'native'); | ||
packagesConfigContents.documentElement.appendChild(newPkg); | ||
packagesConfig.content.documentElement.appendChild(newPkg); | ||
changed = true; | ||
}); | ||
return changed; | ||
} | ||
} | ||
exports.AutolinkWindows = AutolinkWindows; | ||
/** | ||
* Locates the react-native-windows directory | ||
* @param config project configuration | ||
*/ | ||
function resolveRnwRoot(projectConfig) { | ||
const rnwPackage = path.dirname(require.resolve('react-native-windows/package.json', { | ||
paths: [projectConfig.folder], | ||
})); | ||
return rnwPackage; | ||
} | ||
/** | ||
* Locates the react-native-windows directory containing template files | ||
* @param config project configuration | ||
*/ | ||
function resolveTemplateRoot(projectConfig) { | ||
const rnwPackage = resolveRnwRoot(projectConfig); | ||
return path.join(rnwPackage, 'template'); | ||
} | ||
/** | ||
* Logs the given message if verbose is True. | ||
* @param message The message to log. | ||
* @param verbose Whether or not verbose logging is enabled. | ||
*/ | ||
function verboseMessage(message, verbose) { | ||
if (verbose) { | ||
console.log(message); | ||
} | ||
} | ||
/** | ||
* Performs auto-linking for RNW native modules and apps. | ||
* @param args Unprocessed args passed from react-native CLI. | ||
* @param config Config passed from react-native CLI. | ||
* @param options Options passed from react-native CLI. | ||
*/ | ||
async function updateAutoLink(args, config, options) { | ||
const startTime = perf_hooks_1.performance.now(); | ||
const spinner = commandWithProgress_1.newSpinner(options.check ? 'Checking auto-linked files...' : 'Auto-linking...'); | ||
try { | ||
const autolink = new AutolinkWindows(config.project, config.dependencies, options); | ||
autolink.run(spinner); | ||
const endTime = perf_hooks_1.performance.now(); | ||
if (!autolink.areChangesNeeded()) { | ||
console.log(`${chalk.green('Success:')} No auto-linking changes necessary. (${Math.round(endTime - startTime)}ms)`); | ||
} | ||
if (!checkMode && changed) { | ||
const serializer = new xmldom_1.XMLSerializer(); | ||
const output = serializer.serializeToString(packagesConfigContents); | ||
const formattedXml = formatter(output, { indentation: ' ' }); | ||
fs.writeFileSync(packagesConfig, formattedXml, { encoding: 'utf-8' }); | ||
else if (options.check) { | ||
console.log(`${chalk.yellow('Warning:')} Auto-linking changes were necessary but ${chalk.bold('--check')} specified. Run ${chalk.bold("'npx react-native autolink-windows'")} to apply the changes. (${Math.round(endTime - startTime)}ms)`); | ||
throw new telemetry_1.CodedError('NeedAutolinking', 'Auto-linking changes were necessary but --check was specified'); | ||
} | ||
else { | ||
console.log(`${chalk.green('Success:')} Auto-linking changes completed. (${Math.round(endTime - startTime)}ms)`); | ||
} | ||
} | ||
return changed; | ||
catch (e) { | ||
spinner.fail(); | ||
const endTime = perf_hooks_1.performance.now(); | ||
console.log(`${chalk.red('Error:')} ${e.toString()}. (${Math.round(endTime - startTime)}ms)`); | ||
throw e; | ||
} | ||
} | ||
@@ -462,0 +572,0 @@ exports.autoLinkCommand = { |
{ | ||
"name": "@react-native-windows/cli", | ||
"version": "0.0.0-canary.57", | ||
"version": "0.0.0-canary.58", | ||
"license": "MIT", | ||
@@ -20,4 +20,4 @@ "main": "lib-commonjs/index.js", | ||
"dependencies": { | ||
"@react-native-windows/package-utils": "^0.0.0-canary.15", | ||
"@react-native-windows/telemetry": "^0.0.0-canary.16", | ||
"@react-native-windows/package-utils": "^0.0.0-canary.16", | ||
"@react-native-windows/telemetry": "^0.0.0-canary.17", | ||
"chalk": "^4.1.0", | ||
@@ -42,4 +42,4 @@ "cli-spinners": "^2.2.0", | ||
"@rnw-scripts/eslint-config": "1.1.6", | ||
"@rnw-scripts/jest-unittest-config": "1.1.1", | ||
"@rnw-scripts/just-task": "2.0.2", | ||
"@rnw-scripts/jest-unittest-config": "1.2.0", | ||
"@rnw-scripts/just-task": "2.1.0", | ||
"@rnw-scripts/ts-config": "1.1.0", | ||
@@ -46,0 +46,0 @@ "@types/chalk": "^2.2.0", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
405442
78
5593
7
20
38