appium-mcp 1.86.0 → 1.86.2
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 +12 -0
- package/dist/documentation.d.ts +5 -5
- package/dist/documentation.d.ts.map +1 -1
- package/dist/documentation.js +86 -17
- package/dist/documentation.js.map +1 -1
- package/dist/tools/gestures/schema.d.ts +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/documentation.ts +99 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [1.86.2](https://github.com/appium/appium-mcp/compare/v1.86.1...v1.86.2) (2026-06-20)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* **docs:** support global resolution of doc package ([#417](https://github.com/appium/appium-mcp/issues/417)) ([dfeea2f](https://github.com/appium/appium-mcp/commit/dfeea2ffe16389d57b1cec0c95ddfed30c3c6dfa))
|
|
6
|
+
|
|
7
|
+
## [1.86.1](https://github.com/appium/appium-mcp/compare/v1.86.0...v1.86.1) (2026-06-19)
|
|
8
|
+
|
|
9
|
+
### Miscellaneous Chores
|
|
10
|
+
|
|
11
|
+
* **deps-dev:** bump @types/node from 25.9.4 to 26.0.0 ([#416](https://github.com/appium/appium-mcp/issues/416)) ([00bcac5](https://github.com/appium/appium-mcp/commit/00bcac51f50025f1bc4fa71d80c6915eb11cdf13))
|
|
12
|
+
|
|
1
13
|
## [1.86.0](https://github.com/appium/appium-mcp/compare/v1.85.10...v1.86.0) (2026-06-19)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/dist/documentation.d.ts
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* - `APPIUM_MCP_DOCS_ENABLED` unset / not truthy → docs tools are NOT registered.
|
|
12
12
|
* This is the default; nothing extra is loaded.
|
|
13
13
|
* - `APPIUM_MCP_DOCS_ENABLED` truthy → the plugin is loaded if
|
|
14
|
-
* `@appium/mcp-documentation` is installed
|
|
15
|
-
*
|
|
14
|
+
* `@appium/mcp-documentation` is installed (locally next to appium-mcp OR in
|
|
15
|
+
* the global npm root). If it cannot be found, the server logs an actionable
|
|
16
|
+
* install hint and starts normally without the docs tools.
|
|
16
17
|
*
|
|
17
18
|
* Installation is intentionally left to the user's package manager (run at install
|
|
18
19
|
* time, where it belongs) rather than shelled out from the running server: that
|
|
@@ -25,9 +26,8 @@ export declare function isDocumentationEnabled(): boolean;
|
|
|
25
26
|
/**
|
|
26
27
|
* Load the documentation plugin when the user has opted in.
|
|
27
28
|
*
|
|
28
|
-
* @returns the plugin instance, or `null` if the optional package
|
|
29
|
-
*
|
|
30
|
-
* documentation tools).
|
|
29
|
+
* @returns the plugin instance, or `null` if the optional package cannot be
|
|
30
|
+
* found or fails to load (in which case the server runs without the docs tools).
|
|
31
31
|
*/
|
|
32
32
|
export declare function loadDocumentationPlugin(): Promise<AppiumMcpPlugin | null>;
|
|
33
33
|
//# sourceMappingURL=documentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentation.d.ts","sourceRoot":"","sources":["../src/documentation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"documentation.d.ts","sourceRoot":"","sources":["../src/documentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAWjD,iEAAiE;AACjE,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA0B/E"}
|
package/dist/documentation.js
CHANGED
|
@@ -11,14 +11,19 @@
|
|
|
11
11
|
* - `APPIUM_MCP_DOCS_ENABLED` unset / not truthy → docs tools are NOT registered.
|
|
12
12
|
* This is the default; nothing extra is loaded.
|
|
13
13
|
* - `APPIUM_MCP_DOCS_ENABLED` truthy → the plugin is loaded if
|
|
14
|
-
* `@appium/mcp-documentation` is installed
|
|
15
|
-
*
|
|
14
|
+
* `@appium/mcp-documentation` is installed (locally next to appium-mcp OR in
|
|
15
|
+
* the global npm root). If it cannot be found, the server logs an actionable
|
|
16
|
+
* install hint and starts normally without the docs tools.
|
|
16
17
|
*
|
|
17
18
|
* Installation is intentionally left to the user's package manager (run at install
|
|
18
19
|
* time, where it belongs) rather than shelled out from the running server: that
|
|
19
20
|
* dedupes against appium-mcp's existing dependencies, works across npm/pnpm/yarn,
|
|
20
21
|
* and never blocks server startup.
|
|
21
22
|
*/
|
|
23
|
+
import { readFileSync } from 'node:fs';
|
|
24
|
+
import { createRequire } from 'node:module';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { pathToFileURL } from 'node:url';
|
|
22
27
|
import log from './logger.js';
|
|
23
28
|
import { isTruthyEnvValue } from './utils/env.js';
|
|
24
29
|
const ENABLED_FLAG = 'APPIUM_MCP_DOCS_ENABLED';
|
|
@@ -30,31 +35,95 @@ export function isDocumentationEnabled() {
|
|
|
30
35
|
/**
|
|
31
36
|
* Load the documentation plugin when the user has opted in.
|
|
32
37
|
*
|
|
33
|
-
* @returns the plugin instance, or `null` if the optional package
|
|
34
|
-
*
|
|
35
|
-
* documentation tools).
|
|
38
|
+
* @returns the plugin instance, or `null` if the optional package cannot be
|
|
39
|
+
* found or fails to load (in which case the server runs without the docs tools).
|
|
36
40
|
*/
|
|
37
41
|
export async function loadDocumentationPlugin() {
|
|
42
|
+
let mod;
|
|
43
|
+
try {
|
|
44
|
+
mod = await importDocumentationModule();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
// Found, but threw while evaluating (broken/partial install, bad version).
|
|
48
|
+
log.error(`${PACKAGE_NAME} is installed but failed to load:`, err);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (!mod) {
|
|
52
|
+
log.warn(`${ENABLED_FLAG} is set but ${PACKAGE_NAME} could not be found. ` +
|
|
53
|
+
'The documentation tools (appium_documentation_query, appium_skills) ' +
|
|
54
|
+
'will be unavailable. Install it where appium-mcp can resolve it:\n' +
|
|
55
|
+
' - globally (recommended; works with npx / global / standalone use):\n' +
|
|
56
|
+
` npm install -g ${PACKAGE_NAME}\n` +
|
|
57
|
+
' - or, only if appium-mcp is a dependency of your project, in that project root:\n' +
|
|
58
|
+
` npm install ${PACKAGE_NAME}`);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const plugin = new mod.AppiumDocumentation();
|
|
62
|
+
log.info(`Documentation tools enabled (${PACKAGE_NAME} loaded).`);
|
|
63
|
+
return plugin;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Import the documentation module.
|
|
67
|
+
*
|
|
68
|
+
* @returns the module, or `null` when the package is not installed anywhere
|
|
69
|
+
* resolvable. Throws only when the package WAS found but failed to evaluate.
|
|
70
|
+
*
|
|
71
|
+
* Resolution order:
|
|
72
|
+
* 1. Standard ESM resolution from appium-mcp's location (local / co-located /
|
|
73
|
+
* hoisted installs, including pnpm/yarn).
|
|
74
|
+
* 2. The global npm root — so `npm install -g @appium/mcp-documentation` works
|
|
75
|
+
* even when appium-mcp itself runs from a different location (a local
|
|
76
|
+
* checkout, a global bin, or `npx`), where the global modules are not on
|
|
77
|
+
* the default resolution path.
|
|
78
|
+
*/
|
|
79
|
+
async function importDocumentationModule() {
|
|
38
80
|
try {
|
|
39
81
|
// Widen to `string` so TypeScript treats this as a fully dynamic import and
|
|
40
82
|
// does not require @appium/mcp-documentation to be resolvable at build time
|
|
41
83
|
// (it is an optional, opt-in dependency, not installed by default).
|
|
42
84
|
const specifier = PACKAGE_NAME;
|
|
43
|
-
|
|
44
|
-
const plugin = new mod.AppiumDocumentation();
|
|
45
|
-
log.info(`Documentation tools enabled (${PACKAGE_NAME} loaded).`);
|
|
46
|
-
return plugin;
|
|
85
|
+
return (await import(specifier));
|
|
47
86
|
}
|
|
48
87
|
catch (err) {
|
|
49
|
-
if (isModuleNotFound(err)) {
|
|
50
|
-
|
|
51
|
-
'The documentation tools (appium_documentation_query, appium_skills) ' +
|
|
52
|
-
'will be unavailable. Install the package to enable them:\n' +
|
|
53
|
-
` npm install ${PACKAGE_NAME}`);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
log.error(`${PACKAGE_NAME} is installed but failed to load:`, err);
|
|
88
|
+
if (!isModuleNotFound(err)) {
|
|
89
|
+
throw err;
|
|
57
90
|
}
|
|
91
|
+
}
|
|
92
|
+
const globalEntry = resolveGlobalEntryUrl();
|
|
93
|
+
if (!globalEntry) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return (await import(globalEntry));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Locate the package in the global npm root and return a file URL to its entry
|
|
100
|
+
* point, or `null` if it is not installed globally.
|
|
101
|
+
*
|
|
102
|
+
* We resolve the package's `package.json` (always exported) and read its entry
|
|
103
|
+
* rather than resolving the package directly: the package's "." export is
|
|
104
|
+
* ESM-only, so `require.resolve(PACKAGE_NAME)` fails with ERR_PACKAGE_PATH_NOT_EXPORTED.
|
|
105
|
+
*/
|
|
106
|
+
function resolveGlobalEntryUrl() {
|
|
107
|
+
try {
|
|
108
|
+
const require = createRequire(import.meta.url);
|
|
109
|
+
const execDir = path.dirname(process.execPath);
|
|
110
|
+
// npm global roots by platform (each entry is a resolution starting point,
|
|
111
|
+
// so `<entry>/node_modules` is checked):
|
|
112
|
+
// - POSIX (nvm, system, Homebrew): <prefix>/lib/node_modules
|
|
113
|
+
// - Windows (node.exe in <prefix>): <prefix>/node_modules
|
|
114
|
+
const paths = [path.join(execDir, '..', 'lib'), execDir];
|
|
115
|
+
const pkgJsonPath = require.resolve(`${PACKAGE_NAME}/package.json`, {
|
|
116
|
+
paths,
|
|
117
|
+
});
|
|
118
|
+
const pkgDir = path.dirname(pkgJsonPath);
|
|
119
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
|
120
|
+
const entryRelative = pkg.exports?.['.']?.import ??
|
|
121
|
+
pkg.exports?.['.']?.default ??
|
|
122
|
+
pkg.main ??
|
|
123
|
+
'index.js';
|
|
124
|
+
return pathToFileURL(path.join(pkgDir, entryRelative)).href;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
58
127
|
return null;
|
|
59
128
|
}
|
|
60
129
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentation.js","sourceRoot":"","sources":["../src/documentation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"documentation.js","sourceRoot":"","sources":["../src/documentation.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,10 +8,10 @@ export type ScrollDistancePreset = (typeof SCROLL_DISTANCE_PRESETS)[number];
|
|
|
8
8
|
export declare const LOCATOR_STRATEGIES: readonly ["accessibility id", "id", "-ios predicate string", "-ios class chain", "-android uiautomator", "xpath", "name", "class name", "css selector"];
|
|
9
9
|
export declare const gestureSchema: z.ZodObject<{
|
|
10
10
|
action: z.ZodEnum<{
|
|
11
|
-
scroll: "scroll";
|
|
12
11
|
tap: "tap";
|
|
13
12
|
double_tap: "double_tap";
|
|
14
13
|
long_press: "long_press";
|
|
14
|
+
scroll: "scroll";
|
|
15
15
|
swipe: "swipe";
|
|
16
16
|
pinch_zoom: "pinch_zoom";
|
|
17
17
|
scroll_to_element: "scroll_to_element";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-mcp",
|
|
3
3
|
"mcpName": "io.github.appium/appium-mcp",
|
|
4
|
-
"version": "1.86.
|
|
4
|
+
"version": "1.86.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"@semantic-release/changelog": "^6.0.3",
|
|
95
95
|
"@semantic-release/git": "^10.0.1",
|
|
96
96
|
"@types/jest": "^30.0.0",
|
|
97
|
-
"@types/node": "^
|
|
97
|
+
"@types/node": "^26.0.0",
|
|
98
98
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
99
99
|
"husky": "^9.1.7",
|
|
100
100
|
"jest": "^30.2.0",
|
package/server.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"name": "io.github.appium/appium-mcp",
|
|
4
4
|
"title": "MCP Appium - Mobile Development and Automation Server",
|
|
5
5
|
"description": "MCP server for Appium mobile automation on iOS and Android devices with test creation tools.",
|
|
6
|
-
"version": "1.86.
|
|
6
|
+
"version": "1.86.2",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "appium-mcp",
|
|
11
|
-
"version": "1.86.
|
|
11
|
+
"version": "1.86.2",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
}
|
package/src/documentation.ts
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* - `APPIUM_MCP_DOCS_ENABLED` unset / not truthy → docs tools are NOT registered.
|
|
12
12
|
* This is the default; nothing extra is loaded.
|
|
13
13
|
* - `APPIUM_MCP_DOCS_ENABLED` truthy → the plugin is loaded if
|
|
14
|
-
* `@appium/mcp-documentation` is installed
|
|
15
|
-
*
|
|
14
|
+
* `@appium/mcp-documentation` is installed (locally next to appium-mcp OR in
|
|
15
|
+
* the global npm root). If it cannot be found, the server logs an actionable
|
|
16
|
+
* install hint and starts normally without the docs tools.
|
|
16
17
|
*
|
|
17
18
|
* Installation is intentionally left to the user's package manager (run at install
|
|
18
19
|
* time, where it belongs) rather than shelled out from the running server: that
|
|
@@ -20,6 +21,10 @@
|
|
|
20
21
|
* and never blocks server startup.
|
|
21
22
|
*/
|
|
22
23
|
|
|
24
|
+
import { readFileSync } from 'node:fs';
|
|
25
|
+
import { createRequire } from 'node:module';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import { pathToFileURL } from 'node:url';
|
|
23
28
|
import type { AppiumMcpPlugin } from './core.js';
|
|
24
29
|
import log from './logger.js';
|
|
25
30
|
import { isTruthyEnvValue } from './utils/env.js';
|
|
@@ -27,6 +32,10 @@ import { isTruthyEnvValue } from './utils/env.js';
|
|
|
27
32
|
const ENABLED_FLAG = 'APPIUM_MCP_DOCS_ENABLED';
|
|
28
33
|
const PACKAGE_NAME = '@appium/mcp-documentation';
|
|
29
34
|
|
|
35
|
+
interface DocumentationModule {
|
|
36
|
+
AppiumDocumentation: new () => AppiumMcpPlugin;
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
/** True when the user has opted into the documentation tools. */
|
|
31
40
|
export function isDocumentationEnabled(): boolean {
|
|
32
41
|
return isTruthyEnvValue(process.env[ENABLED_FLAG]);
|
|
@@ -35,33 +44,103 @@ export function isDocumentationEnabled(): boolean {
|
|
|
35
44
|
/**
|
|
36
45
|
* Load the documentation plugin when the user has opted in.
|
|
37
46
|
*
|
|
38
|
-
* @returns the plugin instance, or `null` if the optional package
|
|
39
|
-
*
|
|
40
|
-
* documentation tools).
|
|
47
|
+
* @returns the plugin instance, or `null` if the optional package cannot be
|
|
48
|
+
* found or fails to load (in which case the server runs without the docs tools).
|
|
41
49
|
*/
|
|
42
50
|
export async function loadDocumentationPlugin(): Promise<AppiumMcpPlugin | null> {
|
|
51
|
+
let mod: DocumentationModule | null;
|
|
52
|
+
try {
|
|
53
|
+
mod = await importDocumentationModule();
|
|
54
|
+
} catch (err) {
|
|
55
|
+
// Found, but threw while evaluating (broken/partial install, bad version).
|
|
56
|
+
log.error(`${PACKAGE_NAME} is installed but failed to load:`, err);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!mod) {
|
|
61
|
+
log.warn(
|
|
62
|
+
`${ENABLED_FLAG} is set but ${PACKAGE_NAME} could not be found. ` +
|
|
63
|
+
'The documentation tools (appium_documentation_query, appium_skills) ' +
|
|
64
|
+
'will be unavailable. Install it where appium-mcp can resolve it:\n' +
|
|
65
|
+
' - globally (recommended; works with npx / global / standalone use):\n' +
|
|
66
|
+
` npm install -g ${PACKAGE_NAME}\n` +
|
|
67
|
+
' - or, only if appium-mcp is a dependency of your project, in that project root:\n' +
|
|
68
|
+
` npm install ${PACKAGE_NAME}`
|
|
69
|
+
);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const plugin = new mod.AppiumDocumentation();
|
|
74
|
+
log.info(`Documentation tools enabled (${PACKAGE_NAME} loaded).`);
|
|
75
|
+
return plugin;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Import the documentation module.
|
|
80
|
+
*
|
|
81
|
+
* @returns the module, or `null` when the package is not installed anywhere
|
|
82
|
+
* resolvable. Throws only when the package WAS found but failed to evaluate.
|
|
83
|
+
*
|
|
84
|
+
* Resolution order:
|
|
85
|
+
* 1. Standard ESM resolution from appium-mcp's location (local / co-located /
|
|
86
|
+
* hoisted installs, including pnpm/yarn).
|
|
87
|
+
* 2. The global npm root — so `npm install -g @appium/mcp-documentation` works
|
|
88
|
+
* even when appium-mcp itself runs from a different location (a local
|
|
89
|
+
* checkout, a global bin, or `npx`), where the global modules are not on
|
|
90
|
+
* the default resolution path.
|
|
91
|
+
*/
|
|
92
|
+
async function importDocumentationModule(): Promise<DocumentationModule | null> {
|
|
43
93
|
try {
|
|
44
94
|
// Widen to `string` so TypeScript treats this as a fully dynamic import and
|
|
45
95
|
// does not require @appium/mcp-documentation to be resolvable at build time
|
|
46
96
|
// (it is an optional, opt-in dependency, not installed by default).
|
|
47
97
|
const specifier: string = PACKAGE_NAME;
|
|
48
|
-
|
|
49
|
-
AppiumDocumentation: new () => AppiumMcpPlugin;
|
|
50
|
-
};
|
|
51
|
-
const plugin = new mod.AppiumDocumentation();
|
|
52
|
-
log.info(`Documentation tools enabled (${PACKAGE_NAME} loaded).`);
|
|
53
|
-
return plugin;
|
|
98
|
+
return (await import(specifier)) as DocumentationModule;
|
|
54
99
|
} catch (err) {
|
|
55
|
-
if (isModuleNotFound(err)) {
|
|
56
|
-
|
|
57
|
-
`${ENABLED_FLAG} is set but ${PACKAGE_NAME} is not installed. ` +
|
|
58
|
-
'The documentation tools (appium_documentation_query, appium_skills) ' +
|
|
59
|
-
'will be unavailable. Install the package to enable them:\n' +
|
|
60
|
-
` npm install ${PACKAGE_NAME}`
|
|
61
|
-
);
|
|
62
|
-
} else {
|
|
63
|
-
log.error(`${PACKAGE_NAME} is installed but failed to load:`, err);
|
|
100
|
+
if (!isModuleNotFound(err)) {
|
|
101
|
+
throw err;
|
|
64
102
|
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const globalEntry = resolveGlobalEntryUrl();
|
|
106
|
+
if (!globalEntry) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return (await import(globalEntry)) as DocumentationModule;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Locate the package in the global npm root and return a file URL to its entry
|
|
114
|
+
* point, or `null` if it is not installed globally.
|
|
115
|
+
*
|
|
116
|
+
* We resolve the package's `package.json` (always exported) and read its entry
|
|
117
|
+
* rather than resolving the package directly: the package's "." export is
|
|
118
|
+
* ESM-only, so `require.resolve(PACKAGE_NAME)` fails with ERR_PACKAGE_PATH_NOT_EXPORTED.
|
|
119
|
+
*/
|
|
120
|
+
function resolveGlobalEntryUrl(): string | null {
|
|
121
|
+
try {
|
|
122
|
+
const require = createRequire(import.meta.url);
|
|
123
|
+
const execDir = path.dirname(process.execPath);
|
|
124
|
+
// npm global roots by platform (each entry is a resolution starting point,
|
|
125
|
+
// so `<entry>/node_modules` is checked):
|
|
126
|
+
// - POSIX (nvm, system, Homebrew): <prefix>/lib/node_modules
|
|
127
|
+
// - Windows (node.exe in <prefix>): <prefix>/node_modules
|
|
128
|
+
const paths = [path.join(execDir, '..', 'lib'), execDir];
|
|
129
|
+
const pkgJsonPath = require.resolve(`${PACKAGE_NAME}/package.json`, {
|
|
130
|
+
paths,
|
|
131
|
+
});
|
|
132
|
+
const pkgDir = path.dirname(pkgJsonPath);
|
|
133
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as {
|
|
134
|
+
main?: string;
|
|
135
|
+
exports?: { ['.']?: { import?: string; default?: string } };
|
|
136
|
+
};
|
|
137
|
+
const entryRelative =
|
|
138
|
+
pkg.exports?.['.']?.import ??
|
|
139
|
+
pkg.exports?.['.']?.default ??
|
|
140
|
+
pkg.main ??
|
|
141
|
+
'index.js';
|
|
142
|
+
return pathToFileURL(path.join(pkgDir, entryRelative)).href;
|
|
143
|
+
} catch {
|
|
65
144
|
return null;
|
|
66
145
|
}
|
|
67
146
|
}
|