@markuplint/file-resolver 5.0.0-rc.0 → 5.0.0-rc.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 CHANGED
@@ -3,6 +3,22 @@
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.2](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2026-04-15)
7
+
8
+ - feat(file-resolver)!: remove deprecated autoLoad parameter from resolveRules ([71cab2d](https://github.com/markuplint/markuplint/commit/71cab2d5fe5bedb3e28b162666cbad3d0a2773ff))
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ - The autoLoad parameter has been removed from resolveRules().
13
+ Rules are now always auto-loaded unconditionally.
14
+
15
+ # [5.0.0-rc.1](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.0...v5.0.0-rc.1) (2026-03-27)
16
+
17
+ ### Bug Fixes
18
+
19
+ - **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)
20
+ - **file-resolver:** resolve package subpaths before import to avoid runtime-specific errors ([cb2b641](https://github.com/markuplint/markuplint/commit/cb2b641e25cd9de487d395b6f219dd8b60ecd306))
21
+
6
22
  # [5.0.0-rc.0](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.3...v5.0.0-rc.0) (2026-03-12)
7
23
 
8
24
  ### Bug Fixes
@@ -1,7 +1,4 @@
1
1
  import type { Ruleset, AnyMLRule } from '@markuplint/ml-core';
2
- /**
3
- * @deprecated
4
- */
5
2
  export declare function autoLoadRules(ruleset: Ruleset): Promise<{
6
3
  rules: AnyMLRule[];
7
4
  errors: unknown[];
@@ -1,8 +1,5 @@
1
1
  import { MLRule } from '@markuplint/ml-core';
2
2
  const cache = new Map();
3
- /**
4
- * @deprecated
5
- */
6
3
  export async function autoLoadRules(ruleset) {
7
4
  const rules = [];
8
5
  const errors = [];
@@ -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';
@@ -1,5 +1,5 @@
1
1
  import type { LoaderSync } from 'cosmiconfig';
2
- import { ConfigLoadError } from './config-load-error.js';
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;
@@ -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 './config-load-error.js';
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) => {
@@ -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
- const file = await fs.readFile(name, 'utf8');
68
- const json = JSON.parse(file);
69
- gLogSuccess('Success by readFile("%s") and JSON.parse: %O', name, json);
70
- cache.set(name, json);
71
- return json;
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
+ }
@@ -6,13 +6,6 @@ import type { AnyMLRule, Ruleset, Plugin } from '@markuplint/ml-core';
6
6
  * @param plugins - The resolved plugins that may provide custom rules
7
7
  * @param ruleset - The current ruleset (used for auto-loading)
8
8
  * @param importPreset - Whether to import the built-in preset rules from `@markuplint/rules`
9
- * @param autoLoad - Whether to auto-load rules referenced in the ruleset
10
9
  * @returns An array of all resolved MLRule instances
11
- *
12
- * @deprecated The `autoLoad` parameter is deprecated
13
- */
14
- export declare function resolveRules(plugins: readonly Plugin[], ruleset: Ruleset, importPreset: boolean,
15
- /**
16
- * @deprecated
17
10
  */
18
- autoLoad: boolean): Promise<Readonly<AnyMLRule>[]>;
11
+ export declare function resolveRules(plugins: readonly Plugin[], ruleset: Ruleset, importPreset: boolean): Promise<Readonly<AnyMLRule>[]>;
@@ -8,16 +8,9 @@ let cachedPresetRules = null;
8
8
  * @param plugins - The resolved plugins that may provide custom rules
9
9
  * @param ruleset - The current ruleset (used for auto-loading)
10
10
  * @param importPreset - Whether to import the built-in preset rules from `@markuplint/rules`
11
- * @param autoLoad - Whether to auto-load rules referenced in the ruleset
12
11
  * @returns An array of all resolved MLRule instances
13
- *
14
- * @deprecated The `autoLoad` parameter is deprecated
15
- */
16
- export async function resolveRules(plugins, ruleset, importPreset,
17
- /**
18
- * @deprecated
19
12
  */
20
- autoLoad) {
13
+ export async function resolveRules(plugins, ruleset, importPreset) {
21
14
  const rules = importPreset ? await importPresetRules() : [];
22
15
  for (const plugin of plugins) {
23
16
  if (!plugin.rules) {
@@ -31,11 +24,9 @@ autoLoad) {
31
24
  rules.push(rule);
32
25
  }
33
26
  }
34
- if (autoLoad) {
35
- const { rules: additionalRules } = await autoLoadRules(ruleset);
36
- for (const rule of additionalRules) {
37
- rules.push(rule);
38
- }
27
+ const { rules: additionalRules } = await autoLoadRules(ruleset);
28
+ for (const rule of additionalRules) {
29
+ rules.push(rule);
39
30
  }
40
31
  // Clone
41
32
  return [...rules];
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@markuplint/file-resolver",
3
- "version": "5.0.0-rc.0",
3
+ "version": "5.0.0-rc.2",
4
4
  "description": "The file resolver of markuplint",
5
- "repository": "git@github.com:markuplint/markuplint.git",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/markuplint/markuplint.git",
8
+ "directory": "packages/@markuplint/file-resolver"
9
+ },
6
10
  "author": "Yusuke Hirao <yusukehirao@me.com>",
7
11
  "license": "MIT",
8
12
  "engines": {
@@ -30,22 +34,22 @@
30
34
  "@types/node": "24.5.1"
31
35
  },
32
36
  "dependencies": {
33
- "@markuplint/html-parser": "5.0.0-rc.0",
34
- "@markuplint/ml-ast": "5.0.0-rc.0",
35
- "@markuplint/ml-config": "5.0.0-rc.0",
36
- "@markuplint/ml-core": "5.0.0-rc.0",
37
- "@markuplint/ml-spec": "5.0.0-rc.0",
38
- "@markuplint/parser-utils": "5.0.0-rc.0",
39
- "@markuplint/pretenders": "5.0.0-rc.0",
40
- "@markuplint/selector": "5.0.0-rc.0",
41
- "@markuplint/shared": "5.0.0-rc.0",
37
+ "@markuplint/html-parser": "5.0.0-rc.2",
38
+ "@markuplint/ml-ast": "5.0.0-rc.2",
39
+ "@markuplint/ml-config": "5.0.0-rc.2",
40
+ "@markuplint/ml-core": "5.0.0-rc.2",
41
+ "@markuplint/ml-spec": "5.0.0-rc.2",
42
+ "@markuplint/parser-utils": "5.0.0-rc.2",
43
+ "@markuplint/pretenders": "5.0.0-rc.2",
44
+ "@markuplint/selector": "5.0.0-rc.2",
45
+ "@markuplint/shared": "5.0.0-rc.2",
42
46
  "cosmiconfig": "9.0.1",
43
47
  "debug": "4.4.3",
44
48
  "glob": "13.0.6",
45
49
  "ignore": "7.0.5",
46
50
  "import-meta-resolve": "4.2.0",
47
51
  "jsonc": "2.0.0",
48
- "minimatch": "10.2.4"
52
+ "minimatch": "10.2.5"
49
53
  },
50
- "gitHead": "ebf4d7cfca0c259aead3b292c6b8a202db4cd802"
54
+ "gitHead": "e43763858d9234c417053becc73dbd088c1e7ea6"
51
55
  }
@@ -1,6 +0,0 @@
1
- export declare class ConfigLoadError extends Error {
2
- filePath: string;
3
- name: string;
4
- referrer: string;
5
- constructor(message: string, filePath: string, referrer: string);
6
- }
@@ -1,10 +0,0 @@
1
- export class ConfigLoadError extends Error {
2
- filePath;
3
- name = 'ConfigLoadError';
4
- referrer;
5
- constructor(message, filePath, referrer) {
6
- super(message + ` in ${referrer}`);
7
- this.filePath = filePath;
8
- this.referrer = referrer;
9
- }
10
- }