UNPKG

style-dictionary

Version:

Style once, use everywhere. A build system for creating cross-platform styles.

130 lines (116 loc) 4.4 kB
/* * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0 * * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ import { globSync } from '@bundled-es-modules/glob'; 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'; import { loadFile } from './loadFile.js'; /** * @typedef {import('../../types/Volume.d.ts').Volume} Volume * @typedef {import('../../types/DesignToken.d.ts').DesignTokens} Tokens * @typedef {import('../../types/DesignToken.d.ts').DesignToken} Token * @typedef {import('../../types/Parser.d.ts').Parser} Parser */ /** * @param {Tokens} obj * @param {(obj: Tokens|Token, key: keyof Tokens|Token, slice: Tokens|Token|string) => void} fn */ function traverseObj(obj, fn) { for (let key in obj) { const prop = obj[key]; if (prop != null) { fn.apply(null, [obj, key, prop]); if (typeof prop === 'object') { traverseObj(prop, fn); } } } } /** * Takes an array of json files and merges * them together. Optionally does a deep extend. * @private * @param {string[]} arr - Array of paths to json (or node modules that export objects) files * @param {Boolean} [deep=false] - If it should perform a deep merge * @param {Function} [collision] - A function to be called when a name collision happens that isn't a normal deep merge of objects * @param {boolean} [source] - If json files are "sources", tag tokens * @param {Record<string, Omit<Parser, 'name'>>} [parsers] - Custom file parsers * @param {boolean} [usesDtcg] - Whether or not tokens are using DTCG syntax. * @param {Volume} [vol] - Filesystem volume to use * @returns {Promise<{tokens: Tokens, usesDtcg: boolean|undefined }>} */ export default async function combineJSON( arr, deep = false, collision, source = true, parsers = {}, usesDtcg, vol, ) { const volume = vol ?? fs; /** @type {Tokens} */ const to_ret = {}; /** @type {string[]} */ let files = []; for (let i = 0; i < arr.length; i++) { /** @ts-expect-error memfs's IFs type does not seem to implement readdir correctly, 2nd param seems incorrect? path-scurry throws type error on it */ const new_files = globSync(arr[i], { fs: volume, posix: true }).sort(); files = files.concat(new_files); } 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(/^\//, '')); } for (let i = 0; i < files.length; i++) { const filePath = files[i]; const resolvedPath = resolve(filePath, vol?.__custom_fs__); let file_content = null; for (const { pattern, parser } of Object.values(parsers)) { if (filePath.match(pattern)) { file_content = await parser({ contents: /** @type {string} */ (volume.readFileSync(resolvedPath, 'utf-8')), filePath: resolvedPath, }); } } // If there is no file_content then no custom parser ran on that file if (!file_content) { file_content = /** @type {Tokens} */ (await loadFile(filePath, vol)); } if (file_content) { if (usesDtcg === undefined) { usesDtcg = detectDtcgSyntax(file_content); } // Add some side data on each property to make filtering easier traverseObj(file_content, (obj) => { if (Object.hasOwn(obj, `${usesDtcg ? '$' : ''}value`) && !obj.filePath) { obj.filePath = filePath; obj.isSource = source; } }); if (deep) { deepExtend([to_ret, file_content], { collision, overrideKeys: [usesDtcg ? '$value' : 'value'], }); } else { Object.assign(to_ret, file_content); } } } return { tokens: to_ret, usesDtcg }; }