Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/imports_normalize-relative-json-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@equinor/fusion-imports": patch
---

Resolve relative JSON file paths from the current working directory consistently with script and configuration imports.
3 changes: 2 additions & 1 deletion packages/utils/imports/src/import-json.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';

/** Union of types that `JSON.parse` can produce. */
type JSONContent = Record<string, unknown> | string | number | boolean | null | unknown[];
Expand Down Expand Up @@ -28,7 +29,7 @@ export const importJSON = async <T extends JSONContent>(
encoding: BufferEncoding = 'utf-8',
): Promise<T> => {
try {
const content = await readFile(filePath, encoding);
const content = await readFile(resolve(filePath), encoding);
return JSON.parse(content);
} catch (error) {
throw new Error(`Failed to parse JSON from file: '${filePath}'`, { cause: error });
Expand Down
15 changes: 8 additions & 7 deletions packages/utils/imports/tests/import-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
import { vol } from 'memfs';

import { importConfig } from '../src/import-config';
import { testFilePath } from './test-file-path';

const generateFileContent = (...content: string[]) => {
return content.join('\n');
Expand All @@ -17,14 +18,14 @@ const mockConfig = {

describe('import-config', () => {
it('should load config from a json file', async () => {
vol.writeFileSync('config.json', JSON.stringify(mockConfig));
vol.writeFileSync(testFilePath('config.json'), JSON.stringify(mockConfig));
const result = await importConfig('config');
expect(result.config).toMatchObject(mockConfig);
});

it('should load config from a javascript file', async () => {
vol.writeFileSync(
'config.js',
testFilePath('config.js'),
generateFileContent(
`export const foo = ${JSON.stringify(mockConfig, null, 2)};`,
'export default foo;',
Expand All @@ -36,11 +37,11 @@ describe('import-config', () => {

it('should prefer typescript over javascript', async () => {
vol.writeFileSync(
'config.js',
testFilePath('config.js'),
generateFileContent(`export const foo = 'should not be used';`, 'export default foo;'),
);
vol.writeFileSync(
'config.ts',
testFilePath('config.ts'),
generateFileContent(
`export const foo = ${JSON.stringify(mockConfig, null, 2)};`,
'export default foo;',
Expand All @@ -49,13 +50,13 @@ describe('import-config', () => {
const result = await importConfig('config');
expect(result.config).toMatchObject(mockConfig);
expect(result.extension).toBe('.ts');
expect(vol.existsSync('config.js')).toBe(true);
expect(vol.existsSync(testFilePath('config.js'))).toBe(true);
});

it('should load config with a custom resolver', async () => {
type Module = { foo: (value: number) => number };
vol.writeFileSync(
'config.ts',
testFilePath('config.ts'),
generateFileContent('export const foo = (value: number) => value + 1;'),
);
const result = await importConfig('config', {
Expand All @@ -69,7 +70,7 @@ describe('import-config', () => {
it('should load a config with a custom async resolver', async () => {
type Module = { foo: (value: number) => Promise<number> };
vol.writeFileSync(
'config.ts',
testFilePath('config.ts'),
generateFileContent('export const foo = async (value: number) => value + 1;'),
);
const result = await importConfig('config', {
Expand Down
15 changes: 8 additions & 7 deletions packages/utils/imports/tests/import-json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,47 @@ import { describe, expect, it } from 'vitest';
import { vol } from 'memfs';

import importJSON from '../src/import-json';
import { testFilePath } from './test-file-path';

describe('import-json', () => {
it('should load a JSON file with a object', async () => {
const expected = { foo: 'bar' };
vol.writeFileSync('config.json', JSON.stringify(expected));
vol.writeFileSync(testFilePath('config.json'), JSON.stringify(expected));
const result = await importJSON('config.json');
expect(result).toEqual(expected);
});

it('should load a JSON file with a nested object', async () => {
const expected = { foo: { bar: 'baz' } };
vol.writeFileSync('config.json', JSON.stringify(expected));
vol.writeFileSync(testFilePath('config.json'), JSON.stringify(expected));
const result = await importJSON('config.json');
expect(result).toEqual(expected);
});

it('should load a JSON file with a string', async () => {
const expected = 'foo';
vol.writeFileSync('config.json', JSON.stringify(expected));
vol.writeFileSync(testFilePath('config.json'), JSON.stringify(expected));
const result = await importJSON('config.json');
expect(result).toBe(expected);
});

it('should load a JSON file with a number', async () => {
const expected = 1;
vol.writeFileSync('config.json', JSON.stringify(expected));
vol.writeFileSync(testFilePath('config.json'), JSON.stringify(expected));
const result = await importJSON('config.json');
expect(result).toBe(expected);
});

it('should load a JSON file with a boolean', async () => {
const expected = true;
vol.writeFileSync('config.json', JSON.stringify(expected));
vol.writeFileSync(testFilePath('config.json'), JSON.stringify(expected));
const result = await importJSON('config.json');
expect(result).toBe(expected);
});

it('should load a JSON file with an array', async () => {
const expected = [1, 2, 3];
vol.writeFileSync('config.json', JSON.stringify(expected));
vol.writeFileSync(testFilePath('config.json'), JSON.stringify(expected));
const result = await importJSON('config.json');
expect(result).toEqual(expected);
});
Expand All @@ -57,7 +58,7 @@ describe('import-json', () => {
});

it('should throw an error if the content is not valid JSON', async () => {
vol.writeFileSync('invalid.json', 'foo');
vol.writeFileSync(testFilePath('invalid.json'), 'foo');
await expect(importJSON('invalid.json')).rejects.toMatchObject({
message: expect.stringContaining('Failed to parse JSON'),
cause: {
Expand Down
24 changes: 14 additions & 10 deletions packages/utils/imports/tests/import-script.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
import { vol } from 'memfs';

import { importScript } from '../src/import-script';
import { testFilePath } from './test-file-path';

const generateFileContent = (...content: string[]) => {
return content.join('\n');
Expand All @@ -11,7 +12,7 @@ const generateFileContent = (...content: string[]) => {
describe('import-script', () => {
it('should load a javascript file', async () => {
vol.writeFileSync(
'config.js',
testFilePath('config.js'),
generateFileContent('export const foo = 1;', 'export default foo;'),
);
const result = await importScript('config.js');
Expand All @@ -20,7 +21,7 @@ describe('import-script', () => {

it('should load a typescript file', async () => {
vol.writeFileSync(
'config.ts',
testFilePath('config.ts'),
generateFileContent('export const foo: number = 1;', 'export default foo;'),
);
const result = await importScript('config.ts');
Expand All @@ -29,9 +30,12 @@ describe('import-script', () => {
});

it('should load a typescript file with imports', async () => {
vol.writeFileSync('base.ts', generateFileContent('export const foo: number = 1;'));
vol.writeFileSync(
'extended.ts',
testFilePath('base.ts'),
generateFileContent('export const foo: number = 1;'),
);
vol.writeFileSync(
testFilePath('extended.ts'),
generateFileContent(
'import { foo } from "./base";',
'export const bar: number = foo + 1;',
Expand All @@ -46,7 +50,7 @@ describe('import-script', () => {
it('should load a typescript file with functions', async () => {
type MockModule = { increment: (by?: number) => number };
vol.writeFileSync(
'config.ts',
testFilePath('config.ts'),
generateFileContent(
'let count = 0;',
'export const increment = (by?: number) => {',
Expand All @@ -70,9 +74,9 @@ describe('import-script', () => {

it('should load a markdown file with ?raw query parameter', async () => {
const markdownContent = '# Test Markdown\n\nThis is a test file.';
vol.writeFileSync('README.md', markdownContent);
vol.writeFileSync(testFilePath('README.md'), markdownContent);
vol.writeFileSync(
'test.ts',
testFilePath('test.ts'),
generateFileContent(
"import readmeContent from './README.md?raw';",
'export default readmeContent;',
Expand All @@ -84,10 +88,10 @@ describe('import-script', () => {

it('should load a markdown file with ?raw query parameter using relative path with ../', async () => {
const markdownContent = '# Parent README\n\nThis is a parent README file.';
vol.writeFileSync('README.md', markdownContent);
vol.mkdirSync('subdir/nested', { recursive: true });
vol.writeFileSync(testFilePath('README.md'), markdownContent);
vol.mkdirSync(testFilePath('subdir/nested'), { recursive: true });
vol.writeFileSync(
'subdir/nested/test.ts',
testFilePath('subdir/nested/test.ts'),
generateFileContent(
"import readmeContent from '../../README.md?raw';",
'export default readmeContent;',
Expand Down
9 changes: 9 additions & 0 deletions packages/utils/imports/tests/test-file-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { resolve } from 'node:path';

/**
* Resolves a virtual test fixture path from the mocked process working directory.
*
* @param path - Relative path used by an import test fixture.
* @returns Absolute path inside the virtual project directory.
*/
export const testFilePath = (path: string): string => resolve(process.cwd(), path);
Loading
Loading