UNPKG

@theemo/build

Version:
1 lines 25.9 kB
{"version":3,"file":"index.cjs","names":["DEFAULT_CONFIG: Partial<BuildConfig>","config: BuildConfig","feature: BuildFeature","MEDIA_QUERY: Record<BrowserMechanic, string>","file: string","config: BuildConfig","feature: ColorSchemeBuildFeature","feature: ModalBuildFeature","feature: MediaQueryBuildFeature","config: BuildConfigWithDefaults"],"sources":["../../theme/dist/index.js","../src/config.ts","../src/features.ts","../src/build.ts"],"sourcesContent":["//#region src/config.ts\n/**\n* Default Theemo options\n*\n* @internal\n*/\nconst DEFAULT_OPTIONS = { outDir: \"theemo\" };\n/**\n* ID to identify Theemo `<meta>` element\n*\n* @internal\n*/\nconst THEEMO_CONFIG_ID = \"theemo-config\";\n/**\n* Extract the config from `<meta>` element\n*\n* @param rootElement element to look for the `<meta>` element in\n* @returns runtime config\n*/\nfunction extractConfig(rootElement = document) {\n\tlet meta = rootElement.querySelector(`meta[name=\"${THEEMO_CONFIG_ID}\"]`);\n\tlet config = meta ? JSON.parse(decodeURI(meta.content)) : {};\n\treturn {\n\t\toptions: {\n\t\t\t...DEFAULT_OPTIONS,\n\t\t\t...config.options ?? {}\n\t\t},\n\t\tthemes: config.themes ?? []\n\t};\n}\n\n//#endregion\n//#region src/features.ts\n/**\n* Color Contrasts\n*\n* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast\n*/\nconst ColorContrast = {\n\tNoPreference: \"no-preference\",\n\tMore: \"more\",\n\tLess: \"less\",\n\tCustom: \"custom\"\n};\n/**\n* Color Scheme\n*\n* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme\n*/\nconst ColorScheme = {\n\tLight: \"light\",\n\tDark: \"dark\"\n};\n/**\n* Motion\n*\n* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion\n*/\nconst Motion = {\n\tNoPreference: \"no-preference\",\n\tReduce: \"reduce\"\n};\nconst BrowserMechanic = {\n\tColorScheme: \"color-scheme\",\n\tColorContrast: \"color-contrast\",\n\tMotion: \"motion\"\n};\n/**\n* The principal who is responsible for the value\n*\n* @see https://theemo.io/design-tokens/traits#principals-and-agents\n*/\nconst Principal = {\n\tBrowser: \"browser\",\n\tUser: \"user\"\n};\n/**\n* Verifies if the given feature has the browser as principal that can change\n* it based on media query or other mechanics\n*\n* @see https://theemo.io/design-tokens/traits#principals-and-agents\n* @param feature the feature in question\n* @returns `true` for a browser feature, otherwise `false`\n*/\nfunction isBrowserFeature(feature) {\n\treturn [\n\t\tBrowserMechanic.ColorContrast,\n\t\tBrowserMechanic.ColorScheme,\n\t\tBrowserMechanic.Motion\n\t].includes(feature.browserFeature);\n}\n/**\n* Checks wether the given feature uses mode behavior\n*\n* @param feature the feature in question\n* @returns `true` for a mode behavior, otherwise `false`\n*/\nfunction isModalFeature(feature) {\n\treturn feature.defaultOption !== void 0;\n}\n/**\n* Checks if the feature contains all valid fields to be operational\n*\n* @param feature the feature in question\n* @returns validation result\n*/\nfunction validateFeature(feature) {\n\tconst errors = [];\n\tif (!feature.name) errors.push(\"Feature is missing a name\");\n\tif (!feature.options) errors.push(`Feature '${feature.name}' requires options`);\n\tif (!feature.browserFeature && !feature.defaultOption) errors.push(`Feature '${feature.name}' requires 'defaultOption' or 'browserFeature'`);\n\tif (feature.defaultOption && !feature.options.includes(feature.defaultOption)) errors.push(`Feature '${feature.name}' has 'defaultOption' to be set to '${feature.defaultOption}', which is not allowed. Allowed values are '${feature.options.join(`', '`)}'.`);\n\tif (feature.browserFeature && !isBrowserFeature(feature)) errors.push(`Feature '${feature.name}' uses invalid browser mechanic: '${feature.browserFeature}' does not exist. Supported values are: '${Object.values(BrowserMechanic).join(\"', \")}'.`);\n\treturn {\n\t\tsuccess: errors.length === 0,\n\t\terrors\n\t};\n}\n\n//#endregion\n//#region src/manager.ts\nconst queries = {\n\t[BrowserMechanic.ColorScheme]: {\n\t\t[ColorScheme.Dark]: \"(prefers-color-scheme: dark)\",\n\t\t[ColorScheme.Light]: \"(prefers-color-scheme: light)\"\n\t},\n\t[BrowserMechanic.ColorContrast]: {\n\t\t[ColorContrast.NoPreference]: \"(prefers-color-contrast: no-preference)\",\n\t\t[ColorContrast.More]: \"(prefers-color-contrast: more)\",\n\t\t[ColorContrast.Less]: \"(prefers-color-contrast: less)\",\n\t\t[ColorContrast.Custom]: \"(prefers-color-contrast: custom)\"\n\t},\n\t[BrowserMechanic.Motion]: {\n\t\t[Motion.NoPreference]: \"(prefers-reduced-motion: no-preference)\",\n\t\t[Motion.Reduce]: \"(prefers-reduced-motion)\"\n\t}\n};\nfunction match(feature) {\n\tconst values = {};\n\tfor (const [value, query] of Object.entries(queries[feature.browserFeature])) {\n\t\tconst check = window.matchMedia(query);\n\t\tif (check.matches) values[feature.name] = value;\n\t}\n\treturn values;\n}\nfunction setupListeners(features, cb) {\n\tconst disposals = [];\n\tconst values = {};\n\tfor (const feature of features) for (const [value, query] of Object.entries(queries[feature.browserFeature])) {\n\t\tconst check = window.matchMedia(query);\n\t\tconst handler = () => cb(match(feature));\n\t\tcheck.addEventListener(\"change\", handler);\n\t\tdisposals.push(() => check.removeEventListener(\"change\", handler));\n\t\tif (check.matches) values[feature.name] = value;\n\t}\n\treturn {\n\t\tvalues,\n\t\tdispose: () => {\n\t\t\tdisposals.forEach((dispose) => dispose());\n\t\t}\n\t};\n}\n/**\n* Manages theming at runtime\n*\n* - switch themes\n* - turn features on and off\n*/\nvar ThemeManager = class {\n\t#elements = /* @__PURE__ */ new Map();\n\t#config;\n\t#options;\n\t#activeThemeManager = {};\n\t#defaultFeatureValues = {};\n\t#browserFeatureValues = {};\n\t#modeFeatureValues = {};\n\t/** The active theme */\n\tactiveTheme;\n\t/** Features of the active theme */\n\tfeatures = [];\n\tconstructor(options = {}) {\n\t\tthis.#options = options;\n\t\tthis.#config = extractConfig();\n\t\tfor (const link of document.querySelectorAll(\"head > link\")) if (link.dataset.theemo) {\n\t\t\tthis.#elements.set(link.dataset.theemo, link);\n\t\t\tlink.disabled = link.dataset.theemo !== this.#config.options.defaultTheme;\n\t\t}\n\t\tif (this.#config.options.defaultTheme) this.switchTheme(this.#config.options.defaultTheme);\n\t}\n\t/**\n\t* List of available themes\n\t*/\n\tget themes() {\n\t\treturn this.#config.themes;\n\t}\n\tget #featureValues() {\n\t\treturn {\n\t\t\t...this.#defaultFeatureValues,\n\t\t\t...this.#browserFeatureValues,\n\t\t\t...this.#modeFeatureValues\n\t\t};\n\t}\n\t#findFeature(name) {\n\t\tconst feature = this.features.find((f) => f.name === name);\n\t\tif (!feature) throw new Error(`Cannot find feature '${name}': feature doesn't exist`);\n\t\treturn feature;\n\t}\n\t#getPrincipal(featureOrName) {\n\t\tconst feature = typeof featureOrName === \"string\" ? this.#findFeature(featureOrName) : featureOrName;\n\t\tconst overriddenValue = isBrowserFeature(feature) && this.#modeFeatureValues[feature.name];\n\t\tconst principal = isBrowserFeature(feature) ? overriddenValue ? Principal.User : Principal.Browser : Principal.User;\n\t\treturn principal;\n\t}\n\t/**\n\t* Set the feature to a value\n\t*\n\t* @param featureName the name of the feature\n\t* @param value the value for that feature\n\t*/\n\tsetFeature(featureName, value) {\n\t\tconst feature = this.#findFeature(featureName);\n\t\tif (!feature.options.includes(value)) throw new Error(`Cannot set mode '${feature.name}' to '${value}': option doesn't exist`);\n\t\tthis.#modeFeatureValues[feature.name] = value;\n\t\tthis.#updateFeatures();\n\t\tdocument.documentElement.setAttribute(`data-theemo-${feature.name}`, value);\n\t\tthis.#options.featureChanged?.(feature);\n\t}\n\t/**\n\t* Turn off a feature. Revert to its default.\n\t*\n\t* @param featureName the name of the feature\n\t*/\n\tunsetFeature(featureName) {\n\t\tconst feature = this.#findFeature(featureName);\n\t\tdelete this.#modeFeatureValues[feature.name];\n\t\tthis.#updateFeatures();\n\t\tdocument.documentElement.removeAttribute(`data-theemo-${feature.name}`);\n\t\tthis.#options.featureChanged?.(feature);\n\t}\n\t/**\n\t* Switch to another theme\n\t*\n\t* @param name theme name\n\t*/\n\tasync switchTheme(name) {\n\t\tif (this.activeTheme?.name === name) return;\n\t\tconst theme = this.themes.find((t) => t.name === name);\n\t\tif (!theme) throw new Error(`Cannot switch theme '${name}': theme doesn't exist`);\n\t\tawait this.#ensureThemeIsLoaded(theme);\n\t\tthis.#activateTheme(theme);\n\t\tif (this.activeTheme) this.#deactivateTheme(this.activeTheme);\n\t\tthis.#setupDefaultFeatures(theme);\n\t\tthis.#setupBrowserFeatures(theme);\n\t\tthis.activeTheme = theme;\n\t\tthis.#updateFeatures();\n\t\tthis.#options.themeChanged?.(theme);\n\t}\n\tasync #ensureThemeIsLoaded(theme) {\n\t\tif (!this.#elements.has(theme.name)) await this.#loadTheme(theme);\n\t}\n\t#activateTheme(theme) {\n\t\tconst element = this.#elements.get(theme.name);\n\t\telement.disabled = false;\n\t}\n\t#setupBrowserFeatures(theme) {\n\t\tconst browserBrowserFeatures = (theme.features ?? []).filter(isBrowserFeature);\n\t\tconst browser = setupListeners(browserBrowserFeatures, (values) => this.#handleChangeFeatures(values));\n\t\tthis.#activeThemeManager.teardownListeners = browser.dispose;\n\t\tthis.#browserFeatureValues = browser.values;\n\t}\n\t#handleChangeFeatures(values) {\n\t\tconst dump = { ...this.#featureValues };\n\t\tthis.#browserFeatureValues = {\n\t\t\t...this.#browserFeatureValues,\n\t\t\t...values\n\t\t};\n\t\tthis.#updateFeatures();\n\t\tconst changes = Object.keys(Object.entries(this.#featureValues).filter(([k, v]) => dump[k] !== v));\n\t\tfor (const featureName of changes) this.#options.featureChanged?.(this.#findFeature(featureName));\n\t}\n\t#updateFeatures() {\n\t\tthis.features = (this.activeTheme?.features ?? []).map((f) => ({\n\t\t\t...f,\n\t\t\tvalue: this.#featureValues[f.name],\n\t\t\tbrowserValue: isBrowserFeature(f) ? this.#browserFeatureValues[f.name] : void 0,\n\t\t\tprincipal: this.#getPrincipal(f)\n\t\t}));\n\t}\n\t#setupDefaultFeatures(theme) {\n\t\tconst defaultFeatures = (theme.features ?? []).filter(isModalFeature);\n\t\tthis.#defaultFeatureValues = defaultFeatures.reduce((values, f) => ({\n\t\t\t...values,\n\t\t\t[f.name]: f.defaultOption\n\t\t}), {});\n\t}\n\t#deactivateTheme(theme) {\n\t\tthis.#defaultFeatureValues = {};\n\t\tthis.#browserFeatureValues = {};\n\t\tthis.#modeFeatureValues = {};\n\t\tthis.#clearModes(theme);\n\t\tthis.#elements.get(theme.name).disabled = true;\n\t}\n\t#clearModes(theme) {\n\t\tfor (const feature of theme.features ?? []) document.documentElement.removeAttribute(`data-theemo-${feature.name}`);\n\t}\n\tasync #loadTheme(theme) {\n\t\tconst element = await this.#createLinkElement(theme);\n\t\tthis.#elements.set(theme.name, element);\n\t}\n\t#createLinkElement(theme) {\n\t\tconst linkElement = document.createElement(\"link\");\n\t\tlinkElement.setAttribute(\"href\", `/${this.#config.options.outDir}/${theme.filename}.css`);\n\t\tlinkElement.setAttribute(\"type\", \"text/css\");\n\t\tlinkElement.setAttribute(\"rel\", \"stylesheet\");\n\t\tlinkElement.setAttribute(\"title\", theme.name);\n\t\tlinkElement.dataset.theemo = theme.name;\n\t\tdocument.head.append(linkElement);\n\t\treturn new Promise((resolve) => {\n\t\t\tconst listener = () => {\n\t\t\t\tlinkElement.removeEventListener(\"load\", listener);\n\t\t\t\tlinkElement.disabled = true;\n\t\t\t\tresolve(linkElement);\n\t\t\t};\n\t\t\tlinkElement.addEventListener(\"load\", listener);\n\t\t});\n\t}\n};\n\n//#endregion\n//#region src/theme.ts\n/**\n* Validates a theme for being correct\n*\n* @example\n*\n* Check for a valid theme:\n*\n* ```ts\n* const validation = validateTheme(myTheme);\n*\n* if (validation.success) {\n* // proceed with valid theme\n* } else {\n* console.log(validation.errors);\n* }\n* ```\n*\n*\n* @param pkg the given theme\n* @returns the validation result\n*/\nfunction validateTheme(theme) {\n\tconst errors = [];\n\tif (!theme.name) errors.push(`Theme requires 'name'`);\n\tfor (const feature of theme.features ?? []) errors.push(...validateFeature(feature).errors);\n\treturn {\n\t\tsuccess: errors.length === 0,\n\t\terrors\n\t};\n}\n\n//#endregion\n//#region src/package.ts\n/**\n* The keyword to look for in a `package.json` file\n*/\nconst KEYWORD = \"theemo-theme\";\n/**\n* Checks if the given package is a Theemo package\n*\n* @param pkg the given package\n* @returns `true` if it is a Theemo package, otherwise `false`\n*/\nfunction isTheemoPackage(pkg) {\n\treturn (pkg.keywords ?? []).includes(KEYWORD);\n}\n/**\n* Validates a package for being correct\n*\n* @example\n*\n* Check for a valid package:\n*\n* ```ts\n* const validation = validateTheemoPackage(myPkg);\n*\n* if (validation.success) {\n* // proceed with valid package\n* } else {\n* console.log(validation.errors);\n* }\n* ```\n*\n* @param pkg the given package\n* @returns the validation result\n*/\nfunction validateTheemoPackage(pkg) {\n\tconst errors = [];\n\tif (!isTheemoPackage(pkg)) errors.push(`Package '${pkg.name}' requires keyword '${KEYWORD}'`);\n\tif (!pkg.theemo) errors.push(`Package '${pkg.name}' requires 'theemo' field`);\n\telse {\n\t\tconst themeValidation = validateTheme(pkg.theemo);\n\t\tif (!pkg.theemo.file) errors.push(`Theemo in package '${pkg.name}' requires 'file'`);\n\t\terrors.push(...themeValidation.errors);\n\t}\n\treturn {\n\t\tsuccess: errors.length === 0,\n\t\terrors\n\t};\n}\n\n//#endregion\nexport { BrowserMechanic, ColorContrast, ColorScheme, DEFAULT_OPTIONS, Motion, Principal, THEEMO_CONFIG_ID, ThemeManager, isBrowserFeature, isModalFeature, isTheemoPackage, validateTheemoPackage };\n//# sourceMappingURL=index.js.map","import type { BuildFeature } from './features';\nimport type { CustomAtRules, TransformOptions } from 'lightningcss';\n\n/**\n * Config for building a theme\n */\nexport interface BuildConfig {\n /**\n * Specify the output directory\n *\n * @default `dist`\n */\n outDir?: string;\n\n /**\n * The files that will be concatenated into the output file\n */\n files?: string[];\n\n /**\n * Instructions for how to build the features\n */\n features?: BuildFeature[];\n\n /**\n * Lightning CSS is used for postprocess. You can pass options to lightning\n * css here or turn it off entirely.\n *\n * @default `true`\n */\n lightningcss?: boolean | Omit<TransformOptions<CustomAtRules>, 'code' | 'filename'>;\n}\n\n// types for optional keys\n// by https://gist.github.com/eddiemoore/7873191f366675e520e802a9fb2531d8\n\ntype Undefined<T> = { [P in keyof T]: P extends undefined ? T[P] : never };\n\ntype FilterFlags<Base, Condition> = {\n [Key in keyof Base]: Base[Key] extends Condition ? Key : never;\n};\n\ntype AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];\n\ntype SubType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;\n\ntype OptionalKeys<T> = Exclude<keyof T, NonNullable<keyof SubType<Undefined<T>, never>>>;\n\ntype DefaultBuildConfig = Omit<\n Required<Pick<BuildConfig, OptionalKeys<BuildConfig>>>,\n 'output' | 'files'\n>;\n\nexport type BuildConfigWithDefaults = BuildConfig & DefaultBuildConfig;\n\nexport const DEFAULT_CONFIG: Partial<BuildConfig> = {\n outDir: 'dist',\n features: [] as BuildFeature[],\n lightningcss: true\n};\n\nexport function configWithDefaults(config: BuildConfig): BuildConfigWithDefaults {\n return {\n ...DEFAULT_CONFIG,\n ...config\n } as BuildConfigWithDefaults;\n}\n","import { BrowserMechanic, isModalFeature } from '@theemo/theme';\n\nimport type {\n ColorContrast,\n ColorContrastFeature,\n ColorSchemeFeature,\n CustomFeature,\n Feature,\n Motion,\n MotionFeature\n} from '@theemo/theme';\n\nexport interface CustomBuildFeature extends Omit<CustomFeature, 'options'> {\n /**\n * Use options to point at a CSS file for that option\n *\n * @example\n *\n * ```json\n * {\n * \"options\": {\n * \"option-a\": \"build/option-a.css\",\n * \"option-b\": \"build/option-b.css\"\n * }\n * }\n * ```\n */\n options: Record<string, string>;\n}\n\nexport type ColorSchemeBuildFeature = ColorSchemeFeature;\n\nexport interface ColorContrastBuildFeature extends Omit<ColorContrastFeature, 'options'> {\n /**\n * Use options to point at a CSS file for that option\n *\n * @example\n *\n * ```json\n * {\n * \"options\": {\n * \"less\": \"build/contrast-less.css\",\n * \"more\": \"build/contrast-more.css\"\n * }\n * }\n * ```\n */\n options: Record<ColorContrast, string>;\n}\n\nexport interface MotionBuildFeature extends Omit<MotionFeature, 'options'> {\n /**\n * Use options to point at a CSS file for that option\n *\n * @example\n *\n * ```json\n * {\n * \"options\": {\n * \"no-preference\": \"build/motion.css\",\n * \"reduce\": \"build/motion-reduce.css\"\n * }\n * }\n * ```\n */\n options: Record<Motion, string>;\n}\n\nexport type MediaQueryBuildFeature = ColorContrastBuildFeature | MotionBuildFeature;\n\nexport type BuildFeature =\n | ColorSchemeBuildFeature\n | ColorContrastBuildFeature\n | MotionBuildFeature\n | CustomBuildFeature;\n\nexport type ModalBuildFeature = Exclude<BuildFeature, ColorSchemeBuildFeature>;\n\nexport function isModalBuildFeature(feature: BuildFeature): feature is ModalBuildFeature {\n return isModalFeature(feature as unknown as Feature);\n}\n\nexport function isColorSchemeFeature(feature: BuildFeature): feature is ColorSchemeBuildFeature {\n return feature.browserFeature === BrowserMechanic.ColorScheme;\n}\n\nexport function isMediaQueryFeature(feature: BuildFeature): feature is MediaQueryBuildFeature {\n return (\n feature.browserFeature === BrowserMechanic.ColorContrast ||\n feature.browserFeature === BrowserMechanic.Motion\n );\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { transform } from 'lightningcss';\nimport { readPackageSync } from 'read-pkg';\nimport { writePackageSync } from 'write-package';\n\nimport { BrowserMechanic } from '@theemo/theme';\n\nimport { configWithDefaults } from './config';\nimport { isColorSchemeFeature, isMediaQueryFeature, isModalBuildFeature } from './features';\n\nimport type { BuildConfig, BuildConfigWithDefaults } from './config';\nimport type {\n ColorSchemeBuildFeature,\n MediaQueryBuildFeature,\n ModalBuildFeature\n} from './features';\nimport type { Feature, TheemoPackage, Theme } from '@theemo/theme';\n\nconst MEDIA_QUERY: Record<BrowserMechanic, string> = {\n [BrowserMechanic.ColorScheme]: 'prefers-color-scheme',\n [BrowserMechanic.ColorContrast]: 'prefers-contrast',\n [BrowserMechanic.Motion]: 'prefers-reduced-motion'\n};\n\nfunction readFile(file: string) {\n const contents = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : '';\n\n return contents;\n}\n\nfunction combineFiles(config: BuildConfig) {\n const contents = [];\n\n for (const file of config.files ?? []) {\n contents.push(readFile(file).trim());\n }\n\n return contents;\n}\n\nfunction buildColorSchemeFeature(feature: ColorSchemeBuildFeature) {\n return `\n:root {\n color-scheme: ${(feature.options as string[]).join(' ')};\n}\n\n[data-theemo-color-scheme=\"light\"] {\n color-scheme: light;\n}\n\n[data-theemo-color-scheme=\"dark\"] {\n color-scheme: dark;\n}\n `.trim();\n}\n\nfunction buildFeature(feature: ModalBuildFeature) {\n const contents = [];\n\n for (const [option, file] of Object.entries(feature.options)) {\n const fileContents = readFile(file);\n\n const selectors = option === feature.defaultOption ? [':root'] : [];\n\n selectors.push(`[data-theemo-${feature.name}=\"${option}\"]`);\n\n const css = fileContents.replace(':root', selectors.join(', ')).trim();\n\n contents.push(css);\n }\n\n return contents;\n}\n\nfunction buildMediaQueryBrowserFeature(feature: MediaQueryBuildFeature) {\n const contents = [];\n\n const mediaQuery = MEDIA_QUERY[feature.browserFeature as BrowserMechanic];\n\n for (const [option, file] of Object.entries(feature.options)) {\n const fileContents = readFile(file);\n\n const css = `@media (${mediaQuery}: ${option}) {\n ${fileContents}\n}`;\n\n contents.push(css);\n }\n\n return contents;\n}\n\nfunction buildFeatures(config: BuildConfigWithDefaults) {\n const contents = [];\n\n for (const feature of config.features) {\n if (isColorSchemeFeature(feature)) {\n contents.push(buildColorSchemeFeature(feature));\n } else if (isModalBuildFeature(feature)) {\n contents.push(...buildFeature(feature));\n }\n\n if (isMediaQueryFeature(feature)) {\n contents.push(...buildMediaQueryBrowserFeature(feature));\n }\n }\n\n return contents;\n}\n\nfunction getThemeName() {\n const data = readPackageSync();\n\n return (data.theemo as Theme | undefined)?.name ?? data.name;\n}\n\nfunction buildFiles(config: BuildConfigWithDefaults) {\n const name = getThemeName();\n const contents = [...combineFiles(config), ...buildFeatures(config)];\n\n if (!fs.existsSync(config.outDir)) {\n fs.mkdirSync(config.outDir);\n }\n\n const outFile = path.join(config.outDir, `${name}.css`);\n const css = contents.join('\\n\\n');\n\n fs.writeFileSync(outFile, css);\n\n if (config.lightningcss !== false) {\n const options = typeof config.lightningcss === 'object' ? config.lightningcss : {};\n\n const { code } = transform({\n code: Buffer.from(css),\n filename: outFile,\n ...options\n });\n\n fs.writeFileSync(outFile, code);\n }\n}\n\nfunction updatePackage(config: BuildConfigWithDefaults) {\n const packageJson = readPackageSync({ normalize: false }) as TheemoPackage;\n const theemo = packageJson.theemo;\n\n theemo.file = path.join(config.outDir, `${theemo.name}.css`);\n theemo.features = config.features.map(\n (f) =>\n ({\n ...f,\n options: Array.isArray(f.options) ? f.options : Object.keys(f.options)\n }) as Feature\n );\n\n if (!Array.isArray(packageJson.keywords)) {\n packageJson.keywords = [];\n }\n\n if (!packageJson.keywords.includes('theemo-theme')) {\n packageJson.keywords.push('theemo-theme');\n }\n\n writePackageSync(packageJson);\n}\n\n/**\n * Build a theme, which can be managed by theemo\n *\n * @param config The configuration for the theme and its behavior\n */\nexport function build(config: BuildConfig): void {\n const defaultConfig = configWithDefaults(config);\n\n buildFiles(defaultConfig);\n\n updatePackage(defaultConfig);\n}\n"],"mappings":"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"}