nice-path 0.1.1 → 2.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/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "nice-path",
3
- "version": "0.1.1",
3
+ "version": "2.0.0",
4
4
  "main": "dist/index.js",
5
- "repository": "suchipi/nice-path",
6
- "author": "Lily Scott <me@suchipi.com>",
5
+ "types": "dist/index.d.ts",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/suchipi/nice-path.git"
9
+ },
10
+ "author": "Lily Skye <me@suchipi.com>",
7
11
  "license": "MIT",
8
12
  "keywords": [
9
13
  "path",
@@ -13,17 +17,14 @@
13
17
  "file"
14
18
  ],
15
19
  "devDependencies": {
16
- "@types/jest": "^24.0.18",
17
- "@types/node": "^9.6.22",
18
- "jest": "^24.9.0",
19
- "mock-fs": "^4.10.3",
20
- "prettier": "^1.18.2",
21
- "ts-jest": "^24.1.0",
22
- "typescript": "^3.6.4"
20
+ "@types/node": "^20.11.0",
21
+ "prettier": "^3.2.2",
22
+ "typescript": "^5.3.3",
23
+ "vitest": "^1.2.0"
23
24
  },
24
25
  "scripts": {
25
- "build": "rm -rf dist && tsc && cp src/index.ts dist/index.ts",
26
+ "build": "rm -rf dist && tsc",
26
27
  "build:watch": "tsc -w",
27
- "test": "jest"
28
+ "test": "vitest"
28
29
  }
29
30
  }
package/tsconfig.json CHANGED
@@ -3,10 +3,11 @@
3
3
  "exclude": ["./src/**/*.test.ts"],
4
4
 
