@asciidoctor/core 3.0.4 → 4.0.0-alpha.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/README.md +42 -10
- package/build/browser/index.js +24156 -0
- package/build/node/index.cjs +24737 -0
- package/{dist/css/asciidoctor.css → data/asciidoctor-default.css} +54 -53
- package/package.json +55 -97
- package/src/abstract_block.js +857 -0
- package/src/abstract_node.js +954 -0
- package/src/attribute_entry.js +12 -0
- package/src/attribute_list.js +380 -0
- package/src/block.js +168 -0
- package/src/browser/asset.js +22 -0
- package/src/browser/reader.js +138 -0
- package/src/browser.js +121 -0
- package/src/callouts.js +85 -0
- package/src/compliance.js +54 -0
- package/src/constants.js +665 -0
- package/src/convert.js +370 -0
- package/src/converter/composite.js +83 -0
- package/src/converter/docbook5.js +1031 -0
- package/src/converter/html5.js +1893 -0
- package/src/converter/manpage.js +935 -0
- package/src/converter/template.js +459 -0
- package/src/converter.js +478 -0
- package/src/data/stylesheet-data.js +2 -0
- package/src/document.js +2134 -0
- package/src/extensions.js +1952 -0
- package/src/footnote.js +28 -0
- package/src/helpers.js +355 -0
- package/src/index.js +138 -0
- package/src/inline.js +158 -0
- package/src/list.js +240 -0
- package/src/load.js +276 -0
- package/src/logging.js +526 -0
- package/src/parser.js +3661 -0
- package/src/path_resolver.js +472 -0
- package/src/reader.js +1755 -0
- package/src/rx.js +829 -0
- package/src/section.js +354 -0
- package/src/stylesheets.js +30 -0
- package/src/substitutors.js +2241 -0
- package/src/syntaxHighlighter/highlightjs.js +90 -0
- package/src/syntaxHighlighter/html_pipeline.js +33 -0
- package/src/syntax_highlighter.js +304 -0
- package/src/table.js +952 -0
- package/src/timings.js +78 -0
- package/types/abstract_block.d.ts +346 -0
- package/types/abstract_node.d.ts +471 -0
- package/types/attribute_entry.d.ts +7 -0
- package/types/attribute_list.d.ts +52 -0
- package/types/block.d.ts +55 -0
- package/types/browser/asset.d.ts +7 -0
- package/types/browser/reader.d.ts +29 -0
- package/types/callouts.d.ts +36 -0
- package/types/compliance.d.ts +23 -0
- package/types/constants.d.ts +268 -0
- package/types/convert.d.ts +34 -0
- package/types/converter/composite.d.ts +20 -0
- package/types/converter/docbook5.d.ts +41 -0
- package/types/converter/html5.d.ts +51 -0
- package/types/converter/manpage.d.ts +59 -0
- package/types/converter/template.d.ts +83 -0
- package/types/converter.d.ts +150 -0
- package/types/data/stylesheet-data.d.ts +2 -0
- package/types/document.d.ts +495 -0
- package/types/extensions.d.ts +876 -0
- package/types/footnote.d.ts +18 -0
- package/types/helpers.d.ts +146 -0
- package/types/index.d.cts +75 -0
- package/types/index.d.ts +73 -3731
- package/types/inline.d.ts +69 -0
- package/types/list.d.ts +114 -0
- package/types/load.d.ts +39 -0
- package/types/logging.d.ts +187 -0
- package/types/parser.d.ts +114 -0
- package/types/path_resolver.d.ts +103 -0
- package/types/reader.d.ts +184 -0
- package/types/rx.d.ts +513 -0
- package/types/section.d.ts +122 -0
- package/types/stylesheets.d.ts +10 -0
- package/types/substitutors.d.ts +208 -0
- package/types/syntaxHighlighter/highlightjs.d.ts +33 -0
- package/types/syntaxHighlighter/html_pipeline.d.ts +16 -0
- package/types/syntax_highlighter.d.ts +167 -0
- package/types/table.d.ts +231 -0
- package/types/timings.d.ts +25 -0
- package/types/tsconfig.json +9 -0
- package/LICENSE +0 -21
- package/dist/browser/asciidoctor.js +0 -47654
- package/dist/browser/asciidoctor.min.js +0 -1452
- package/dist/graalvm/asciidoctor.js +0 -47402
- package/dist/node/asciidoctor.cjs +0 -21567
- package/dist/node/asciidoctor.js +0 -23037
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
// ESM conversion of path_resolver.rb
|
|
2
|
+
//
|
|
3
|
+
// Ruby-to-JavaScript notes:
|
|
4
|
+
// - Ruby's File::ALT_SEPARATOR / File::SEPARATOR / Dir.pwd → process.cwd() on Node.js.
|
|
5
|
+
// - Ruby's Pathname#relative_path_from → manual relative-path computation.
|
|
6
|
+
// - Ruby's require 'pathname' is not needed; logic is inlined.
|
|
7
|
+
// - The Opal / JRuby conditional root? overloads are omitted (Node.js only).
|
|
8
|
+
// - Logging mixin is applied via applyLogging() after class definition.
|
|
9
|
+
|
|
10
|
+
import { applyLogging } from './logging.js'
|
|
11
|
+
import { UriSniffRx } from './rx.js'
|
|
12
|
+
|
|
13
|
+
const DOT = '.'
|
|
14
|
+
const DOT_DOT = '..'
|
|
15
|
+
const DOT_SLASH = './'
|
|
16
|
+
const SLASH = '/'
|
|
17
|
+
const BACKSLASH = '\\'
|
|
18
|
+
const DOUBLE_SLASH = '//'
|
|
19
|
+
const URI_CLASSLOADER = 'uri:classloader:'
|
|
20
|
+
const WINDOWS_ROOT_RX = /^(?:[a-zA-Z]:)?[\\/]/
|
|
21
|
+
|
|
22
|
+
/** Handles all operations for resolving, cleaning and joining paths. */
|
|
23
|
+
export class PathResolver {
|
|
24
|
+
/**
|
|
25
|
+
* Construct a new PathResolver.
|
|
26
|
+
* @param {string|null} fileSeparator - The file separator (default: '/' or '\\' on Windows).
|
|
27
|
+
* @param {string|null} workingDir - The working directory (default: process.cwd()).
|
|
28
|
+
*/
|
|
29
|
+
constructor(fileSeparator = null, workingDir = null) {
|
|
30
|
+
this.fileSeparator = fileSeparator ?? _platformSeparator()
|
|
31
|
+
if (workingDir) {
|
|
32
|
+
this.workingDir = this.root(workingDir)
|
|
33
|
+
? this.posixify(workingDir)
|
|
34
|
+
: _expandPath(workingDir)
|
|
35
|
+
} else {
|
|
36
|
+
this.workingDir = typeof process !== 'undefined' ? process.cwd() : '/'
|
|
37
|
+
}
|
|
38
|
+
this._partitionPathSys = {}
|
|
39
|
+
this._partitionPathWeb = {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check whether the specified path is an absolute path.
|
|
44
|
+
* @param {string} path
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
absolutePath(path) {
|
|
48
|
+
return (
|
|
49
|
+
path.startsWith(SLASH) ||
|
|
50
|
+
(this.fileSeparator === BACKSLASH && WINDOWS_ROOT_RX.test(path)) ||
|
|
51
|
+
UriSniffRx.test(path)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if the specified path is an absolute root path.
|
|
57
|
+
* @param {string} path
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
root(path) {
|
|
61
|
+
return this.absolutePath(path)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Determine if the path is a UNC (root) path.
|
|
66
|
+
* @param {string} path
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
unc(path) {
|
|
70
|
+
return path.startsWith(DOUBLE_SLASH)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Determine if the path is an absolute (root) web path.
|
|
75
|
+
* @param {string} path
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
webRoot(path) {
|
|
79
|
+
return path.startsWith(SLASH)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Determine whether path descends from base.
|
|
84
|
+
* @param {string} path
|
|
85
|
+
* @param {string} base
|
|
86
|
+
* @returns {number|false} Offset if path descends from base, false otherwise.
|
|
87
|
+
*/
|
|
88
|
+
descendsFrom(path, base) {
|
|
89
|
+
if (base === path) return 0
|
|
90
|
+
if (base === SLASH) return path.startsWith(SLASH) ? 1 : false
|
|
91
|
+
return path.startsWith(base + SLASH) ? base.length + 1 : false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Calculate the relative path to this absolute path from the specified base directory.
|
|
96
|
+
* @param {string} path
|
|
97
|
+
* @param {string} base
|
|
98
|
+
* @returns {string} Relative path, or the original path if it cannot be made relative.
|
|
99
|
+
*/
|
|
100
|
+
relativePath(path, base) {
|
|
101
|
+
if (this.root(path)) {
|
|
102
|
+
const posixBase = this.posixify(base)
|
|
103
|
+
const offset = this.descendsFrom(path, posixBase)
|
|
104
|
+
if (offset !== false) return path.slice(offset)
|
|
105
|
+
try {
|
|
106
|
+
return _computeRelativePath(path, posixBase)
|
|
107
|
+
} catch {
|
|
108
|
+
return path
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return path
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Normalize path by converting backslashes to forward slashes.
|
|
116
|
+
* @param {string} path
|
|
117
|
+
* @returns {string} The posixified path.
|
|
118
|
+
*/
|
|
119
|
+
posixify(path) {
|
|
120
|
+
if (!path) return ''
|
|
121
|
+
return this.fileSeparator === BACKSLASH && path.includes(BACKSLASH)
|
|
122
|
+
? path.replace(/\\/g, SLASH)
|
|
123
|
+
: path
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {string} path
|
|
128
|
+
* @returns {string}
|
|
129
|
+
*/
|
|
130
|
+
posixfy(path) {
|
|
131
|
+
return this.posixify(path)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Expand the path by resolving parent references (..) and removing self references (.).
|
|
136
|
+
* @param {string} path
|
|
137
|
+
* @returns {string} The expanded path.
|
|
138
|
+
*/
|
|
139
|
+
expandPath(path) {
|
|
140
|
+
const [pathSegments, pathRoot] = this.partitionPath(path)
|
|
141
|
+
if (path.includes(DOT_DOT)) {
|
|
142
|
+
const resolved = []
|
|
143
|
+
for (const seg of pathSegments) {
|
|
144
|
+
seg === DOT_DOT ? resolved.pop() : resolved.push(seg)
|
|
145
|
+
}
|
|
146
|
+
return this.joinPath(resolved, pathRoot)
|
|
147
|
+
}
|
|
148
|
+
return this.joinPath(pathSegments, pathRoot)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Partition the path into segments and a root prefix.
|
|
153
|
+
* @param {string} path - The path to partition.
|
|
154
|
+
* @param {boolean} [web=false] - Treat as web path.
|
|
155
|
+
* @returns {[string[], string|null]} A 2-item array [segments, root] where root may be null.
|
|
156
|
+
*/
|
|
157
|
+
partitionPath(path, web = false) {
|
|
158
|
+
const cache = web ? this._partitionPathWeb : this._partitionPathSys
|
|
159
|
+
if (cache[path]) return cache[path]
|
|
160
|
+
|
|
161
|
+
const posixPath = this.posixify(path)
|
|
162
|
+
let root = null
|
|
163
|
+
|
|
164
|
+
if (web) {
|
|
165
|
+
if (this.webRoot(posixPath)) {
|
|
166
|
+
root = SLASH
|
|
167
|
+
} else if (posixPath.startsWith(DOT_SLASH)) {
|
|
168
|
+
root = DOT_SLASH
|
|
169
|
+
}
|
|
170
|
+
} else if (this.root(posixPath)) {
|
|
171
|
+
if (this.unc(posixPath)) {
|
|
172
|
+
root = DOUBLE_SLASH
|
|
173
|
+
} else if (posixPath.startsWith(SLASH)) {
|
|
174
|
+
root = SLASH
|
|
175
|
+
} else if (posixPath.startsWith(URI_CLASSLOADER)) {
|
|
176
|
+
root = URI_CLASSLOADER
|
|
177
|
+
} else {
|
|
178
|
+
const extracted = this._extractUriPrefix(posixPath)
|
|
179
|
+
root = Array.isArray(extracted)
|
|
180
|
+
? extracted[1] // URL scheme, e.g. 'http://'
|
|
181
|
+
: posixPath.slice(0, posixPath.indexOf(SLASH) + 1) // Windows drive, e.g. 'C:/'
|
|
182
|
+
}
|
|
183
|
+
} else if (posixPath.startsWith(DOT_SLASH)) {
|
|
184
|
+
root = DOT_SLASH
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const relative = root ? posixPath.slice(root.length) : posixPath
|
|
188
|
+
let segments = relative.split(SLASH).filter((s) => s !== DOT && s !== '')
|
|
189
|
+
// Re-add non-empty-string DOT segments removal is as above; preserve empty for UNC
|
|
190
|
+
segments = relative.split(SLASH).filter((s) => s !== DOT)
|
|
191
|
+
// Remove any empty segments (trailing slash artifacts) except retain intent
|
|
192
|
+
segments = segments.filter((s) => s !== '')
|
|
193
|
+
|
|
194
|
+
const result = [segments, root]
|
|
195
|
+
cache[path] = result
|
|
196
|
+
return result
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Join segments with posix separator, prepending root if provided.
|
|
201
|
+
* @param {string[]} segments
|
|
202
|
+
* @param {string|null} [root=null]
|
|
203
|
+
* @returns {string} The joined path.
|
|
204
|
+
*/
|
|
205
|
+
joinPath(segments, root = null) {
|
|
206
|
+
return root ? `${root}${segments.join(SLASH)}` : segments.join(SLASH)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Securely resolve a system path.
|
|
211
|
+
* @param {string} target - The target path.
|
|
212
|
+
* @param {string|null} [start=null] - The start path.
|
|
213
|
+
* @param {string|null} [jail=null] - The jail path.
|
|
214
|
+
* @param {Object} [opts={}] - Options.
|
|
215
|
+
* @param {boolean} [opts.recover=true] - Recover from jail escapes instead of throwing.
|
|
216
|
+
* @param {string} [opts.targetName='path'] - Name used in error messages.
|
|
217
|
+
* @returns {string} An absolute posix path.
|
|
218
|
+
*/
|
|
219
|
+
systemPath(target, start = null, jail = null, opts = {}) {
|
|
220
|
+
const recover = opts.recover !== false
|
|
221
|
+
const targetName = opts.targetName ?? opts.target_name ?? 'path'
|
|
222
|
+
|
|
223
|
+
if (jail) {
|
|
224
|
+
if (!this.root(jail))
|
|
225
|
+
throw new Error(`Jail is not an absolute path: ${jail}`)
|
|
226
|
+
jail = this.posixify(jail)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let targetSegments
|
|
230
|
+
if (target) {
|
|
231
|
+
if (this.root(target)) {
|
|
232
|
+
const targetPath = this.expandPath(target)
|
|
233
|
+
if (jail && this.descendsFrom(targetPath, jail) === false) {
|
|
234
|
+
if (!recover)
|
|
235
|
+
throw new SecurityError(
|
|
236
|
+
`${targetName} ${target} is outside of jail: ${jail} (disallowed in safe mode)`
|
|
237
|
+
)
|
|
238
|
+
this.logger.warn(
|
|
239
|
+
`${targetName} is outside of jail; recovering automatically`
|
|
240
|
+
)
|
|
241
|
+
const [ts] = this.partitionPath(targetPath)
|
|
242
|
+
const [js, jr] = this.partitionPath(jail)
|
|
243
|
+
return this.joinPath(js.concat(ts), jr)
|
|
244
|
+
}
|
|
245
|
+
return targetPath
|
|
246
|
+
}
|
|
247
|
+
;[targetSegments] = this.partitionPath(target)
|
|
248
|
+
} else {
|
|
249
|
+
targetSegments = []
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let startSegments, jailRoot, recheck
|
|
253
|
+
|
|
254
|
+
if (targetSegments.length === 0) {
|
|
255
|
+
if (!start) {
|
|
256
|
+
return jail ?? this.workingDir
|
|
257
|
+
} else if (this.root(start)) {
|
|
258
|
+
if (!jail) return this.expandPath(start)
|
|
259
|
+
start = this.posixify(start)
|
|
260
|
+
} else {
|
|
261
|
+
;[targetSegments] = this.partitionPath(start)
|
|
262
|
+
start = jail ?? this.workingDir
|
|
263
|
+
}
|
|
264
|
+
} else if (!start) {
|
|
265
|
+
start = jail ?? this.workingDir
|
|
266
|
+
} else if (this.root(start)) {
|
|
267
|
+
if (jail) start = this.posixify(start)
|
|
268
|
+
} else {
|
|
269
|
+
start = `${(jail ?? this.workingDir).replace(/\/$/, '')}/${start}`
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Check if start is within jail
|
|
273
|
+
if (
|
|
274
|
+
jail &&
|
|
275
|
+
(recheck = this.descendsFrom(start, jail) === false) &&
|
|
276
|
+
this.fileSeparator === BACKSLASH
|
|
277
|
+
) {
|
|
278
|
+
const [ss, sr] = this.partitionPath(start)
|
|
279
|
+
const [js, jr] = this.partitionPath(jail)
|
|
280
|
+
if (sr !== jr) {
|
|
281
|
+
if (!recover)
|
|
282
|
+
throw new SecurityError(
|
|
283
|
+
`start path for ${targetName} ${start} refers to location outside jail root: ${jail} (disallowed in safe mode)`
|
|
284
|
+
)
|
|
285
|
+
this.logger.warn(
|
|
286
|
+
`start path for ${targetName} is outside of jail root; recovering automatically`
|
|
287
|
+
)
|
|
288
|
+
startSegments = js
|
|
289
|
+
jailRoot = jr
|
|
290
|
+
recheck = false
|
|
291
|
+
} else {
|
|
292
|
+
;[startSegments, jailRoot] = [ss, sr]
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
;[startSegments, jailRoot] = this.partitionPath(start)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let resolvedSegments = startSegments.concat(targetSegments)
|
|
299
|
+
|
|
300
|
+
if (resolvedSegments.includes(DOT_DOT)) {
|
|
301
|
+
const unresolved = resolvedSegments
|
|
302
|
+
resolvedSegments = []
|
|
303
|
+
|
|
304
|
+
if (jail) {
|
|
305
|
+
let jailSegments
|
|
306
|
+
;[jailSegments] = this.partitionPath(jail)
|
|
307
|
+
let warned = false
|
|
308
|
+
for (const seg of unresolved) {
|
|
309
|
+
if (seg === DOT_DOT) {
|
|
310
|
+
if (resolvedSegments.length > jailSegments.length) {
|
|
311
|
+
resolvedSegments.pop()
|
|
312
|
+
} else if (recover) {
|
|
313
|
+
if (!warned) {
|
|
314
|
+
this.logger.warn(
|
|
315
|
+
`${targetName} has illegal reference to ancestor of jail; recovering automatically`
|
|
316
|
+
)
|
|
317
|
+
warned = true
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
throw new SecurityError(
|
|
321
|
+
`${targetName} ${target} refers to location outside jail: ${jail} (disallowed in safe mode)`
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
resolvedSegments.push(seg)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
for (const seg of unresolved) {
|
|
330
|
+
seg === DOT_DOT ? resolvedSegments.pop() : resolvedSegments.push(seg)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (recheck) {
|
|
336
|
+
const targetPath = this.joinPath(resolvedSegments, jailRoot)
|
|
337
|
+
if (this.descendsFrom(targetPath, jail) !== false) {
|
|
338
|
+
return targetPath
|
|
339
|
+
} else if (recover) {
|
|
340
|
+
this.logger.warn(
|
|
341
|
+
`${targetName} is outside of jail; recovering automatically`
|
|
342
|
+
)
|
|
343
|
+
const [jailSegments] = this.partitionPath(jail)
|
|
344
|
+
return this.joinPath(jailSegments.concat(targetSegments), jailRoot)
|
|
345
|
+
} else {
|
|
346
|
+
throw new SecurityError(
|
|
347
|
+
`${targetName} ${target} is outside of jail: ${jail} (disallowed in safe mode)`
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return this.joinPath(resolvedSegments, jailRoot)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Resolve a web path from the target and start paths.
|
|
357
|
+
* @param {string} target - The target path.
|
|
358
|
+
* @param {string|null} [start=null] - The start (parent) path.
|
|
359
|
+
* @returns {string} Path with parent references resolved and self references removed.
|
|
360
|
+
*/
|
|
361
|
+
webPath(target, start = null) {
|
|
362
|
+
target = this.posixify(target)
|
|
363
|
+
start = this.posixify(start)
|
|
364
|
+
|
|
365
|
+
let uriPrefix = null
|
|
366
|
+
if (start && !this.webRoot(target)) {
|
|
367
|
+
const combined = `${start}${start.endsWith(SLASH) ? '' : SLASH}${target}`
|
|
368
|
+
const extracted = this._extractUriPrefix(combined)
|
|
369
|
+
if (Array.isArray(extracted)) {
|
|
370
|
+
;[target, uriPrefix] = extracted
|
|
371
|
+
} else {
|
|
372
|
+
target = extracted
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const [targetSegments, targetRoot] = this.partitionPath(target, true)
|
|
377
|
+
const resolved = []
|
|
378
|
+
for (const seg of targetSegments) {
|
|
379
|
+
if (seg === DOT_DOT) {
|
|
380
|
+
if (resolved.length === 0) {
|
|
381
|
+
if (!targetRoot || targetRoot === DOT_SLASH) resolved.push(seg)
|
|
382
|
+
} else if (resolved[resolved.length - 1] === DOT_DOT) {
|
|
383
|
+
resolved.push(seg)
|
|
384
|
+
} else {
|
|
385
|
+
resolved.pop()
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
resolved.push(seg)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let resolvedPath = this.joinPath(resolved, targetRoot)
|
|
393
|
+
if (resolvedPath.includes(' '))
|
|
394
|
+
resolvedPath = resolvedPath.replace(/ /g, '%20')
|
|
395
|
+
|
|
396
|
+
return uriPrefix ? `${uriPrefix}${resolvedPath}` : resolvedPath
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Extract the URI prefix from a string if it is a URI.
|
|
401
|
+
* @param {string} str
|
|
402
|
+
* @returns {[string, string]|string} [string_without_prefix, prefix] if URI, or the original string.
|
|
403
|
+
* @internal
|
|
404
|
+
*/
|
|
405
|
+
_extractUriPrefix(str) {
|
|
406
|
+
if (str.includes(':')) {
|
|
407
|
+
const m = str.match(UriSniffRx)
|
|
408
|
+
if (m) return [str.slice(m[0].length), m[0]]
|
|
409
|
+
}
|
|
410
|
+
return str
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
applyLogging(PathResolver.prototype)
|
|
415
|
+
|
|
416
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* @returns {string}
|
|
420
|
+
* @internal
|
|
421
|
+
*/
|
|
422
|
+
function _platformSeparator() {
|
|
423
|
+
if (typeof process !== 'undefined' && process.platform === 'win32')
|
|
424
|
+
return '\\'
|
|
425
|
+
return '/'
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* @param {string} p
|
|
430
|
+
* @returns {string}
|
|
431
|
+
* @internal
|
|
432
|
+
*/
|
|
433
|
+
function _expandPath(p) {
|
|
434
|
+
if (typeof process !== 'undefined') {
|
|
435
|
+
// Lazy import to avoid top-level await
|
|
436
|
+
try {
|
|
437
|
+
const path = require('node:path')
|
|
438
|
+
return path.resolve(p).replace(/\\/g, '/')
|
|
439
|
+
} catch {}
|
|
440
|
+
}
|
|
441
|
+
return p
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* @param {string} target
|
|
446
|
+
* @param {string} base
|
|
447
|
+
* @returns {string}
|
|
448
|
+
* @internal
|
|
449
|
+
*/
|
|
450
|
+
function _computeRelativePath(target, base) {
|
|
451
|
+
const targetParts = target.split('/').filter(Boolean)
|
|
452
|
+
const baseParts = base.split('/').filter(Boolean)
|
|
453
|
+
let common = 0
|
|
454
|
+
while (
|
|
455
|
+
common < targetParts.length &&
|
|
456
|
+
common < baseParts.length &&
|
|
457
|
+
targetParts[common] === baseParts[common]
|
|
458
|
+
) {
|
|
459
|
+
common++
|
|
460
|
+
}
|
|
461
|
+
const up = baseParts.length - common
|
|
462
|
+
const down = targetParts.slice(common)
|
|
463
|
+
return [...Array(up).fill('..'), ...down].join('/') || '.'
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Simple SecurityError class (Ruby raises SecurityError).
|
|
467
|
+
class SecurityError extends Error {
|
|
468
|
+
constructor(msg) {
|
|
469
|
+
super(msg)
|
|
470
|
+
this.name = 'SecurityError'
|
|
471
|
+
}
|
|
472
|
+
}
|