@react-native-windows/telemetry 0.0.0-canary.3 → 0.0.0-canary.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib-commonjs/index.d.ts +5 -1
- package/lib-commonjs/index.js +15 -3
- package/lib-commonjs/index.js.map +1 -1
- package/lib-commonjs/telemetry.d.ts +76 -12
- package/lib-commonjs/telemetry.js +321 -108
- package/lib-commonjs/telemetry.js.map +1 -1
- package/lib-commonjs/test/{sanitize.test.d.ts → basePropUtils.test.d.ts} +0 -0
- package/lib-commonjs/test/basePropUtils.test.js +116 -0
- package/lib-commonjs/test/basePropUtils.test.js.map +1 -0
- package/lib-commonjs/test/errorUtils.test.d.ts +7 -0
- package/lib-commonjs/test/errorUtils.test.js +159 -0
- package/lib-commonjs/test/errorUtils.test.js.map +1 -0
- package/lib-commonjs/test/projectUtils.test.d.ts +7 -0
- package/lib-commonjs/test/projectUtils.test.js +84 -0
- package/lib-commonjs/test/projectUtils.test.js.map +1 -0
- package/lib-commonjs/test/sanitizeUtils.test.d.ts +7 -0
- package/lib-commonjs/test/sanitizeUtils.test.js +94 -0
- package/lib-commonjs/test/sanitizeUtils.test.js.map +1 -0
- package/lib-commonjs/test/telemetry.test.d.ts +26 -0
- package/lib-commonjs/test/telemetry.test.js +469 -0
- package/lib-commonjs/test/telemetry.test.js.map +1 -0
- package/lib-commonjs/test/versionUtils.test.d.ts +7 -0
- package/lib-commonjs/test/versionUtils.test.js +111 -0
- package/lib-commonjs/test/versionUtils.test.js.map +1 -0
- package/lib-commonjs/utils/basePropUtils.d.ts +66 -0
- package/lib-commonjs/utils/basePropUtils.js +131 -0
- package/lib-commonjs/utils/basePropUtils.js.map +1 -0
- package/lib-commonjs/utils/errorUtils.d.ts +80 -0
- package/lib-commonjs/utils/errorUtils.js +164 -0
- package/lib-commonjs/utils/errorUtils.js.map +1 -0
- package/lib-commonjs/utils/optionUtils.d.ts +45 -0
- package/lib-commonjs/utils/optionUtils.js +96 -0
- package/lib-commonjs/utils/optionUtils.js.map +1 -0
- package/lib-commonjs/utils/projectUtils.d.ts +50 -0
- package/lib-commonjs/utils/projectUtils.js +187 -0
- package/lib-commonjs/utils/projectUtils.js.map +1 -0
- package/lib-commonjs/utils/sanitizeUtils.d.ts +12 -0
- package/lib-commonjs/utils/sanitizeUtils.js +82 -0
- package/lib-commonjs/utils/sanitizeUtils.js.map +1 -0
- package/lib-commonjs/utils/versionUtils.d.ts +38 -0
- package/lib-commonjs/utils/versionUtils.js +156 -0
- package/lib-commonjs/utils/versionUtils.js.map +1 -0
- package/package.json +35 -19
- package/CHANGELOG.json +0 -35
- package/CHANGELOG.md +0 -23
- package/lib-commonjs/test/sanitize.test.js +0 -174
- package/lib-commonjs/test/sanitize.test.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
* @format
|
|
5
|
+
*/
|
|
6
|
+
interface ProjectInfo {
|
|
7
|
+
id: string | null;
|
|
8
|
+
platforms: Array<string>;
|
|
9
|
+
rnwLang: 'cpp' | 'cs' | 'cpp+cs' | null;
|
|
10
|
+
}
|
|
11
|
+
export interface DependencyProjectInfo extends ProjectInfo {
|
|
12
|
+
}
|
|
13
|
+
export interface AppProjectInfo extends ProjectInfo {
|
|
14
|
+
usesTS: boolean;
|
|
15
|
+
usesRNConfig: boolean;
|
|
16
|
+
jsEngine: string;
|
|
17
|
+
rnwSource: string;
|
|
18
|
+
dependencies: Array<DependencyProjectInfo>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Gets a unique, telemetry-safe project ID based on the project name.
|
|
22
|
+
* @param projectName The project name.
|
|
23
|
+
* @returns The telemetry-safe project ID.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getProjectId(projectName: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Gets whether the project at the given path has a react-native.config.js file.
|
|
28
|
+
* @param projectRoot The project root path to look in.
|
|
29
|
+
* @returns Whether the project at the given path has a react-native.config.js file.
|
|
30
|
+
*/
|
|
31
|
+
export declare function usesReactNativeConfig(projectRoot: string): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Gets whether the project at the given path is using TypeScript.
|
|
34
|
+
* @param projectRoot The project root path to look in.
|
|
35
|
+
* @returns Whether the project at the given path is using TypeScript.
|
|
36
|
+
*/
|
|
37
|
+
export declare function usesTypeScript(projectRoot: string): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Calculate the project telemetry info from a react-native CLI config.
|
|
40
|
+
* @param config Config passed from react-native CLI.
|
|
41
|
+
* @returns The calculated project info.
|
|
42
|
+
*/
|
|
43
|
+
export declare function configToProjectInfo(config: Record<string, any>): Promise<AppProjectInfo | DependencyProjectInfo | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Gets the full path to the app's native project file from a react-native CLI config.
|
|
46
|
+
* @param config Config passed from react-native CLI.
|
|
47
|
+
* @returns The full path to the app's native project file
|
|
48
|
+
*/
|
|
49
|
+
export declare function getProjectFileFromConfig(config: Record<string, any>): string | null;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
* @format
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getProjectFileFromConfig = exports.configToProjectInfo = exports.usesTypeScript = exports.usesReactNativeConfig = exports.getProjectId = void 0;
|
|
12
|
+
const crypto_1 = require("crypto");
|
|
13
|
+
const fs_1 = __importDefault(require("@react-native-windows/fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
/**
|
|
16
|
+
* Gets a unique, telemetry-safe project ID based on the project name.
|
|
17
|
+
* @param projectName The project name.
|
|
18
|
+
* @returns The telemetry-safe project ID.
|
|
19
|
+
*/
|
|
20
|
+
function getProjectId(projectName) {
|
|
21
|
+
const hash = (0, crypto_1.createHash)('sha256');
|
|
22
|
+
hash.update(projectName);
|
|
23
|
+
return hash.digest('hex');
|
|
24
|
+
}
|
|
25
|
+
exports.getProjectId = getProjectId;
|
|
26
|
+
/**
|
|
27
|
+
* Checks that a given file exits in the path specified.
|
|
28
|
+
* @param fileName The file to check for.
|
|
29
|
+
* @param projectRoot The root path to look in.
|
|
30
|
+
* @returns Whether the file exists.
|
|
31
|
+
*/
|
|
32
|
+
async function fileExists(fileName, projectRoot) {
|
|
33
|
+
try {
|
|
34
|
+
const reactNativeConfigPath = path_1.default.resolve(projectRoot, fileName);
|
|
35
|
+
const stats = await fs_1.default.stat(reactNativeConfigPath);
|
|
36
|
+
return stats.isFile();
|
|
37
|
+
}
|
|
38
|
+
catch (_a) { }
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Gets whether the project at the given path has a react-native.config.js file.
|
|
43
|
+
* @param projectRoot The project root path to look in.
|
|
44
|
+
* @returns Whether the project at the given path has a react-native.config.js file.
|
|
45
|
+
*/
|
|
46
|
+
async function usesReactNativeConfig(projectRoot) {
|
|
47
|
+
return fileExists('./react-native.config.js', projectRoot);
|
|
48
|
+
}
|
|
49
|
+
exports.usesReactNativeConfig = usesReactNativeConfig;
|
|
50
|
+
/**
|
|
51
|
+
* Gets whether the project at the given path is using TypeScript.
|
|
52
|
+
* @param projectRoot The project root path to look in.
|
|
53
|
+
* @returns Whether the project at the given path is using TypeScript.
|
|
54
|
+
*/
|
|
55
|
+
async function usesTypeScript(projectRoot) {
|
|
56
|
+
return fileExists('./tsconfig.json', projectRoot);
|
|
57
|
+
}
|
|
58
|
+
exports.usesTypeScript = usesTypeScript;
|
|
59
|
+
/**
|
|
60
|
+
* Get the list of keys in the object for which the value is defined.
|
|
61
|
+
* @param obj The object to search.
|
|
62
|
+
* @returns The list of keys.
|
|
63
|
+
*/
|
|
64
|
+
function getDefinedKeys(obj) {
|
|
65
|
+
return Object.keys(obj).filter((value) => obj[value] !== undefined && obj[value] !== null && value);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Given a react-native CLI config, determine the language of the RNW dependency if possible.
|
|
69
|
+
* @param config Dependency config passed from react-native CLI.
|
|
70
|
+
* @returns The language of the RNW dependency.
|
|
71
|
+
*/
|
|
72
|
+
function getDependencyRnwLang(config) {
|
|
73
|
+
if (config) {
|
|
74
|
+
let cppCount = 0;
|
|
75
|
+
let csCount = 0;
|
|
76
|
+
for (const project of config.projects) {
|
|
77
|
+
switch (project.projectLang) {
|
|
78
|
+
case 'cpp':
|
|
79
|
+
cppCount++;
|
|
80
|
+
break;
|
|
81
|
+
case 'cs':
|
|
82
|
+
csCount++;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (cppCount > 0 && csCount > 0) {
|
|
87
|
+
return 'cpp+cs';
|
|
88
|
+
}
|
|
89
|
+
else if (cppCount > 0) {
|
|
90
|
+
return 'cpp';
|
|
91
|
+
}
|
|
92
|
+
else if (csCount > 0) {
|
|
93
|
+
return 'cs';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns true if the item is a boolean with a value of true or a string with a value of 'true'.
|
|
100
|
+
* @param item The item to parse.
|
|
101
|
+
* @returns The boolean value.
|
|
102
|
+
*/
|
|
103
|
+
function parseBoolean(item) {
|
|
104
|
+
if (typeof item === 'boolean') {
|
|
105
|
+
return item;
|
|
106
|
+
}
|
|
107
|
+
else if (typeof item === 'string') {
|
|
108
|
+
return item.toLowerCase() === 'true';
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Calculate the project telemetry info from a react-native CLI config.
|
|
114
|
+
* @param config Config passed from react-native CLI.
|
|
115
|
+
* @returns The calculated project info.
|
|
116
|
+
*/
|
|
117
|
+
async function configToProjectInfo(config) {
|
|
118
|
+
var _a, _b, _c;
|
|
119
|
+
try {
|
|
120
|
+
const id = getProjectId(require(path_1.default.join(config.root, 'package.json')).name);
|
|
121
|
+
const platforms = getDefinedKeys(config.project);
|
|
122
|
+
if ('windows' in config.project && config.project.windows !== null) {
|
|
123
|
+
const rnwLang = (_a = config.project.windows.project) === null || _a === void 0 ? void 0 : _a.projectLang;
|
|
124
|
+
const usesTS = await usesTypeScript(config.project.windows.folder);
|
|
125
|
+
const usesRNConfig = await usesReactNativeConfig(config.project.windows.folder);
|
|
126
|
+
const jsEngine = parseBoolean((_b = config.project.windows.experimentalFeatures) === null || _b === void 0 ? void 0 : _b.UseHermes)
|
|
127
|
+
? 'Hermes'
|
|
128
|
+
: 'Chakra';
|
|
129
|
+
const rnwSource = parseBoolean((_c = config.project.windows.experimentalFeatures) === null || _c === void 0 ? void 0 : _c.UseExperimentalNuget)
|
|
130
|
+
? 'NuGet'
|
|
131
|
+
: 'Source';
|
|
132
|
+
const dependencies = [];
|
|
133
|
+
for (const dependencyName in config.dependencies) {
|
|
134
|
+
if (!Object.prototype.hasOwnProperty(dependencyName)) {
|
|
135
|
+
const dependencyId = getProjectId(dependencyName);
|
|
136
|
+
const dependencyPlatforms = getDefinedKeys(config.dependencies[dependencyName].platforms);
|
|
137
|
+
if (dependencyPlatforms.length > 0) {
|
|
138
|
+
const dependencyRnwLang = getDependencyRnwLang(config.dependencies[dependencyName].platforms.windows);
|
|
139
|
+
const dependencyInfo = {
|
|
140
|
+
id: dependencyId,
|
|
141
|
+
platforms: dependencyPlatforms,
|
|
142
|
+
rnwLang: dependencyRnwLang,
|
|
143
|
+
};
|
|
144
|
+
dependencies.push(dependencyInfo);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const appInfo = {
|
|
149
|
+
id,
|
|
150
|
+
platforms,
|
|
151
|
+
rnwLang,
|
|
152
|
+
usesTS,
|
|
153
|
+
usesRNConfig,
|
|
154
|
+
jsEngine,
|
|
155
|
+
rnwSource,
|
|
156
|
+
dependencies,
|
|
157
|
+
};
|
|
158
|
+
return appInfo;
|
|
159
|
+
}
|
|
160
|
+
// Probably just a dependency project, return minimal info
|
|
161
|
+
const dependencyInfo = {
|
|
162
|
+
id,
|
|
163
|
+
platforms,
|
|
164
|
+
rnwLang: null,
|
|
165
|
+
};
|
|
166
|
+
return dependencyInfo;
|
|
167
|
+
}
|
|
168
|
+
catch (_d) { }
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
exports.configToProjectInfo = configToProjectInfo;
|
|
172
|
+
/**
|
|
173
|
+
* Gets the full path to the app's native project file from a react-native CLI config.
|
|
174
|
+
* @param config Config passed from react-native CLI.
|
|
175
|
+
* @returns The full path to the app's native project file
|
|
176
|
+
*/
|
|
177
|
+
function getProjectFileFromConfig(config) {
|
|
178
|
+
try {
|
|
179
|
+
if ('windows' in config.project && config.project.windows !== null) {
|
|
180
|
+
return path_1.default.join(config.project.windows.folder, config.project.windows.sourceDir, config.project.windows.project.projectFile);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (_a) { }
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
exports.getProjectFileFromConfig = getProjectFileFromConfig;
|
|
187
|
+
//# sourceMappingURL=projectUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectUtils.js","sourceRoot":"","sources":["../../src/utils/projectUtils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n * @format\n */\n\nimport {createHash} from 'crypto';\nimport fs from '@react-native-windows/fs';\nimport path from 'path';\n\ninterface ProjectInfo {\n id: string | null;\n platforms: Array<string>;\n rnwLang: 'cpp' | 'cs' | 'cpp+cs' | null;\n}\n\nexport interface DependencyProjectInfo extends ProjectInfo {}\n\nexport interface AppProjectInfo extends ProjectInfo {\n usesTS: boolean;\n usesRNConfig: boolean;\n jsEngine: string;\n rnwSource: string;\n dependencies: Array<DependencyProjectInfo>;\n}\n\n/**\n * Gets a unique, telemetry-safe project ID based on the project name.\n * @param projectName The project name.\n * @returns The telemetry-safe project ID.\n */\nexport function getProjectId(projectName: string): string {\n const hash = createHash('sha256');\n hash.update(projectName);\n return hash.digest('hex');\n}\n\n/**\n * Checks that a given file exits in the path specified.\n * @param fileName The file to check for.\n * @param projectRoot The root path to look in.\n * @returns Whether the file exists.\n */\nasync function fileExists(\n fileName: string,\n projectRoot: string,\n): Promise<boolean> {\n try {\n const reactNativeConfigPath = path.resolve(projectRoot, fileName);\n const stats = await fs.stat(reactNativeConfigPath);\n return stats.isFile();\n } catch {}\n return false;\n}\n\n/**\n * Gets whether the project at the given path has a react-native.config.js file.\n * @param projectRoot The project root path to look in.\n * @returns Whether the project at the given path has a react-native.config.js file.\n */\nexport async function usesReactNativeConfig(\n projectRoot: string,\n): Promise<boolean> {\n return fileExists('./react-native.config.js', projectRoot);\n}\n\n/**\n * Gets whether the project at the given path is using TypeScript.\n * @param projectRoot The project root path to look in.\n * @returns Whether the project at the given path is using TypeScript.\n */\nexport async function usesTypeScript(projectRoot: string): Promise<boolean> {\n return fileExists('./tsconfig.json', projectRoot);\n}\n\n/**\n * Get the list of keys in the object for which the value is defined.\n * @param obj The object to search.\n * @returns The list of keys.\n */\nfunction getDefinedKeys(obj: Record<string, any>): string[] {\n return Object.keys(obj).filter(\n (value) => obj[value] !== undefined && obj[value] !== null && value,\n );\n}\n\n/**\n * Given a react-native CLI config, determine the language of the RNW dependency if possible.\n * @param config Dependency config passed from react-native CLI.\n * @returns The language of the RNW dependency.\n */\nfunction getDependencyRnwLang(\n config: Record<string, any> | null | undefined,\n): 'cpp' | 'cs' | 'cpp+cs' | null {\n if (config) {\n let cppCount = 0;\n let csCount = 0;\n for (const project of config.projects) {\n switch (project.projectLang) {\n case 'cpp':\n cppCount++;\n break;\n case 'cs':\n csCount++;\n break;\n }\n }\n if (cppCount > 0 && csCount > 0) {\n return 'cpp+cs';\n } else if (cppCount > 0) {\n return 'cpp';\n } else if (csCount > 0) {\n return 'cs';\n }\n }\n return null;\n}\n\n/**\n * Returns true if the item is a boolean with a value of true or a string with a value of 'true'.\n * @param item The item to parse.\n * @returns The boolean value.\n */\nfunction parseBoolean(item: any): boolean {\n if (typeof item === 'boolean') {\n return item;\n } else if (typeof item === 'string') {\n return item.toLowerCase() === 'true';\n }\n return false;\n}\n\n/**\n * Calculate the project telemetry info from a react-native CLI config.\n * @param config Config passed from react-native CLI.\n * @returns The calculated project info.\n */\nexport async function configToProjectInfo(\n config: Record<string, any>,\n): Promise<AppProjectInfo | DependencyProjectInfo | null> {\n try {\n const id: string = getProjectId(\n require(path.join(config.root, 'package.json')).name,\n );\n const platforms = getDefinedKeys(config.project);\n\n if ('windows' in config.project && config.project.windows !== null) {\n const rnwLang = config.project.windows.project?.projectLang;\n const usesTS = await usesTypeScript(config.project.windows.folder);\n const usesRNConfig = await usesReactNativeConfig(\n config.project.windows.folder,\n );\n const jsEngine = parseBoolean(\n config.project.windows.experimentalFeatures?.UseHermes,\n )\n ? 'Hermes'\n : 'Chakra';\n const rnwSource = parseBoolean(\n config.project.windows.experimentalFeatures?.UseExperimentalNuget,\n )\n ? 'NuGet'\n : 'Source';\n\n const dependencies: DependencyProjectInfo[] = [];\n for (const dependencyName in config.dependencies) {\n if (!Object.prototype.hasOwnProperty(dependencyName)) {\n const dependencyId = getProjectId(dependencyName);\n const dependencyPlatforms = getDefinedKeys(\n config.dependencies[dependencyName].platforms,\n );\n\n if (dependencyPlatforms.length > 0) {\n const dependencyRnwLang = getDependencyRnwLang(\n config.dependencies[dependencyName].platforms.windows,\n );\n const dependencyInfo: DependencyProjectInfo = {\n id: dependencyId,\n platforms: dependencyPlatforms,\n rnwLang: dependencyRnwLang,\n };\n dependencies.push(dependencyInfo);\n }\n }\n }\n\n const appInfo: AppProjectInfo = {\n id,\n platforms,\n rnwLang,\n usesTS,\n usesRNConfig,\n jsEngine,\n rnwSource,\n dependencies,\n };\n\n return appInfo;\n }\n\n // Probably just a dependency project, return minimal info\n const dependencyInfo: DependencyProjectInfo = {\n id,\n platforms,\n rnwLang: null,\n };\n return dependencyInfo;\n } catch {}\n return null;\n}\n\n/**\n * Gets the full path to the app's native project file from a react-native CLI config.\n * @param config Config passed from react-native CLI.\n * @returns The full path to the app's native project file\n */\nexport function getProjectFileFromConfig(\n config: Record<string, any>,\n): string | null {\n try {\n if ('windows' in config.project && config.project.windows !== null) {\n return path.join(\n config.project.windows.folder,\n config.project.windows.sourceDir,\n config.project.windows.project.projectFile,\n );\n }\n } catch {}\n return null;\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
* @format
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Gets an anonymized version of the given path, suitable for Telemetry.
|
|
8
|
+
* @param filepath The path to anonymize.
|
|
9
|
+
* @param projectRoot Optional root path for the project. Defaults to process.cwd().
|
|
10
|
+
* @returns The anonymized path.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getAnonymizedPath(filepath: string, projectRoot?: string): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
* @format
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getAnonymizedPath = void 0;
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const telemetry_1 = require("../telemetry");
|
|
14
|
+
const nodeModules = '\\node_modules\\';
|
|
15
|
+
const windows = '\\windows\\';
|
|
16
|
+
const knownEnvironmentVariablePaths = [
|
|
17
|
+
'AppData',
|
|
18
|
+
'LocalAppData',
|
|
19
|
+
'UserProfile',
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Gets an anonymized version of the given path, suitable for Telemetry.
|
|
23
|
+
* @param filepath The path to anonymize.
|
|
24
|
+
* @param projectRoot Optional root path for the project. Defaults to process.cwd().
|
|
25
|
+
* @returns The anonymized path.
|
|
26
|
+
*/
|
|
27
|
+
function getAnonymizedPath(filepath, projectRoot) {
|
|
28
|
+
projectRoot = (projectRoot !== null && projectRoot !== void 0 ? projectRoot : process.cwd())
|
|
29
|
+
.replace(/\//g, '\\')
|
|
30
|
+
.toLowerCase();
|
|
31
|
+
projectRoot = projectRoot.endsWith('\\')
|
|
32
|
+
? projectRoot.slice(0, -1)
|
|
33
|
+
: projectRoot;
|
|
34
|
+
filepath = filepath.replace(/\//g, '\\');
|
|
35
|
+
const ext = path_1.default.extname(filepath);
|
|
36
|
+
// Check if we're under node_modules
|
|
37
|
+
const nodeModulesIndex = filepath.toLowerCase().lastIndexOf(nodeModules);
|
|
38
|
+
if (nodeModulesIndex >= 0) {
|
|
39
|
+
// We are under node_modules
|
|
40
|
+
// Check if it's an npm package we're tracking
|
|
41
|
+
for (const trackedNpmPackage of telemetry_1.NpmPackagesWeTrack) {
|
|
42
|
+
const startIndex = filepath
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.lastIndexOf(nodeModules + trackedNpmPackage.replace(/\//g, '\\') + '\\');
|
|
45
|
+
if (startIndex >= 0) {
|
|
46
|
+
// We are under node_modules within an npm package we're tracking, anonymize by removing root
|
|
47
|
+
return ('[node_modules]\\' + filepath.slice(startIndex + nodeModules.length));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// It's an npm package we're not tracking, anonymize with [node_modules]
|
|
51
|
+
return `[node_modules]\\???${ext}(${filepath.slice(nodeModulesIndex).length - nodeModules.length})`;
|
|
52
|
+
}
|
|
53
|
+
// Check if we're under the projectRoot
|
|
54
|
+
if (filepath.toLowerCase().startsWith(projectRoot)) {
|
|
55
|
+
// We are under the projectRoot
|
|
56
|
+
const rest = filepath.slice(projectRoot.length);
|
|
57
|
+
if (rest.toLowerCase().startsWith(windows)) {
|
|
58
|
+
// We are under the windows path, anonymize with [windows]
|
|
59
|
+
return `[windows]\\???${ext}(${rest.length - windows.length - 1})`;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// We are just within the projectRoot, anonymize with [project_dir]
|
|
63
|
+
if (rest === '' || rest === '\\') {
|
|
64
|
+
return '[project_dir]';
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
return `[project_dir]\\???${ext}(${rest.length - (rest.startsWith('\\') ? 1 : 0)})`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Check if we're under a known environmental variable path
|
|
72
|
+
for (const knownPath of knownEnvironmentVariablePaths) {
|
|
73
|
+
if (process.env[knownPath] &&
|
|
74
|
+
filepath.toLowerCase().startsWith(process.env[knownPath].toLowerCase())) {
|
|
75
|
+
return `[${knownPath}]\\???(${filepath.length - process.env[knownPath].length})`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// We are somewhere else, anonymize with [path]
|
|
79
|
+
return '[path]';
|
|
80
|
+
}
|
|
81
|
+
exports.getAnonymizedPath = getAnonymizedPath;
|
|
82
|
+
//# sourceMappingURL=sanitizeUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizeUtils.js","sourceRoot":"","sources":["../../src/utils/sanitizeUtils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n * @format\n */\n\nimport path from 'path';\n\nimport {NpmPackagesWeTrack} from '../telemetry';\n\nconst nodeModules = '\\\\node_modules\\\\';\nconst windows = '\\\\windows\\\\';\n\nconst knownEnvironmentVariablePaths = [\n 'AppData',\n 'LocalAppData',\n 'UserProfile',\n];\n\n/**\n * Gets an anonymized version of the given path, suitable for Telemetry.\n * @param filepath The path to anonymize.\n * @param projectRoot Optional root path for the project. Defaults to process.cwd().\n * @returns The anonymized path.\n */\nexport function getAnonymizedPath(\n filepath: string,\n projectRoot?: string,\n): string {\n projectRoot = (projectRoot ?? process.cwd())\n .replace(/\\//g, '\\\\')\n .toLowerCase();\n projectRoot = projectRoot.endsWith('\\\\')\n ? projectRoot.slice(0, -1)\n : projectRoot;\n filepath = filepath.replace(/\\//g, '\\\\');\n\n const ext = path.extname(filepath);\n\n // Check if we're under node_modules\n const nodeModulesIndex = filepath.toLowerCase().lastIndexOf(nodeModules);\n if (nodeModulesIndex >= 0) {\n // We are under node_modules\n\n // Check if it's an npm package we're tracking\n for (const trackedNpmPackage of NpmPackagesWeTrack) {\n const startIndex = filepath\n .toLowerCase()\n .lastIndexOf(\n nodeModules + trackedNpmPackage.replace(/\\//g, '\\\\') + '\\\\',\n );\n if (startIndex >= 0) {\n // We are under node_modules within an npm package we're tracking, anonymize by removing root\n return (\n '[node_modules]\\\\' + filepath.slice(startIndex + nodeModules.length)\n );\n }\n }\n\n // It's an npm package we're not tracking, anonymize with [node_modules]\n return `[node_modules]\\\\???${ext}(${\n filepath.slice(nodeModulesIndex).length - nodeModules.length\n })`;\n }\n\n // Check if we're under the projectRoot\n if (filepath.toLowerCase().startsWith(projectRoot)) {\n // We are under the projectRoot\n const rest = filepath.slice(projectRoot.length);\n if (rest.toLowerCase().startsWith(windows)) {\n // We are under the windows path, anonymize with [windows]\n return `[windows]\\\\???${ext}(${rest.length - windows.length - 1})`;\n } else {\n // We are just within the projectRoot, anonymize with [project_dir]\n if (rest === '' || rest === '\\\\') {\n return '[project_dir]';\n } else {\n return `[project_dir]\\\\???${ext}(${\n rest.length - (rest.startsWith('\\\\') ? 1 : 0)\n })`;\n }\n }\n }\n\n // Check if we're under a known environmental variable path\n for (const knownPath of knownEnvironmentVariablePaths) {\n if (\n process.env[knownPath] &&\n filepath.toLowerCase().startsWith(process.env[knownPath]!.toLowerCase())\n ) {\n return `[${knownPath}]\\\\???(${\n filepath.length - process.env[knownPath]!.length\n })`;\n }\n }\n\n // We are somewhere else, anonymize with [path]\n return '[path]';\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
* @format
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Gets the version of node being used.
|
|
8
|
+
* @returns The version of node being used.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getNodeVersion(): Promise<string | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Gets the version of npm installed, if available.
|
|
13
|
+
* @returns The version of npm installed, if available.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getNpmVersion(): Promise<string | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Gets the version of yarn installed, if available.
|
|
18
|
+
* @returns The version of yarn installed, if available.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getYarnVersion(): Promise<string | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Gets the latest version of Visual Studio installed, if available.
|
|
23
|
+
* @returns The latest version of Visual Studio installed, if available.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getVisualStudioVersion(): Promise<string | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Gets the version installed of the specified npm package.
|
|
28
|
+
* @param pkgName The npm package name.
|
|
29
|
+
* @returns The version installed, if available.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getVersionOfNpmPackage(pkgName: string): Promise<string | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Gets the versions of the specified NuGet packages referenced in a project file.
|
|
34
|
+
* @param projectFile Path to the native project file.
|
|
35
|
+
* @param nugetPackages The NuGet package names to look for.
|
|
36
|
+
* @returns The mapping of NuGet package names and their versions.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getVersionsOfNuGetPackages(projectFile: string, nugetPackages: string[]): Promise<Record<string, string>>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
* @format
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getVersionsOfNuGetPackages = exports.getVersionOfNpmPackage = exports.getVisualStudioVersion = exports.getYarnVersion = exports.getNpmVersion = exports.getNodeVersion = void 0;
|
|
12
|
+
const envinfo_1 = __importDefault(require("envinfo"));
|
|
13
|
+
const fs_1 = __importDefault(require("@react-native-windows/fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const xmldom_1 = require("@xmldom/xmldom");
|
|
16
|
+
const xpath_1 = __importDefault(require("xpath"));
|
|
17
|
+
const msbuildSelect = xpath_1.default.useNamespaces({
|
|
18
|
+
msbuild: 'http://schemas.microsoft.com/developer/msbuild/2003',
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Gets the version of node being used.
|
|
22
|
+
* @returns The version of node being used.
|
|
23
|
+
*/
|
|
24
|
+
async function getNodeVersion() {
|
|
25
|
+
return process.version.slice(1);
|
|
26
|
+
}
|
|
27
|
+
exports.getNodeVersion = getNodeVersion;
|
|
28
|
+
/**
|
|
29
|
+
* Gets the version of npm installed, if available.
|
|
30
|
+
* @returns The version of npm installed, if available.
|
|
31
|
+
*/
|
|
32
|
+
async function getNpmVersion() {
|
|
33
|
+
try {
|
|
34
|
+
const info = await envinfo_1.default.helpers.getnpmInfo();
|
|
35
|
+
return info[1];
|
|
36
|
+
}
|
|
37
|
+
catch (_a) { }
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
exports.getNpmVersion = getNpmVersion;
|
|
41
|
+
/**
|
|
42
|
+
* Gets the version of yarn installed, if available.
|
|
43
|
+
* @returns The version of yarn installed, if available.
|
|
44
|
+
*/
|
|
45
|
+
async function getYarnVersion() {
|
|
46
|
+
try {
|
|
47
|
+
const info = await envinfo_1.default.helpers.getYarnInfo();
|
|
48
|
+
return info[1];
|
|
49
|
+
}
|
|
50
|
+
catch (_a) { }
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
exports.getYarnVersion = getYarnVersion;
|
|
54
|
+
/**
|
|
55
|
+
* Gets the latest version of Visual Studio installed, if available.
|
|
56
|
+
* @returns The latest version of Visual Studio installed, if available.
|
|
57
|
+
*/
|
|
58
|
+
async function getVisualStudioVersion() {
|
|
59
|
+
try {
|
|
60
|
+
const info = await envinfo_1.default.helpers.getVisualStudioInfo();
|
|
61
|
+
const versions = info[1];
|
|
62
|
+
return versions.sort().slice(-1)[0].split(' ')[0];
|
|
63
|
+
}
|
|
64
|
+
catch (_a) { }
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
exports.getVisualStudioVersion = getVisualStudioVersion;
|
|
68
|
+
/**
|
|
69
|
+
* Gets the version installed of the specified npm package.
|
|
70
|
+
* @param pkgName The npm package name.
|
|
71
|
+
* @returns The version installed, if available.
|
|
72
|
+
*/
|
|
73
|
+
async function getVersionOfNpmPackage(pkgName) {
|
|
74
|
+
try {
|
|
75
|
+
const pkgJsonPath = require.resolve(`${pkgName.trim()}/package.json`, {
|
|
76
|
+
paths: [process.cwd(), __dirname],
|
|
77
|
+
});
|
|
78
|
+
const pkgJson = await fs_1.default.readJsonFile(pkgJsonPath);
|
|
79
|
+
if (pkgJson.name === pkgName) {
|
|
80
|
+
return pkgJson.version;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (_a) { }
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
exports.getVersionOfNpmPackage = getVersionOfNpmPackage;
|
|
87
|
+
/**
|
|
88
|
+
* Reads and parses an XML file into a Document.
|
|
89
|
+
* @param filePath The path to the XML file.
|
|
90
|
+
* @returns The parsed Document.
|
|
91
|
+
*/
|
|
92
|
+
async function readXmlFile(filePath) {
|
|
93
|
+
const contents = await fs_1.default.readFile(filePath, 'utf-8');
|
|
94
|
+
return new xmldom_1.DOMParser().parseFromString(contents);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Gets the versions of the specified NuGet packages referenced in a packages.config file.
|
|
98
|
+
* @param projectDoc The XML document of the packages.connfig file.
|
|
99
|
+
* @param nugetPackages The NuGet package names to look for.
|
|
100
|
+
* @returns The mapping of NuGet package names and their versions.
|
|
101
|
+
*/
|
|
102
|
+
function getVersionsFromPackagesConfig(packagesConfigDoc, nugetPackages) {
|
|
103
|
+
const versions = {};
|
|
104
|
+
for (const pkgName of nugetPackages) {
|
|
105
|
+
const version = xpath_1.default.select1(`//packages/package[@id='${pkgName}']/@version`, packagesConfigDoc);
|
|
106
|
+
if (version) {
|
|
107
|
+
const versionValue = version.nodeValue;
|
|
108
|
+
if (versionValue !== null) {
|
|
109
|
+
versions[pkgName] = versionValue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return versions;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Gets the versions of the specified NuGet packages referenced in a project file.
|
|
117
|
+
* @param projectDoc The XML document of the project file.
|
|
118
|
+
* @param nugetPackages The NuGet package names to look for.
|
|
119
|
+
* @returns The mapping of NuGet package names and their versions.
|
|
120
|
+
*/
|
|
121
|
+
function getVersionsFromProjectFile(projectDoc, nugetPackages) {
|
|
122
|
+
const versions = {};
|
|
123
|
+
for (const pkgName of nugetPackages) {
|
|
124
|
+
const version = msbuildSelect(`//msbuild:ItemGroup/msbuild:PackageReference[@Include='${pkgName}']/msbuild:Version`, projectDoc, true);
|
|
125
|
+
if (version) {
|
|
126
|
+
const versionValue = version.textContent;
|
|
127
|
+
if (versionValue !== null) {
|
|
128
|
+
versions[pkgName] = versionValue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return versions;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Gets the versions of the specified NuGet packages referenced in a project file.
|
|
136
|
+
* @param projectFile Path to the native project file.
|
|
137
|
+
* @param nugetPackages The NuGet package names to look for.
|
|
138
|
+
* @returns The mapping of NuGet package names and their versions.
|
|
139
|
+
*/
|
|
140
|
+
async function getVersionsOfNuGetPackages(projectFile, nugetPackages) {
|
|
141
|
+
try {
|
|
142
|
+
// First check for the presence of a packages.config file
|
|
143
|
+
const packagesConfigFile = path_1.default.join(path_1.default.dirname(projectFile), 'packages.config');
|
|
144
|
+
const packagesConfigDoc = await readXmlFile(packagesConfigFile);
|
|
145
|
+
return getVersionsFromPackagesConfig(packagesConfigDoc, nugetPackages);
|
|
146
|
+
}
|
|
147
|
+
catch (_a) { }
|
|
148
|
+
try {
|
|
149
|
+
const projectDoc = await readXmlFile(projectFile);
|
|
150
|
+
return getVersionsFromProjectFile(projectDoc, nugetPackages);
|
|
151
|
+
}
|
|
152
|
+
catch (_b) { }
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
exports.getVersionsOfNuGetPackages = getVersionsOfNuGetPackages;
|
|
156
|
+
//# sourceMappingURL=versionUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versionUtils.js","sourceRoot":"","sources":["../../src/utils/versionUtils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n * @format\n */\n\nimport envinfo from 'envinfo';\nimport fs from '@react-native-windows/fs';\nimport path from 'path';\nimport {DOMParser} from '@xmldom/xmldom';\nimport xpath from 'xpath';\n\nconst msbuildSelect = xpath.useNamespaces({\n msbuild: 'http://schemas.microsoft.com/developer/msbuild/2003',\n});\n\n/**\n * Gets the version of node being used.\n * @returns The version of node being used.\n */\nexport async function getNodeVersion(): Promise<string | null> {\n return process.version.slice(1);\n}\n\n/**\n * Gets the version of npm installed, if available.\n * @returns The version of npm installed, if available.\n */\nexport async function getNpmVersion(): Promise<string | null> {\n try {\n const info: any = await envinfo.helpers.getnpmInfo();\n return info[1] as string;\n } catch {}\n return null;\n}\n\n/**\n * Gets the version of yarn installed, if available.\n * @returns The version of yarn installed, if available.\n */\nexport async function getYarnVersion(): Promise<string | null> {\n try {\n const info: any = await envinfo.helpers.getYarnInfo();\n return info[1] as string;\n } catch {}\n return null;\n}\n\n/**\n * Gets the latest version of Visual Studio installed, if available.\n * @returns The latest version of Visual Studio installed, if available.\n */\nexport async function getVisualStudioVersion(): Promise<string | null> {\n try {\n const info: any = await envinfo.helpers.getVisualStudioInfo();\n const versions = info[1] as string[];\n return versions.sort().slice(-1)[0].split(' ')[0];\n } catch {}\n return null;\n}\n\n/**\n * Gets the version installed of the specified npm package.\n * @param pkgName The npm package name.\n * @returns The version installed, if available.\n */\nexport async function getVersionOfNpmPackage(\n pkgName: string,\n): Promise<string | null> {\n try {\n const pkgJsonPath = require.resolve(`${pkgName.trim()}/package.json`, {\n paths: [process.cwd(), __dirname],\n });\n const pkgJson = await fs.readJsonFile<{name: string; version: string}>(\n pkgJsonPath,\n );\n if (pkgJson.name === pkgName) {\n return pkgJson.version;\n }\n } catch {}\n return null;\n}\n\n/**\n * Reads and parses an XML file into a Document.\n * @param filePath The path to the XML file.\n * @returns The parsed Document.\n */\nasync function readXmlFile(filePath: string): Promise<Document> {\n const contents = await fs.readFile(filePath, 'utf-8');\n return new DOMParser().parseFromString(contents);\n}\n\n/**\n * Gets the versions of the specified NuGet packages referenced in a packages.config file.\n * @param projectDoc The XML document of the packages.connfig file.\n * @param nugetPackages The NuGet package names to look for.\n * @returns The mapping of NuGet package names and their versions.\n */\nfunction getVersionsFromPackagesConfig(\n packagesConfigDoc: Document,\n nugetPackages: string[],\n): Record<string, string> {\n const versions: Record<string, string> = {};\n for (const pkgName of nugetPackages) {\n const version = xpath.select1(\n `//packages/package[@id='${pkgName}']/@version`,\n packagesConfigDoc,\n );\n\n if (version) {\n const versionValue = (version as Attr).nodeValue;\n if (versionValue !== null) {\n versions[pkgName] = versionValue;\n }\n }\n }\n return versions;\n}\n\n/**\n * Gets the versions of the specified NuGet packages referenced in a project file.\n * @param projectDoc The XML document of the project file.\n * @param nugetPackages The NuGet package names to look for.\n * @returns The mapping of NuGet package names and their versions.\n */\nfunction getVersionsFromProjectFile(\n projectDoc: Document,\n nugetPackages: string[],\n): Record<string, string> {\n const versions: Record<string, string> = {};\n for (const pkgName of nugetPackages) {\n const version = msbuildSelect(\n `//msbuild:ItemGroup/msbuild:PackageReference[@Include='${pkgName}']/msbuild:Version`,\n projectDoc,\n true,\n );\n\n if (version) {\n const versionValue = (version as Node).textContent;\n if (versionValue !== null) {\n versions[pkgName] = versionValue;\n }\n }\n }\n return versions;\n}\n\n/**\n * Gets the versions of the specified NuGet packages referenced in a project file.\n * @param projectFile Path to the native project file.\n * @param nugetPackages The NuGet package names to look for.\n * @returns The mapping of NuGet package names and their versions.\n */\nexport async function getVersionsOfNuGetPackages(\n projectFile: string,\n nugetPackages: string[],\n): Promise<Record<string, string>> {\n try {\n // First check for the presence of a packages.config file\n const packagesConfigFile = path.join(\n path.dirname(projectFile),\n 'packages.config',\n );\n const packagesConfigDoc = await readXmlFile(packagesConfigFile);\n return getVersionsFromPackagesConfig(packagesConfigDoc, nugetPackages);\n } catch {}\n\n try {\n const projectDoc = await readXmlFile(projectFile);\n return getVersionsFromProjectFile(projectDoc, nugetPackages);\n } catch {}\n\n return {};\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-windows/telemetry",
|
|
3
|
-
"version": "0.0.0-canary.
|
|
3
|
+
"version": "0.0.0-canary.33",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "lib-commonjs/index.js",
|
|
6
6
|
"typings": "lib-commonjs/index.d.ts",
|
|
@@ -10,29 +10,41 @@
|
|
|
10
10
|
"directory": "packages/@react-native-windows/telemetry"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "
|
|
14
|
-
"clean": "
|
|
15
|
-
"lint": "
|
|
16
|
-
"lint:fix": "
|
|
17
|
-
"test": "set RNW_CLI_TEST=true&&
|
|
18
|
-
"watch": "
|
|
13
|
+
"build": "rnw-scripts build",
|
|
14
|
+
"clean": "rnw-scripts clean",
|
|
15
|
+
"lint": "rnw-scripts lint",
|
|
16
|
+
"lint:fix": "rnw-scripts lint:fix",
|
|
17
|
+
"test": "set RNW_CLI_TEST=true&& rnw-scripts test",
|
|
18
|
+
"watch": "rnw-scripts watch"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"
|
|
21
|
+
"@react-native-windows/fs": "^1.0.2",
|
|
22
|
+
"@xmldom/xmldom": "^0.7.5",
|
|
23
|
+
"applicationinsights": "^2.1.5",
|
|
24
|
+
"ci-info": "^3.2.0",
|
|
25
|
+
"envinfo": "^7.8.1",
|
|
26
|
+
"lodash": "^4.17.21",
|
|
27
|
+
"node-machine-id": "^1.1.12",
|
|
28
|
+
"os-locale": "^5.0.0",
|
|
29
|
+
"xpath": "^0.0.27"
|
|
22
30
|
},
|
|
23
31
|
"devDependencies": {
|
|
24
|
-
"@rnw-scripts/eslint-config": "
|
|
25
|
-
"@rnw-scripts/jest-unittest-config": "
|
|
26
|
-
"@rnw-scripts/just-task": "
|
|
27
|
-
"@rnw-scripts/ts-config": "0.
|
|
28
|
-
"@types/
|
|
32
|
+
"@rnw-scripts/eslint-config": "1.1.11",
|
|
33
|
+
"@rnw-scripts/jest-unittest-config": "1.2.6",
|
|
34
|
+
"@rnw-scripts/just-task": "2.2.3",
|
|
35
|
+
"@rnw-scripts/ts-config": "2.0.2",
|
|
36
|
+
"@types/envinfo": "^7.8.1",
|
|
37
|
+
"@types/jest": "^26.0.20",
|
|
38
|
+
"@types/node": "^14.14.22",
|
|
29
39
|
"@types/semver": "^7.3.3",
|
|
30
40
|
"babel-jest": "^26.3.0",
|
|
31
|
-
"eslint": "
|
|
32
|
-
"jest": "^26.
|
|
33
|
-
"just-scripts": "^
|
|
34
|
-
"
|
|
35
|
-
"
|
|
41
|
+
"eslint": "^7.32.0",
|
|
42
|
+
"jest": "^26.6.3",
|
|
43
|
+
"just-scripts": "^1.3.3",
|
|
44
|
+
"lookpath": "^1.2.1",
|
|
45
|
+
"prettier": "^2.4.1",
|
|
46
|
+
"semver": "^7.3.5",
|
|
47
|
+
"typescript": "^4.4.4"
|
|
36
48
|
},
|
|
37
49
|
"files": [
|
|
38
50
|
"lib-commonjs"
|
|
@@ -45,5 +57,9 @@
|
|
|
45
57
|
"patch"
|
|
46
58
|
]
|
|
47
59
|
},
|
|
48
|
-
"promoteRelease": true
|
|
60
|
+
"promoteRelease": true,
|
|
61
|
+
"windowsOnly": true,
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">= 14"
|
|
64
|
+
}
|
|
49
65
|
}
|