@startracex/node-resolve 0.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/LICENSE +21 -0
- package/README.md +35 -0
- package/index.d.ts +4 -0
- package/index.d.ts.map +1 -0
- package/index.js +3 -0
- package/index.ts +3 -0
- package/module-resolver.d.ts +34 -0
- package/module-resolver.d.ts.map +1 -0
- package/module-resolver.js +141 -0
- package/module-resolver.ts +186 -0
- package/package.json +34 -0
- package/specifier.d.ts +14 -0
- package/specifier.d.ts.map +1 -0
- package/specifier.js +28 -0
- package/specifier.ts +40 -0
- package/subpath-resolver.d.ts +18 -0
- package/subpath-resolver.d.ts.map +1 -0
- package/subpath-resolver.js +114 -0
- package/subpath-resolver.ts +150 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 startracex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# node-resolve
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import { ModuleResolver } from "@startracex/node-resolve";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
const resolver = new ModuleResolver({
|
|
9
|
+
extensions: [".js"],
|
|
10
|
+
isCoreModule: (id) => id.startsWith("node:"),
|
|
11
|
+
mainFields: ["main"],
|
|
12
|
+
path: path,
|
|
13
|
+
fs: fs,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const resolved = resolver.resolve("@scope/pkg", "/dir/of/resolver"); // /dir/node_modules/@scope/pkg/index.js
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```go
|
|
20
|
+
import (
|
|
21
|
+
"strings"
|
|
22
|
+
resolve "github.com/startracex/node-resolve"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
func main() {
|
|
26
|
+
resolver := resolve.NewModuleResolver(&resolve.ResolverConfig{
|
|
27
|
+
Extensions: []string{".js"},
|
|
28
|
+
IsCoreModule: func(s string) bool {
|
|
29
|
+
return strings.HasPrefix(s, "node:")
|
|
30
|
+
},
|
|
31
|
+
MainFields: []string{"main"},
|
|
32
|
+
})
|
|
33
|
+
resolved := resolver.Resolve("@scope/pkg", "/dir/of/resolver") // /dir/node_modules/@scope/pkg/index.js
|
|
34
|
+
}
|
|
35
|
+
```
|
package/index.d.ts
ADDED
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC"}
|
package/index.js
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SubpathResolver } from "./subpath-resolver.js";
|
|
2
|
+
import { Specifier } from "./specifier.js";
|
|
3
|
+
export declare class ModuleResolver extends SubpathResolver {
|
|
4
|
+
extensions?: string[];
|
|
5
|
+
modulesDirectoryName: string;
|
|
6
|
+
manifestFileName: string;
|
|
7
|
+
mainFields: string[];
|
|
8
|
+
isCoreModule: (id: string) => boolean;
|
|
9
|
+
path: {
|
|
10
|
+
dirname: (path: string) => string;
|
|
11
|
+
join: (...paths: string[]) => string;
|
|
12
|
+
};
|
|
13
|
+
fs: {
|
|
14
|
+
statSync: (path: string) => {
|
|
15
|
+
isFile: () => boolean;
|
|
16
|
+
isDirectory: () => boolean;
|
|
17
|
+
};
|
|
18
|
+
readFileSync: (path: string) => string | {};
|
|
19
|
+
};
|
|
20
|
+
index: string;
|
|
21
|
+
constructor({ extensions, isCoreModule, modulesDirectoryName, manifestFileName, mainFields, index, path, fs, ...args }?: Partial<ModuleResolver>);
|
|
22
|
+
modulesPaths(start?: string, name?: string): string[];
|
|
23
|
+
resolve(path: string, base: string): string | undefined;
|
|
24
|
+
resolveModuleSpecifier({ name, path }: Specifier, base?: string): string | undefined;
|
|
25
|
+
protected resolveFile(filePath: string): string | undefined;
|
|
26
|
+
protected resolveDir(dirPath: string, entry?: string): string | undefined;
|
|
27
|
+
protected resolveFileOrDir(subPath: string, entry?: string): string | undefined;
|
|
28
|
+
protected readJSON(fullPath: string): Record<string, any>;
|
|
29
|
+
protected stat(path: string): {
|
|
30
|
+
isFile(): boolean;
|
|
31
|
+
isDirectory(): boolean;
|
|
32
|
+
} | null;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=module-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-resolver.d.ts","sourceRoot":"","sources":["module-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAkB,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3D,qBAAa,cAAe,SAAQ,eAAe;IACjD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,IAAI,EAAE;QACJ,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;QAClC,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;KACtC,CAAC;IACF,EAAE,EAAE;QACF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;YAC1B,MAAM,EAAE,MAAM,OAAO,CAAC;YACtB,WAAW,EAAE,MAAM,OAAO,CAAC;SAC5B,CAAC;QACF,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,EAAE,CAAC;KAC7C,CAAC;IACF,KAAK,EAAE,MAAM,CAAC;gBAEF,EACV,UAAU,EACV,YAA0B,EAC1B,oBAAqC,EACrC,gBAAiC,EACjC,UAAqB,EACrB,KAAe,EACf,IAAI,EACJ,EAAE,EACF,GAAG,IAAI,EACR,GAAE,OAAO,CAAC,cAAc,CAAM;IAY/B,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,EAAE;IAmBjD,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA2BvD,sBAAsB,CACpB,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,SAAS,EACzB,IAAI,CAAC,EAAE,MAAM,GACZ,MAAM,GAAG,SAAS;IAarB,SAAS,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAgB3D,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA0CzE,SAAS,CAAC,gBAAgB,CACxB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACb,MAAM,GAAG,SAAS;IAIrB,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAIzD,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG;QAC5B,MAAM,IAAI,OAAO,CAAC;QAClB,WAAW,IAAI,OAAO,CAAC;KACxB,GAAG,IAAI;CAOT"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { SubpathResolver } from "./subpath-resolver.js";
|
|
2
|
+
import { parseSpecifier } from "./specifier.js";
|
|
3
|
+
export class ModuleResolver extends SubpathResolver {
|
|
4
|
+
extensions;
|
|
5
|
+
modulesDirectoryName;
|
|
6
|
+
manifestFileName;
|
|
7
|
+
mainFields;
|
|
8
|
+
isCoreModule;
|
|
9
|
+
path;
|
|
10
|
+
fs;
|
|
11
|
+
index;
|
|
12
|
+
constructor({ extensions, isCoreModule = () => false, modulesDirectoryName = "node_modules", manifestFileName = "package.json", mainFields = ["main"], index = "index", path, fs, ...args } = {}) {
|
|
13
|
+
super(args);
|
|
14
|
+
this.extensions = extensions;
|
|
15
|
+
this.isCoreModule = isCoreModule;
|
|
16
|
+
this.modulesDirectoryName = modulesDirectoryName;
|
|
17
|
+
this.manifestFileName = manifestFileName;
|
|
18
|
+
this.mainFields = mainFields;
|
|
19
|
+
this.path = path;
|
|
20
|
+
this.fs = fs;
|
|
21
|
+
this.index = index;
|
|
22
|
+
}
|
|
23
|
+
modulesPaths(start, name = "") {
|
|
24
|
+
const paths = [];
|
|
25
|
+
if (!start) {
|
|
26
|
+
return paths;
|
|
27
|
+
}
|
|
28
|
+
for (;;) {
|
|
29
|
+
paths.push(this.path.join(start, this.modulesDirectoryName, name));
|
|
30
|
+
const parent = this.path.dirname(start);
|
|
31
|
+
if (parent === start) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
start = parent;
|
|
35
|
+
}
|
|
36
|
+
return paths;
|
|
37
|
+
}
|
|
38
|
+
resolve(path, base) {
|
|
39
|
+
if (this.isCoreModule?.(path)) {
|
|
40
|
+
return path;
|
|
41
|
+
}
|
|
42
|
+
if (path.startsWith("#")) {
|
|
43
|
+
const paths = this.resolveImports(path);
|
|
44
|
+
if (paths) {
|
|
45
|
+
for (const p of paths) {
|
|
46
|
+
const rd = this.resolveFileOrDir(p);
|
|
47
|
+
if (rd) {
|
|
48
|
+
return rd;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const spec = parseSpecifier(path);
|
|
55
|
+
if (spec?.name) {
|
|
56
|
+
return this.resolveModuleSpecifier(spec, base);
|
|
57
|
+
}
|
|
58
|
+
return this.resolveFileOrDir(this.path.join(base, path));
|
|
59
|
+
}
|
|
60
|
+
resolveModuleSpecifier({ name, path }, base) {
|
|
61
|
+
const dirs = this.modulesPaths(base, name);
|
|
62
|
+
for (const dir of dirs) {
|
|
63
|
+
const stat = this.stat(dir);
|
|
64
|
+
if (stat?.isDirectory()) {
|
|
65
|
+
const rd = this.resolveDir(dir, path);
|
|
66
|
+
if (rd) {
|
|
67
|
+
return rd;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
resolveFile(filePath) {
|
|
73
|
+
const candidates = [
|
|
74
|
+
filePath,
|
|
75
|
+
...(this.extensions || []).map((ext) => filePath + ext),
|
|
76
|
+
];
|
|
77
|
+
for (const file of candidates) {
|
|
78
|
+
const stat = this.stat(file);
|
|
79
|
+
if (!stat || stat.isDirectory()) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (stat.isFile()) {
|
|
83
|
+
return file;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
resolveDir(dirPath, entry) {
|
|
88
|
+
const packageJsonPath = this.path.join(dirPath, this.manifestFileName);
|
|
89
|
+
const stat = this.stat(packageJsonPath);
|
|
90
|
+
if (!stat?.isFile()) {
|
|
91
|
+
return this.resolveFile(this.path.join(dirPath, this.index));
|
|
92
|
+
}
|
|
93
|
+
const pkg = this.readJSON(packageJsonPath);
|
|
94
|
+
if (pkg.exports) {
|
|
95
|
+
const exportsMatchArray = new SubpathResolver({
|
|
96
|
+
exports: pkg.exports,
|
|
97
|
+
conditions: this.conditions,
|
|
98
|
+
}).resolveExports(entry);
|
|
99
|
+
if (exportsMatchArray) {
|
|
100
|
+
for (let match of exportsMatchArray) {
|
|
101
|
+
match = this.path.join(dirPath, match);
|
|
102
|
+
const stat = this.stat(match);
|
|
103
|
+
if (stat?.isFile()) {
|
|
104
|
+
return match;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (!entry) {
|
|
111
|
+
for (const field of this.mainFields) {
|
|
112
|
+
let main = pkg[field];
|
|
113
|
+
if (!main) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
main = this.path.join(dirPath, main);
|
|
117
|
+
const stat = this.stat(main);
|
|
118
|
+
if (stat?.isFile()) {
|
|
119
|
+
return main;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const subPath = this.path.join(dirPath, entry);
|
|
125
|
+
return this.resolveFileOrDir(subPath);
|
|
126
|
+
}
|
|
127
|
+
resolveFileOrDir(subPath, entry) {
|
|
128
|
+
return this.resolveFile(subPath) || this.resolveDir(subPath, entry);
|
|
129
|
+
}
|
|
130
|
+
readJSON(fullPath) {
|
|
131
|
+
return JSON.parse(this.fs.readFileSync(fullPath).toString());
|
|
132
|
+
}
|
|
133
|
+
stat(path) {
|
|
134
|
+
try {
|
|
135
|
+
return this.fs.statSync(path);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { SubpathResolver } from "./subpath-resolver.js";
|
|
2
|
+
import { parseSpecifier, Specifier } from "./specifier.js";
|
|
3
|
+
|
|
4
|
+
export class ModuleResolver extends SubpathResolver {
|
|
5
|
+
extensions?: string[];
|
|
6
|
+
modulesDirectoryName: string;
|
|
7
|
+
manifestFileName: string;
|
|
8
|
+
mainFields: string[];
|
|
9
|
+
isCoreModule: (id: string) => boolean;
|
|
10
|
+
path: {
|
|
11
|
+
dirname: (path: string) => string;
|
|
12
|
+
join: (...paths: string[]) => string;
|
|
13
|
+
};
|
|
14
|
+
fs: {
|
|
15
|
+
statSync: (path: string) => {
|
|
16
|
+
isFile: () => boolean;
|
|
17
|
+
isDirectory: () => boolean;
|
|
18
|
+
};
|
|
19
|
+
readFileSync: (path: string) => string | {};
|
|
20
|
+
};
|
|
21
|
+
index: string;
|
|
22
|
+
|
|
23
|
+
constructor({
|
|
24
|
+
extensions,
|
|
25
|
+
isCoreModule = () => false,
|
|
26
|
+
modulesDirectoryName = "node_modules",
|
|
27
|
+
manifestFileName = "package.json",
|
|
28
|
+
mainFields = ["main"],
|
|
29
|
+
index = "index",
|
|
30
|
+
path,
|
|
31
|
+
fs,
|
|
32
|
+
...args
|
|
33
|
+
}: Partial<ModuleResolver> = {}) {
|
|
34
|
+
super(args);
|
|
35
|
+
this.extensions = extensions;
|
|
36
|
+
this.isCoreModule = isCoreModule;
|
|
37
|
+
this.modulesDirectoryName = modulesDirectoryName;
|
|
38
|
+
this.manifestFileName = manifestFileName;
|
|
39
|
+
this.mainFields = mainFields;
|
|
40
|
+
this.path = path;
|
|
41
|
+
this.fs = fs;
|
|
42
|
+
this.index = index;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
modulesPaths(start?: string, name = ""): string[] {
|
|
46
|
+
const paths: string[] = [];
|
|
47
|
+
|
|
48
|
+
if (!start) {
|
|
49
|
+
return paths;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (;;) {
|
|
53
|
+
paths.push(this.path.join(start, this.modulesDirectoryName, name));
|
|
54
|
+
const parent = this.path.dirname(start);
|
|
55
|
+
if (parent === start) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
start = parent;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return paths;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
resolve(path: string, base: string): string | undefined {
|
|
65
|
+
if (this.isCoreModule?.(path)) {
|
|
66
|
+
return path;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (path.startsWith("#")) {
|
|
70
|
+
const paths = this.resolveImports(path);
|
|
71
|
+
if (paths) {
|
|
72
|
+
for (const p of paths) {
|
|
73
|
+
const rd = this.resolveFileOrDir(p);
|
|
74
|
+
if (rd) {
|
|
75
|
+
return rd;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const spec = parseSpecifier(path);
|
|
83
|
+
|
|
84
|
+
if (spec?.name) {
|
|
85
|
+
return this.resolveModuleSpecifier(spec, base);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return this.resolveFileOrDir(this.path.join(base, path));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
resolveModuleSpecifier(
|
|
92
|
+
{ name, path }: Specifier,
|
|
93
|
+
base?: string,
|
|
94
|
+
): string | undefined {
|
|
95
|
+
const dirs = this.modulesPaths(base, name);
|
|
96
|
+
for (const dir of dirs) {
|
|
97
|
+
const stat = this.stat(dir);
|
|
98
|
+
if (stat?.isDirectory()) {
|
|
99
|
+
const rd = this.resolveDir(dir, path);
|
|
100
|
+
if (rd) {
|
|
101
|
+
return rd;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected resolveFile(filePath: string): string | undefined {
|
|
108
|
+
const candidates = [
|
|
109
|
+
filePath,
|
|
110
|
+
...(this.extensions || []).map((ext) => filePath + ext),
|
|
111
|
+
];
|
|
112
|
+
for (const file of candidates) {
|
|
113
|
+
const stat = this.stat(file);
|
|
114
|
+
if (!stat || stat.isDirectory()) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (stat.isFile()) {
|
|
118
|
+
return file;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
protected resolveDir(dirPath: string, entry?: string): string | undefined {
|
|
124
|
+
const packageJsonPath = this.path.join(dirPath, this.manifestFileName);
|
|
125
|
+
const stat = this.stat(packageJsonPath);
|
|
126
|
+
if (!stat?.isFile()) {
|
|
127
|
+
return this.resolveFile(this.path.join(dirPath, this.index));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const pkg = this.readJSON(packageJsonPath);
|
|
131
|
+
if (pkg.exports) {
|
|
132
|
+
const exportsMatchArray = new SubpathResolver({
|
|
133
|
+
exports: pkg.exports,
|
|
134
|
+
conditions: this.conditions,
|
|
135
|
+
}).resolveExports(entry);
|
|
136
|
+
if (exportsMatchArray) {
|
|
137
|
+
for (let match of exportsMatchArray) {
|
|
138
|
+
match = this.path.join(dirPath, match);
|
|
139
|
+
const stat = this.stat(match);
|
|
140
|
+
if (stat?.isFile()) {
|
|
141
|
+
return match;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!entry) {
|
|
148
|
+
for (const field of this.mainFields) {
|
|
149
|
+
let main = pkg[field];
|
|
150
|
+
if (!main) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
main = this.path.join(dirPath, main);
|
|
154
|
+
const stat = this.stat(main);
|
|
155
|
+
if (stat?.isFile()) {
|
|
156
|
+
return main;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const subPath = this.path.join(dirPath, entry);
|
|
162
|
+
return this.resolveFileOrDir(subPath);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
protected resolveFileOrDir(
|
|
166
|
+
subPath: string,
|
|
167
|
+
entry?: string,
|
|
168
|
+
): string | undefined {
|
|
169
|
+
return this.resolveFile(subPath) || this.resolveDir(subPath, entry);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
protected readJSON(fullPath: string): Record<string, any> {
|
|
173
|
+
return JSON.parse(this.fs.readFileSync(fullPath).toString());
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected stat(path: string): {
|
|
177
|
+
isFile(): boolean;
|
|
178
|
+
isDirectory(): boolean;
|
|
179
|
+
} | null {
|
|
180
|
+
try {
|
|
181
|
+
return this.fs.statSync(path);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@startracex/node-resolve",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Node resolve algorithm",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"fmt": "dprint fmt",
|
|
11
|
+
"check": "dprint check"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@startracex/dev-config": "^0.2.2",
|
|
15
|
+
"dprint": "^0.50.0",
|
|
16
|
+
"typescript": "^5.8.3"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"node",
|
|
20
|
+
"resolve",
|
|
21
|
+
"algorithm",
|
|
22
|
+
"exports"
|
|
23
|
+
],
|
|
24
|
+
"author": "startracex",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/startracex/node-resolve.git"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"*.{js,ts}+(|.map)"
|
|
32
|
+
],
|
|
33
|
+
"packageManager": "pnpm@10.11.0"
|
|
34
|
+
}
|
package/specifier.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Specifier = {
|
|
2
|
+
proto?: string;
|
|
3
|
+
scope?: string;
|
|
4
|
+
pkg?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Parse a input string into a Specifier.
|
|
10
|
+
*
|
|
11
|
+
* input can be a npm package name with optional proto, subpath.
|
|
12
|
+
*/
|
|
13
|
+
export declare const parseSpecifier: (input: string) => Specifier | null;
|
|
14
|
+
//# sourceMappingURL=specifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specifier.d.ts","sourceRoot":"","sources":["specifier.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,KAAG,SAAS,GAAG,IAwB1D,CAAC"}
|
package/specifier.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const regexp = /^(?:@(\w[\w-.]*)\/)?(\w[\w-.]*)(\/(.*))?/;
|
|
2
|
+
/**
|
|
3
|
+
* Parse a input string into a Specifier.
|
|
4
|
+
*
|
|
5
|
+
* input can be a npm package name with optional proto, subpath.
|
|
6
|
+
*/
|
|
7
|
+
export const parseSpecifier = (input) => {
|
|
8
|
+
if (!input) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const sp = input.split(":");
|
|
12
|
+
const path = sp.pop();
|
|
13
|
+
const match = path.match(regexp);
|
|
14
|
+
console.log(match);
|
|
15
|
+
if (!match || !match[0] || !match[2]) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const scope = match[1];
|
|
19
|
+
const pkg = match[2];
|
|
20
|
+
const result = {
|
|
21
|
+
name: scope ? `@${scope}/${pkg}` : pkg,
|
|
22
|
+
scope,
|
|
23
|
+
pkg,
|
|
24
|
+
proto: sp.pop(),
|
|
25
|
+
path: match[4],
|
|
26
|
+
};
|
|
27
|
+
return result;
|
|
28
|
+
};
|
package/specifier.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type Specifier = {
|
|
2
|
+
proto?: string;
|
|
3
|
+
scope?: string;
|
|
4
|
+
pkg?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const regexp = /^(?:@(\w[\w-.]*)\/)?(\w[\w-.]*)(\/(.*))?/;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse a input string into a Specifier.
|
|
13
|
+
*
|
|
14
|
+
* input can be a npm package name with optional proto, subpath.
|
|
15
|
+
*/
|
|
16
|
+
export const parseSpecifier = (input: string): Specifier | null => {
|
|
17
|
+
if (!input) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const sp = input.split(":");
|
|
21
|
+
const path = sp.pop();
|
|
22
|
+
|
|
23
|
+
const match = path.match(regexp);
|
|
24
|
+
|
|
25
|
+
console.log(match);
|
|
26
|
+
if (!match || !match[0] || !match[2]) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const scope = match[1];
|
|
31
|
+
const pkg = match[2];
|
|
32
|
+
const result: Specifier = {
|
|
33
|
+
name: scope ? `@${scope}/${pkg}` : pkg,
|
|
34
|
+
scope,
|
|
35
|
+
pkg,
|
|
36
|
+
proto: sp.pop(),
|
|
37
|
+
path: match[4],
|
|
38
|
+
};
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Value = string | string[] | {
|
|
2
|
+
[key: string]: Value;
|
|
3
|
+
};
|
|
4
|
+
export declare const resolveMapping: (mapping: Record<string, Value>, conditions: string[], input: string) => string[] | null;
|
|
5
|
+
export declare class SubpathResolver {
|
|
6
|
+
conditions: string[];
|
|
7
|
+
exports: Record<string, Value>;
|
|
8
|
+
imports: Record<string, Value>;
|
|
9
|
+
constructor({ exports, imports, conditions, }: {
|
|
10
|
+
exports?: Value;
|
|
11
|
+
imports?: Record<string, Value>;
|
|
12
|
+
conditions?: string[];
|
|
13
|
+
});
|
|
14
|
+
resolveExports(entry?: string): string[];
|
|
15
|
+
resolveImports(entry: string): string[] | null;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=subpath-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subpath-resolver.d.ts","sourceRoot":"","sources":["subpath-resolver.ts"],"names":[],"mappings":"AAAA,KAAK,KAAK,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,CAAC;AAyE1D,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC9B,YAAY,MAAM,EAAE,EACpB,OAAO,MAAM,KACZ,MAAM,EAAE,GAAG,IA6Bb,CAAC;AAcF,qBAAa,eAAe;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBAEnB,EACV,OAAO,EACP,OAAO,EACP,UAAwB,GACzB,EAAE;QACD,OAAO,CAAC,EAAE,KAAK,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAChC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB;IAMD,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE;IAIxC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;CAM/C"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const normalizeMapping = (m) => {
|
|
2
|
+
if (!m) {
|
|
3
|
+
return {};
|
|
4
|
+
}
|
|
5
|
+
if (typeof m === "string" || Array.isArray(m)) {
|
|
6
|
+
return { ".": m };
|
|
7
|
+
}
|
|
8
|
+
return m;
|
|
9
|
+
};
|
|
10
|
+
const findWildcardMatch = (mapping, input) => {
|
|
11
|
+
let bestMatch = null;
|
|
12
|
+
let replacement = "";
|
|
13
|
+
for (const key in mapping) {
|
|
14
|
+
if (key.endsWith("/*")) {
|
|
15
|
+
const prefix = key.slice(0, -1);
|
|
16
|
+
if (input.startsWith(prefix)) {
|
|
17
|
+
const matchLength = prefix.length;
|
|
18
|
+
if (!bestMatch || matchLength > bestMatch.length) {
|
|
19
|
+
bestMatch = { key, length: matchLength };
|
|
20
|
+
replacement = input.slice(prefix.length);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (key.includes("*")) {
|
|
25
|
+
const [before, after] = key.split("*");
|
|
26
|
+
if (input.startsWith(before) && input.endsWith(after)) {
|
|
27
|
+
const matchLength = before.length + after.length;
|
|
28
|
+
if (!bestMatch || matchLength > bestMatch.length) {
|
|
29
|
+
bestMatch = { key, length: matchLength };
|
|
30
|
+
replacement = input.slice(before.length, input.length - after.length);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (key.endsWith("/") && input.startsWith(key)) {
|
|
35
|
+
const matchLength = key.length;
|
|
36
|
+
if (!bestMatch || matchLength > bestMatch.length) {
|
|
37
|
+
bestMatch = { key, length: matchLength };
|
|
38
|
+
replacement = input.slice(key.length);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return bestMatch ? { key: bestMatch.key, replacement } : null;
|
|
43
|
+
};
|
|
44
|
+
const resolveMappingValue = (value, conditions) => {
|
|
45
|
+
if (!value)
|
|
46
|
+
return null;
|
|
47
|
+
if (typeof value === "string") {
|
|
48
|
+
return [value];
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
return value.flatMap((v) => resolveMappingValue(v, conditions) || []);
|
|
52
|
+
}
|
|
53
|
+
for (const [condition, subValue] of Object.entries(value)) {
|
|
54
|
+
if (conditions.includes(condition)) {
|
|
55
|
+
return resolveMappingValue(subValue, conditions);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
export const resolveMapping = (mapping, conditions, input) => {
|
|
61
|
+
let value = mapping[input];
|
|
62
|
+
let wildcardMatch = null;
|
|
63
|
+
if (value === undefined) {
|
|
64
|
+
wildcardMatch = findWildcardMatch(mapping, input);
|
|
65
|
+
if (wildcardMatch) {
|
|
66
|
+
value = mapping[wildcardMatch.key];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const resolved = resolveMappingValue(value, conditions);
|
|
70
|
+
if (!resolved) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (!wildcardMatch) {
|
|
74
|
+
return resolved;
|
|
75
|
+
}
|
|
76
|
+
return resolved.map((r) => {
|
|
77
|
+
if (r.includes("*")) {
|
|
78
|
+
return r.replace("*", wildcardMatch.replacement);
|
|
79
|
+
}
|
|
80
|
+
if (r.endsWith("/")) {
|
|
81
|
+
return r + wildcardMatch.replacement;
|
|
82
|
+
}
|
|
83
|
+
return r;
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const subpathPrefix = "./";
|
|
87
|
+
const normalizeEntry = (entry) => {
|
|
88
|
+
if (!entry || entry === "." || entry === subpathPrefix) {
|
|
89
|
+
return ".";
|
|
90
|
+
}
|
|
91
|
+
if (entry.startsWith(subpathPrefix)) {
|
|
92
|
+
return entry;
|
|
93
|
+
}
|
|
94
|
+
return subpathPrefix + entry;
|
|
95
|
+
};
|
|
96
|
+
export class SubpathResolver {
|
|
97
|
+
conditions;
|
|
98
|
+
exports;
|
|
99
|
+
imports;
|
|
100
|
+
constructor({ exports, imports, conditions = ["default"], }) {
|
|
101
|
+
this.imports = imports;
|
|
102
|
+
this.exports = normalizeMapping(exports);
|
|
103
|
+
this.conditions = conditions;
|
|
104
|
+
}
|
|
105
|
+
resolveExports(entry) {
|
|
106
|
+
return resolveMapping(this.exports, this.conditions, normalizeEntry(entry));
|
|
107
|
+
}
|
|
108
|
+
resolveImports(entry) {
|
|
109
|
+
if (!this.imports) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return resolveMapping(this.imports, this.conditions, entry);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
type Value = string | string[] | { [key: string]: Value };
|
|
2
|
+
|
|
3
|
+
const normalizeMapping = (m: Value): Record<string, Value> => {
|
|
4
|
+
if (!m) {
|
|
5
|
+
return {};
|
|
6
|
+
}
|
|
7
|
+
if (typeof m === "string" || Array.isArray(m)) {
|
|
8
|
+
return { ".": m };
|
|
9
|
+
}
|
|
10
|
+
return m;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const findWildcardMatch = (
|
|
14
|
+
mapping: Record<string, Value>,
|
|
15
|
+
input: string,
|
|
16
|
+
): { key: string; replacement: string } | null => {
|
|
17
|
+
let bestMatch: { key: string; length: number } | null = null;
|
|
18
|
+
let replacement = "";
|
|
19
|
+
|
|
20
|
+
for (const key in mapping) {
|
|
21
|
+
if (key.endsWith("/*")) {
|
|
22
|
+
const prefix = key.slice(0, -1);
|
|
23
|
+
if (input.startsWith(prefix)) {
|
|
24
|
+
const matchLength = prefix.length;
|
|
25
|
+
if (!bestMatch || matchLength > bestMatch.length) {
|
|
26
|
+
bestMatch = { key, length: matchLength };
|
|
27
|
+
replacement = input.slice(prefix.length);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} else if (key.includes("*")) {
|
|
31
|
+
const [before, after] = key.split("*");
|
|
32
|
+
if (input.startsWith(before) && input.endsWith(after)) {
|
|
33
|
+
const matchLength = before.length + after.length;
|
|
34
|
+
if (!bestMatch || matchLength > bestMatch.length) {
|
|
35
|
+
bestMatch = { key, length: matchLength };
|
|
36
|
+
replacement = input.slice(before.length, input.length - after.length);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} else if (key.endsWith("/") && input.startsWith(key)) {
|
|
40
|
+
const matchLength = key.length;
|
|
41
|
+
if (!bestMatch || matchLength > bestMatch.length) {
|
|
42
|
+
bestMatch = { key, length: matchLength };
|
|
43
|
+
replacement = input.slice(key.length);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return bestMatch ? { key: bestMatch.key, replacement } : null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const resolveMappingValue = (
|
|
52
|
+
value: Value | undefined,
|
|
53
|
+
conditions: string[],
|
|
54
|
+
): string[] | null => {
|
|
55
|
+
if (!value) return null;
|
|
56
|
+
|
|
57
|
+
if (typeof value === "string") {
|
|
58
|
+
return [value];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
return value.flatMap((v) => resolveMappingValue(v, conditions) || []);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const [condition, subValue] of Object.entries(value)) {
|
|
66
|
+
if (conditions.includes(condition)) {
|
|
67
|
+
return resolveMappingValue(subValue, conditions);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const resolveMapping = (
|
|
75
|
+
mapping: Record<string, Value>,
|
|
76
|
+
conditions: string[],
|
|
77
|
+
input: string,
|
|
78
|
+
): string[] | null => {
|
|
79
|
+
let value = mapping[input];
|
|
80
|
+
let wildcardMatch: { key: string; replacement: string } | null = null;
|
|
81
|
+
|
|
82
|
+
if (value === undefined) {
|
|
83
|
+
wildcardMatch = findWildcardMatch(mapping, input);
|
|
84
|
+
if (wildcardMatch) {
|
|
85
|
+
value = mapping[wildcardMatch.key];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const resolved = resolveMappingValue(value, conditions);
|
|
90
|
+
if (!resolved) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!wildcardMatch) {
|
|
95
|
+
return resolved;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return resolved.map((r) => {
|
|
99
|
+
if (r.includes("*")) {
|
|
100
|
+
return r.replace("*", wildcardMatch.replacement);
|
|
101
|
+
}
|
|
102
|
+
if (r.endsWith("/")) {
|
|
103
|
+
return r + wildcardMatch.replacement;
|
|
104
|
+
}
|
|
105
|
+
return r;
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const subpathPrefix = "./";
|
|
110
|
+
|
|
111
|
+
const normalizeEntry = (entry: string) => {
|
|
112
|
+
if (!entry || entry === "." || entry === subpathPrefix) {
|
|
113
|
+
return ".";
|
|
114
|
+
}
|
|
115
|
+
if (entry.startsWith(subpathPrefix)) {
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
|
118
|
+
return subpathPrefix + entry;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export class SubpathResolver {
|
|
122
|
+
conditions: string[];
|
|
123
|
+
exports: Record<string, Value>;
|
|
124
|
+
imports: Record<string, Value>;
|
|
125
|
+
|
|
126
|
+
constructor({
|
|
127
|
+
exports,
|
|
128
|
+
imports,
|
|
129
|
+
conditions = ["default"],
|
|
130
|
+
}: {
|
|
131
|
+
exports?: Value;
|
|
132
|
+
imports?: Record<string, Value>;
|
|
133
|
+
conditions?: string[];
|
|
134
|
+
}) {
|
|
135
|
+
this.imports = imports;
|
|
136
|
+
this.exports = normalizeMapping(exports);
|
|
137
|
+
this.conditions = conditions;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
resolveExports(entry?: string): string[] {
|
|
141
|
+
return resolveMapping(this.exports, this.conditions, normalizeEntry(entry));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
resolveImports(entry: string): string[] | null {
|
|
145
|
+
if (!this.imports) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return resolveMapping(this.imports, this.conditions, entry);
|
|
149
|
+
}
|
|
150
|
+
}
|