@yume-chan/adb-daemon-webusb 0.0.23 → 1.0.0
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/CHANGELOG.md +43 -2
- package/LICENSE +1 -1
- package/README.md +6 -3
- package/esm/device.d.ts +11 -14
- package/esm/device.d.ts.map +1 -1
- package/esm/device.js +46 -56
- package/esm/device.js.map +1 -1
- package/esm/index.d.ts +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -1
- package/esm/index.js.map +1 -1
- package/esm/manager.d.ts +6 -25
- package/esm/manager.d.ts.map +1 -1
- package/esm/manager.js +30 -60
- package/esm/manager.js.map +1 -1
- package/esm/observer.d.ts +17 -0
- package/esm/observer.d.ts.map +1 -0
- package/esm/observer.js +54 -0
- package/esm/observer.js.map +1 -0
- package/esm/utils.d.ts +22 -5
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +76 -5
- package/esm/utils.js.map +1 -1
- package/package.json +13 -11
- package/src/device.ts +56 -71
- package/src/index.ts +1 -1
- package/src/manager.ts +49 -75
- package/src/observer.ts +90 -0
- package/src/utils.ts +142 -18
- package/tsconfig.build.json +2 -13
- package/tsconfig.build.tsbuildinfo +1 -1
- package/CHANGELOG.json +0 -193
- package/esm/watcher.d.ts +0 -7
- package/esm/watcher.d.ts.map +0 -1
- package/esm/watcher.js +0 -21
- package/esm/watcher.js.map +0 -1
- package/src/watcher.ts +0 -28
package/src/manager.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { ADB_DEFAULT_DEVICE_FILTER, AdbDaemonWebUsbDevice } from "./device.js";
|
|
2
|
-
import type { AdbDeviceFilter } from "./utils.js";
|
|
3
1
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "./
|
|
2
|
+
AdbDaemonWebUsbDevice,
|
|
3
|
+
mergeDefaultAdbInterfaceFilter,
|
|
4
|
+
} from "./device.js";
|
|
5
|
+
import { AdbDaemonWebUsbDeviceObserver } from "./observer.js";
|
|
6
|
+
import { isErrorName, matchFilters } from "./utils.js";
|
|
8
7
|
|
|
9
8
|
export namespace AdbDaemonWebUsbDeviceManager {
|
|
10
9
|
export interface RequestDeviceOptions {
|
|
11
|
-
filters?:
|
|
10
|
+
filters?: USBDeviceFilter[] | undefined;
|
|
12
11
|
exclusionFilters?: USBDeviceFilter[] | undefined;
|
|
13
12
|
}
|
|
14
13
|
}
|
|
@@ -36,34 +35,33 @@ export class AdbDaemonWebUsbDeviceManager {
|
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
|
-
*
|
|
40
|
-
* This is a convince method for `usb.requestDevice()`.
|
|
41
|
-
* @param filters
|
|
42
|
-
* The filters to apply to the device list.
|
|
43
|
-
*
|
|
44
|
-
* It must have `classCode`, `subclassCode` and `protocolCode` fields for selecting the ADB interface,
|
|
45
|
-
* but might also have `vendorId`, `productId` or `serialNumber` fields to limit the displayed device list.
|
|
46
|
-
*
|
|
47
|
-
* Defaults to {@link ADB_DEFAULT_DEVICE_FILTER}.
|
|
48
|
-
* @returns An {@link AdbDaemonWebUsbDevice} instance if the user selected a device,
|
|
49
|
-
* or `undefined` if the user cancelled the device picker.
|
|
38
|
+
* Call `USB#requestDevice()` to prompt the user to select a device.
|
|
50
39
|
*/
|
|
51
40
|
async requestDevice(
|
|
52
41
|
options: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions = {},
|
|
53
42
|
): Promise<AdbDaemonWebUsbDevice | undefined> {
|
|
54
|
-
|
|
55
|
-
options.filters = [ADB_DEFAULT_DEVICE_FILTER];
|
|
56
|
-
} else if (options.filters.length === 0) {
|
|
57
|
-
throw new TypeError("filters must not be empty");
|
|
58
|
-
}
|
|
43
|
+
const filters = mergeDefaultAdbInterfaceFilter(options.filters);
|
|
59
44
|
|
|
60
45
|
try {
|
|
61
|
-
const device = await this.#usbManager.requestDevice(
|
|
62
|
-
|
|
46
|
+
const device = await this.#usbManager.requestDevice({
|
|
47
|
+
filters,
|
|
48
|
+
exclusionFilters: options.exclusionFilters,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const interface_ = matchFilters(
|
|
52
|
+
device,
|
|
53
|
+
filters,
|
|
54
|
+
options.exclusionFilters,
|
|
63
55
|
);
|
|
56
|
+
if (!interface_) {
|
|
57
|
+
// `#usbManager` doesn't support `exclusionFilters`,
|
|
58
|
+
// selected device is invalid
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
64
62
|
return new AdbDaemonWebUsbDevice(
|
|
65
63
|
device,
|
|
66
|
-
|
|
64
|
+
interface_,
|
|
67
65
|
this.#usbManager,
|
|
68
66
|
);
|
|
69
67
|
} catch (e) {
|
|
@@ -77,63 +75,39 @@ export class AdbDaemonWebUsbDeviceManager {
|
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
/**
|
|
80
|
-
* Get all connected and
|
|
81
|
-
* This is a convince method for `usb.getDevices()`.
|
|
82
|
-
* @param filters
|
|
83
|
-
* The filters to apply to the device list.
|
|
84
|
-
*
|
|
85
|
-
* It must have `classCode`, `subclassCode` and `protocolCode` fields for selecting the ADB interface,
|
|
86
|
-
* but might also have `vendorId`, `productId` or `serialNumber` fields to limit the device list.
|
|
87
|
-
*
|
|
88
|
-
* Defaults to {@link ADB_DEFAULT_DEVICE_FILTER}.
|
|
89
|
-
* @returns An array of {@link AdbDaemonWebUsbDevice} instances for all connected and authenticated devices.
|
|
78
|
+
* Get all connected and requested devices that match the specified filters.
|
|
90
79
|
*/
|
|
91
80
|
async getDevices(
|
|
92
|
-
|
|
81
|
+
options: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions = {},
|
|
93
82
|
): Promise<AdbDaemonWebUsbDevice[]> {
|
|
94
|
-
|
|
95
|
-
throw new TypeError("filters must not be empty");
|
|
96
|
-
}
|
|
83
|
+
const filters = mergeDefaultAdbInterfaceFilter(options.filters);
|
|
97
84
|
|
|
98
85
|
const devices = await this.#usbManager.getDevices();
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
"productId" in filter &&
|
|
110
|
-
device.productId !== filter.productId
|
|
111
|
-
) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
if (
|
|
115
|
-
"serialNumber" in filter &&
|
|
116
|
-
getSerialNumber(device) !== filter.serialNumber
|
|
117
|
-
) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
findUsbAlternateInterface(device, filters);
|
|
123
|
-
return true;
|
|
124
|
-
} catch {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return false;
|
|
129
|
-
})
|
|
130
|
-
.map(
|
|
131
|
-
(device) =>
|
|
86
|
+
// filter map
|
|
87
|
+
const result: AdbDaemonWebUsbDevice[] = [];
|
|
88
|
+
for (const device of devices) {
|
|
89
|
+
const interface_ = matchFilters(
|
|
90
|
+
device,
|
|
91
|
+
filters,
|
|
92
|
+
options.exclusionFilters,
|
|
93
|
+
);
|
|
94
|
+
if (interface_) {
|
|
95
|
+
result.push(
|
|
132
96
|
new AdbDaemonWebUsbDevice(
|
|
133
97
|
device,
|
|
134
|
-
|
|
98
|
+
interface_,
|
|
135
99
|
this.#usbManager,
|
|
136
100
|
),
|
|
137
|
-
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
trackDevices(
|
|
109
|
+
options: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions = {},
|
|
110
|
+
): AdbDaemonWebUsbDeviceObserver {
|
|
111
|
+
return new AdbDaemonWebUsbDeviceObserver(this.#usbManager, options);
|
|
138
112
|
}
|
|
139
113
|
}
|
package/src/observer.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { DeviceObserver } from "@yume-chan/adb";
|
|
2
|
+
import { EventEmitter } from "@yume-chan/event";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AdbDaemonWebUsbDevice,
|
|
6
|
+
mergeDefaultAdbInterfaceFilter,
|
|
7
|
+
} from "./device.js";
|
|
8
|
+
import type { AdbDaemonWebUsbDeviceManager } from "./manager.js";
|
|
9
|
+
import type { UsbInterfaceFilter } from "./utils.js";
|
|
10
|
+
import { matchFilters } from "./utils.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A watcher that listens for new WebUSB devices and notifies the callback when
|
|
14
|
+
* a new device is connected or disconnected.
|
|
15
|
+
*/
|
|
16
|
+
export class AdbDaemonWebUsbDeviceObserver
|
|
17
|
+
implements DeviceObserver<AdbDaemonWebUsbDevice>
|
|
18
|
+
{
|
|
19
|
+
#filters: (USBDeviceFilter & UsbInterfaceFilter)[];
|
|
20
|
+
#exclusionFilters?: USBDeviceFilter[] | undefined;
|
|
21
|
+
#usbManager: USB;
|
|
22
|
+
|
|
23
|
+
#onDeviceAdd = new EventEmitter<AdbDaemonWebUsbDevice[]>();
|
|
24
|
+
onDeviceAdd = this.#onDeviceAdd.event;
|
|
25
|
+
|
|
26
|
+
#onDeviceRemove = new EventEmitter<AdbDaemonWebUsbDevice[]>();
|
|
27
|
+
onDeviceRemove = this.#onDeviceRemove.event;
|
|
28
|
+
|
|
29
|
+
#onListChange = new EventEmitter<AdbDaemonWebUsbDevice[]>();
|
|
30
|
+
onListChange = this.#onListChange.event;
|
|
31
|
+
|
|
32
|
+
current: AdbDaemonWebUsbDevice[] = [];
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
usb: USB,
|
|
36
|
+
options: AdbDaemonWebUsbDeviceManager.RequestDeviceOptions = {},
|
|
37
|
+
) {
|
|
38
|
+
this.#filters = mergeDefaultAdbInterfaceFilter(options.filters);
|
|
39
|
+
this.#exclusionFilters = options.exclusionFilters;
|
|
40
|
+
this.#usbManager = usb;
|
|
41
|
+
|
|
42
|
+
this.#usbManager.addEventListener("connect", this.#handleConnect);
|
|
43
|
+
this.#usbManager.addEventListener("disconnect", this.#handleDisconnect);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#handleConnect = (e: USBConnectionEvent) => {
|
|
47
|
+
const interface_ = matchFilters(
|
|
48
|
+
e.device,
|
|
49
|
+
this.#filters,
|
|
50
|
+
this.#exclusionFilters,
|
|
51
|
+
);
|
|
52
|
+
if (!interface_) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const device = new AdbDaemonWebUsbDevice(
|
|
57
|
+
e.device,
|
|
58
|
+
interface_,
|
|
59
|
+
this.#usbManager,
|
|
60
|
+
);
|
|
61
|
+
this.#onDeviceAdd.fire([device]);
|
|
62
|
+
this.current.push(device);
|
|
63
|
+
this.#onListChange.fire(this.current);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
#handleDisconnect = (e: USBConnectionEvent) => {
|
|
67
|
+
const index = this.current.findIndex(
|
|
68
|
+
(device) => device.raw === e.device,
|
|
69
|
+
);
|
|
70
|
+
if (index !== -1) {
|
|
71
|
+
const device = this.current[index]!;
|
|
72
|
+
this.#onDeviceRemove.fire([device]);
|
|
73
|
+
this.current[index] = this.current[this.current.length - 1]!;
|
|
74
|
+
this.current.length -= 1;
|
|
75
|
+
this.#onListChange.fire(this.current);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
stop(): void {
|
|
80
|
+
this.#usbManager.removeEventListener("connect", this.#handleConnect);
|
|
81
|
+
this.#usbManager.removeEventListener(
|
|
82
|
+
"disconnect",
|
|
83
|
+
this.#handleDisconnect,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
this.#onDeviceAdd.dispose();
|
|
87
|
+
this.#onDeviceRemove.dispose();
|
|
88
|
+
this.#onListChange.dispose();
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function isErrorName(e: unknown, name: string):
|
|
1
|
+
export function isErrorName(e: unknown, name: string): e is Error {
|
|
2
2
|
// node-usb package doesn't use `DOMException`,
|
|
3
3
|
// so use a looser check
|
|
4
4
|
// https://github.com/node-usb/node-usb/issues/573
|
|
@@ -7,42 +7,60 @@ export function isErrorName(e: unknown, name: string): boolean {
|
|
|
7
7
|
);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export type PickNonNullable<T, K extends keyof T> = {
|
|
11
|
+
[P in K]-?: NonNullable<T[P]>;
|
|
12
|
+
};
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* `classCode`, `subclassCode` and `protocolCode` are required
|
|
12
16
|
* for selecting correct USB configuration and interface.
|
|
13
17
|
*/
|
|
14
|
-
export type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
export type UsbInterfaceFilter = PickNonNullable<
|
|
19
|
+
USBDeviceFilter,
|
|
20
|
+
"classCode" | "subclassCode" | "protocolCode"
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export function isUsbInterfaceFilter(
|
|
24
|
+
filter: USBDeviceFilter,
|
|
25
|
+
): filter is UsbInterfaceFilter {
|
|
26
|
+
return (
|
|
27
|
+
filter.classCode !== undefined &&
|
|
28
|
+
filter.subclassCode !== undefined &&
|
|
29
|
+
filter.protocolCode !== undefined
|
|
30
|
+
);
|
|
31
|
+
}
|
|
18
32
|
|
|
19
|
-
function
|
|
33
|
+
function matchUsbInterfaceFilter(
|
|
20
34
|
alternate: USBAlternateInterface,
|
|
21
|
-
|
|
35
|
+
filter: UsbInterfaceFilter,
|
|
22
36
|
) {
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
alternate.interfaceProtocol === filter.protocolCode,
|
|
37
|
+
return (
|
|
38
|
+
alternate.interfaceClass === filter.classCode &&
|
|
39
|
+
alternate.interfaceSubclass === filter.subclassCode &&
|
|
40
|
+
alternate.interfaceProtocol === filter.protocolCode
|
|
28
41
|
);
|
|
29
42
|
}
|
|
30
43
|
|
|
31
|
-
export
|
|
44
|
+
export interface UsbInterfaceIdentifier {
|
|
45
|
+
configuration: USBConfiguration;
|
|
46
|
+
interface_: USBInterface;
|
|
47
|
+
alternate: USBAlternateInterface;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function findUsbInterface(
|
|
32
51
|
device: USBDevice,
|
|
33
|
-
|
|
34
|
-
) {
|
|
52
|
+
filter: UsbInterfaceFilter,
|
|
53
|
+
): UsbInterfaceIdentifier | undefined {
|
|
35
54
|
for (const configuration of device.configurations) {
|
|
36
55
|
for (const interface_ of configuration.interfaces) {
|
|
37
56
|
for (const alternate of interface_.alternates) {
|
|
38
|
-
if (
|
|
57
|
+
if (matchUsbInterfaceFilter(alternate, filter)) {
|
|
39
58
|
return { configuration, interface_, alternate };
|
|
40
59
|
}
|
|
41
60
|
}
|
|
42
61
|
}
|
|
43
62
|
}
|
|
44
|
-
|
|
45
|
-
throw new Error("No matched alternate interface found");
|
|
63
|
+
return undefined;
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
function padNumber(value: number) {
|
|
@@ -56,3 +74,109 @@ export function getSerialNumber(device: USBDevice) {
|
|
|
56
74
|
|
|
57
75
|
return padNumber(device.vendorId) + "x" + padNumber(device.productId);
|
|
58
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find the first pair of input and output endpoints from an alternate interface.
|
|
80
|
+
*
|
|
81
|
+
* ADB interface only has two endpoints, one for input and one for output.
|
|
82
|
+
*/
|
|
83
|
+
export function findUsbEndpoints(endpoints: USBEndpoint[]) {
|
|
84
|
+
if (endpoints.length === 0) {
|
|
85
|
+
throw new TypeError("No endpoints given");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let inEndpoint: USBEndpoint | undefined;
|
|
89
|
+
let outEndpoint: USBEndpoint | undefined;
|
|
90
|
+
|
|
91
|
+
for (const endpoint of endpoints) {
|
|
92
|
+
switch (endpoint.direction) {
|
|
93
|
+
case "in":
|
|
94
|
+
inEndpoint = endpoint;
|
|
95
|
+
if (outEndpoint) {
|
|
96
|
+
return { inEndpoint, outEndpoint };
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
case "out":
|
|
100
|
+
outEndpoint = endpoint;
|
|
101
|
+
if (inEndpoint) {
|
|
102
|
+
return { inEndpoint, outEndpoint };
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!inEndpoint) {
|
|
109
|
+
throw new TypeError("No input endpoint found.");
|
|
110
|
+
}
|
|
111
|
+
if (!outEndpoint) {
|
|
112
|
+
throw new TypeError("No output endpoint found.");
|
|
113
|
+
}
|
|
114
|
+
throw new Error("unreachable");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function matchFilter(
|
|
118
|
+
device: USBDevice,
|
|
119
|
+
filter: USBDeviceFilter & UsbInterfaceFilter,
|
|
120
|
+
): UsbInterfaceIdentifier | false;
|
|
121
|
+
export function matchFilter(
|
|
122
|
+
device: USBDevice,
|
|
123
|
+
filter: USBDeviceFilter,
|
|
124
|
+
): boolean;
|
|
125
|
+
export function matchFilter(
|
|
126
|
+
device: USBDevice,
|
|
127
|
+
filter: USBDeviceFilter,
|
|
128
|
+
): UsbInterfaceIdentifier | boolean {
|
|
129
|
+
if (filter.vendorId !== undefined && device.vendorId !== filter.vendorId) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
filter.productId !== undefined &&
|
|
135
|
+
device.productId !== filter.productId
|
|
136
|
+
) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
filter.serialNumber !== undefined &&
|
|
142
|
+
getSerialNumber(device) !== filter.serialNumber
|
|
143
|
+
) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (isUsbInterfaceFilter(filter)) {
|
|
148
|
+
return findUsbInterface(device, filter) || false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function matchFilters(
|
|
155
|
+
device: USBDevice,
|
|
156
|
+
filters: (USBDeviceFilter & UsbInterfaceFilter)[],
|
|
157
|
+
exclusionFilters?: USBDeviceFilter[],
|
|
158
|
+
): UsbInterfaceIdentifier | false;
|
|
159
|
+
export function matchFilters(
|
|
160
|
+
device: USBDevice,
|
|
161
|
+
filters: USBDeviceFilter[],
|
|
162
|
+
exclusionFilters?: USBDeviceFilter[],
|
|
163
|
+
): boolean;
|
|
164
|
+
export function matchFilters(
|
|
165
|
+
device: USBDevice,
|
|
166
|
+
filters: USBDeviceFilter[],
|
|
167
|
+
exclusionFilters?: USBDeviceFilter[],
|
|
168
|
+
): UsbInterfaceIdentifier | boolean {
|
|
169
|
+
if (exclusionFilters && exclusionFilters.length > 0) {
|
|
170
|
+
if (matchFilters(device, exclusionFilters)) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const filter of filters) {
|
|
176
|
+
const result = matchFilter(device, filter);
|
|
177
|
+
if (result) {
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
package/tsconfig.build.json
CHANGED
|
@@ -6,18 +6,7 @@
|
|
|
6
6
|
"DOM"
|
|
7
7
|
],
|
|
8
8
|
"types": [
|
|
9
|
-
"w3c-web-usb"
|
|
9
|
+
"w3c-web-usb"
|
|
10
10
|
]
|
|
11
|
-
}
|
|
12
|
-
"references": [
|
|
13
|
-
{
|
|
14
|
-
"path": "../adb/tsconfig.build.json"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"path": "../stream-extra/tsconfig.build.json"
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"path": "../struct/tsconfig.build.json"
|
|
21
|
-
},
|
|
22
|
-
]
|
|
11
|
+
}
|
|
23
12
|
}
|