Skip to content

Commit

Permalink
fix: convert binary files to b64, reuse isNode util
Browse files Browse the repository at this point in the history
  • Loading branch information
jorenbroekema committed Oct 14, 2024
1 parent 9288a75 commit 5b80939
Show file tree
Hide file tree
Showing 15 changed files with 104 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/hungry-phones-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'style-dictionary': patch
---

Fix `convertToBase64` util to support converting binary files such as fonts, for both Browser and NodeJS.
3 changes: 2 additions & 1 deletion __tests__/__helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { expect } from 'chai';
import { fs } from 'style-dictionary/fs';
import { resolve } from '../lib/resolve.js';
import isPlainObject from 'is-plain-obj';
import { isNode } from '../lib/utils/isNode.js';

export const cleanConsoleOutput = (str) => {
const arr = str
Expand Down Expand Up @@ -70,7 +71,7 @@ export const dirExists = (dirPath, _fs = fs) => {
export function fixDate() {
const constantDate = new Date('2000-01-01');
// eslint-disable-next-line no-undef
const __global = typeof window === 'object' ? window : globalThis;
const __global = isNode ? globalThis : window;

// eslint-disable-next-line no-undef
__global.Date = function () {
Expand Down
3 changes: 2 additions & 1 deletion __tests__/__setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { fs } from 'style-dictionary/fs';
import { chaiWtrSnapshot } from '../snapshot-plugin/chai-wtr-snapshot.js';
import { fixDate } from './__helpers.js';
import { writeZIP } from '../lib/utils/convertToDTCG.js';
import { isNode } from '../lib/utils/isNode.js';

/**
* We have a bunch of files that we use a mock data for our tests
Expand All @@ -23,7 +24,7 @@ export const hasInitialized = new Promise((resolve) => {
hasInitializedResolve = resolve;
});
// in case of Node env, we can resolve it immediately since we don't do this setup stuff
if (typeof window !== 'object') {
if (isNode) {
hasInitializedResolve();
}

Expand Down
3 changes: 2 additions & 1 deletion __tests__/formats/lessIcons.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { expect } from 'chai';
import formats from '../../lib/common/formats.js';
import createFormatArgs from '../../lib/utils/createFormatArgs.js';
import flattenTokens from '../../lib/utils/flattenTokens.js';
import { isNode } from '../../lib/utils/isNode.js';

const file = {
destination: '__output/',
Expand Down Expand Up @@ -67,7 +68,7 @@ describe('formats', () => {
);

let _less;
if (typeof window === 'object') {
if (!isNode) {
await import('less/dist/less.js');
// eslint-disable-next-line no-undef
_less = less;
Expand Down
3 changes: 2 additions & 1 deletion __tests__/formats/lessVariables.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { expect } from 'chai';
import formats from '../../lib/common/formats.js';
import createFormatArgs from '../../lib/utils/createFormatArgs.js';
import flattenTokens from '../../lib/utils/flattenTokens.js';
import { isNode } from '../../lib/utils/isNode.js';

const file = {
destination: '__output/',
Expand Down Expand Up @@ -56,7 +57,7 @@ describe('formats', () => {
file,
);
let _less;
if (typeof window === 'object') {
if (!isNode) {
await import('less/dist/less.js');
// eslint-disable-next-line no-undef
_less = less;
Expand Down
3 changes: 2 additions & 1 deletion __tests__/register/overwrite.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import StyleDictionary from 'style-dictionary';
import { expect } from 'chai';
import transformBuiltins from '../../lib/common/transforms.js';
import { isNode } from '../../lib/utils/isNode.js';

describe('Register overwrites', () => {
const reset = () => {
Expand Down Expand Up @@ -31,7 +32,7 @@ describe('Register overwrites', () => {
// Unfortunately, Mocha Node runs this in parallel with other test files and these tests
// fail purely due to multiple test files writing stuff to the Register class
// TODO: In the future we may be able to run mocha test files in parallel processes
if (typeof window !== 'undefined') {
if (!isNode) {
expect(sd1.hooks.transforms[builtInHookName].type).to.equal('value');
expect(sd2.hooks.transforms[builtInHookName].type).to.equal('name');
expect(sd3.hooks.transforms[builtInHookName].type).to.equal('name');
Expand Down
11 changes: 11 additions & 0 deletions __tests__/utils/__snapshots__/convertToBase64.test.snap.js

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

42 changes: 32 additions & 10 deletions __tests__/utils/convertToBase64.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@
*/
import { expect } from 'chai';
import convertToBase64 from '../../lib/utils/convertToBase64.js';
import { isNode } from '../../lib/utils/isNode.js';

describe('utils', () => {
describe('convertToBase64', () => {
it('should error if filePath isnt a string', () => {
expect(convertToBase64.bind(null)).to.throw('filePath name must be a string');
expect(convertToBase64.bind(null, [])).to.throw('filePath name must be a string');
expect(convertToBase64.bind(null, {})).to.throw('filePath name must be a string');
it('should error if filePath isnt a string', async () => {
const err = 'filePath name must be a string';
await expect(convertToBase64()).to.eventually.be.rejectedWith(err);
await expect(convertToBase64([])).to.eventually.be.rejectedWith(err);
await expect(convertToBase64({})).to.eventually.be.rejectedWith(err);
});

it('should error if filePath isnt a file', () => {
it('should error if filePath isnt a file', async () => {
let errMessage;
try {
convertToBase64('foo');
await convertToBase64('foo');
} catch (e) {
errMessage = e.message;
}
Expand All @@ -35,14 +37,34 @@ describe('utils', () => {
);
});

it('should return a string', () => {
expect(typeof convertToBase64('__tests__/__configs/test.json')).to.equal('string');
it('should return a string', async () => {
expect(typeof (await convertToBase64('__tests__/__configs/test.json'))).to.equal('string');
});

it('should be a valid base64 string', () => {
expect(convertToBase64('__tests__/__json_files/simple.json')).to.equal(
it('should be a valid base64 string', async () => {
expect(await convertToBase64('__tests__/__json_files/simple.json')).to.equal(
'ewogICJmb28iOiAiYmFyIiwKICAiYmFyIjogIntmb299Igp9',
);
});

it('should convert binary files', async () => {
/**
* The base64 is different between these environment because of
* the fact that the browser uses the FileReaderAPI as a round-about way
* to turn the binary file into a base64 string, so the data representation
* is different in browsers than when doing buffer.toString in Node.
* Using memfs in browser to read/store a binary file is also not really supported I think..
* which may also cause a different base64 string result, but the utility is mostly for Node users anyways
*/
if (!isNode) {
await expect(
await convertToBase64('__tests__/__assets/images/mdpi/flag_us_small.png'),
).to.matchSnapshot(1);
} else {
await expect(
await convertToBase64('__tests__/__assets/images/mdpi/flag_us_small.png'),
).to.matchSnapshot(2);
}
});
});
});
3 changes: 2 additions & 1 deletion lib/StyleDictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import filterTokens from './filterTokens.js';
import cleanFiles from './cleanFiles.js';
import cleanDirs from './cleanDirs.js';
import cleanActions from './cleanActions.js';
import { isNode } from './utils/isNode.js';

/**
* @typedef {import('../types/Volume.ts').Volume} Volume
Expand Down Expand Up @@ -227,7 +228,7 @@ export default class StyleDictionary extends Register {
);
} else {
let _filePath = cfgFilePath;
if (typeof window !== 'object' && process?.platform === 'win32') {
if (isNode && process?.platform === 'win32') {
// Windows FS compatibility. If in browser, we use an FS shim which doesn't require this Windows workaround
_filePath = new URL(`file:///${_filePath}`).href;
}
Expand Down
3 changes: 2 additions & 1 deletion lib/resolve.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { posix, win32 } from 'path-unified';
import { isNode } from './utils/isNode.js';

/**
* If we're on Windows AND we're not in browser context, use win32 resolve (with \'s)
Expand All @@ -11,7 +12,7 @@ export const resolve = (path, customVolumeUsed = false) => {
if (customVolumeUsed) {
return path;
}
if (process?.platform === 'win32' && typeof window !== 'object') {
if (isNode && process?.platform === 'win32') {
return win32.resolve(path);
}
return posix.resolve(path);
Expand Down
5 changes: 3 additions & 2 deletions lib/utils/combineJSON.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { fs } from 'style-dictionary/fs';
import { resolve } from '../resolve.js';
import deepExtend from './deepExtend.js';
import { detectDtcgSyntax } from './detectDtcgSyntax.js';
import { isNode } from './isNode.js';

/**
* @typedef {import('../../types/Volume.ts').Volume} Volume
Expand Down Expand Up @@ -76,7 +77,7 @@ export default async function combineJSON(
files = files.concat(new_files);
}

if (typeof window === 'object') {
if (!isNode) {
// adjust for browser env glob results have leading slash
// make sure we dont remove these in Node, that would break absolute paths!!
files = files.map((f) => f.replace(/^\//, ''));
Expand All @@ -101,7 +102,7 @@ export default async function combineJSON(
if (['.js', '.mjs'].includes(extname(filePath))) {
let resolvedPath = resolve(filePath, vol?.__custom_fs__);
// eslint-disable-next-line no-undef
if (typeof window !== 'object' && process?.platform === 'win32') {
if (isNode && process?.platform === 'win32') {
// Windows FS compatibility. If in browser, we use an FS shim which doesn't require this Windows workaround
resolvedPath = new URL(`file:///${resolvedPath}`).href;
}
Expand Down
39 changes: 31 additions & 8 deletions lib/utils/convertToBase64.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,43 @@

import { fs } from 'style-dictionary/fs';
import { resolve } from '../resolve.js';
import { isNode } from './isNode.js';

/**
* @param {Buffer} buffer
* @returns {string|Promise<string>}
*/
function toBase64(buffer) {
if (isNode) {
// Node.js environment
return buffer.toString('base64');
} else {
// Browser environment
return new Promise((resolve, reject) => {
const blob = new Blob([buffer], { type: 'application/octet-stream' });
const reader = new FileReader();
reader.onloadend = () => {
const base64String = /** @type {string } */ (reader.result).split(',')[1];
resolve(base64String);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
}

/**
* @typedef {import('../../types/Volume.ts').Volume} Volume
* Takes a file and converts it to a base64 string.
* @private
* @param {String} filePath - Path to the file you want base64'd
* @param {string} filePath - Path to the file you want base64'd
* @param {Volume} [vol]
* @returns {String}
* @returns {Promise<string>}
*/
export default function convertToBase64(filePath, vol = fs) {
export default async function convertToBase64(filePath, vol = fs) {
if (typeof filePath !== 'string') throw new Error('Token filePath name must be a string');

const body = /** @type {string} */ (
vol.readFileSync(resolve(filePath, vol.__custom_fs__), 'utf-8')
);
return btoa(body);
// typecast to Buffer because we know that without specifying encoding, this returns a Buffer
// @ts-expect-error requires encoding options, this is a mistake in memfs types definition
const body = /** @type {Buffer} */ (vol.readFileSync(resolve(filePath, vol.__custom_fs__)));
return toBase64(body);
}
1 change: 1 addition & 0 deletions lib/utils/isNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const isNode = typeof window === 'undefined';
4 changes: 2 additions & 2 deletions package-lock.json

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

10 changes: 5 additions & 5 deletions snapshot-plugin/chai-wtr-snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
* e.g. with https://www.npmjs.com/package/pretty-format (right now we only escape tildes)
*/

import { isNode } from '../lib/utils/isNode.js';

// Exclude from code coverage since this is just a devtool
/* c8 ignore start */

/**
* @typedef {import('@types/chai')} Chai
*/

const isBrowser = typeof window === 'object';

async function blobToDataUrl(blob) {
let buffer = Buffer.from(await blob.text());
return 'data:' + blob.type + ';base64,' + buffer.toString('base64');
Expand Down Expand Up @@ -75,7 +75,7 @@ export function chaiWtrSnapshot(chai, utils) {
let updateSnapshots = false;
let currentSnapshot;
let name;
if (isBrowser) {
if (!isNode) {
// WTR ENV
const { getSnapshot, getSnapshotConfig } = await import('@web/test-runner-commands');
// eslint-disable-next-line no-undef
Expand Down Expand Up @@ -110,7 +110,7 @@ export function chaiWtrSnapshot(chai, utils) {
);
}
} else if (currentSnapshot !== snapshot) {
if (isBrowser) {
if (!isNode) {
const { saveSnapshot: saveSnapshotWTR } = await import('@web/test-runner-commands');
await saveSnapshotWTR({ name, content: snapshot });
} else {
Expand Down Expand Up @@ -148,7 +148,7 @@ export function chaiWtrSnapshot(chai, utils) {
},
};

if (isBrowser) {
if (!isNode) {
return;
}

Expand Down

0 comments on commit 5b80939

Please sign in to comment.