UNPKG

@tokens-studio/sdk

Version:
199 lines (197 loc) 7.15 kB
import { parse } from 'acorn'; import { asyncWalk } from 'estree-walker'; import * as prettier from 'prettier/standalone'; import * as parserBabel from 'prettier/plugins/babel'; import * as prettierPluginEstree from 'prettier/plugins/estree'; async function analyzeDependencies(code) { const dependencies = []; const ast = parse(code, { allowImportExportEverywhere: true, ecmaVersion: 'latest', }); // @ts-expect-error mismatch between acorn and estree type for Program await asyncWalk(ast, { enter: async (node) => { if (node.type === 'ImportDeclaration') { const source = `${node.source.value}`; dependencies.push({ source: source, specifiers: node.specifiers.map((spec) => ({ name: spec.local.name, default: spec.type === 'ImportDefaultSpecifier', })), package: source .split('/') .slice(0, source.startsWith('@') ? 2 : 1) .join('/'), }); } }, }); return dependencies; } function getImportsStrMap(dependencies, hasThemes) { const addToDeps = (dependencies, { package: pkg, source, specifiers }) => { const foundSource = dependencies.find((dep) => dep.source === source); if (foundSource) { // add to specifiers and dedupe foundSource.specifiers = [ ...new Set([...foundSource.specifiers, ...specifiers]), ]; } else { dependencies.push({ source, package: pkg, specifiers, }); } }; // if there's theming, we will import permutateThemes, so add to dependencies if (hasThemes) { addToDeps(dependencies, { package: '@tokens-studio/sd-transforms', source: '@tokens-studio/sd-transforms', specifiers: [ { name: 'permutateThemes', default: false, }, ], }); addToDeps(dependencies, { package: 'node:fs', source: 'node:fs', specifiers: [ { name: 'readFileSync', default: false, }, ], }); addToDeps(dependencies, { package: 'node:path', source: 'node:path', specifiers: [ { name: 'path', default: true, }, ], }); } const importsStrMap = new Map(); // convert our dependencies array to a ESM imports string dependencies.forEach((dep) => { importsStrMap.set(dep.source, { namedImportsStr: dep.specifiers .filter((spec) => !spec.default) .map((spec) => spec.name) .join(', '), defaultImportStr: dep.specifiers.find((spec) => spec.default)?.name, }); }); return importsStrMap; } function getDepsESMString(dependencies, hasThemes) { const importsStrMap = getImportsStrMap(dependencies, hasThemes); return Array.from(importsStrMap) .map(([source, data]) => { const { defaultImportStr, namedImportsStr } = data; // named, default, or both: // import foo, { named } from 'bar'; // import foo from 'bar'; // import { named } from 'bar'; return `import ${defaultImportStr ? `${namedImportsStr ? `${defaultImportStr}, ` : defaultImportStr}` : ''}${namedImportsStr ? `{ ${namedImportsStr} }` : ''} from '${source}';`; }) .join('\n'); } // replace {theme} placeholder with ${name} // handle "" to become ``, otherwise ${} doesn't work function handleThemePlaceholder(str) { const reg = /(:\s*?)"(.*){theme}(.*)"/g; return str.replace(reg, (_match, colonPlusSpace, w1, w2) => { return `${colonPlusSpace}\`${w1}\${name}${w2}\``; }); } // make it relative to the script's module location function handleBuildPath(cfg) { if (cfg.platforms) { Object.entries(cfg.platforms).forEach(([key, platform]) => { if (platform.buildPath) { cfg.platforms[key].buildPath = `\`\${path.resolve(import.meta.dirname, '${platform.buildPath}')}/\``; } }); } } function handleBuildPathString(cfg) { // remove wrapping "" around the buildPath values, as a consequence of JSON stringifying the object const matches = [...cfg.matchAll(/"buildPath": ("`.+?`")/g)]; matches.forEach((match) => { if (match[1]) { cfg = cfg.replace(match[1], match[1].slice(1, match[1].length - 1)); } }); return cfg; } function handleConfig(cfg, hasThemes = false) { if (hasThemes) { delete cfg.source; } handleBuildPath(cfg); let str = handleBuildPathString(JSON.stringify(cfg, null, '\t')); if (hasThemes) { str = str.replace('{', `{ source: tokensets.map(tokenset => path.resolve(import.meta.dirname, 'tokens', \`\${tokenset}.json\`)),`); str = str.replace(`"filter": "enabled-sets-from-themes",`, ''); str = handleThemePlaceholder(str); } return str; } export async function combineFunctionsConfig(configsArtifact) { const hasThemes = !!configsArtifact.themeOptions; const dependencies = await analyzeDependencies(configsArtifact.functions); // regex that covers imports const reg = /import[\S\s]+?(from)\s+?['"].+?['"];?/g; const functionsWithoutImports = configsArtifact.functions.replace(reg, ''); const cfg = handleConfig(configsArtifact.config, hasThemes); const functionsContent = `// Note: make sure you install the dependencies used in these imports ${getDepsESMString(dependencies, hasThemes)} ${functionsWithoutImports.trim()}`; let newFileContent = `/** * DO NOT EDIT. This file was automatically generated, and any changes * you make here will probably be overwritten. * Feel free to copy this to another location for you to maintain yourself. */ ${functionsContent} `; const buildString = `// optionally, cleanup files first.. await sd.cleanAllPlatforms(); await sd.buildAllPlatforms();`; if (hasThemes) { newFileContent += ` const $themes = JSON.parse( readFileSync(path.resolve(import.meta.dirname, '$themes.json'), 'utf-8') ); const themes = permutateThemes($themes, { separator: '_' }); const configs = Object.entries(themes).map(([name, tokensets]) => (${cfg})); for (const cfg of configs) { const sd = new StyleDictionary(cfg); ${buildString} } `; } else { newFileContent += ` const sd = new StyleDictionary(${cfg}); ${buildString} `; } const formattedCode = await prettier.format(newFileContent, { parser: 'babel', plugins: [prettierPluginEstree, parserBabel], singleQuote: true, }); return formattedCode; } //# sourceMappingURL=combine-functions-config.js.map