@react-native-windows/automation-commands 0.0.0-canary.1016
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/README.md +31 -0
- package/lib-commonjs/dumpVisualTree.d.ts +73 -0
- package/lib-commonjs/dumpVisualTree.js +124 -0
- package/lib-commonjs/dumpVisualTree.js.map +1 -0
- package/lib-commonjs/index.d.ts +8 -0
- package/lib-commonjs/index.js +15 -0
- package/lib-commonjs/index.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @react-native-windows/automation-commands
|
|
2
|
+
|
|
3
|
+
`@react-native-windows/automation-commands` provides built-in commands that may
|
|
4
|
+
be used with `@react-native-windows/automation-channel` to make it easier to
|
|
5
|
+
test your application.
|
|
6
|
+
|
|
7
|
+
**This package is a work in progress**
|
|
8
|
+
|
|
9
|
+
## Adding to your application
|
|
10
|
+
|
|
11
|
+
**TBD**
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### dumpVisualTree
|
|
16
|
+
|
|
17
|
+
The `dumpVisualTree` command creates a JSON object corresponding to the XAML
|
|
18
|
+
tree under a given testID. This can be used in conjunction with snapshot
|
|
19
|
+
testing to validate that your native UI looks how you could expect.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import {dumpVisualTree} from '@react-native-windows/automation-commands';
|
|
23
|
+
|
|
24
|
+
test('Widget', async () => {
|
|
25
|
+
const dump = await dumpVisualTree('widget-test-id');
|
|
26
|
+
expect(dump).toMatchSnapshot();
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Options
|
|
31
|
+
**TBD**
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*
|
|
5
|
+
* @format
|
|
6
|
+
*/
|
|
7
|
+
import { AutomationClient } from '@react-native-windows/automation-channel';
|
|
8
|
+
/**
|
|
9
|
+
* Schema of tree dumped node
|
|
10
|
+
*/
|
|
11
|
+
export type UIElement = {
|
|
12
|
+
XamlType: string;
|
|
13
|
+
Foreground?: string | null;
|
|
14
|
+
Background?: string | null;
|
|
15
|
+
Padding?: string | null;
|
|
16
|
+
Margin?: string | null;
|
|
17
|
+
RenderSize?: number[] | null;
|
|
18
|
+
Visibility?: 'Collapsed' | 'Visible' | null;
|
|
19
|
+
CornerRadius?: string | null;
|
|
20
|
+
BorderThickness?: string | null;
|
|
21
|
+
Width?: number | null;
|
|
22
|
+
Height?: number | null;
|
|
23
|
+
BorderBrush?: string | null;
|
|
24
|
+
VerticalAlignment?: string | null;
|
|
25
|
+
HorizontalAlignment?: string | null;
|
|
26
|
+
Clip?: string | null;
|
|
27
|
+
FlowDirection?: string | null;
|
|
28
|
+
Name?: string | null;
|
|
29
|
+
Text?: string | null;
|
|
30
|
+
children?: UIElement[];
|
|
31
|
+
[index: string]: unknown;
|
|
32
|
+
};
|
|
33
|
+
export type AutomationNode = {
|
|
34
|
+
AutomationId?: string;
|
|
35
|
+
ControlType?: number;
|
|
36
|
+
LocalizedControlType?: string;
|
|
37
|
+
__Children?: [AutomationNode];
|
|
38
|
+
};
|
|
39
|
+
export type ComponentNode = {
|
|
40
|
+
Type: string;
|
|
41
|
+
_Props?: {
|
|
42
|
+
TestId?: string;
|
|
43
|
+
Sources?: [{
|
|
44
|
+
Uri?: string;
|
|
45
|
+
}];
|
|
46
|
+
};
|
|
47
|
+
__Children?: [ComponentNode];
|
|
48
|
+
};
|
|
49
|
+
export type VisualNode = {
|
|
50
|
+
Comment?: string;
|
|
51
|
+
Offset?: `${number} ${number} ${number}`;
|
|
52
|
+
Size?: `${number} ${number}`;
|
|
53
|
+
'Visual Type'?: string;
|
|
54
|
+
__Children?: [VisualNode];
|
|
55
|
+
};
|
|
56
|
+
export type VisualTree = {
|
|
57
|
+
'Automation Tree': AutomationNode;
|
|
58
|
+
'Component Tree': ComponentNode;
|
|
59
|
+
'Visual Tree': VisualNode;
|
|
60
|
+
};
|
|
61
|
+
declare global {
|
|
62
|
+
const automationClient: AutomationClient | undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Dump a section of the native visual tree.
|
|
66
|
+
*/
|
|
67
|
+
export default function dumpVisualTree(accessibilityId: string, opts?: {
|
|
68
|
+
pruneCollapsed?: boolean;
|
|
69
|
+
deterministicOnly?: boolean;
|
|
70
|
+
removeDefaultProps?: boolean;
|
|
71
|
+
removeGuidsFromImageSources?: boolean;
|
|
72
|
+
additionalProperties?: string[];
|
|
73
|
+
}): Promise<UIElement | VisualTree>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*
|
|
6
|
+
* @format
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
/**
|
|
10
|
+
* Dump a section of the native visual tree.
|
|
11
|
+
*/
|
|
12
|
+
async function dumpVisualTree(accessibilityId, opts) {
|
|
13
|
+
if (!automationClient) {
|
|
14
|
+
throw new Error('RPC client is not enabled');
|
|
15
|
+
}
|
|
16
|
+
const dumpResponse = await automationClient.invoke('DumpVisualTree', {
|
|
17
|
+
accessibilityId,
|
|
18
|
+
...opts,
|
|
19
|
+
});
|
|
20
|
+
if (dumpResponse.type === 'error') {
|
|
21
|
+
throw new Error(dumpResponse.message);
|
|
22
|
+
}
|
|
23
|
+
const element = dumpResponse.result;
|
|
24
|
+
if ('XamlType' in element && (opts === null || opts === void 0 ? void 0 : opts.pruneCollapsed) !== false) {
|
|
25
|
+
pruneCollapsedElements(element);
|
|
26
|
+
}
|
|
27
|
+
if ('XamlType' in element && (opts === null || opts === void 0 ? void 0 : opts.deterministicOnly) !== false) {
|
|
28
|
+
removeNonDeterministicProps(element);
|
|
29
|
+
}
|
|
30
|
+
if ('XamlType' in element && (opts === null || opts === void 0 ? void 0 : opts.removeDefaultProps) !== false) {
|
|
31
|
+
removeDefaultProps(element);
|
|
32
|
+
}
|
|
33
|
+
if (!('XamlType' in element) && (opts === null || opts === void 0 ? void 0 : opts.removeGuidsFromImageSources) !== false) {
|
|
34
|
+
removeGuidsFromImageSources(element);
|
|
35
|
+
}
|
|
36
|
+
return element;
|
|
37
|
+
}
|
|
38
|
+
exports.default = dumpVisualTree;
|
|
39
|
+
function removeGuidsFromImageSourcesHelper(node) {
|
|
40
|
+
if (node._Props && node._Props.Sources) {
|
|
41
|
+
node._Props.Sources.forEach((source) => {
|
|
42
|
+
if (source.Uri) {
|
|
43
|
+
if (source.Uri.startsWith('blob:')) {
|
|
44
|
+
source.Uri = source.Uri.replace(/blob:[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+/, 'blob:<some_guid_here>');
|
|
45
|
+
source.Uri = source.Uri.replace(/size=\d{5}/, 'size=<size>');
|
|
46
|
+
}
|
|
47
|
+
else if (source.Uri.startsWith('https://www.facebook.com/assets/fb_lite_messaging/E2EE-settings@3x.png?r=1&t=')) {
|
|
48
|
+
source.Uri =
|
|
49
|
+
'https://www.facebook.com/assets/fb_lite_messaging/E2EE-settings@3x.png?r=1&t=<some_hash_here>';
|
|
50
|
+
}
|
|
51
|
+
else if (source.Uri.startsWith('https://www.facebook.com/ar_effect/external_textures/648609739826677.png?hash=')) {
|
|
52
|
+
source.Uri =
|
|
53
|
+
'https://www.facebook.com/ar_effect/external_textures/648609739826677.png?hash=<some_hash_here>';
|
|
54
|
+
}
|
|
55
|
+
else if (source.Uri.startsWith('https://www.facebook.com/ads/pics/successstories.png?hash=')) {
|
|
56
|
+
source.Uri =
|
|
57
|
+
'https://www.facebook.com/ads/pics/successstories.png?hash=<some_hash_here>';
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// When getting files from a prebuilt bundle the uri is going to include a local path, which would make snapshots inconsistent,
|
|
61
|
+
// This logic replaces the local path so that we get consistent results.
|
|
62
|
+
// file://E:\\repos\\react-native-windows\\packages\\e2e-test-app-fabric\\windows\\RNTesterApp-Fabric.Package\\bin\\x64\\Release\\AppX\\RNTesterApp-Fabric\\Bundle\\@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png
|
|
63
|
+
// becomes
|
|
64
|
+
// <localOrBundlerUri>@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png
|
|
65
|
+
const packagesPath = require('path')
|
|
66
|
+
.resolve(__dirname, '../../..')
|
|
67
|
+
.replace(/\\/g, '\\\\');
|
|
68
|
+
source.Uri = source.Uri.replace(new RegExp(`file://${packagesPath}.*\\\\Bundle\\\\assets/_+`), '<localOrBundlerUri>');
|
|
69
|
+
// When loading the bundle from metro local paths will be replaced with paths to localhost, which will not align with snapshots made with prebuilt bundles.
|
|
70
|
+
// This logic replaces the localhost uri, with the same uri that we would have gotten from a prebuild bundle. This makes it easier to debug without breaking snapshots
|
|
71
|
+
// http://localhost:8081/assets/@@/@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png?platform=windows&hash=c6f5aec4d9e0aa47c0887e4266796224
|
|
72
|
+
// becomes
|
|
73
|
+
// "<localOrBundlerUri>@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png"
|
|
74
|
+
source.Uri = source.Uri.replace(/http:\/\/localhost:8081\/assets\/(@@\/)+/, '<localOrBundlerUri>');
|
|
75
|
+
source.Uri = source.Uri.replace(/\?platform=windows&hash=[^=]$/, '');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (node.__Children) {
|
|
81
|
+
node.__Children.forEach((child) => removeGuidsFromImageSourcesHelper(child));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function removeGuidsFromImageSources(visualTree) {
|
|
85
|
+
removeGuidsFromImageSourcesHelper(visualTree['Component Tree']);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Removes trees of XAML that are not visible.
|
|
89
|
+
*/
|
|
90
|
+
function pruneCollapsedElements(element) {
|
|
91
|
+
if (!element.children) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
element.children = element.children.filter(child => child.Visibility !== 'Collapsed');
|
|
95
|
+
element.children.forEach(pruneCollapsedElements);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Removes trees of properties that are not deterministic
|
|
99
|
+
*/
|
|
100
|
+
function removeNonDeterministicProps(element) {
|
|
101
|
+
if (element.RenderSize) {
|
|
102
|
+
// RenderSize is subject to rounding, etc and should mostly be derived from
|
|
103
|
+
// other deterministic properties in the tree.
|
|
104
|
+
delete element.RenderSize;
|
|
105
|
+
}
|
|
106
|
+
if (element.children) {
|
|
107
|
+
element.children.forEach(removeNonDeterministicProps);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Removes noise from snapshot by removing properties with the default value
|
|
112
|
+
*/
|
|
113
|
+
function removeDefaultProps(element) {
|
|
114
|
+
const defaultValues = [['Tooltip', null]];
|
|
115
|
+
defaultValues.forEach(([propname, defaultValue]) => {
|
|
116
|
+
if (element[propname] === defaultValue) {
|
|
117
|
+
delete element[propname];
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
if (element.children) {
|
|
121
|
+
element.children.forEach(removeDefaultProps);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=dumpVisualTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dumpVisualTree.js","sourceRoot":"","sources":["../src/dumpVisualTree.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * @format\n */\n\nimport {AutomationClient} from '@react-native-windows/automation-channel';\n\n/**\n * Schema of tree dumped node\n */\nexport type UIElement = {\n XamlType: string;\n Foreground?: string | null;\n Background?: string | null;\n Padding?: string | null;\n Margin?: string | null;\n RenderSize?: number[] | null;\n Visibility?: 'Collapsed' | 'Visible' | null;\n CornerRadius?: string | null;\n BorderThickness?: string | null;\n Width?: number | null;\n Height?: number | null;\n BorderBrush?: string | null;\n VerticalAlignment?: string | null;\n HorizontalAlignment?: string | null;\n Clip?: string | null;\n FlowDirection?: string | null;\n Name?: string | null;\n Text?: string | null;\n children?: UIElement[];\n [index: string]: unknown;\n};\n\nexport type AutomationNode = {\n AutomationId?: string;\n ControlType?: number;\n LocalizedControlType?: string;\n __Children?: [AutomationNode];\n};\n\nexport type ComponentNode = {\n Type: string;\n _Props?: {\n TestId?: string;\n Sources?: [{Uri?: string}];\n };\n __Children?: [ComponentNode];\n};\n\nexport type VisualNode = {\n Comment?: string;\n Offset?: `${number} ${number} ${number}`;\n Size?: `${number} ${number}`;\n 'Visual Type'?: string;\n __Children?: [VisualNode];\n};\n\nexport type VisualTree = {\n 'Automation Tree': AutomationNode;\n 'Component Tree': ComponentNode;\n 'Visual Tree': VisualNode;\n};\n\ndeclare global {\n const automationClient: AutomationClient | undefined;\n}\n\n/**\n * Dump a section of the native visual tree.\n */\nexport default async function dumpVisualTree(\n accessibilityId: string,\n opts?: {\n pruneCollapsed?: boolean;\n deterministicOnly?: boolean;\n removeDefaultProps?: boolean;\n removeGuidsFromImageSources?: boolean;\n additionalProperties?: string[];\n },\n): Promise<UIElement | VisualTree> {\n if (!automationClient) {\n throw new Error('RPC client is not enabled');\n }\n\n const dumpResponse = await automationClient.invoke('DumpVisualTree', {\n accessibilityId,\n ...opts,\n });\n\n if (dumpResponse.type === 'error') {\n throw new Error(dumpResponse.message);\n }\n\n const element: UIElement | VisualTree = dumpResponse.result;\n\n if ('XamlType' in element && opts?.pruneCollapsed !== false) {\n pruneCollapsedElements(element);\n }\n\n if ('XamlType' in element && opts?.deterministicOnly !== false) {\n removeNonDeterministicProps(element);\n }\n\n if ('XamlType' in element && opts?.removeDefaultProps !== false) {\n removeDefaultProps(element);\n }\n\n if (!('XamlType' in element) && opts?.removeGuidsFromImageSources !== false) {\n removeGuidsFromImageSources(element);\n }\n\n return element;\n}\n\nfunction removeGuidsFromImageSourcesHelper(node: ComponentNode) {\n if (node._Props && node._Props.Sources) {\n node._Props.Sources.forEach((source: any) => {\n if (source.Uri) {\n if (source.Uri.startsWith('blob:')) {\n source.Uri = source.Uri.replace(\n /blob:[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+/,\n 'blob:<some_guid_here>',\n );\n source.Uri = source.Uri.replace(/size=\\d{5}/, 'size=<size>');\n } else if (\n source.Uri.startsWith(\n 'https://www.facebook.com/assets/fb_lite_messaging/E2EE-settings@3x.png?r=1&t=',\n )\n ) {\n source.Uri =\n 'https://www.facebook.com/assets/fb_lite_messaging/E2EE-settings@3x.png?r=1&t=<some_hash_here>';\n } else if (\n source.Uri.startsWith(\n 'https://www.facebook.com/ar_effect/external_textures/648609739826677.png?hash=',\n )\n ) {\n source.Uri =\n 'https://www.facebook.com/ar_effect/external_textures/648609739826677.png?hash=<some_hash_here>';\n } else if (\n source.Uri.startsWith(\n 'https://www.facebook.com/ads/pics/successstories.png?hash=',\n )\n ) {\n source.Uri =\n 'https://www.facebook.com/ads/pics/successstories.png?hash=<some_hash_here>';\n } else {\n // When getting files from a prebuilt bundle the uri is going to include a local path, which would make snapshots inconsistent,\n // This logic replaces the local path so that we get consistent results.\n // file://E:\\\\repos\\\\react-native-windows\\\\packages\\\\e2e-test-app-fabric\\\\windows\\\\RNTesterApp-Fabric.Package\\\\bin\\\\x64\\\\Release\\\\AppX\\\\RNTesterApp-Fabric\\\\Bundle\\\\@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png\n // becomes\n // <localOrBundlerUri>@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png\n const packagesPath = require('path')\n .resolve(__dirname, '../../..')\n .replace(/\\\\/g, '\\\\\\\\');\n source.Uri = source.Uri.replace(\n new RegExp(`file://${packagesPath}.*\\\\\\\\Bundle\\\\\\\\assets/_+`),\n '<localOrBundlerUri>',\n );\n\n // When loading the bundle from metro local paths will be replaced with paths to localhost, which will not align with snapshots made with prebuilt bundles.\n // This logic replaces the localhost uri, with the same uri that we would have gotten from a prebuild bundle. This makes it easier to debug without breaking snapshots\n // http://localhost:8081/assets/@@/@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png?platform=windows&hash=c6f5aec4d9e0aa47c0887e4266796224\n // becomes\n // \"<localOrBundlerUri>@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png\"\n source.Uri = source.Uri.replace(\n /http:\\/\\/localhost:8081\\/assets\\/(@@\\/)+/,\n '<localOrBundlerUri>',\n );\n source.Uri = source.Uri.replace(/\\?platform=windows&hash=[^=]$/, '');\n }\n }\n });\n }\n if (node.__Children) {\n node.__Children.forEach((child: any) =>\n removeGuidsFromImageSourcesHelper(child),\n );\n }\n}\n\nfunction removeGuidsFromImageSources(visualTree: VisualTree) {\n removeGuidsFromImageSourcesHelper(visualTree['Component Tree']);\n}\n\n/**\n * Removes trees of XAML that are not visible.\n */\nfunction pruneCollapsedElements(element: UIElement) {\n if (!element.children) {\n return;\n }\n\n element.children = element.children.filter(\n child => child.Visibility !== 'Collapsed',\n );\n\n element.children.forEach(pruneCollapsedElements);\n}\n\n/**\n * Removes trees of properties that are not deterministic\n */\nfunction removeNonDeterministicProps(element: UIElement) {\n if (element.RenderSize) {\n // RenderSize is subject to rounding, etc and should mostly be derived from\n // other deterministic properties in the tree.\n delete element.RenderSize;\n }\n\n if (element.children) {\n element.children.forEach(removeNonDeterministicProps);\n }\n}\n\n/**\n * Removes noise from snapshot by removing properties with the default value\n */\nfunction removeDefaultProps(element: UIElement) {\n const defaultValues: [string, unknown][] = [['Tooltip', null]];\n\n defaultValues.forEach(([propname, defaultValue]) => {\n if (element[propname] === defaultValue) {\n delete element[propname];\n }\n });\n\n if (element.children) {\n element.children.forEach(removeDefaultProps);\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*
|
|
6
|
+
* @format
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.dumpVisualTree = void 0;
|
|
13
|
+
const dumpVisualTree_1 = __importDefault(require("./dumpVisualTree"));
|
|
14
|
+
exports.dumpVisualTree = dumpVisualTree_1.default;
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;AAEH,sEAA8C;AAEtC,yBAFD,wBAAc,CAEC","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * @format\n */\n\nimport dumpVisualTree from './dumpVisualTree';\n\nexport {dumpVisualTree};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@react-native-windows/automation-commands",
|
|
3
|
+
"version": "0.0.0-canary.1016",
|
|
4
|
+
"description": "Allows controlling your react-native-windows application",
|
|
5
|
+
"main": "lib-commonjs/index.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/microsoft/react-native-windows",
|
|
10
|
+
"directory": "packages/@react-native-windows/automation-commands"
|
|
11
|
+
},
|
|
12
|
+
"private": false,
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "rnw-scripts build",
|
|
15
|
+
"clean": "rnw-scripts clean",
|
|
16
|
+
"lint": "rnw-scripts lint",
|
|
17
|
+
"lint:fix": "rnw-scripts lint:fix",
|
|
18
|
+
"watch": "rnw-scripts watch"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@react-native-windows/automation-channel": "0.0.0-canary.1016",
|
|
22
|
+
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
|
23
|
+
"@typescript-eslint/parser": "^7.1.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@jest/types": "^29.2.1",
|
|
27
|
+
"@rnw-scripts/eslint-config": "1.2.38",
|
|
28
|
+
"@rnw-scripts/just-task": "2.3.58",
|
|
29
|
+
"@rnw-scripts/ts-config": "2.0.6",
|
|
30
|
+
"@types/jest": "^29.2.2",
|
|
31
|
+
"@types/node": "^22.14.0",
|
|
32
|
+
"eslint": "^8.19.0",
|
|
33
|
+
"prettier": "2.8.8",
|
|
34
|
+
"typescript": "5.0.4"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"lib-commonjs",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"beachball": {
|
|
41
|
+
"defaultNpmTag": "canary",
|
|
42
|
+
"disallowedChangeTypes": [
|
|
43
|
+
"major",
|
|
44
|
+
"minor",
|
|
45
|
+
"patch",
|
|
46
|
+
"premajor",
|
|
47
|
+
"preminor",
|
|
48
|
+
"prepatch"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"promoteRelease": true,
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">= 22"
|
|
54
|
+
}
|
|
55
|
+
}
|