Skip to content

Commit

Permalink
[Backport 1.x] Adds @osd/cross-platform (#2681 & #2703) (#2989) (#2997)
Browse files Browse the repository at this point in the history
* [@osd/cross-platform] Adds cross-platform helpers (#2681)

cherry-pick from 887093d

Signed-off-by: Miki <[email protected]>

* Adds @osd/cross-platform (#2703)

* Adds helper functions, @osd/cross-platform, to work around the differences of platforms

cherry-pick from 0c9ca96

Signed-off-by: Miki <[email protected]>

Signed-off-by: Miki <[email protected]>
Co-authored-by: Anan Zhuang <[email protected]>
(cherry picked from commit f9bffcb)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

# Conflicts:
#	CHANGELOG.md

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
1 parent ec75039 commit e7fab2e
Show file tree
Hide file tree
Showing 48 changed files with 663 additions and 146 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"@osd/apm-config-loader": "1.0.0",
"@osd/config": "1.0.0",
"@osd/config-schema": "1.0.0",
"@osd/cross-platform": "1.0.0",
"@osd/i18n": "1.0.0",
"@osd/interpreter": "1.0.0",
"@osd/logging": "1.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/osd-config-schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"osd:bootstrap": "yarn build"
},
"devDependencies": {
"@osd/cross-platform": "1.0.0",
"typescript": "4.0.2",
"tsd": "^0.16.0"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/osd-config-schema/src/errors/schema_error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import { relative, sep } from 'path';
import { SchemaError } from '.';

import { standardize, getRepoRoot } from '@osd/cross-platform';

/**
* Make all paths in stacktrace relative.
*/
Expand All @@ -48,9 +50,7 @@ export const cleanStack = (stack: string) =>
}

const path = parts[1];
// Cannot use `standardize` from `@osd/utils
let relativePath = relative(process.cwd(), path);
if (process.platform === 'win32') relativePath = relativePath.replace(/\\/g, '/');
const relativePath = standardize(relative(getRepoRoot(path) || '.', path));

return line.replace(path, relativePath);
})
Expand Down
28 changes: 28 additions & 0 deletions packages/osd-cross-platform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# `@osd/cross-platform` — OpenSearch Dashboards cross-platform helpers

This package contains the helpers to work around the differences across platforms, such as the difference in the path segment separator and the possibility of referencing a path using the short 8.3 name (SFN), a long name, and a long UNC on Windows.

Some helpers are functions that `standardize` the reference to a path or help `getRepoRoot`, and some are constants referencing the `PROCESS_WORKING_DIR` or `REPO_ROOT`.

### Example

When the relative reference of `path` to the working directory is needed, using the code below would produce different results on Linux that it would on Windows and if the process was started in a Windows shell that used short paths, the results differ from a Windows shell that used long paths.
```js
import { relative } from 'path';

const relativePath = relative(process.cwd(), path);

// Output on Linux: relative-path/to/a/file
// Windows: relative-path\to\a\file
// Windows SFN: RELATI~1\to\a\file
```

To avoid those differences, helper functions and constants can be used:
```js
import { relative } from 'path';
import { standardize, PROCESS_WORKING_DIR } from '@osd/cross-platform';

const relativePath = standardize(relative(PROCESS_WORKING_DIR, path));

// Output: relative-path/to/a/file
```
15 changes: 15 additions & 0 deletions packages/osd-cross-platform/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@osd/cross-platform",
"main": "./target/index.js",
"version": "1.0.0",
"license": "Apache-2.0",
"private": true,
"scripts": {
"build": "tsc",
"osd:bootstrap": "yarn build"
},
"devDependencies": {
"typescript": "4.0.2",
"tsd": "^0.16.0"
}
}
13 changes: 13 additions & 0 deletions packages/osd-cross-platform/src/__snapshots__/path.test.ts.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/osd-cross-platform/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export * from './path';
export * from './process';
export * from './repo_root';
207 changes: 207 additions & 0 deletions packages/osd-cross-platform/src/path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import path from 'path';
import fs from 'fs';
import del from 'del';

import {
resolveToFullNameSync,
resolveToFullPathSync,
resolveToShortNameSync,
resolveToShortPathSync,
shortNamesSupportedSync,
realPathSync,
realShortPathSync,
standardize,
} from './path';

const tmpTestFolder = './__test_artifacts__';
const longFolderName = '.long-folder-name';
const longFileName = '.long-file-name.txt';
const longSymlinkName = '.sym.link';
const shortFolderName = 'LONG-F~1';
const shortFileName = 'LONG-F~1.TXT';
const dummyWindowsPath = 'C:\\a\\b\\c';
const dummyWindowsPOSIXPath = 'C:/a/b/c';
const dummyPOSIXPath = '/a/b/c';

const onWindows = process.platform === 'win32' ? describe : xdescribe;
const onWindowsWithShortNames = shortNamesSupportedSync() ? describe : xdescribe;

// Save the real process.platform
const realPlatform = Object.getOwnPropertyDescriptor(process, 'platform')!;