5
5
  "compilerOptions": {
6
- "lib": ["es2015"],
7
- "target": "es5",
6
+ "lib": ["es2020"],
7
+ "target": "es2020",
8
8
  "module": "commonjs",
9
9
  "outDir": "./dist",
10
+ "declaration": true,
10
11
 
11
12
  "strict": true,
12
13
  "noImplicitAny": true,
package/dist/index.ts DELETED
@@ -1,506 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import util from "util";
4
-
5
- const readdirP = util.promisify(fs.readdir);
6
-
7
- function aOrAn(nextWord: string) {
8
- return /^[aeiou]/i.test(nextWord) ? "an" : "a";
9
- }
10
-
11
- function pathKindErrorMessage(expected: string, actual: string): string {
12
- return `Expected to receive ${aOrAn(
13
- expected
14
- )} ${expected} path, but received ${aOrAn(actual)} ${actual} path instead`;
15
- }
16
-
17
- function comparisonRootKindMismatchErrorMessage(
18
- firstPath: string,
19
- secondPath: string
20
- ) {
21
- const firstRootKind = detectRootKind(firstPath);
22
- const secondRootKind = detectRootKind(secondPath);
23
-
24
- return (
25
- `The two paths being compared are not in the same namespace; ` +
26
- `the first path (${JSON.stringify(
27
- firstPath
28
- )}) has root kind ${JSON.stringify(firstRootKind)} ` +
29
- `but the second path (${JSON.stringify(secondPath)}) has root kind ` +
30
- `'${JSON.stringify(secondRootKind)}'.`
31
- );
32
- }
33
-
34
- function getSeparator(pathString: string): string {
35
- let sep = "/";
36
- if (!pathString.includes(sep)) {
37
- sep = "\\";
38
- if (!pathString.includes(sep)) {
39
- sep = path.sep;
40
- }
41
- }
42
- return sep;
43
- }
44
-
45
- function getSegments(pathString: string): Array<string> {
46
- const sep = getSeparator(pathString);
47
- const segments = pathString.split(sep).filter(Boolean);
48
- return segments;
49
- }
50
-
51
- // Normal path.join always uses path.sep and removes leading . or .. segments,
52
- // so this is a safer alternative.
53
- function safeJoin(pathsOrSegments: Array<string>, sep: string) {
54
- const trimmedSegments = pathsOrSegments.map(
55
- (pathOrSegment, pathOrSegmentIndex) =>
56
- pathOrSegment
57
- .split(sep)
58
- .filter((segment) => {
59
- if (pathOrSegmentIndex === 0) {
60
- return true;
61
- } else {
62
- return Boolean(segment);
63
- }
64
- })
65
- .join(sep)
66
- );
67
-
68
- return trimmedSegments.join(sep);
69
- }
70
-
71
- function detectPathKind(pathString: string) {
72
- const sep = getSeparator(pathString);
73
- const segments = getSegments(pathString);
74
-
75
- if (
76
- pathString.startsWith(sep) ||
77
- pathString.startsWith("\\\\") ||
78
- pathString.match(/^[A-Z]:/i)
79
- ) {
80
- return "absolute";
81
- } else if (segments[0] === "." || segments[0] === "..") {
82
- return "relative";
83
- } else {
84
- return "unqualified";
85
- }
86
- }
87
-
88
- function detectRootKind(
89
- pathString: string
90
- ): "leading-slash" | "letter-drive" | "unc" {
91
- const sep = getSeparator(pathString);
92
- if (pathString.match(/^[A-Z]:/i)) {
93
- return "letter-drive";
94
- } else if (pathString.startsWith("\\\\")) {
95
- return "unc";
96
- } else if (pathString.startsWith(sep)) {
97
- return "leading-slash";
98
- } else {
99
- throw new Error(
100
- `Unable to detect root kind from path string: '${pathString}'. Is it an absolute path?`
101
- );
102
- }
103
- }
104
-
105
- function reconsistuteAbsolutePath(
106
- originalRaw: string,
107
- newSegments: Array<string>
108
- ) {
109
- const sep = getSeparator(originalRaw);
110
- const rootKind = detectRootKind(originalRaw);
111
-
112
- let suffix = safeJoin(newSegments, sep);
113
-
114
- let prefix = "";
115
- switch (rootKind) {
116
- case "leading-slash": {
117
- prefix = sep;
118
- break;
119
- }
120
- case "unc": {
121
- prefix = sep.repeat(2);
122
- break;
123
- }
124
- }
125
-
126
- return prefix + suffix;
127
- }
128
-
129
- export class Path<Kind extends "absolute" | "relative" | "unqualified"> {
130
- kind: Kind;
131
- raw: string;
132
-
133
- constructor(raw: string, kind: Kind) {
134
- this.raw = raw;
135
- this.kind = kind;
136
- }
137
-
138
- toString() {
139
- return this.raw;
140
- }
141
-
142
- static fromAbsolutePathString(absolutePath: string): AbsolutePath {
143
- const kind = detectPathKind(absolutePath);
144
- if (kind !== "absolute") {
145
- throw new Error(pathKindErrorMessage("absolute", kind));
146
- }
147
-
148
- return new AbsolutePath(absolutePath);
149
- }
150
-
151
- static fromRelativePathString(relativePath: string) {
152
- const kind = detectPathKind(relativePath);
153
- if (kind !== "relative") {
154
- throw new Error(pathKindErrorMessage("relative", kind));
155
- }
156
-
157
- return new RelativePath(relativePath);
158
- }
159
-
160
- static fromUnqualifiedPathString(unqualifiedPath: string) {
161
- const kind = detectPathKind(unqualifiedPath);
162
- if (kind !== "unqualified") {
163
- throw new Error(pathKindErrorMessage("unqualified", kind));
164
- }
165
-
166
- return new UnqualifiedPath(unqualifiedPath);
167
- }
168
-
169
- static from(raw: string) {
170
- const kind = detectPathKind(raw);
171
- switch (kind) {
172
- case "absolute":
173
- return new AbsolutePath(raw);
174
- case "relative":
175
- return new RelativePath(raw);
176
- case "unqualified":
177
- return new UnqualifiedPath(raw);
178
- default: {
179
- throw new Error("Unable to detect path type from input string");
180
- }
181
- }
182
- }
183
-
184
- isAbsolute(): this is AbsolutePath {
185
- return this.kind === "absolute";
186
- }
187
-
188
- isRelative(): this is RelativePath {
189
- return this.kind === "relative";
190
- }
191
-
192
- isUnqualified(): this is UnqualifiedPath {
193
- return this.kind === "unqualified";
194
- }
195
-
196
- hasTrailingSlash(): boolean {
197
- const sep = getSeparator(this.raw);
198
- return this.raw.endsWith(sep);
199
- }
200
-
201
- removeTrailingSlash(): Path<Kind> {
202
- if (!this.hasTrailingSlash()) {
203
- return this;
204
- } else {
205
- return new Path<Kind>(this.raw.replace(/(?:[/\\])+$/, ""), this.kind);
206
- }
207
- }
208
-
209
- append(...segments: Array<string>): Path<Kind> {
210
- const sep = getSeparator(this.raw);
211
- return new Path<Kind>(safeJoin([this.raw, ...segments], sep), this.kind);
212
- }
213
-
214
- resolve(): Path<Kind> {
215
- const segments = getSegments(this.raw);
216
- const nextSegments = [];
217
-
218
- for (let i = 0; i < segments.length; i++) {
219
- const currentSegment = segments[i];
220
-
221
- if (currentSegment === "." && i !== 0) {
222
- continue;
223
- } else if (currentSegment === ".." && nextSegments.length > 0) {
224
- nextSegments.pop();
225
- } else {
226
- nextSegments.push(currentSegment);
227
- }
228
- }
229
-
230
- const nextRaw = reconsistuteAbsolutePath(this.raw, nextSegments);
231
- return new Path<Kind>(nextRaw, this.kind);
232
- }
233
- }
234
-
235
- export class AbsolutePath extends Path<"absolute"> {
236
- constructor(raw: string) {
237
- super(raw, "absolute");
238
- }
239
-
240
- relativeTo(relativeTo: AbsolutePath | string) {
241
- if (typeof relativeTo === "string") {
242
- const pathKind = detectPathKind(relativeTo);
243
- if (pathKind !== "absolute") {
244
- throw new Error(pathKindErrorMessage("absolute", pathKind));
245
- }
246
- }
247
-
248
- let relativeToString =
249
- typeof relativeTo === "string" ? relativeTo : relativeTo.raw;
250
-
251
- const thisRootKind = detectRootKind(this.raw);
252
- const relativeToRootKind = detectRootKind(relativeToString);
253
-
254
- if (thisRootKind !== relativeToRootKind) {
255
- throw new Error(
256
- comparisonRootKindMismatchErrorMessage(this.raw, relativeToString)
257
- );
258
- }
259
-
260
- if (
261
- thisRootKind === "letter-drive" &&
262
- relativeToRootKind === "letter-drive" &&
263
- this.raw.slice(0, 1).toUpperCase() !==
264
- relativeToString.slice(0, 1).toUpperCase()
265
- ) {
266
- throw new Error(
267
- `The two paths being compared are not on the same drive; comparing ` +
268
- JSON.stringify(this.raw) +
269
- " and " +
270
- JSON.stringify(relativeToString)
271
- );
272
- }
273
-
274
- const thisSegments = getSegments(this.raw);
275
- const relativeToSegments = getSegments(relativeToString);
276
-
277
- if (
278
- thisRootKind === "unc" &&
279
- relativeToRootKind === "unc" &&
280
- thisSegments[0] !== relativeToSegments[0]
281
- ) {
282
- throw new Error(
283
- "The two paths being compared are not on the same UNC host; comparing " +
284
- JSON.stringify(this.raw) +
285
- " and " +
286
- JSON.stringify(relativeToString)
287
- );
288
- }
289
-
290
- const longerLength = Math.max(
291
- thisSegments.length,
292
- relativeToSegments.length
293
- );
294
- let lastSharedSegmentIndex = -1;
295
- for (let i = 0; i < longerLength; i++) {
296
- const thisSegment = thisSegments[i];
297
- const relativeToSegment = relativeToSegments[i];
298
-
299
- if (thisSegment === relativeToSegment) {
300
- lastSharedSegmentIndex = i;
301
- } else {
302
- break;
303
- }
304
- }
305
-
306
- let nextSegments = [];
307
- if (lastSharedSegmentIndex === -1) {
308
- // No shared commonality between the paths
309
- nextSegments = relativeToSegments.map(() => "..");
310
- nextSegments.push(...thisSegments);
311
- } else {
312
- const remainingThisSegments = [];
313
- for (let i = 0; i < thisSegments.length; i++) {
314
- if (i > lastSharedSegmentIndex) {
315
- remainingThisSegments.push(thisSegments[i]);
316
- }
317
- }
318
-
319
- const remainingRelativeToSegments = [];
320
-
321
- for (let i = 0; i < relativeToSegments.length; i++) {
322
- if (i > lastSharedSegmentIndex) {
323
- remainingRelativeToSegments.push(thisSegments[i]);
324
- }
325
- }
326
-
327
- nextSegments.push(
328
- ...remainingRelativeToSegments.map(() => ".."),
329
- ...remainingThisSegments
330
- );
331
- }
332
-
333
- if (nextSegments[0] !== "..") {
334
- nextSegments = [".", ...nextSegments];
335
- }
336
-
337
- const sep = getSeparator(this.raw);
338
- const raw = safeJoin(nextSegments, sep);
339
-
340
- return new RelativePath(raw);
341
- }
342
-
343
- append(...segments: Array<string>): AbsolutePath {
344
- const normalPath = super.append(...segments);
345
- return new AbsolutePath(normalPath.raw);
346
- }
347
-
348
- removeTrailingSlash(): AbsolutePath {
349
- const normalPath = super.removeTrailingSlash();
350
- return new AbsolutePath(normalPath.raw);
351
- }
352
-
353
- resolve(): AbsolutePath {
354
- const normalPath = super.resolve();
355
- return new AbsolutePath(normalPath.raw);
356
- }
357
-
358
- async readdir(
359
- options?:
360
- | {
361
- encoding: string | null;
362
- }
363
- | string
364
- | null
365
- | undefined
366
- ): Promise<Array<AbsolutePath>> {
367
- const dirs = await readdirP(this.raw, options);
368
- // @ts-ignore
369
- return dirs.map(
370
- (dir: string | Buffer) =>
371
- new AbsolutePath(path.join(this.raw, dir.toString("utf-8")))
372
- );
373
- }
374
-
375
- readdirSync(
376
- options?:
377
- | {
378
- encoding: string | null;
379
- }
380
- | string
381
- | null
382
- | undefined
383
- ): Array<AbsolutePath> {
384
- const dirs = fs.readdirSync(this.raw, options);
385
- // @ts-ignore
386
- return dirs.map(
387
- (dir: string | Buffer) =>
388
- new AbsolutePath(path.join(this.raw, dir.toString("utf-8")))
389
- );
390
- }
391
-
392
- parentDirectory(): AbsolutePath {
393
- const segments = getSegments(this.raw);
394
- const nextSegments = segments.slice(0, -1);
395
- const nextRaw = reconsistuteAbsolutePath(this.raw, nextSegments);
396
-
397
- return new AbsolutePath(nextRaw);
398
- }
399
- }
400
-
401
- export class RelativePath extends Path<"relative"> {
402
- constructor(raw: string) {
403
- super(raw, "relative");
404
- }
405
-
406
- toAbsolute(contextDir: AbsolutePath | string) {
407
- const contextDirPath =
408
- typeof contextDir === "string"
409
- ? Path.fromAbsolutePathString(contextDir)
410
- : contextDir;
411
-
412
- let thisSegments = getSegments(this.raw);
413
-
414
- let nextSegments = [
415
- contextDirPath.removeTrailingSlash().raw,
416
- ...thisSegments,
417
- ];
418
-
419
- return new AbsolutePath(
420
- safeJoin(nextSegments, getSeparator(contextDirPath.raw))
421
- ).resolve();
422
- }
423
-
424
- toUnqualified() {
425
- const segments = getSegments(this.raw);
426
- const sep = getSeparator(this.raw);
427
-
428
- let firstNonRelativeSegmentIndex = 0;
429
- for (const segment of segments) {
430
- if (segment === "." || segment === "..") {
431
- firstNonRelativeSegmentIndex++;
432
- } else {
433
- break;
434
- }
435
- }
436
-
437
- const unqualifiedSegments = segments.slice(firstNonRelativeSegmentIndex);
438
- const newRaw = safeJoin(unqualifiedSegments, sep);
439
- return new UnqualifiedPath(newRaw);
440
- }
441
-
442
- append(...segments: Array<string>): RelativePath {
443
- const normalPath = super.append(...segments);
444
- return new RelativePath(normalPath.raw);
445
- }
446
-
447
- removeTrailingSlash(): RelativePath {
448
- const normalPath = super.removeTrailingSlash();
449
- return new RelativePath(normalPath.raw);
450
- }
451
-
452
- resolve(): RelativePath {
453
- const normalPath = super.resolve();
454
- return new RelativePath(normalPath.raw);
455
- }
456
- }
457
-
458
- export class UnqualifiedPath extends Path<"unqualified"> {
459
- constructor(raw: string) {
460
- super(raw, "unqualified");
461
- }
462
-
463
- toAbsolute(contextDir: AbsolutePath | string) {
464
- const contextDirPath =
465
- typeof contextDir === "string"
466
- ? Path.fromAbsolutePathString(contextDir)
467
- : contextDir;
468
-
469
- let thisSegments = getSegments(this.raw);
470
-
471
- let nextSegments = [
472
- contextDirPath.removeTrailingSlash().raw,
473
- ...thisSegments,
474
- ];
475
-
476
- return new AbsolutePath(
477
- safeJoin(nextSegments, getSeparator(contextDirPath.raw))
478
- ).resolve();
479
- }
480
-
481
- append(...segments: Array<string>): UnqualifiedPath {
482
- const normalPath = super.append(...segments);
483
- return new UnqualifiedPath(normalPath.raw);
484
- }
485
-
486
- prepend(...segments: Array<string>): UnqualifiedPath {
487
- const sep = getSeparator(this.raw);
488
- return new UnqualifiedPath(safeJoin([...segments, this.raw], sep));
489
- }
490
-
491
- removeTrailingSlash(): UnqualifiedPath {
492
- const normalPath = super.removeTrailingSlash();
493
- return new UnqualifiedPath(normalPath.raw);
494
- }
495
-
496
- resolve(): UnqualifiedPath {
497
- const normalPath = super.resolve();
498
- return new UnqualifiedPath(normalPath.raw);
499
- }
500
- }
501
-
502
- module.exports = Path;
503
- module.exports.Path = Path;
504
- module.exports.AbsolutePath = AbsolutePath;
505
- module.exports.RelativePath = RelativePath;
506
- module.exports.UnqualifiedPath = UnqualifiedPath;