feat(iso): add isomorphic path module with cross-platform utilities
This commit is contained in:
141
.gitlab-ci.yml
141
.gitlab-ci.yml
@@ -1,141 +0,0 @@
|
||||
# gitzone ci_default
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .npmci_cache/
|
||||
key: '$CI_BUILD_STAGE'
|
||||
|
||||
stages:
|
||||
- security
|
||||
- test
|
||||
- release
|
||||
- metadata
|
||||
|
||||
before_script:
|
||||
- npm install -g @shipzone/npmci
|
||||
|
||||
# ====================
|
||||
# security stage
|
||||
# ====================
|
||||
mirror:
|
||||
stage: security
|
||||
script:
|
||||
- npmci git mirror
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
auditProductionDependencies:
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
stage: security
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci command npm install --production --ignore-scripts
|
||||
- npmci command npm config set registry https://registry.npmjs.org
|
||||
- npmci command npm audit --audit-level=high --only=prod --production
|
||||
tags:
|
||||
- docker
|
||||
allow_failure: true
|
||||
|
||||
auditDevDependencies:
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
stage: security
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci command npm install --ignore-scripts
|
||||
- npmci command npm config set registry https://registry.npmjs.org
|
||||
- npmci command npm audit --audit-level=high --only=dev
|
||||
tags:
|
||||
- docker
|
||||
allow_failure: true
|
||||
|
||||
# ====================
|
||||
# test stage
|
||||
# ====================
|
||||
|
||||
testStable:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
|
||||
testBuild:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci command npm run build
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
|
||||
release:
|
||||
stage: release
|
||||
script:
|
||||
- npmci node install stable
|
||||
- npmci npm publish
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# metadata stage
|
||||
# ====================
|
||||
codequality:
|
||||
stage: metadata
|
||||
allow_failure: true
|
||||
only:
|
||||
- tags
|
||||
script:
|
||||
- npmci command npm install -g tslint typescript
|
||||
- npmci npm prepare
|
||||
- npmci npm install
|
||||
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
|
||||
tags:
|
||||
- lossless
|
||||
- docker
|
||||
- priv
|
||||
|
||||
trigger:
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci trigger
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
pages:
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci node install stable
|
||||
- npmci npm prepare
|
||||
- npmci command npm install -g @git.zone/tsdoc
|
||||
- npmci npm install
|
||||
- npmci command tsdoc
|
||||
tags:
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
only:
|
||||
- tags
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
||||
allow_failure: true
|
18
changelog.md
Normal file
18
changelog.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## [5.1.0] - 2025-01-28
|
||||
|
||||
### Added
|
||||
- New isomorphic path module at `/iso` export path for cross-platform path operations
|
||||
- `pathJoin()` function for joining paths across different platforms and environments
|
||||
- `fileUrlToPath()` function for converting file:// URLs to system paths
|
||||
- `pathToFileUrl()` function for converting system paths to file:// URLs
|
||||
- `dirname()` function for extracting directory from paths and URLs
|
||||
- Comprehensive test coverage for isomorphic path functions
|
||||
|
||||
### Changed
|
||||
- Build system now uses tsfolders for multi-folder compilation
|
||||
- Package exports now use modern exports field instead of main/typings
|
||||
|
||||
### Removed
|
||||
- Removed .gitlab-ci.yml file
|
17
package.json
17
package.json
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"name": "@push.rocks/smartpath",
|
||||
"version": "5.0.18",
|
||||
"version": "5.1.0",
|
||||
"private": false,
|
||||
"description": "A library offering smart ways to handle file and directory paths.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist_ts/index.js",
|
||||
"./iso": "./dist_ts_iso/index.js"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(tstest test)",
|
||||
"build": "(tsbuild --allowimplicitany)",
|
||||
"test": "(tstest test/ --verbose)",
|
||||
"build": "(tsbuild tsfolders)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"repository": {
|
||||
@@ -59,5 +61,6 @@
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
]
|
||||
}
|
||||
],
|
||||
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
|
||||
}
|
||||
|
11630
pnpm-lock.yaml
generated
11630
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
31
readme.md
31
readme.md
@@ -18,6 +18,37 @@ To begin utilizing `smartpath` in your project, start by importing it in your Ty
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
```
|
||||
|
||||
### Isomorphic Path Module
|
||||
For cross-platform path operations that work in any JavaScript environment (Node.js, browsers, Deno, etc.), use the isomorphic module:
|
||||
|
||||
```typescript
|
||||
import * as isoPath from '@push.rocks/smartpath/iso';
|
||||
|
||||
// Join paths with automatic platform detection
|
||||
const joinedPath = isoPath.pathJoin('/home/user', 'documents', 'file.txt');
|
||||
// Unix: /home/user/documents/file.txt
|
||||
// Windows: C:\Users\documents\file.txt
|
||||
|
||||
// Convert file:// URLs to system paths
|
||||
const systemPath = isoPath.fileUrlToPath('file:///home/user/file.txt');
|
||||
// Unix: /home/user/file.txt
|
||||
// Windows: C:\home\user\file.txt
|
||||
|
||||
// Convert system paths to file:// URLs
|
||||
const fileUrl = isoPath.pathToFileUrl('/home/user/file.txt');
|
||||
// Result: file:///home/user/file.txt
|
||||
|
||||
// Get directory from path or file URL
|
||||
const dir = isoPath.dirname('/home/user/documents/file.txt');
|
||||
// Result: /home/user/documents
|
||||
```
|
||||
|
||||
The isomorphic module automatically detects the path style (Windows vs POSIX) and handles:
|
||||
- file:// URL conversions
|
||||
- Mixed path separators
|
||||
- Cross-platform compatibility
|
||||
- Proper handling of Windows drive letters and UNC paths
|
||||
|
||||
### Creating a Smartpath Instance
|
||||
Instantiating a `Smartpath` object allows for the enrichment of path strings with additional context and manipulation capabilities:
|
||||
|
||||
|
145
test/test.iso.both.ts
Normal file
145
test/test.iso.both.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as smartpath from '../ts_iso/index.js';
|
||||
|
||||
tap.test('pathJoin - should join path segments correctly', async () => {
|
||||
// Basic path joining
|
||||
expect(smartpath.pathJoin('path', 'to', 'file.txt')).toEqual('path/to/file.txt');
|
||||
expect(smartpath.pathJoin('/path', 'to', 'file.txt')).toEqual('/path/to/file.txt');
|
||||
|
||||
// Windows paths
|
||||
expect(smartpath.pathJoin('C:', 'Users', 'test')).toEqual('C:\\Users\\test');
|
||||
expect(smartpath.pathJoin('C:\\Users', 'test', 'file.txt')).toEqual('C:\\Users\\test\\file.txt');
|
||||
|
||||
// Empty segments
|
||||
expect(smartpath.pathJoin('path', '', 'file.txt')).toEqual('path/file.txt');
|
||||
expect(smartpath.pathJoin('', 'path', 'file.txt')).toEqual('path/file.txt');
|
||||
|
||||
// No segments
|
||||
expect(smartpath.pathJoin()).toEqual('');
|
||||
expect(smartpath.pathJoin('')).toEqual('');
|
||||
|
||||
// Single segment
|
||||
expect(smartpath.pathJoin('path')).toEqual('path');
|
||||
expect(smartpath.pathJoin('/path')).toEqual('/path');
|
||||
|
||||
// Multiple separators
|
||||
expect(smartpath.pathJoin('path/', '/to/', '/file.txt')).toEqual('path/to/file.txt');
|
||||
expect(smartpath.pathJoin('path\\\\', '\\\\to\\\\', '\\\\file.txt')).toEqual('path\\to\\file.txt');
|
||||
|
||||
// Root paths
|
||||
expect(smartpath.pathJoin('/')).toEqual('/');
|
||||
expect(smartpath.pathJoin('/', 'path')).toEqual('/path');
|
||||
});
|
||||
|
||||
tap.test('pathJoin - should handle file:// URLs', async () => {
|
||||
// Unix file URLs
|
||||
expect(smartpath.pathJoin('file:///home/user', 'documents', 'file.txt')).toEqual('/home/user/documents/file.txt');
|
||||
expect(smartpath.pathJoin('file:///home/user/', 'documents')).toEqual('/home/user/documents');
|
||||
|
||||
// Windows file URLs
|
||||
expect(smartpath.pathJoin('file:///C:/Users', 'test', 'file.txt')).toEqual('C:\\Users\\test\\file.txt');
|
||||
expect(smartpath.pathJoin('file:///D:/Projects/', 'app')).toEqual('D:\\Projects\\app');
|
||||
|
||||
// Mixed file URL and path
|
||||
expect(smartpath.pathJoin('file:///home/user', '../test')).toEqual('/home/user/../test');
|
||||
expect(smartpath.pathJoin('documents', 'file:///home/user/file.txt')).toEqual('documents/home/user/file.txt');
|
||||
});
|
||||
|
||||
tap.test('fileUrlToPath - should convert file URLs to paths', async () => {
|
||||
// Unix file URLs
|
||||
expect(smartpath.fileUrlToPath('file:///home/user/file.txt')).toEqual('/home/user/file.txt');
|
||||
expect(smartpath.fileUrlToPath('file:///home/user%20name/file.txt')).toEqual('/home/user name/file.txt');
|
||||
|
||||
// Windows file URLs
|
||||
expect(smartpath.fileUrlToPath('file:///C:/Users/test/file.txt')).toEqual('C:\\Users\\test\\file.txt');
|
||||
expect(smartpath.fileUrlToPath('file:///D:/My%20Documents/file.txt')).toEqual('D:\\My Documents\\file.txt');
|
||||
|
||||
// Not a file URL
|
||||
expect(smartpath.fileUrlToPath('/home/user/file.txt')).toEqual('/home/user/file.txt');
|
||||
expect(smartpath.fileUrlToPath('C:\\Users\\test\\file.txt')).toEqual('C:\\Users\\test\\file.txt');
|
||||
expect(smartpath.fileUrlToPath('https://example.com')).toEqual('https://example.com');
|
||||
});
|
||||
|
||||
tap.test('pathToFileUrl - should convert paths to file URLs', async () => {
|
||||
// Unix paths
|
||||
expect(smartpath.pathToFileUrl('/home/user/file.txt')).toEqual('file:///home/user/file.txt');
|
||||
expect(smartpath.pathToFileUrl('/home/user name/file.txt')).toEqual('file:///home/user%20name/file.txt');
|
||||
|
||||
// Windows paths
|
||||
expect(smartpath.pathToFileUrl('C:\\Users\\test\\file.txt')).toEqual('file:///C:/Users/test/file.txt');
|
||||
expect(smartpath.pathToFileUrl('D:\\My Documents\\file.txt')).toEqual('file:///D:/My%20Documents/file.txt');
|
||||
expect(smartpath.pathToFileUrl('C:/Users/test/file.txt')).toEqual('file:///C:/Users/test/file.txt');
|
||||
|
||||
// Already a file URL
|
||||
expect(smartpath.pathToFileUrl('file:///home/user/file.txt')).toEqual('file:///home/user/file.txt');
|
||||
|
||||
// Relative paths (can't make file URLs from these)
|
||||
expect(smartpath.pathToFileUrl('relative/path/file.txt')).toEqual('relative/path/file.txt');
|
||||
expect(smartpath.pathToFileUrl('./file.txt')).toEqual('./file.txt');
|
||||
|
||||
// Special characters
|
||||
expect(smartpath.pathToFileUrl('/path/with?query')).toEqual('file:///path/with%3Fquery');
|
||||
expect(smartpath.pathToFileUrl('/path/with#hash')).toEqual('file:///path/with%23hash');
|
||||
});
|
||||
|
||||
tap.test('dirname - should extract directory from paths and URLs', async () => {
|
||||
// Unix paths
|
||||
expect(smartpath.dirname('/home/user/file.txt')).toEqual('/home/user');
|
||||
expect(smartpath.dirname('/home/user/')).toEqual('/home');
|
||||
expect(smartpath.dirname('/file.txt')).toEqual('/');
|
||||
expect(smartpath.dirname('/')).toEqual('/');
|
||||
|
||||
// Windows paths
|
||||
expect(smartpath.dirname('C:\\Users\\test\\file.txt')).toEqual('C:\\Users\\test');
|
||||
expect(smartpath.dirname('C:\\file.txt')).toEqual('C:\\');
|
||||
expect(smartpath.dirname('C:\\')).toEqual('C:\\');
|
||||
|
||||
// File URLs
|
||||
expect(smartpath.dirname('file:///home/user/file.txt')).toEqual('/home/user');
|
||||
expect(smartpath.dirname('file:///C:/Users/test/file.txt')).toEqual('C:\\Users\\test');
|
||||
|
||||
// Relative paths
|
||||
expect(smartpath.dirname('relative/path/file.txt')).toEqual('relative/path');
|
||||
expect(smartpath.dirname('file.txt')).toEqual('.');
|
||||
expect(smartpath.dirname('')).toEqual('.');
|
||||
|
||||
// Mixed separators
|
||||
expect(smartpath.dirname('path\\to/file.txt')).toEqual('path\\to');
|
||||
expect(smartpath.dirname('path/to\\file.txt')).toEqual('path/to');
|
||||
});
|
||||
|
||||
tap.test('edge cases - should handle edge cases correctly', async () => {
|
||||
// Non-string values filtered out
|
||||
expect(smartpath.pathJoin('path', null as any, 'file.txt')).toEqual('path/file.txt');
|
||||
expect(smartpath.pathJoin('path', undefined as any, 'file.txt')).toEqual('path/file.txt');
|
||||
expect(smartpath.pathJoin('path', 123 as any, 'file.txt')).toEqual('path/file.txt');
|
||||
|
||||
// Very long paths
|
||||
const longSegment = 'a'.repeat(100);
|
||||
const result = smartpath.pathJoin(longSegment, longSegment, 'file.txt');
|
||||
expect(result).toEqual(`${longSegment}/${longSegment}/file.txt`);
|
||||
|
||||
// Unicode characters
|
||||
expect(smartpath.pathJoin('path', '文件夹', 'файл.txt')).toEqual('path/文件夹/файл.txt');
|
||||
expect(smartpath.fileUrlToPath('file:///home/用户/文件.txt')).toEqual('/home/用户/文件.txt');
|
||||
expect(smartpath.pathToFileUrl('/home/用户/文件.txt')).toEqual('file:///home/%E7%94%A8%E6%88%B7/%E6%96%87%E4%BB%B6.txt');
|
||||
|
||||
// Backslashes in Unix-style paths (should be preserved)
|
||||
expect(smartpath.pathJoin('/home/user', 'path\\with\\backslashes')).toEqual('/home/user/path\\with\\backslashes');
|
||||
});
|
||||
|
||||
tap.test('cross-platform behavior - should detect separators correctly', async () => {
|
||||
// Should detect Windows paths and use backslashes
|
||||
expect(smartpath.pathJoin('C:\\Users', 'test')).toEqual('C:\\Users\\test');
|
||||
expect(smartpath.pathJoin('D:', 'Projects', 'app')).toEqual('D:\\Projects\\app');
|
||||
|
||||
// Should detect Unix paths and use forward slashes
|
||||
expect(smartpath.pathJoin('/home', 'user')).toEqual('/home/user');
|
||||
expect(smartpath.pathJoin('/var', 'log', 'app.log')).toEqual('/var/log/app.log');
|
||||
|
||||
// Mixed paths - first segment determines separator
|
||||
expect(smartpath.pathJoin('C:\\Users', 'test/file.txt')).toEqual('C:\\Users\\test\\file.txt');
|
||||
expect(smartpath.pathJoin('/home', 'user\\documents')).toEqual('/home/user\\documents');
|
||||
});
|
||||
|
||||
await tap.start();
|
191
ts_iso/index.ts
Normal file
191
ts_iso/index.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Cross-platform path.join function that works in any JavaScript environment
|
||||
* Handles regular paths and file:// URLs from import.meta.url
|
||||
* @param segments - Path segments to join
|
||||
* @returns Joined path string
|
||||
*/
|
||||
export function pathJoin(...segments: string[]): string {
|
||||
// Filter out empty strings and non-string values
|
||||
const validSegments = segments.filter(segment =>
|
||||
typeof segment === 'string' && segment.length > 0
|
||||
);
|
||||
|
||||
// If no valid segments, return empty string
|
||||
if (validSegments.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Convert file:// URLs to paths
|
||||
const processedSegments = validSegments.map(segment => {
|
||||
return fileUrlToPath(segment);
|
||||
});
|
||||
|
||||
// Detect if we're dealing with Windows-style paths
|
||||
const isWindowsPath = processedSegments.some(segment => {
|
||||
// Check for Windows drive letter
|
||||
if (/^[a-zA-Z]:/.test(segment)) return true;
|
||||
// Check if first segment has backslashes (indicating Windows)
|
||||
if (processedSegments[0] === segment && segment.includes('\\')) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Choose separator and normalize function based on path style
|
||||
const separator = isWindowsPath ? '\\' : '/';
|
||||
|
||||
// Normalize segments based on path style
|
||||
const normalizedSegments = processedSegments.map((segment) => {
|
||||
if (isWindowsPath) {
|
||||
// On Windows, both / and \ are separators
|
||||
return segment.replace(/[\/\\]+/g, '\\');
|
||||
} else {
|
||||
// On POSIX, only / is a separator, \ is literal
|
||||
return segment.replace(/\/+/g, '/');
|
||||
}
|
||||
});
|
||||
|
||||
// Join segments and handle edge cases
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < normalizedSegments.length; i++) {
|
||||
const segment = normalizedSegments[i];
|
||||
|
||||
if (i === 0) {
|
||||
result = segment;
|
||||
} else {
|
||||
// Remove leading separator from segment if result already ends with one
|
||||
let cleanSegment = segment;
|
||||
if (segment.startsWith(separator)) {
|
||||
cleanSegment = segment.slice(1);
|
||||
}
|
||||
|
||||
// Add separator if result doesn't end with one
|
||||
if (result && !result.endsWith(separator)) {
|
||||
result += separator;
|
||||
}
|
||||
|
||||
result += cleanSegment;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle edge cases
|
||||
if (result === '' && validSegments.some(s => s === '/' || s === '\\')) {
|
||||
result = separator;
|
||||
}
|
||||
|
||||
// Clean up multiple consecutive separators
|
||||
if (isWindowsPath) {
|
||||
result = result.replace(/\\+/g, '\\');
|
||||
// Special case for UNC paths
|
||||
if (result.startsWith('\\\\') && !result.startsWith('\\\\\\')) {
|
||||
// Keep double backslash for UNC paths
|
||||
} else if (result.match(/^\\[^\\]/)) {
|
||||
// Single leading backslash on Windows (unusual but valid)
|
||||
}
|
||||
} else {
|
||||
result = result.replace(/\/+/g, '/');
|
||||
// Preserve leading slash for absolute paths
|
||||
if (processedSegments[0].startsWith('/') && !result.startsWith('/')) {
|
||||
result = '/' + result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a file:// URL to a system path
|
||||
* @param fileUrl - A file:// URL (e.g., from import.meta.url)
|
||||
* @returns System path
|
||||
*/
|
||||
export function fileUrlToPath(fileUrl: string): string {
|
||||
if (!fileUrl.startsWith('file://')) {
|
||||
return fileUrl;
|
||||
}
|
||||
|
||||
// Remove file:// protocol
|
||||
let path = fileUrl.slice(7);
|
||||
|
||||
// Handle Windows file URLs: file:///C:/path -> C:\path
|
||||
if (/^\/[a-zA-Z]:/.test(path)) {
|
||||
path = path.slice(1);
|
||||
// Convert forward slashes to backslashes for Windows
|
||||
path = path.replace(/\//g, '\\');
|
||||
}
|
||||
|
||||
// Decode URL encoding
|
||||
path = decodeURIComponent(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a system path to a file:// URL
|
||||
* @param path - System path
|
||||
* @returns file:// URL
|
||||
*/
|
||||
export function pathToFileUrl(path: string): string {
|
||||
if (path.startsWith('file://')) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// Normalize slashes to forward slashes for URL
|
||||
let urlPath = path.replace(/\\/g, '/');
|
||||
|
||||
// Encode special characters
|
||||
urlPath = encodeURI(urlPath).replace(/[?#]/g, encodeURIComponent);
|
||||
|
||||
// Check if it's a Windows absolute path
|
||||
if (/^[a-zA-Z]:/.test(urlPath)) {
|
||||
return `file:///${urlPath}`;
|
||||
}
|
||||
|
||||
// Check if it's an absolute path
|
||||
if (urlPath.startsWith('/')) {
|
||||
return `file://${urlPath}`;
|
||||
}
|
||||
|
||||
// Relative path - just return as-is (can't make a file URL from relative path)
|
||||
return urlPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory from a file URL or path
|
||||
* @param urlOrPath - File URL (like import.meta.url) or regular path
|
||||
* @returns Directory path
|
||||
*/
|
||||
export function dirname(urlOrPath: string): string {
|
||||
// Convert file URL to path if needed
|
||||
let path = fileUrlToPath(urlOrPath);
|
||||
|
||||
// Remove trailing slashes (but keep root slashes)
|
||||
if (path.length > 1 && (path.endsWith('/') || path.endsWith('\\'))) {
|
||||
// Special case: don't remove trailing slash for Windows drive root
|
||||
if (!(path.length === 3 && path[1] === ':')) {
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for Windows drive root (C:\ or C:)
|
||||
if (path.match(/^[a-zA-Z]:\\?$/)) {
|
||||
return path.endsWith('\\') ? path : path + '\\';
|
||||
}
|
||||
|
||||
// Find the last separator
|
||||
const lastSlash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
|
||||
|
||||
if (lastSlash === -1) {
|
||||
return '.';
|
||||
}
|
||||
|
||||
// Special case for root
|
||||
if (lastSlash === 0) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
// Special case for Windows drive root (C:\)
|
||||
if (lastSlash === 2 && path[1] === ':') {
|
||||
return path.slice(0, 3);
|
||||
}
|
||||
|
||||
return path.slice(0, lastSlash);
|
||||
}
|
Reference in New Issue
Block a user