describe('Cross Platform', () => {
describe('path', () => {
onWindows('on Windows', () => {
onWindowsWithShortNames('when 8.3 is supported', () => {
beforeAll(() => {
// Cleanup
try {
// If leftover artifacts were found, get rid of them
del.sync(tmpTestFolder);
} catch (ex) {
// Do nothing; if `rmdir` failed, let the `mkdir` below throw the error
}

fs.mkdirSync(tmpTestFolder);
fs.mkdirSync(path.resolve(tmpTestFolder, longFolderName));
fs.writeFileSync(path.resolve(tmpTestFolder, longFolderName, longFileName), '');
fs.symlinkSync(
path.resolve(tmpTestFolder, longFolderName),
path.resolve(tmpTestFolder, longSymlinkName),
'junction'
);
});

afterAll(() => {
try {
del.sync(tmpTestFolder);
} catch (ex) {
// Do nothing
}
});

it('can synchronously extract full name of a folder', () => {
const name = path.basename(
resolveToFullPathSync(path.resolve(tmpTestFolder, shortFolderName))
);
expect(name).toBe(longFolderName);
});

it('can synchronously extract full name of a file', () => {
const name = path.basename(
resolveToFullNameSync(path.resolve(tmpTestFolder, shortFolderName, shortFileName))
);
expect(name).toBe(longFileName);
});

it('can synchronously extract short name of a folder', () => {
const name = path.basename(
resolveToShortPathSync(path.resolve(tmpTestFolder, longFolderName))
);
expect(name).toBe(shortFolderName);
});

it('can synchronously extract short name of a file', () => {
const name = path.basename(
resolveToShortNameSync(path.resolve(tmpTestFolder, longFolderName, longFileName))
);
expect(name).toBe(shortFileName);
});

it('can synchronously extract full name of a symbolic link', () => {
const name = path.basename(realPathSync(path.resolve(tmpTestFolder, longSymlinkName)));
expect(name).toBe(longFolderName);
});

it('can synchronously extract short name of a symbolic link', () => {
const name = path.basename(
realShortPathSync(path.resolve(tmpTestFolder, longSymlinkName))
);
expect(name).toBe(shortFolderName);
});
});
});

describe('on platforms other than Windows', () => {
let mockPathNormalize: jest.SpyInstance<string, [p: string]>;
let mockPathResolve: jest.SpyInstance<string, string[]>;
let mockFSRealPathSync: jest.SpyInstance<string>;

beforeAll(() => {
Object.defineProperty(process, 'platform', {
...Object.getOwnPropertyDescriptor(process, 'property'),
value: 'linux',
});

mockPathNormalize = jest.spyOn(path, 'normalize').mockReturnValue(dummyPOSIXPath);
mockPathResolve = jest.spyOn(path, 'resolve').mockReturnValue(dummyPOSIXPath);
mockFSRealPathSync = jest
.spyOn(fs, 'realpathSync')
.mockReturnValue(dummyPOSIXPath) as jest.SpyInstance<string>;
});

afterAll(() => {
// Restore the real property value after each test
Object.defineProperty(process, 'platform', realPlatform);
mockPathNormalize.mockRestore();
mockPathResolve.mockRestore();
mockFSRealPathSync.mockRestore();
});

it('all short and full name methods return just the normalized paths', () => {
expect(shortNamesSupportedSync()).toBe(false);
expect(resolveToFullPathSync(dummyPOSIXPath)).toBe(dummyPOSIXPath);
expect(resolveToShortPathSync(dummyPOSIXPath)).toBe(dummyPOSIXPath);
});
});

describe('standardize', () => {
describe('on Windows', () => {
let mockPathNormalize: jest.SpyInstance<string, [p: string]>;

beforeAll(() => {
Object.defineProperty(process, 'platform', {
...Object.getOwnPropertyDescriptor(process, 'property'),
value: 'win32',
});

mockPathNormalize = jest.spyOn(path, 'normalize').mockReturnValue(dummyWindowsPath);
});

afterAll(() => {
// Restore the real property value after each test
Object.defineProperty(process, 'platform', realPlatform);
mockPathNormalize.mockRestore();
});

it('produces a path in native format', () => {
expect(standardize(dummyWindowsPath, false, false)).toMatchSnapshot();
});

it('produces a path in native format even for POSIX input', () => {
expect(standardize(dummyWindowsPOSIXPath, false, false)).toMatchSnapshot();
});

it('produces a path in native format with escaped backslashes', () => {
expect(standardize(dummyWindowsPath, false, true)).toMatchSnapshot();
});

it('produces a path in POSIX format', () => {
expect(standardize(dummyWindowsPath)).toMatchSnapshot();
});
});

describe('on POSIX-compatible platforms', () => {
let mockPathNormalize: jest.SpyInstance<string, [p: string]>;

beforeAll(() => {
Object.defineProperty(process, 'platform', {
...Object.getOwnPropertyDescriptor(process, 'property'),
value: 'linux',
});

mockPathNormalize = jest.spyOn(path, 'normalize').mockReturnValue(dummyPOSIXPath);
});

afterAll(() => {
// Restore the real property value after each test
Object.defineProperty(process, 'platform', realPlatform);
mockPathNormalize.mockRestore();
});

it('produces a path in POSIX format', () => {
expect(standardize(dummyPOSIXPath)).toMatchSnapshot();
});

it('ignores additional parameters', () => {
expect(standardize(dummyPOSIXPath, false, true)).toMatchSnapshot();
});
});
});
});
});
Loading

0 comments on commit e7fab2e

Please sign in to comment.