Skip to content

Commit 5c21244

Browse files
alexander-akaitlaymonageclaude
authored
feat: allow file:// protocol prefix in absolute paths (#222)
* feat: allow `file://` protocol prefix in absolute paths Paths coming from `import.meta.resolve()` are `file://` URLs, so the `absolutePath` keyword accepts an optional `file://` prefix now. Fixes #209 Recreated from #210, rebased on `main`. Co-Authored-By: Sage Abdullah <laymonage@gmail.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wqLmVHkAGBsWQgXknEQCK * test: cover every webpack option that takes an absolute path Walks webpack's own schema for every option reachable from the root that uses `absolutePath: true` - 34 of them, from `output.path` and `context` to the `module.rules` conditions and the `snapshot` path lists - and checks a `file://` URL validates for each, so the support is verified against the real consumer rather than the fixture schema alone. The options that require a relative path are covered too: the prefix must not make one of them accept an absolute path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wqLmVHkAGBsWQgXknEQCK * chore: add a changeset for the `file://` prefix Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wqLmVHkAGBsWQgXknEQCK * chore: link the changeset to the pull request Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wqLmVHkAGBsWQgXknEQCK --------- Co-authored-by: Sage Abdullah <laymonage@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent fc918a9 commit 5c21244

3 files changed

Lines changed: 140 additions & 1 deletion

File tree

.changeset/olive-files-resolve.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"schema-utils": minor
3+
---
4+
5+
pr: 222
6+
author: laymonage
7+
author: alexander-akait
8+
9+
The `absolutePath` keyword accepts an optional `file://` prefix now, so a path from `import.meta.resolve()` can be passed to an option that takes an absolute path. Options that take a relative path reject such a value instead, they used to accept it.

src/keywords/absolutePath.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ function addAbsolutePathKeyword(ajv) {
7070
passes = false;
7171
}
7272

73+
// (?:file:\/\/)? - optional file:// protocol prefix
7374
// ?:[A-Za-z]:\\ - Windows absolute path
7475
// \\\\ - Windows network absolute path
7576
// \/ - Unix-like OS absolute path
7677
const isCorrectAbsolutePath =
77-
schema === /^(?:[A-Za-z]:(\\|\/)|\\\\|\/)/.test(data);
78+
schema === /^(?:file:\/\/)?(?:[A-Za-z]:(\\|\/)|\\\\|\/)/.test(data);
7879

