@markuplint/file-resolver 5.0.0-dev.5 → 5.0.0-rc.1
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 +17 -0
- package/lib/config-provider.js +7 -2
- package/lib/cosmiconfig.d.ts +1 -1
- package/lib/cosmiconfig.js +1 -1
- package/lib/general-import.js +113 -5
- package/lib/resolve-pretenders.d.ts +11 -2
- package/lib/resolve-pretenders.js +27 -2
- package/package.json +12 -11
- package/lib/config-load-error.d.ts +0 -6
- package/lib/config-load-error.js +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [5.0.0-rc.1](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.0...v5.0.0-rc.1) (2026-03-27)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **file-resolver:** handle ERR_PACKAGE_PATH_NOT_EXPORTED in generalImport ([d563d60](https://github.com/markuplint/markuplint/commit/d563d6000c4c38be54c17ea015b0f26c80c5229c)), closes [#3516](https://github.com/markuplint/markuplint/issues/3516)
|
|
11
|
+
- **file-resolver:** resolve package subpaths before import to avoid runtime-specific errors ([cb2b641](https://github.com/markuplint/markuplint/commit/cb2b641e25cd9de487d395b6f219dd8b60ecd306))
|
|
12
|
+
|
|
13
|
+
# [5.0.0-rc.0](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.3...v5.0.0-rc.0) (2026-03-12)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- **file-resolver:** resolve glob results to absolute paths for Windows compatibility ([79a7844](https://github.com/markuplint/markuplint/commit/79a7844823755d886466297f960c3b21f1feec8f))
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- **ml-config,file-resolver:** wire scan field into config pipeline ([76b042a](https://github.com/markuplint/markuplint/commit/76b042a159b4037d3fff4e7c9a5c9be4d6dba44c)), closes [#3335](https://github.com/markuplint/markuplint/issues/3335) [#3336](https://github.com/markuplint/markuplint/issues/3336) [-#3341](https://github.com/-/issues/3341) [#3335](https://github.com/markuplint/markuplint/issues/3335)
|
|
22
|
+
|
|
6
23
|
# [5.0.0-alpha.3](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.2...v5.0.0-alpha.3) (2026-02-26)
|
|
7
24
|
|
|
8
25
|
**Note:** Version bump only for package @markuplint/file-resolver
|
package/lib/config-provider.js
CHANGED
|
@@ -2,8 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { mergeConfig } from '@markuplint/ml-config';
|
|
3
3
|
import { ConfigParserError } from '@markuplint/parser-utils';
|
|
4
4
|
import { InvalidSelectorError, createSelector } from '@markuplint/selector';
|
|
5
|
-
import { nonNullableFilter, toNoEmptyStringArrayFromStringOrArray } from '@markuplint/shared';
|
|
6
|
-
import { ConfigLoadError } from './config-load-error.js';
|
|
5
|
+
import { nonNullableFilter, toNoEmptyStringArrayFromStringOrArray, ConfigLoadError } from '@markuplint/shared';
|
|
7
6
|
import { load as loadConfig, search } from './cosmiconfig.js';
|
|
8
7
|
import { log } from './debug.js';
|
|
9
8
|
import { generalImport } from './general-import.js';
|
|
@@ -255,6 +254,12 @@ export class ConfigProvider {
|
|
|
255
254
|
? {
|
|
256
255
|
...optimizedConfig.pretenders,
|
|
257
256
|
files: await relPathToNameOrAbsPath(dir, optimizedConfig.pretenders?.files),
|
|
257
|
+
scan: optimizedConfig.pretenders?.scan?.map(entry => ({
|
|
258
|
+
...entry,
|
|
259
|
+
files: typeof entry.files === 'string'
|
|
260
|
+
? path.resolve(dir, entry.files)
|
|
261
|
+
: entry.files.map(f => path.resolve(dir, f)),
|
|
262
|
+
})),
|
|
258
263
|
}
|
|
259
264
|
: undefined,
|
|
260
265
|
overrides: await relPathToNameOrAbsPath(dir, optimizedConfig.overrides, undefined, true),
|
package/lib/cosmiconfig.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LoaderSync } from 'cosmiconfig';
|
|
2
|
-
import { ConfigLoadError } from '
|
|
2
|
+
import { ConfigLoadError } from '@markuplint/shared';
|
|
3
3
|
type CosmiConfig = ReturnType<LoaderSync>;
|
|
4
4
|
export declare function search<T = CosmiConfig>(filePath: string, cacheClear: boolean): Promise<{
|
|
5
5
|
filePath: string;
|
package/lib/cosmiconfig.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { ConfigParserError } from '@markuplint/parser-utils';
|
|
3
3
|
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
|
|
4
4
|
import { jsonc } from 'jsonc';
|
|
5
|
-
import { ConfigLoadError } from '
|
|
5
|
+
import { ConfigLoadError } from '@markuplint/shared';
|
|
6
6
|
import { log } from './debug.js';
|
|
7
7
|
const searchLog = log.extend('search');
|
|
8
8
|
const jsoncLoader = (path, content) => {
|
package/lib/general-import.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { isFatalError } from '@markuplint/shared';
|
|
5
6
|
import { resolve } from 'import-meta-resolve';
|
|
6
7
|
import { log } from './debug.js';
|
|
7
8
|
import { fromFileURL } from './path-utils.js';
|
|
@@ -14,6 +15,17 @@ export async function generalImport(name) {
|
|
|
14
15
|
if (cache.has(name)) {
|
|
15
16
|
return cache.get(name);
|
|
16
17
|
}
|
|
18
|
+
// When the specifier is a package subpath (e.g., "@scope/pkg/file.json"),
|
|
19
|
+
// the package's exports map may not include it. Instead of relying on
|
|
20
|
+
// error codes (which differ between Node.js and vitest/vite), resolve
|
|
21
|
+
// the package directory upfront and convert to an absolute path.
|
|
22
|
+
const resolved = resolvePackageSubpath(name);
|
|
23
|
+
if (resolved) {
|
|
24
|
+
gLog('Resolved package subpath "%s" to "%s"', name, resolved);
|
|
25
|
+
const result = await generalImport(resolved);
|
|
26
|
+
cache.set(name, result);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
17
29
|
try {
|
|
18
30
|
// Convert absolute paths to file:// URL format
|
|
19
31
|
let importPath = name;
|
|
@@ -64,14 +76,110 @@ export async function generalImport(name) {
|
|
|
64
76
|
}
|
|
65
77
|
}
|
|
66
78
|
if (path.isAbsolute(name) && name.endsWith('.json')) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
try {
|
|
80
|
+
const file = await fs.readFile(name, 'utf8');
|
|
81
|
+
const json = JSON.parse(file);
|
|
82
|
+
gLogSuccess('Success by readFile("%s") and JSON.parse: %O', name, json);
|
|
83
|
+
cache.set(name, json);
|
|
84
|
+
return json;
|
|
85
|
+
}
|
|
86
|
+
catch (readError) {
|
|
87
|
+
gLogError('Error in readFile("%s"): %O', name, readError);
|
|
88
|
+
}
|
|
72
89
|
}
|
|
73
90
|
gLogError('Error in `import()`: %O', error);
|
|
74
91
|
cache.set(name, null);
|
|
75
92
|
return null;
|
|
76
93
|
}
|
|
77
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Detects a bare package subpath specifier (e.g., `@scope/pkg/file.json`)
|
|
97
|
+
* and resolves it to an absolute file path when the package's exports map
|
|
98
|
+
* does not include the subpath.
|
|
99
|
+
*
|
|
100
|
+
* Returns the absolute path if resolution succeeds, or `null` if:
|
|
101
|
+
* - The specifier is not a package subpath (absolute path, relative path, no subpath)
|
|
102
|
+
* - The package's exports map already includes the subpath (no bypass needed)
|
|
103
|
+
* - The package cannot be found at all
|
|
104
|
+
*/
|
|
105
|
+
function resolvePackageSubpath(name) {
|
|
106
|
+
if (path.isAbsolute(name) || name.startsWith('.')) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const packageName = name.startsWith('@') ? name.split('/').slice(0, 2).join('/') : name.split('/')[0];
|
|
110
|
+
if (!packageName) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const subpath = name.slice(packageName.length + 1);
|
|
114
|
+
if (!subpath) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const pkgDir = resolvePackageDir(packageName);
|
|
118
|
+
if (!pkgDir) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const pkgJson = require(path.join(pkgDir, 'package.json'));
|
|
123
|
+
if (!pkgJson.exports) {
|
|
124
|
+
return null; // No exports map — import() should work fine
|
|
125
|
+
}
|
|
126
|
+
// If the exports map explicitly includes this subpath, let import() handle it
|
|
127
|
+
const exportKey = `./${subpath}`;
|
|
128
|
+
if (typeof pkgJson.exports === 'object' && !Array.isArray(pkgJson.exports) && exportKey in pkgJson.exports) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
// Subpath not in exports — resolve to absolute path to bypass the restriction
|
|
132
|
+
return path.join(pkgDir, subpath);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (isFatalError(error)) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolves the root directory of a package by name.
|
|
143
|
+
*/
|
|
144
|
+
function resolvePackageDir(packageName) {
|
|
145
|
+
// Try CJS require.resolve first — it can often resolve package.json
|
|
146
|
+
// even when the ESM exports map doesn't include it
|
|
147
|
+
try {
|
|
148
|
+
return path.dirname(require.resolve(`${packageName}/package.json`));
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (isFatalError(error)) {
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
// Fall through
|
|
155
|
+
}
|
|
156
|
+
// Fall back to import-meta-resolve to find the main entry, then walk up
|
|
157
|
+
try {
|
|
158
|
+
const mainUrl = resolve(packageName, import.meta.url);
|
|
159
|
+
const mainPath = mainUrl.startsWith('file:') ? fromFileURL(mainUrl) : mainUrl;
|
|
160
|
+
let dir = path.dirname(mainPath);
|
|
161
|
+
for (let i = 0; i < 10; i++) {
|
|
162
|
+
try {
|
|
163
|
+
require(path.join(dir, 'package.json'));
|
|
164
|
+
return dir;
|
|
165
|
+
}
|
|
166
|
+
catch (innerError) {
|
|
167
|
+
if (isFatalError(innerError)) {
|
|
168
|
+
throw innerError;
|
|
169
|
+
}
|
|
170
|
+
const parent = path.dirname(dir);
|
|
171
|
+
if (parent === dir) {
|
|
172
|
+
break; // Reached filesystem root
|
|
173
|
+
}
|
|
174
|
+
dir = parent;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (isFatalError(error)) {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
// Package not found at all
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { OptimizedConfig, Pretender } from '@markuplint/ml-config';
|
|
2
2
|
type PretendersConfig = OptimizedConfig['pretenders'];
|
|
3
3
|
/**
|
|
4
|
-
* Resolves pretender definitions from files, imported modules,
|
|
5
|
-
* in the configuration.
|
|
4
|
+
* Resolves pretender definitions from files, imported modules, inline data,
|
|
5
|
+
* and dynamic component scanning in the configuration.
|
|
6
|
+
*
|
|
7
|
+
* Resolution order:
|
|
8
|
+
* 1. `config.files` — direct import of pretender data files
|
|
9
|
+
* 2. `config.imports` — for each module, tries `<module>/package.json`
|
|
10
|
+
* (reads the `pretenders` field) first, then falls back to
|
|
11
|
+
* `<module>/pretenders.json`
|
|
12
|
+
* 3. `config.data` — inline pretender definitions
|
|
13
|
+
* 4. `config.scan` — dynamic component scanning via glob patterns
|
|
14
|
+
* (`files` accepts `string | string[]`)
|
|
6
15
|
*
|
|
7
16
|
* @param config - The pretenders configuration section from the optimized config
|
|
8
17
|
* @returns An array of all resolved pretender definitions
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { glob } from 'glob';
|
|
1
3
|
import { generalImport } from './general-import.js';
|
|
2
4
|
/**
|
|
3
|
-
* Resolves pretender definitions from files, imported modules,
|
|
4
|
-
* in the configuration.
|
|
5
|
+
* Resolves pretender definitions from files, imported modules, inline data,
|
|
6
|
+
* and dynamic component scanning in the configuration.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order:
|
|
9
|
+
* 1. `config.files` — direct import of pretender data files
|
|
10
|
+
* 2. `config.imports` — for each module, tries `<module>/package.json`
|
|
11
|
+
* (reads the `pretenders` field) first, then falls back to
|
|
12
|
+
* `<module>/pretenders.json`
|
|
13
|
+
* 3. `config.data` — inline pretender definitions
|
|
14
|
+
* 4. `config.scan` — dynamic component scanning via glob patterns
|
|
15
|
+
* (`files` accepts `string | string[]`)
|
|
5
16
|
*
|
|
6
17
|
* @param config - The pretenders configuration section from the optimized config
|
|
7
18
|
* @returns An array of all resolved pretender definitions
|
|
@@ -35,5 +46,19 @@ export async function resolvePretenders(config) {
|
|
|
35
46
|
if (config.data) {
|
|
36
47
|
data.push(...config.data);
|
|
37
48
|
}
|
|
49
|
+
if (config.scan) {
|
|
50
|
+
const { scan } = await import('@markuplint/pretenders');
|
|
51
|
+
for (const entry of config.scan) {
|
|
52
|
+
const patterns = typeof entry.files === 'string' ? [entry.files] : [...entry.files];
|
|
53
|
+
const globResults = await Promise.all(patterns.map(p => glob(p)));
|
|
54
|
+
const resolved = globResults.flat().map(f => path.resolve(f));
|
|
55
|
+
if (resolved.length > 0) {
|
|
56
|
+
const scanned = await scan(resolved, {
|
|
57
|
+
ignoreComponentNames: entry.ignoreComponentNames ? [...entry.ignoreComponentNames] : undefined,
|
|
58
|
+
});
|
|
59
|
+
data.push(...scanned);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
38
63
|
return data;
|
|
39
64
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/file-resolver",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-rc.1",
|
|
4
4
|
"description": "The file resolver of markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
@@ -30,15 +30,16 @@
|
|
|
30
30
|
"@types/node": "24.5.1"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@markuplint/html-parser": "5.0.0-
|
|
34
|
-
"@markuplint/ml-ast": "5.0.0-
|
|
35
|
-
"@markuplint/ml-config": "5.0.0-
|
|
36
|
-
"@markuplint/ml-core": "5.0.0-
|
|
37
|
-
"@markuplint/ml-spec": "5.0.0-
|
|
38
|
-
"@markuplint/parser-utils": "5.0.0-
|
|
39
|
-
"@markuplint/
|
|
40
|
-
"@markuplint/
|
|
41
|
-
"
|
|
33
|
+
"@markuplint/html-parser": "5.0.0-rc.1",
|
|
34
|
+
"@markuplint/ml-ast": "5.0.0-rc.1",
|
|
35
|
+
"@markuplint/ml-config": "5.0.0-rc.1",
|
|
36
|
+
"@markuplint/ml-core": "5.0.0-rc.1",
|
|
37
|
+
"@markuplint/ml-spec": "5.0.0-rc.1",
|
|
38
|
+
"@markuplint/parser-utils": "5.0.0-rc.1",
|
|
39
|
+
"@markuplint/pretenders": "5.0.0-rc.1",
|
|
40
|
+
"@markuplint/selector": "5.0.0-rc.1",
|
|
41
|
+
"@markuplint/shared": "5.0.0-rc.1",
|
|
42
|
+
"cosmiconfig": "9.0.1",
|
|
42
43
|
"debug": "4.4.3",
|
|
43
44
|
"glob": "13.0.6",
|
|
44
45
|
"ignore": "7.0.5",
|
|
@@ -46,5 +47,5 @@
|
|
|
46
47
|
"jsonc": "2.0.0",
|
|
47
48
|
"minimatch": "10.2.4"
|
|
48
49
|
},
|
|
49
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "0d6b4324d9a7d6b9e1ba57d4a57e45d36975cba9"
|
|
50
51
|
}
|
package/lib/config-load-error.js
DELETED