Skip to content

Commit

Permalink
fix(design): remove Node requirement in core files (#2377)
Browse files Browse the repository at this point in the history
## Proposed change

Remove Node requirement in core files of the Design Tool.
The purpose is to be able to run the design token generation in a
non-Node environment

<!--
Please include a summary of the changes and the related issue.
Please also include relevant motivation and context.
-->

## Related issues

<!--
Please make sure to follow the [contribution
guidelines](https://github.com/amadeus-digital/Otter/blob/main/CONTRIBUTING.md)
-->

*- No issue associated -*

<!-- * 🐛 Fix #issue -->
<!-- * 🐛 Fix resolves #issue -->
<!-- * 🚀 Feature #issue -->
<!-- * 🚀 Feature resolves #issue -->
<!-- * :octocat: Pull Request #issue -->
  • Loading branch information
kpanot authored Oct 31, 2024
2 parents f886a72 + 7a4d507 commit df60398
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { promises as fs } from 'node:fs';
import { resolve } from 'node:path';
import type { DesignTokenGroup, DesignTokenSpecification } from '../design-token-specification.interface';
import type { DesignTokenVariableSet } from '../parsers';
import { computeFileToUpdatePath, getTokenSorterByName, getTokenSorterByRef, renderDesignTokens } from './design-token-style.renderer';
import { computeFileToUpdatePath, getFileToUpdatePath, getTokenSorterByName, getTokenSorterByRef, renderDesignTokens } from './design-token-style.renderer';

const rootPath = resolve('/');

describe('Design Token Renderer', () => {
let exampleVariable!: DesignTokenSpecification;
Expand All @@ -19,7 +21,7 @@ describe('Design Token Renderer', () => {

describe('computeFileToUpdatePath', () => {
const DEFAULT_FILE = 'test-result.json';
const fileToUpdate = computeFileToUpdatePath('/', DEFAULT_FILE);
const fileToUpdate = computeFileToUpdatePath(rootPath, DEFAULT_FILE);

test('should return default file if not specified', () => {
const variable = designTokens.get('example.var1');
Expand All @@ -34,7 +36,28 @@ describe('Design Token Renderer', () => {
const result = fileToUpdate(variable);

expect(variable.extensions.o3rTargetFile).toBeDefined();
expect(result).toBe(resolve('/', variable.extensions.o3rTargetFile));
expect(result).toBe(resolve(rootPath, variable.extensions.o3rTargetFile));
});
});

describe('getFileToUpdatePath', () => {
const DEFAULT_FILE = 'test-result.json';
const fileToUpdate = getFileToUpdatePath(rootPath, DEFAULT_FILE);

test('should return default file if not specified', async () => {
const variable = designTokens.get('example.var1');
const result = (await fileToUpdate)(variable);

expect(variable.extensions.o3rTargetFile).not.toBeDefined();
expect(result).toBe(DEFAULT_FILE);
});

test('should return file specified by the token', async () => {
const variable = designTokens.get('example.test.var2');
const result = (await fileToUpdate)(variable);

expect(variable.extensions.o3rTargetFile).toBeDefined();
expect(result).toBe(resolve(rootPath, variable.extensions.o3rTargetFile));
});
});

Expand All @@ -43,7 +66,7 @@ describe('Design Token Renderer', () => {
const writeFile = jest.fn();
const readFile = jest.fn().mockReturnValue('');
const existsFile = jest.fn().mockReturnValue(true);
const determineFileToUpdate = jest.fn().mockReturnValue(computeFileToUpdatePath('.'));
const determineFileToUpdate = jest.fn().mockReturnValue(await getFileToUpdatePath('.'));
const tokenDefinitionRenderer = jest.fn().mockReturnValue('--test: #000;');

await renderDesignTokens(designTokens, {
Expand All @@ -64,7 +87,7 @@ describe('Design Token Renderer', () => {
const writeFile = jest.fn();
const readFile = jest.fn().mockReturnValue('');
const existsFile = jest.fn().mockReturnValue(true);
const determineFileToUpdate = jest.fn().mockImplementation(computeFileToUpdatePath('.'));
const determineFileToUpdate = jest.fn().mockImplementation(await getFileToUpdatePath('.'));

await renderDesignTokens(designTokens, {
writeFile,
Expand All @@ -87,7 +110,7 @@ describe('Design Token Renderer', () => {
const writeFile = jest.fn().mockImplementation((filename, content) => { result[filename] = content; });
const readFile = jest.fn().mockReturnValue('');
const existsFile = jest.fn().mockReturnValue(true);
const determineFileToUpdate = jest.fn().mockImplementation(computeFileToUpdatePath('.'));
const determineFileToUpdate = jest.fn().mockImplementation(await getFileToUpdatePath('.'));

await renderDesignTokens(designTokens, {
writeFile,
Expand All @@ -106,7 +129,7 @@ describe('Design Token Renderer', () => {
const writeFile = jest.fn().mockImplementation((filename, content) => { result[filename] = content; });
const readFile = jest.fn().mockReturnValue('');
const existsFile = jest.fn().mockReturnValue(true);
const determineFileToUpdate = jest.fn().mockImplementation(computeFileToUpdatePath('.'));
const determineFileToUpdate = jest.fn().mockImplementation(await getFileToUpdatePath('.'));

await renderDesignTokens(designTokens, {
writeFile,
Expand All @@ -126,7 +149,7 @@ describe('Design Token Renderer', () => {
const writeFile = jest.fn().mockImplementation((filename, content) => { result[filename] = content; });
const readFile = jest.fn().mockReturnValue('');
const existsFile = jest.fn().mockReturnValue(true);
const determineFileToUpdate = jest.fn().mockImplementation(computeFileToUpdatePath('.'));
const determineFileToUpdate = jest.fn().mockImplementation(await getFileToUpdatePath('.'));
const tokenListTransform = jest.fn().mockReturnValue([]);
await renderDesignTokens(designTokens, {
writeFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,66 @@ import { getCssTokenDefinitionRenderer } from './css/design-token-definition.ren
import { getCssStyleContentUpdater } from './css/design-token-updater.renderers';
import type { Logger } from '@o3r/core';
import type { promises as fs } from 'node:fs';
import { isAbsolute, resolve } from 'node:path';
import type { DesignTokenListTransform, DesignTokenRendererOptions } from './design-token.renderer.interface';

/**
* Retrieve the path of a target file based on root path if not absolute
* @param targetFile file to target
* @param root Root path used to resolve relative targetFile path
*/
const getFilePath = (targetFile: string, root = '.') => {
const isAbsolutePath = targetFile.startsWith('/') || /^[a-zA-Z]:[/\\]/.test(targetFile);
if (isAbsolutePath) {
return targetFile;
} else {
const joinStr = (root + targetFile);
const sep = joinStr.includes('/') ? '/' : (joinStr.includes('\\') ? '\\' : '/');
const stack = [];
for (const part of `${root.replace(/[\\/]$/, '')}${sep}${targetFile}`.split(sep)) {
if (part === '..') {
stack.pop();
} else if (part !== '.') {
stack.push(part);
}
}
return stack.join(sep);
}
};

/**
* Retrieve the function that determines which file to update for a given token
* @param root Root path used if no base path
* @param defaultFile Default file if not requested by the Token
* @deprecated Use {@link getFileToUpdatePath} instead. Will be removed in v13.
*/
export const computeFileToUpdatePath = (root = process.cwd(), defaultFile = 'styles.scss') => (token: DesignTokenVariableStructure) => {
if (token.extensions.o3rTargetFile) {
return isAbsolute(token.extensions.o3rTargetFile) ? token.extensions.o3rTargetFile : resolve(token.context?.basePath || root, token.extensions.o3rTargetFile);
}
export const computeFileToUpdatePath = (root = '.', defaultFile = 'styles.scss') => (token: DesignTokenVariableStructure) => {
return token.extensions.o3rTargetFile
? getFilePath(token.extensions.o3rTargetFile, token.context?.basePath || root)
: defaultFile;
};

return defaultFile;
/**
* Retrieve the function that determines which file to update for a given token
* @param root Root path used if no base path
* @param defaultFile Default file if not requested by the Token
*/
export const getFileToUpdatePath = async (root?: string, defaultFile = 'styles.scss') => {
try {
const { isAbsolute, resolve } = await import('node:path');
const { cwd } = await import('node:process');
root ||= cwd();
return (token: DesignTokenVariableStructure) => {
return token.extensions.o3rTargetFile
? isAbsolute(token.extensions.o3rTargetFile) ? token.extensions.o3rTargetFile : resolve(token.context?.basePath || root!, token.extensions.o3rTargetFile)
: defaultFile;
};
} catch {
return (token: DesignTokenVariableStructure) => {
return token.extensions.o3rTargetFile
? getFilePath(token.extensions.o3rTargetFile, token.context?.basePath || root)
: defaultFile;
};
}
};

/**
Expand Down Expand Up @@ -142,7 +188,7 @@ export const renderDesignTokens = async (variableSet: DesignTokenVariableSet, op
const readFile = options?.readFile || (async (filePath: string) => (await import('node:fs/promises')).readFile(filePath, { encoding: 'utf8' }));
const existsFile = options?.existsFile || (await import('node:fs')).existsSync;
const writeFile = options?.writeFile || getDefaultFileWriter(existsFile, options?.logger);
const determineFileToUpdate = options?.determineFileToUpdate || computeFileToUpdatePath();
const determineFileToUpdate = options?.determineFileToUpdate || await getFileToUpdatePath();
const tokenDefinitionRenderer = options?.tokenDefinitionRenderer || getCssTokenDefinitionRenderer(options);
const styleContentUpdater = options?.styleContentUpdater || getCssStyleContentUpdater();
const tokenPerFile = Array.from(variableSet.values())
Expand Down

0 comments on commit df60398

Please sign in to comment.