7980
if (!isCorrectAbsolutePath) {
8081
callback.errors = [getErrorFor(schema, parentSchema, data)];

test/index.test.js

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,14 @@ describe("validation", () => {
325325
testAbsolutePath: "//server/directory/deep/tree",
326326
});
327327

328+
createSuccessTestCase("absolutePath #6", {
329+
testAbsolutePath: "file:///Users/username/directory/deep/tree",
330+
});
331+
332+
createSuccessTestCase("absolutePath #7", {
333+
testAbsolutePath: "file:///C:/directory/deep/tree",
334+
});
335+
328336
createSuccessTestCase("$data", {
329337
dollarData: {
330338
smaller: 5,
@@ -3053,6 +3061,127 @@ describe("validation", () => {
30533061
webpackSchema,
30543062
);
30553063

3064+
// `import.meta.resolve()` returns a `file://` URL, so every option of webpack's own schema that
3065+
// takes an absolute path has to accept one - these are all of them
3066+
const WEBPACK_FILE_URL = "file:///directory/deep/tree";
3067+
3068+
/** @type {Record<string, Record<string, EXPECTED_ANY>>} */
3069+
const webpackAbsolutePathOptions = {
3070+
"cache.cacheDirectory": {
3071+
cache: { type: "filesystem", cacheDirectory: WEBPACK_FILE_URL },
3072+
},
3073+
"cache.cacheLocation": {
3074+
cache: { type: "filesystem", cacheLocation: WEBPACK_FILE_URL },
3075+
},
3076+
"cache.immutablePaths": {
3077+
cache: { type: "filesystem", immutablePaths: [WEBPACK_FILE_URL] },
3078+
},
3079+
"cache.managedPaths": {
3080+
cache: { type: "filesystem", managedPaths: [WEBPACK_FILE_URL] },
3081+
},
3082+
context: { context: WEBPACK_FILE_URL },
3083+
"dotenv.dir": { dotenv: { dir: WEBPACK_FILE_URL } },
3084+
"experiments.buildHttp.cacheLocation": {
3085+
experiments: {
3086+
buildHttp: { allowedUris: [], cacheLocation: WEBPACK_FILE_URL },
3087+
},
3088+
},
3089+
"experiments.buildHttp.lockfileLocation": {
3090+
experiments: {
3091+
buildHttp: { allowedUris: [], lockfileLocation: WEBPACK_FILE_URL },
3092+
},
3093+
},
3094+
"module.defaultRules.exclude": {
3095+
module: { defaultRules: [{ exclude: WEBPACK_FILE_URL }] },
3096+
},
3097+
"module.defaultRules.include": {
3098+
module: { defaultRules: [{ include: WEBPACK_FILE_URL }] },
3099+
},
3100+
"module.defaultRules.issuer": {
3101+
module: { defaultRules: [{ issuer: WEBPACK_FILE_URL }] },
3102+
},
3103+
"module.defaultRules.realResource": {
3104+
module: { defaultRules: [{ realResource: WEBPACK_FILE_URL }] },
3105+
},
3106+
"module.defaultRules.resolve.restrictions": {
3107+
module: {
3108+
defaultRules: [{ resolve: { restrictions: [WEBPACK_FILE_URL] } }],
3109+
},
3110+
},
3111+
"module.defaultRules.resource": {
3112+
module: { defaultRules: [{ resource: WEBPACK_FILE_URL }] },
3113+
},
3114+
"module.defaultRules.test": {
3115+
module: { defaultRules: [{ test: WEBPACK_FILE_URL }] },
3116+
},
3117+
"module.noParse": { module: { noParse: WEBPACK_FILE_URL } },
3118+
"module.noParse[]": { module: { noParse: [WEBPACK_FILE_URL] } },
3119+
"module.rules.exclude": {
3120+
module: { rules: [{ exclude: WEBPACK_FILE_URL }] },
3121+
},
3122+
"module.rules.include": {
3123+
module: { rules: [{ include: WEBPACK_FILE_URL }] },
3124+
},
3125+
"module.rules.issuer": {
3126+
module: { rules: [{ issuer: WEBPACK_FILE_URL }] },
3127+
},
3128+
"module.rules.realResource": {
3129+
module: { rules: [{ realResource: WEBPACK_FILE_URL }] },
3130+
},
3131+
"module.rules.resolve.restrictions": {
3132+
module: { rules: [{ resolve: { restrictions: [WEBPACK_FILE_URL] } }] },
3133+
},
3134+
"module.rules.resource": {
3135+
module: { rules: [{ resource: WEBPACK_FILE_URL }] },
3136+
},
3137+
"module.rules.test": { module: { rules: [{ test: WEBPACK_FILE_URL }] } },
3138+
"output.path": { output: { path: WEBPACK_FILE_URL } },
3139+
recordsInputPath: { recordsInputPath: WEBPACK_FILE_URL },
3140+
recordsOutputPath: { recordsOutputPath: WEBPACK_FILE_URL },
3141+
recordsPath: { recordsPath: WEBPACK_FILE_URL },
3142+
"resolve.restrictions": { resolve: { restrictions: [WEBPACK_FILE_URL] } },
3143+
"resolveLoader.restrictions": {
3144+
resolveLoader: { restrictions: [WEBPACK_FILE_URL] },
3145+
},
3146+
"snapshot.immutablePaths": {
3147+
snapshot: { immutablePaths: [WEBPACK_FILE_URL] },
3148+
},
3149+
"snapshot.managedPaths": { snapshot: { managedPaths: [WEBPACK_FILE_URL] } },
3150+
"snapshot.unmanagedPaths": {
3151+
snapshot: { unmanagedPaths: [WEBPACK_FILE_URL] },
3152+
},
3153+
"stats.context": { stats: { context: WEBPACK_FILE_URL } },
3154+
};
3155+
3156+
for (const [option, config] of Object.entries(webpackAbsolutePathOptions)) {
3157+
createSuccessTestCase(
3158+
`\`file://\` for the webpack option \`${option}\``,
3159+
config,
3160+
{},
3161+
webpackSchema,
3162+
);
3163+
}
3164+
3165+
// The prefix must not turn an option that wants a relative path into an absolute one
3166+
it.each([
3167+
["output.filename", { output: { filename: WEBPACK_FILE_URL } }],
3168+
[
3169+
"output.sourceMapFilename",
3170+
{ output: { sourceMapFilename: WEBPACK_FILE_URL } },
3171+
],
3172+
[
3173+
"output.assetModuleFilename",
3174+
{ output: { assetModuleFilename: WEBPACK_FILE_URL } },
3175+
],
3176+
])(
3177+
"should fail validation for `file://` for the relative webpack option `%s`",
3178+
(option, config) => {
3179+
expect(() => validate(webpackSchema, config)).toThrow(
3180+
/is an absolute path!/,
3181+
);
3182+
},
3183+
);
3184+
30563185
createFailedTestCase(
30573186
"formatExclusiveMaximum #1",
30583187
{

0 commit comments

Comments
 (0)