@bezel-labs/bezel-kit 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/collapse.ts","../src/name.ts","../src/resolve.ts","../src/serialize.ts","../src/emit.ts","../src/defaults.ts","../src/options.ts","../src/contexts.ts","../src/fonts.ts","../src/lockfile.ts","../src/patch-applied.ts","../src/index.ts"],"sourcesContent":["import type { DtcgNode, FlatToken, TokenValue } from \"./types\"\n\n/** The W3C \"root token\" key — the base value of a group / logical token. */\nexport const ROOT_CONTEXT = \"$root\"\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v)\n}\n\n/** A \"context wrapper\" is a child object that directly holds a `$value` (e.g. `dark`, `light`, `$root`). */\nfunction isContextWrapper(v: unknown): v is DtcgNode {\n return isObject(v) && \"$value\" in v\n}\n\n/** Keys that are DTCG metadata, never traversed as child tokens/contexts. */\nfunction isMetaKey(key: string): boolean {\n return key === \"$type\" || key === \"$description\" || key === \"$extensions\" || key === \"$value\"\n}\n\n/**\n * Collapse a DTCG token tree to the single value that applies for `context`, and\n * flatten it to a map of dot-path → {@link FlatToken}.\n *\n * Handles three node shapes:\n * - **Simple token** — a node with a direct `$value` (context-invariant).\n * - **Logical token** — a node whose children include context wrappers (one of\n * which is `$root`). The value for `context` is picked, falling back to `$root`.\n * - **Group** — anything else; recursed into.\n *\n * `$type` is inherited from the nearest ancestor that declares one.\n */\nexport function flattenForContext(tokens: DtcgNode, context: string): Map<string, FlatToken> {\n const out = new Map<string, FlatToken>()\n\n const walk = (node: DtcgNode, path: string, inheritedType: string | undefined): void => {\n const type = typeof node.$type === \"string\" ? node.$type : inheritedType\n\n // Simple token: direct $value.\n if (\"$value\" in node) {\n out.set(path, {\n path,\n value: node.$value as TokenValue,\n type,\n extensions: node.$extensions,\n })\n return\n }\n\n // Logical token: has context-wrapper children (dark/light/$root).\n const wrapperKeys = Object.keys(node).filter((k) => !isMetaKey(k) && isContextWrapper(node[k]))\n if (wrapperKeys.length > 0) {\n const chosen =\n (node[context] as DtcgNode | undefined) ??\n (node[ROOT_CONTEXT] as DtcgNode | undefined) ??\n (node[wrapperKeys[0]!] as DtcgNode)\n out.set(path, {\n path,\n value: chosen.$value as TokenValue,\n type,\n extensions: node.$extensions,\n })\n return\n }\n\n // Group: recurse into non-meta children.\n for (const key of Object.keys(node)) {\n if (isMetaKey(key)) continue\n const child = node[key]\n if (isObject(child)) {\n walk(child as DtcgNode, path ? `${path}.${key}` : key, type)\n }\n }\n }\n\n walk(tokens, \"\", undefined)\n return out\n}\n","import type { FlatToken } from \"./types\"\n\n/** Leading path segments dropped when deriving a CSS name from a token path. */\nconst DROPPED_PREFIXES = new Set([\"base\", \"semantic\"])\n/** Trailing path segments dropped (a group's \"default\"/root token maps to the bare group name). */\nconst DROPPED_SUFFIXES = new Set([\"default\", \"$root\"])\n\nfunction exportNames(token: FlatToken, nameExtension: string): string[] | undefined {\n const ext = token.extensions?.[nameExtension]\n if (ext && typeof ext === \"object\" && \"exportName\" in ext) {\n const names = (ext as { exportName: unknown }).exportName\n if (Array.isArray(names)) return names.filter((n): n is string => typeof n === \"string\")\n }\n return undefined\n}\n\n/** True if any token in the map declares an `exportName` under `nameExtension`. */\nexport function usesExportNames(\n tokens: Iterable<FlatToken>,\n nameExtension: string,\n): boolean {\n for (const token of tokens) {\n if (exportNames(token, nameExtension) !== undefined) return true\n }\n return false\n}\n\n/** Derive a CSS variable name from a dot-path (fallback when no `exportName` exists). */\nexport function nameFromPath(path: string): string {\n const segments = path.split(\".\")\n while (segments.length > 1 && DROPPED_PREFIXES.has(segments[0]!)) segments.shift()\n while (segments.length > 1 && DROPPED_SUFFIXES.has(segments[segments.length - 1]!)) {\n segments.pop()\n }\n return segments.join(\"-\")\n}\n\n/**\n * Derive a CSS variable name from the full dot-path, kebab-cased (e.g.\n * `base.color.primary.500` → `base-color-primary-500`). Unlike {@link nameFromPath} it keeps\n * every segment, so the name identifies one token unambiguously.\n */\nexport function pathToKebabName(path: string): string {\n return path\n .split(\".\")\n .map((segment) =>\n segment\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\"),\n )\n .filter((segment) => segment.length > 0)\n .join(\"-\")\n}\n\n/**\n * Resolve the CSS variable name(s) a token emits (without the leading `--`).\n *\n * - In **exportName mode** (`useExportName`), names come solely from the token's\n * `exportName` array. A token without one — or with only empty strings — emits\n * nothing (it exists purely as a reference target). One token may emit many names.\n * - Otherwise (generic DTCG), the name is derived from the token's path.\n */\nexport function cssNames(\n token: FlatToken,\n nameExtension: string,\n useExportName: boolean,\n): string[] {\n if (useExportName) {\n const names = exportNames(token, nameExtension)\n if (!names) return []\n return names.filter((n) => n.length > 0)\n }\n return [nameFromPath(token.path)]\n}\n","import type { FlatToken, TokenValue } from \"./types\"\n\nconst REFERENCE_RE = /^\\{([^{}]+)\\}$/\n\n/** Returns the referenced path if `value` is a whole-value DTCG alias `\"{a.b.c}\"`, else `null`. */\nexport function aliasTarget(value: TokenValue): string | null {\n if (typeof value !== \"string\") return null\n const match = REFERENCE_RE.exec(value.trim())\n return match ? match[1]!.trim() : null\n}\n\n/**\n * Resolve whole-value references (`\"{a.b.c}\"`) in a flattened, single-context token\n * map, in place. Because the map was collapsed for one context, following an alias\n * yields that context's value (with `$root` fallback already applied during collapse).\n *\n * Multi-hop aliases are followed, results memoized, and cycles guarded against.\n */\nexport function resolveReferences(tokens: Map<string, FlatToken>): Map<string, FlatToken> {\n const resolved = new Map<string, TokenValue>()\n\n const resolveValue = (value: TokenValue, seen: Set<string>): TokenValue => {\n const target = aliasTarget(value)\n if (target === null) return value\n\n if (resolved.has(target)) return resolved.get(target)!\n if (seen.has(target)) {\n throw new Error(`bezel: circular token reference detected at \"{${target}}\".`)\n }\n\n const token = tokens.get(target)\n if (!token) {\n throw new Error(`bezel: unresolved token reference \"{${target}}\".`)\n }\n\n seen.add(target)\n const value2 = resolveValue(token.value, seen)\n seen.delete(target)\n resolved.set(target, value2)\n return value2\n }\n\n for (const token of tokens.values()) {\n token.value = resolveValue(token.value, new Set())\n }\n return tokens\n}\n","import type { ResolvedCssOptions } from \"./options\"\nimport type {\n ColorValue,\n DimensionValue,\n TokenValue,\n} from \"./types\"\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v)\n}\n\nfunction isColor(v: unknown): v is ColorValue {\n return isObject(v) && \"colorSpace\" in v && Array.isArray(v[\"components\"])\n}\n\nfunction isDimension(v: unknown): v is DimensionValue {\n return isObject(v) && typeof v[\"value\"] === \"number\" && \"unit\" in v\n}\n\n/** Round to at most `dp` decimals without trailing zeros. */\nfunction round(n: number, dp: number): number {\n const f = 10 ** dp\n return Math.round(n * f) / f\n}\n\nfunction serializeColor(color: ColorValue, format: ResolvedCssOptions[\"colorFormat\"]): string {\n if (format === \"hex\" && color.hex) return color.hex.toLowerCase()\n\n if (color.colorSpace === \"oklch\") {\n const [l = 0, c = 0, h = 0] = color.components\n const base = `oklch(${round(l, 4)} ${round(c, 4)} ${round(h, 3)})`\n if (color.alpha !== undefined && color.alpha < 1) {\n return `oklch(${round(l, 4)} ${round(c, 4)} ${round(h, 3)} / ${round(color.alpha, 4)})`\n }\n return base\n }\n\n // Unknown color space: prefer hex fallback, else CSS color() function.\n if (color.hex) return color.hex.toLowerCase()\n return `color(${color.colorSpace} ${color.components.map((n) => round(n, 4)).join(\" \")})`\n}\n\nfunction serializeDimension(dim: DimensionValue, unit: ResolvedCssOptions[\"dimensionUnit\"]): string {\n if (unit === \"rem\" && dim.unit === \"px\") {\n return `${round(dim.value / 16, 4)}rem`\n }\n return `${dim.value}${dim.unit}`\n}\n\n/** Quote a font-family name if it contains whitespace and isn't already quoted. */\nfunction quoteFamily(name: string): string {\n const trimmed = name.trim()\n if (/^['\"].*['\"]$/.test(trimmed)) return trimmed\n return /\\s/.test(trimmed) ? `\"${trimmed}\"` : trimmed\n}\n\n/** Serialize a resolved token value to a CSS declaration value. */\nexport function serializeValue(value: TokenValue, options: ResolvedCssOptions): string {\n if (isColor(value)) return serializeColor(value, options.colorFormat)\n if (isDimension(value)) return serializeDimension(value, options.dimensionUnit)\n\n // fontFamily as an array of names.\n if (Array.isArray(value)) {\n return value.map((v) => quoteFamily(String(v))).join(\", \")\n }\n\n if (typeof value === \"number\") return String(value)\n\n // Strings: could be a single font-family name or an already-formatted value.\n // Quote multi-word family names; leave comma-lists and plain values untouched.\n const str = String(value)\n if (/\\s/.test(str) && !str.includes(\",\") && !/[()]/.test(str)) {\n return quoteFamily(str)\n }\n return str\n}\n","import { flattenForContext } from \"./collapse\"\nimport { cssNames, pathToKebabName, usesExportNames } from \"./name\"\nimport { resolveReferences } from \"./resolve\"\nimport { serializeValue } from \"./serialize\"\nimport type { ResolvedCssOptions } from \"./options\"\nimport type { DtcgNode } from \"./types\"\n\n/** An ordered map of CSS variable name → serialized value for one context. */\ntype VarMap = Map<string, string>\n\n/** Build the `name → cssValue` map for a single context. */\nfunction buildContextVars(\n tokens: DtcgNode,\n context: string,\n useExportName: boolean,\n options: ResolvedCssOptions,\n): VarMap {\n const flat = resolveReferences(flattenForContext(tokens, context))\n const includeUnnamed = useExportName && options.includeAll\n\n // Export names always win over a generated path name, so collect them up front.\n const exportNames = new Set<string>()\n if (includeUnnamed) {\n for (const token of flat.values()) {\n for (const name of cssNames(token, options.nameExtension, true)) exportNames.add(name)\n }\n }\n\n const vars: VarMap = new Map()\n for (const token of flat.values()) {\n let names = cssNames(token, options.nameExtension, useExportName)\n if (names.length === 0 && includeUnnamed) {\n const generated = pathToKebabName(token.path)\n // First token wins among generated names; never shadow an export name.\n if (generated && !exportNames.has(generated) && !vars.has(generated)) names = [generated]\n }\n if (names.length === 0) continue\n const value = serializeValue(token.value, options)\n for (const name of names) vars.set(name, value)\n }\n return vars\n}\n\nfunction renderBlock(selector: string, vars: Iterable<[string, string]>): string {\n const lines = [...vars].map(([name, value]) => ` --${name}: ${value};`)\n if (lines.length === 0) return \"\"\n return `${selector} {\\n${lines.join(\"\\n\")}\\n}`\n}\n\n/**\n * Convert a DTCG token tree into a scoped CSS string. The first context in\n * `options.contexts` is the base (emitted in full); every other context emits\n * only the variables whose value differs from the base.\n */\nexport function emitCss(tokens: DtcgNode, options: ResolvedCssOptions): string {\n const contextNames = Object.keys(options.contexts)\n const useExportName = usesExportNames(\n flattenForContext(tokens, contextNames[0]!).values(),\n options.nameExtension,\n )\n\n const [baseContext, ...overrideContexts] = contextNames\n const baseVars = buildContextVars(tokens, baseContext!, useExportName, options)\n\n const blocks: string[] = [renderBlock(options.contexts[baseContext!]!, baseVars)]\n\n for (const context of overrideContexts) {\n const vars = buildContextVars(tokens, context, useExportName, options)\n const overrides = [...vars].filter(([name, value]) => baseVars.get(name) !== value)\n const block = renderBlock(options.contexts[context]!, overrides)\n if (block) blocks.push(block)\n }\n\n const header =\n \"/* Generated by bezel — do not edit by hand. */\\n\" +\n \"/* Run `npm run tokens` (or `bezel build`) to regenerate. */\\n\"\n return `${header}\\n${blocks.filter(Boolean).join(\"\\n\\n\")}\\n`\n}\n","/**\n * Pure default constants shared by the isomorphic core and the Node layer.\n * Kept free of `node:*` imports so the browser/edge entry can use them.\n */\n\n/** Default context → selector mapping. The first key is the base context. */\nexport const DEFAULT_CONTEXTS: Record<string, string> = {\n $root: \":root\",\n dark: \".dark\",\n light: \".light\",\n};\n\n/**\n * The DTCG input file the CLI reads from the project root. This is a cross-system\n * contract: it MUST stay in sync with the platform's `TOKENS_FILE_NAME` constant\n * so any repo/app the platform connects to resolves its tokens at the same name\n * and location.\n */\nexport const DEFAULT_INPUT = \"design-tokens.json\";\n\n/**\n * Directory generated outputs are written to, relative to the project root.\n *\n * Everything Bezel emits is generated and gitignored, and `bezel build` overwrites\n * without asking — so outputs live in their own directory rather than alongside\n * hand-written files, where a generic name like `variables.css` or `fonts.ts` could\n * silently clobber something the user wrote. {@link FALLBACK_OUTPUT_DIR} is used by\n * `bezel init` for projects with no `src/`.\n */\nexport const DEFAULT_OUTPUT_DIR = \"src/bezel\";\n/** Output directory `bezel init` picks when the project has no `src/`. */\nexport const FALLBACK_OUTPUT_DIR = \"bezel\";\n\n/** Generated output filenames, joined onto the output directory. */\nexport const VARIABLES_FILE = \"variables.css\";\nexport const CONTEXTS_FILE = \"contexts.ts\";\nexport const FONTS_FILE = \"fonts.ts\";\n\nexport const DEFAULT_VARIABLES_OUTPUT = `${DEFAULT_OUTPUT_DIR}/${VARIABLES_FILE}`;\nexport const DEFAULT_NAME_EXTENSION = \"com.bezel.app\";\n\n/** The config file `bezel init` writes and `bezel build` auto-loads from the project root. */\nexport const DEFAULT_CONFIG_FILE = \"bezel.json\";\n","import { DEFAULT_CONTEXTS, DEFAULT_NAME_EXTENSION } from \"./defaults\"\n\n/**\n * Options for the pure CSS transform ({@link tokensToCss}/{@link emitCss}),\n * decoupled from any filesystem concern so they work in any environment.\n * All optional; defaults make the zero-config case work.\n */\nexport interface CssOptions {\n /**\n * Map of context name → CSS selector. The first entry is the base context (full\n * output); every other context emits only the variables that differ from the base.\n * Default: `{ \"$root\": \":root\", \"dark\": \".dark\", \"light\": \".light\" }`.\n */\n contexts?: Record<string, string>\n /** Color serialization. Default: `\"oklch\"`. */\n colorFormat?: \"oklch\" | \"hex\"\n /** Dimension handling: keep the authored unit, or convert `px` → `rem`. Default: `\"preserve\"`. */\n dimensionUnit?: \"preserve\" | \"rem\"\n /** `$extensions` key holding the `exportName` array. Default: `\"com.bezel.app\"`. */\n nameExtension?: string\n /**\n * Also emit a variable for every token that has no `exportName`, named by kebab-casing its\n * full path (e.g. `base.color.primary.500` → `--base-color-primary-500`). Tokens with an\n * `exportName` still emit only their export names. Has no effect when no token declares an\n * `exportName`, since every token is then emitted with a path-derived name already.\n * Default: `false`.\n */\n includeAll?: boolean\n}\n\n/** Fully-resolved CSS-transform options (no `undefined`). */\nexport interface ResolvedCssOptions {\n contexts: Record<string, string>\n colorFormat: \"oklch\" | \"hex\"\n dimensionUnit: \"preserve\" | \"rem\"\n nameExtension: string\n includeAll: boolean\n}\n\n/** Apply defaults to {@link CssOptions}. Pure — no path resolution, no `node:*`. */\nexport function resolveCssOptions(options: CssOptions = {}): ResolvedCssOptions {\n const contexts = options.contexts ?? DEFAULT_CONTEXTS\n if (Object.keys(contexts).length === 0) {\n throw new Error(\"bezel: `contexts` must define at least one context.\")\n }\n\n return {\n contexts,\n colorFormat: options.colorFormat ?? \"oklch\",\n dimensionUnit: options.dimensionUnit ?? \"preserve\",\n nameExtension: options.nameExtension ?? DEFAULT_NAME_EXTENSION,\n includeAll: options.includeAll ?? false,\n }\n}\n","import { ROOT_CONTEXT } from \"./collapse\"\nimport type { DtcgNode } from \"./types\"\n\n/** Name the `$root` context maps to in generated output (matches the `:root` CSS scope). */\nexport const DEFAULT_CONTEXT_NAME = \"default\"\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v)\n}\n\nfunction isContextWrapper(v: unknown): boolean {\n return isObject(v) && \"$value\" in v\n}\n\n/**\n * Extract the distinct context names declared anywhere in a DTCG token tree.\n *\n * Walks to each logical token (a node with a `$root` context wrapper) and collects the\n * context keys it declares (`$root`, `dark`, `light`, …). `$root` is mapped to\n * `\"default\"` and placed first; the rest follow in first-seen order, deduped.\n */\nexport function getContexts(tokens: DtcgNode, rootName: string = DEFAULT_CONTEXT_NAME): string[] {\n const order: string[] = []\n const seen = new Set<string>()\n\n const walk = (node: DtcgNode): void => {\n if (ROOT_CONTEXT in node && isContextWrapper(node[ROOT_CONTEXT])) {\n for (const key of Object.keys(node)) {\n if (key.startsWith(\"$\") && key !== ROOT_CONTEXT) continue\n if (isContextWrapper(node[key]) && !seen.has(key)) {\n seen.add(key)\n order.push(key)\n }\n }\n return\n }\n for (const key of Object.keys(node)) {\n if (key.startsWith(\"$\")) continue\n const child = node[key]\n if (isObject(child)) walk(child as DtcgNode)\n }\n }\n\n walk(tokens)\n\n const mapped = order.map((k) => (k === ROOT_CONTEXT ? rootName : k))\n return [...new Set([rootName, ...mapped])]\n}\n\n/** Render the generated `contexts.ts` module emitted alongside `variables.css`. */\nexport function formatContextsModule(contexts: string[]): string {\n const list = contexts.map((c) => JSON.stringify(c)).join(\", \")\n return (\n \"/* Generated by bezel — do not edit by hand. */\\n\" +\n \"/* Run `npm run tokens` (or `bezel build`) to regenerate. */\\n\\n\" +\n `export const CONTEXTS = [${list}] as const\\n\\n` +\n \"export type Context = (typeof CONTEXTS)[number]\\n\"\n )\n}\n","import { flattenForContext, ROOT_CONTEXT } from \"./collapse\"\nimport { resolveReferences } from \"./resolve\"\nimport type { DtcgNode, TokenValue } from \"./types\"\n\n/** A web font to load: a family name and the weights it is used at. */\nexport interface FontDef {\n family: string\n weights: number[]\n}\n\n/** Generic CSS family keywords that are never loaded as web fonts. */\nconst GENERIC_FAMILIES = new Set([\n \"serif\",\n \"sans-serif\",\n \"monospace\",\n \"cursive\",\n \"fantasy\",\n \"system-ui\",\n \"ui-serif\",\n \"ui-sans-serif\",\n \"ui-monospace\",\n \"ui-rounded\",\n \"math\",\n \"emoji\",\n \"fangsong\",\n \"inherit\",\n \"initial\",\n \"unset\",\n])\n\nconst DEFAULT_WEIGHT = 400\n\n/** Resolve a fontFamily token value to a primary family name (first non-generic), or null. */\nfunction primaryFamily(value: TokenValue): string | null {\n const candidates = Array.isArray(value) ? value : [value]\n for (const candidate of candidates) {\n if (typeof candidate !== \"string\") continue\n const name = candidate.trim().replace(/^['\"]|['\"]$/g, \"\")\n if (name && !GENERIC_FAMILIES.has(name.toLowerCase())) return name\n }\n return null\n}\n\nfunction parentPath(path: string): string {\n const i = path.lastIndexOf(\".\")\n return i === -1 ? \"\" : path.slice(0, i)\n}\n\n/**\n * Extract the web fonts referenced by a DTCG token tree.\n *\n * Collects every distinct `fontFamily` value (references resolved), then derives each\n * family's weights from the semantic typography composites — a group whose `font-family`\n * resolves to that family contributes its sibling `font-weight`. Families used without an\n * accompanying weight default to `[400]`. Result is sorted by family for determinism.\n */\nexport function getFonts(tokens: DtcgNode): FontDef[] {\n const flat = resolveReferences(flattenForContext(tokens, ROOT_CONTEXT))\n\n const families = new Set<string>()\n const familyByParent = new Map<string, string>()\n const weightByParent = new Map<string, number>()\n\n for (const token of flat.values()) {\n if (token.type === \"fontFamily\") {\n const family = primaryFamily(token.value)\n if (family) {\n families.add(family)\n familyByParent.set(parentPath(token.path), family)\n }\n } else if (token.type === \"fontWeight\" && typeof token.value === \"number\") {\n weightByParent.set(parentPath(token.path), token.value)\n }\n }\n\n const weightsByFamily = new Map<string, Set<number>>()\n for (const [parent, family] of familyByParent) {\n const weight = weightByParent.get(parent)\n if (weight === undefined) continue\n const set = weightsByFamily.get(family) ?? new Set<number>()\n set.add(weight)\n weightsByFamily.set(family, set)\n }\n\n return [...families].sort().map((family) => {\n const weights = weightsByFamily.get(family)\n return {\n family,\n weights: weights ? [...weights].sort((a, b) => a - b) : [DEFAULT_WEIGHT],\n }\n })\n}\n\n/** Render the generated `fonts.ts` module emitted alongside `variables.css`. */\nexport function formatFontsModule(fonts: FontDef[]): string {\n const entries = fonts\n .map((f) => ` { family: ${JSON.stringify(f.family)}, weights: [${f.weights.join(\", \")}] },`)\n .join(\"\\n\")\n return (\n \"/* Generated by bezel — do not edit by hand. */\\n\" +\n \"/* Run `npm run tokens` (or `bezel build`) to regenerate. */\\n\\n\" +\n `export const FONTS = [\\n${entries}\\n] as const\\n\\n` +\n \"export type FontDef = { family: string; weights: number[] }\\n\"\n )\n}\n","/**\n * `design-tokens-lock.json`: what this repo last resolved from Bezel.\n *\n * Like `package-lock.json` next to `package.json`: `bezel.json` says which project and\n * version to track, the lockfile records the exact tokens and project revision the repo\n * last synced or pushed. Pushes diff `design-tokens.json` against the lockfile's tokens,\n * so only the edits travel. Committed to git, written only by Bezel Kit and the Bezel MCP.\n *\n * Pure: no `node:*`, so the Bezel MCP builds the same lockfile the CLI reads.\n */\n\nexport const LOCKFILE_NAME = \"design-tokens-lock.json\"\nexport const LOCKFILE_VERSION = 1\n/** Where an agent writes the lockfile text the Bezel MCP returns, for `bezel sync` to apply. */\nexport const SYNC_STAGING_PATH = \".bezel/sync.json\"\n\nexport interface TokensLockfile {\n lockfileVersion: typeof LOCKFILE_VERSION\n /** The Bezel project the tokens came from */\n projectId: string\n /** \"latest\" (working tokens) or the published semver that was synced */\n version: string\n /** The project revision the tokens belong to */\n revision: number\n /** The tokens exactly as Bezel holds them at `revision` */\n tokens: Record<string, unknown>\n}\n\nexport function buildLockfile(input: {\n projectId: string\n version: string\n revision: number\n tokens: Record<string, unknown>\n}): TokensLockfile {\n return {\n lockfileVersion: LOCKFILE_VERSION,\n projectId: input.projectId,\n version: input.version,\n revision: input.revision,\n tokens: input.tokens,\n }\n}\n\n/** The lockfile's on-disk text: 2-space JSON with a trailing newline. */\nexport function serializeLockfile(lockfile: TokensLockfile): string {\n return `${JSON.stringify(lockfile, null, 2)}\\n`\n}\n\n/** The `design-tokens.json` text Bezel Kit writes: 2-space JSON with a trailing newline. */\nexport function serializeTokensFile(tokens: Record<string, unknown>): string {\n return `${JSON.stringify(tokens, null, 2)}\\n`\n}\n\nexport type LockfileProblem = \"invalid-json\" | \"invalid-shape\"\n\nexport type ParseLockfileResult =\n | { ok: true; lockfile: TokensLockfile }\n | { ok: false; reason: LockfileProblem; message: string }\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n\n/** Parses lockfile text and checks its shape. */\nexport function parseLockfile(text: string, name = LOCKFILE_NAME): ParseLockfileResult {\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch {\n const merged = /^(<<<<<<<|=======|>>>>>>>)/m.test(text)\n return {\n ok: false,\n reason: \"invalid-json\",\n message: merged\n ? `${name} has git merge conflict markers. Do not resolve them by hand; sync from Bezel to rewrite it.`\n : `${name} is not valid JSON. Sync from Bezel to rewrite it.`,\n }\n }\n\n if (\n !isRecord(parsed) ||\n parsed[\"lockfileVersion\"] !== LOCKFILE_VERSION ||\n typeof parsed[\"projectId\"] !== \"string\" ||\n typeof parsed[\"version\"] !== \"string\" ||\n typeof parsed[\"revision\"] !== \"number\" ||\n !isRecord(parsed[\"tokens\"])\n ) {\n return {\n ok: false,\n reason: \"invalid-shape\",\n message: `${name} is not a version ${LOCKFILE_VERSION} Bezel lockfile. Sync from Bezel to rewrite it.`,\n }\n }\n\n return { ok: true, lockfile: parsed as unknown as TokensLockfile }\n}\n","/**\n * Whether RFC 6902 operations are already reflected in a document, without applying them.\n *\n * Two places need this. `bezel sync` uses it to tell local edits Bezel already has (a push\n * that landed) from edits that would be lost. The Bezel MCP uses it to recognize a push\n * that was already saved, so a repeat is \"no changes\" instead of a false conflict. Pure:\n * no dependencies, safe in any runtime.\n */\n\n/** One RFC 6902 operation, as produced by `fast-json-patch`'s `compare`. */\nexport interface JsonPatchOperation {\n op: \"add\" | \"remove\" | \"replace\" | \"move\" | \"copy\" | \"test\"\n path: string\n value?: unknown\n from?: string\n}\n\n/** Anything shaped like an operation, so `fast-json-patch`'s wider `Operation` type fits too. */\nexport interface PatchOperationLike {\n op: string\n path: string\n value?: unknown\n from?: string\n}\n\nconst MISSING = Symbol(\"missing\")\n\nconst decodeSegment = (segment: string) => segment.replace(/~1/g, \"/\").replace(/~0/g, \"~\")\n\n/** The value at a JSON pointer, or `MISSING`. */\nfunction valueAt(document: unknown, pointer: string): unknown {\n if (pointer === \"\") return document\n let node: unknown = document\n for (const raw of pointer.split(\"/\").slice(1)) {\n const segment = decodeSegment(raw)\n if (Array.isArray(node)) {\n const index = Number(segment)\n if (!Number.isInteger(index) || index < 0 || index >= node.length) return MISSING\n node = node[index]\n } else if (typeof node === \"object\" && node !== null && Object.prototype.hasOwnProperty.call(node, segment)) {\n node = (node as Record<string, unknown>)[segment]\n } else {\n return MISSING\n }\n }\n return node\n}\n\n/** Structural equality for JSON values; object key order does not matter. */\nexport function jsonEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false\n if (Array.isArray(a) !== Array.isArray(b)) return false\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((item, i) => jsonEqual(item, b[i]))\n }\n const aKeys = Object.keys(a as object)\n const bKeys = Object.keys(b as object)\n if (aKeys.length !== bKeys.length) return false\n return aKeys.every(\n (key) =>\n Object.prototype.hasOwnProperty.call(b, key) &&\n jsonEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n )\n}\n\n/** True when the document already holds the result of this one operation. `test` is always true. */\nexport function isOperationApplied(document: unknown, operation: PatchOperationLike): boolean {\n switch (operation.op) {\n case \"test\":\n return true\n case \"add\":\n case \"replace\": {\n const current = valueAt(document, operation.path)\n return current !== MISSING && jsonEqual(current, operation.value)\n }\n case \"remove\":\n return valueAt(document, operation.path) === MISSING\n case \"copy\": {\n const target = valueAt(document, operation.path)\n return target !== MISSING && jsonEqual(target, valueAt(document, operation.from ?? \"\"))\n }\n case \"move\":\n return (\n valueAt(document, operation.path) !== MISSING &&\n (operation.from === undefined || valueAt(document, operation.from) === MISSING)\n )\n default:\n return false\n }\n}\n\n/** The operations (ignoring `test` guards) the document does not reflect yet. */\nexport function unappliedOperations<T extends PatchOperationLike>(document: unknown, operations: readonly T[]): T[] {\n return operations.filter((operation) => operation.op !== \"test\" && !isOperationApplied(document, operation))\n}\n\n/** True when every non-`test` operation is already reflected in the document. */\nexport function isPatchApplied(document: unknown, operations: readonly PatchOperationLike[]): boolean {\n return unappliedOperations(document, operations).length === 0\n}\n","/**\n * bezel — isomorphic entry.\n *\n * The pure transform core: convert an in-memory W3C Design Tokens (DTCG) object\n * into a scoped `variables.css` string. Imports no `node:*`, so it runs anywhere\n * — browser, edge, Node. For the filesystem/CLI conveniences (reading a tokens\n * file, writing outputs), import from `bezel/node`.\n */\nimport { emitCss } from \"./emit\"\nimport { resolveCssOptions, type CssOptions } from \"./options\"\nimport type { DtcgNode } from \"./types\"\n\nexport type { CssOptions, ResolvedCssOptions } from \"./options\"\nexport type { DtcgNode, FlatToken, TokenValue, ColorValue, DimensionValue } from \"./types\"\nexport { DEFAULT_CONTEXTS, DEFAULT_NAME_EXTENSION } from \"./defaults\"\nexport { emitCss } from \"./emit\"\nexport { resolveCssOptions } from \"./options\"\nexport { getContexts, formatContextsModule, DEFAULT_CONTEXT_NAME } from \"./contexts\"\nexport { getFonts, formatFontsModule } from \"./fonts\"\nexport type { FontDef } from \"./fonts\"\nexport {\n LOCKFILE_NAME,\n LOCKFILE_VERSION,\n SYNC_STAGING_PATH,\n buildLockfile,\n parseLockfile,\n serializeLockfile,\n serializeTokensFile,\n} from \"./lockfile\"\nexport type { TokensLockfile, ParseLockfileResult, LockfileProblem } from \"./lockfile\"\nexport { isOperationApplied, isPatchApplied, jsonEqual, unappliedOperations } from \"./patch-applied\"\nexport type { JsonPatchOperation, PatchOperationLike } from \"./patch-applied\"\nexport { AGENT_SKILL } from \"./agent-skill\"\nexport type { AgentSkill } from \"./agent-skill\"\n\n/**\n * Convert a DTCG token object into a scoped `variables.css` string.\n *\n * Resolves each configured context (with `$root` fallback and reference\n * resolution) and emits one CSS scope per context — the base context in full,\n * others as override-only blocks. Pure: no filesystem access.\n *\n * @example\n * const css = tokensToCss(tokens)\n *\n * @example\n * const css = tokensToCss(tokens, { colorFormat: \"hex\", contexts: { $root: \":root\" } })\n */\nexport function tokensToCss(tokens: DtcgNode, options: CssOptions = {}): string {\n return emitCss(tokens, resolveCssOptions(options))\n}\n"],"mappings":"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","names":["isObject","exportNames","isObject","isContextWrapper"]}
@@ -0,0 +1,444 @@
1
+ import {
2
+ CONTEXTS_FILE,
3
+ DEFAULT_CONFIG_FILE,
4
+ DEFAULT_INPUT,
5
+ DEFAULT_OUTPUT_DIR,
6
+ DEFAULT_VARIABLES_OUTPUT,
7
+ FALLBACK_OUTPUT_DIR,
8
+ FONTS_FILE,
9
+ LOCKFILE_NAME,
10
+ SYNC_STAGING_PATH,
11
+ VARIABLES_FILE,
12
+ emitCss,
13
+ formatContextsModule,
14
+ formatFontsModule,
15
+ getContexts,
16
+ getFonts,
17
+ jsonEqual,
18
+ parseLockfile,
19
+ resolveCssOptions,
20
+ serializeLockfile,
21
+ serializeTokensFile,
22
+ unappliedOperations
23
+ } from "./chunk-N57FDE2K.js";
24
+
25
+ // src/node.ts
26
+ import { mkdir, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
27
+ import { dirname, relative, resolve as resolve4, sep } from "path";
28
+
29
+ // src/config.ts
30
+ import { isAbsolute, resolve } from "path";
31
+ function resolveOptions(options = {}) {
32
+ const cwd = options.cwd ?? process.cwd();
33
+ const toAbs = (p) => isAbsolute(p) ? p : resolve(cwd, p);
34
+ const css = resolveCssOptions(options);
35
+ return {
36
+ // The tokens file is always `design-tokens.json` at the project root — not configurable.
37
+ inputPath: toAbs(DEFAULT_INPUT),
38
+ variablesOutputPath: toAbs(options.variablesOutput ?? DEFAULT_VARIABLES_OUTPUT),
39
+ ...css,
40
+ contextsOutput: options.contextsOutput ? toAbs(options.contextsOutput) : null,
41
+ fontsOutput: options.fontsOutput ? toAbs(options.fontsOutput) : null,
42
+ write: options.write ?? true,
43
+ gitignore: options.gitignore ?? true,
44
+ gitignorePath: toAbs(options.gitignorePath ?? ".gitignore")
45
+ };
46
+ }
47
+
48
+ // src/managed-block.ts
49
+ function mergeManagedBlock(existing, lines, markers) {
50
+ const block = [markers.start, ...lines, markers.end].join("\n");
51
+ if (existing.trim() === "") return `${block}
52
+ `;
53
+ const start = existing.indexOf(markers.start);
54
+ if (start !== -1) {
55
+ const endMarker = existing.indexOf(markers.end, start);
56
+ if (endMarker !== -1) {
57
+ const before = existing.slice(0, start).replace(/\n+$/, "");
58
+ const after = existing.slice(endMarker + markers.end.length).replace(/^\n+/, "");
59
+ const head = before === "" ? "" : `${before}
60
+
61
+ `;
62
+ const tail = after === "" ? "" : `
63
+
64
+ ${after.replace(/\n+$/, "")}`;
65
+ return `${head}${block}${tail}
66
+ `;
67
+ }
68
+ }
69
+ return `${existing.replace(/\n+$/, "")}
70
+
71
+ ${block}
72
+ `;
73
+ }
74
+
75
+ // src/gitignore.ts
76
+ var START = "# bezel:start (generated \u2014 do not edit)";
77
+ var END = "# bezel:end";
78
+ function mergeGitignore(existing, entries) {
79
+ return mergeManagedBlock(existing, entries, { start: START, end: END });
80
+ }
81
+
82
+ // src/init.ts
83
+ import { existsSync as existsSync2 } from "fs";
84
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
85
+ import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
86
+
87
+ // src/tokens-sync.ts
88
+ import { existsSync } from "fs";
89
+ import { readFile, rm, writeFile } from "fs/promises";
90
+ import { resolve as resolve2 } from "path";
91
+ import jsonPatch from "fast-json-patch";
92
+ var { compare } = jsonPatch;
93
+ var SYNC_HINT = 'Sync from Bezel (ask your agent to "sync my Bezel tokens").';
94
+ async function readLink(cwd) {
95
+ const path = resolve2(cwd, DEFAULT_CONFIG_FILE);
96
+ if (!existsSync(path)) {
97
+ return { ok: false, reason: "no-config", message: `No ${DEFAULT_CONFIG_FILE} here. Run "bezel init --project <uuid>" first.` };
98
+ }
99
+ let config;
100
+ try {
101
+ config = JSON.parse(await readFile(path, "utf8"));
102
+ } catch {
103
+ return { ok: false, reason: "no-config", message: `${DEFAULT_CONFIG_FILE} is not valid JSON.` };
104
+ }
105
+ if (typeof config.projectId !== "string" || config.projectId === "") {
106
+ return {
107
+ ok: false,
108
+ reason: "no-project",
109
+ message: `${DEFAULT_CONFIG_FILE} has no projectId. Link the repo with "bezel init --project <uuid>".`
110
+ };
111
+ }
112
+ const version = typeof config.version === "string" && config.version !== "" ? config.version : "latest";
113
+ return { ok: true, link: { projectId: config.projectId, version } };
114
+ }
115
+ async function readTokensFile(cwd) {
116
+ const path = resolve2(cwd, DEFAULT_INPUT);
117
+ if (!existsSync(path)) {
118
+ return { ok: false, reason: "no-tokens-file", message: `No ${DEFAULT_INPUT} here. ${SYNC_HINT}` };
119
+ }
120
+ try {
121
+ const parsed = JSON.parse(await readFile(path, "utf8"));
122
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("not an object");
123
+ return { ok: true, tokens: parsed };
124
+ } catch {
125
+ return {
126
+ ok: false,
127
+ reason: "tokens-file-invalid",
128
+ message: `${DEFAULT_INPUT} is not a JSON object. Fix the file (or sync from Bezel to replace it).`
129
+ };
130
+ }
131
+ }
132
+ async function readCurrentLockfile(cwd) {
133
+ const path = resolve2(cwd, LOCKFILE_NAME);
134
+ if (!existsSync(path)) return { ok: true, lockfile: null };
135
+ const parsed = parseLockfile(await readFile(path, "utf8"));
136
+ if (parsed.ok === false) return { ok: false, reason: "lockfile-invalid", message: parsed.message };
137
+ return { ok: true, lockfile: parsed.lockfile };
138
+ }
139
+ function checkLockMatchesLink(lockfile, link, name) {
140
+ if (lockfile.projectId !== link.projectId) {
141
+ return {
142
+ ok: false,
143
+ reason: "project-mismatch",
144
+ message: `${name} is for project ${lockfile.projectId}, but ${DEFAULT_CONFIG_FILE} links ${link.projectId}. ${SYNC_HINT}`
145
+ };
146
+ }
147
+ if (lockfile.version !== link.version) {
148
+ return {
149
+ ok: false,
150
+ reason: "version-mismatch",
151
+ message: `${name} holds version "${lockfile.version}", but ${DEFAULT_CONFIG_FILE} tracks "${link.version}". ${SYNC_HINT}`
152
+ };
153
+ }
154
+ return null;
155
+ }
156
+ function changedPaths(operations) {
157
+ const paths = /* @__PURE__ */ new Set();
158
+ for (const operation of operations) {
159
+ if (operation.op === "test") continue;
160
+ const segments = [];
161
+ for (const raw of operation.path.split("/").slice(1)) {
162
+ const segment = raw.replace(/~1/g, "/").replace(/~0/g, "~");
163
+ if (segment.startsWith("$")) break;
164
+ segments.push(segment);
165
+ }
166
+ paths.add(segments.length > 0 ? segments.join(".") : "(root)");
167
+ }
168
+ return [...paths];
169
+ }
170
+ async function diffTokens({ cwd = process.cwd() } = {}) {
171
+ const link = await readLink(cwd);
172
+ if (link.ok === false) return link;
173
+ const current = await readCurrentLockfile(cwd);
174
+ if (current.ok === false) return current;
175
+ if (current.lockfile === null) {
176
+ return {
177
+ ok: false,
178
+ reason: "no-lockfile",
179
+ message: `No ${LOCKFILE_NAME} yet, so local edits cannot be told apart from Bezel's tokens. ${SYNC_HINT}`
180
+ };
181
+ }
182
+ const mismatch = checkLockMatchesLink(current.lockfile, link.link, LOCKFILE_NAME);
183
+ if (mismatch) return mismatch;
184
+ const local = await readTokensFile(cwd);
185
+ if (local.ok === false) return local;
186
+ const operations = compare(current.lockfile.tokens, local.tokens, true);
187
+ return {
188
+ ok: true,
189
+ projectId: current.lockfile.projectId,
190
+ version: current.lockfile.version,
191
+ baseRevision: current.lockfile.revision,
192
+ operations
193
+ };
194
+ }
195
+ async function syncFromStaging({
196
+ cwd = process.cwd(),
197
+ discardLocalEdits = false
198
+ } = {}) {
199
+ const stagingPath = resolve2(cwd, SYNC_STAGING_PATH);
200
+ if (!existsSync(stagingPath)) {
201
+ return {
202
+ ok: false,
203
+ reason: "no-staging",
204
+ message: `Nothing to sync: ${SYNC_STAGING_PATH} is missing. Write the "lockfileContent" from the Bezel MCP's get-design-tokens-file there first.`
205
+ };
206
+ }
207
+ const incoming = parseLockfile(await readFile(stagingPath, "utf8"), SYNC_STAGING_PATH);
208
+ if (incoming.ok === false) {
209
+ return {
210
+ ok: false,
211
+ reason: "staging-invalid",
212
+ message: `${incoming.message.replace(/ Sync from Bezel to rewrite it\.$/, "")} Write the "lockfileContent" from get-design-tokens-file to ${SYNC_STAGING_PATH} exactly as returned, then run "bezel sync" again.`
213
+ };
214
+ }
215
+ const next = incoming.lockfile;
216
+ const link = await readLink(cwd);
217
+ if (link.ok === false) return link;
218
+ const mismatch = checkLockMatchesLink(next, link.link, SYNC_STAGING_PATH);
219
+ if (mismatch) {
220
+ return {
221
+ ...mismatch,
222
+ message: `${SYNC_STAGING_PATH} is for project ${next.projectId} version "${next.version}", but ${DEFAULT_CONFIG_FILE} links ${link.link.projectId} version "${link.link.version}". Fetch the tokens for the linked project and version, or relink with "bezel init --project <uuid> --tokens-version <v>".`
223
+ };
224
+ }
225
+ const tokensPath = resolve2(cwd, DEFAULT_INPUT);
226
+ let discardedEdits = 0;
227
+ let tokensChanged = true;
228
+ if (existsSync(tokensPath)) {
229
+ const local = await readTokensFile(cwd);
230
+ if (local.ok === true) {
231
+ tokensChanged = !jsonEqual(local.tokens, next.tokens);
232
+ const current = await readCurrentLockfile(cwd);
233
+ const base = current.ok === true && current.lockfile !== null && checkLockMatchesLink(current.lockfile, link.link, LOCKFILE_NAME) === null ? current.lockfile : null;
234
+ if (tokensChanged) {
235
+ if (base === null) {
236
+ if (!discardLocalEdits) {
237
+ return {
238
+ ok: false,
239
+ reason: "unknown-local-edits",
240
+ message: `${DEFAULT_INPUT} differs from Bezel's tokens and there is no usable ${LOCKFILE_NAME} to tell local edits from older Bezel tokens. If the file has no edits worth keeping, run "bezel sync --discard-local-edits" to replace it.`
241
+ };
242
+ }
243
+ } else {
244
+ const localEdits = compare(base.tokens, local.tokens, true);
245
+ const unpushed = unappliedOperations(next.tokens, localEdits);
246
+ if (unpushed.length > 0 && !discardLocalEdits) {
247
+ const paths = changedPaths(unpushed);
248
+ return {
249
+ ok: false,
250
+ reason: "unpushed-edits",
251
+ unpushedPaths: paths,
252
+ message: `${DEFAULT_INPUT} has ${unpushed.length} local ${unpushed.length === 1 ? "edit" : "edits"} Bezel does not have (${paths.slice(0, 10).join(", ")}${paths.length > 10 ? ", ..." : ""}). Push them to Bezel first, or run "bezel sync --discard-local-edits" to drop them.`
253
+ };
254
+ }
255
+ discardedEdits = unpushed.length;
256
+ }
257
+ }
258
+ } else if (!discardLocalEdits) {
259
+ return {
260
+ ...local,
261
+ message: `${local.message.replace(/ \(or sync from Bezel to replace it\)/, "")} Or run "bezel sync --discard-local-edits" to replace it with Bezel's tokens.`
262
+ };
263
+ }
264
+ }
265
+ await writeFile(resolve2(cwd, LOCKFILE_NAME), serializeLockfile(next), "utf8");
266
+ if (tokensChanged) await writeFile(tokensPath, serializeTokensFile(next.tokens), "utf8");
267
+ await rm(stagingPath, { force: true });
268
+ return { ok: true, projectId: next.projectId, version: next.version, revision: next.revision, tokensChanged, discardedEdits };
269
+ }
270
+ async function removeLockfileForOtherProject(cwd, projectId) {
271
+ const path = resolve2(cwd, LOCKFILE_NAME);
272
+ if (!existsSync(path)) return false;
273
+ try {
274
+ const parsed = JSON.parse(await readFile(path, "utf8"));
275
+ if (parsed.projectId === projectId) return false;
276
+ } catch {
277
+ }
278
+ await rm(path, { force: true });
279
+ return true;
280
+ }
281
+
282
+ // src/init.ts
283
+ var isUuid = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
284
+ var isTokensVersion = (value) => value === "latest" || /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(value);
285
+ var joinPath = (dir, file) => dir === "" ? file : `${dir.replace(/\/+$/, "")}/${file}`;
286
+ async function initConfig(options = {}) {
287
+ if (options.projectId !== void 0 && !isUuid(options.projectId)) {
288
+ throw new Error(`bezel: --project must be a UUID, got "${options.projectId}"`);
289
+ }
290
+ if (options.version !== void 0 && !isTokensVersion(options.version)) {
291
+ throw new Error(
292
+ `bezel: --tokens-version must be "latest" or a semver like 1.4.0, got "${options.version}"`
293
+ );
294
+ }
295
+ const cwd = options.cwd ?? process.cwd();
296
+ const toAbs = (p) => isAbsolute2(p) ? p : resolve3(cwd, p);
297
+ const configPath = toAbs(DEFAULT_CONFIG_FILE);
298
+ const hasTokensFile = existsSync2(toAbs(DEFAULT_INPUT));
299
+ const isTypeScript = existsSync2(toAbs("tsconfig.json"));
300
+ const hasSrc = existsSync2(toAbs("src"));
301
+ const outputDir = options.outputDir ?? (hasSrc ? DEFAULT_OUTPUT_DIR : FALLBACK_OUTPUT_DIR);
302
+ const wantsContexts = options.contextsOutput !== void 0 || (options.contexts ?? isTypeScript);
303
+ const wantsFonts = options.fontsOutput !== void 0 || (options.fonts ?? isTypeScript);
304
+ const config = {};
305
+ if (options.projectId !== void 0) config.projectId = options.projectId;
306
+ config.version = options.version ?? "latest";
307
+ config.variablesOutput = options.variablesOutput ?? joinPath(outputDir, VARIABLES_FILE);
308
+ if (wantsContexts) {
309
+ config.contextsOutput = options.contextsOutput ?? joinPath(outputDir, CONTEXTS_FILE);
310
+ }
311
+ if (wantsFonts) {
312
+ config.fontsOutput = options.fontsOutput ?? joinPath(outputDir, FONTS_FILE);
313
+ }
314
+ if (options.colorFormat) config.colorFormat = options.colorFormat;
315
+ if (options.dimensionUnit) config.dimensionUnit = options.dimensionUnit;
316
+ if (options.includeAll) config.includeAll = true;
317
+ if (existsSync2(configPath) && !options.force) {
318
+ const wantsLinkUpdate = options.projectId !== void 0 || options.version !== void 0;
319
+ if (!wantsLinkUpdate) {
320
+ return { configPath, config, written: false, updated: false, hasTokensFile, isTypeScript };
321
+ }
322
+ const existing = JSON.parse(await readFile2(configPath, "utf8"));
323
+ delete existing.revision;
324
+ const { projectId: previousProjectId, version: previousVersion, ...rest } = existing;
325
+ const merged = {};
326
+ const projectId = options.projectId ?? previousProjectId;
327
+ if (projectId !== void 0) merged.projectId = projectId;
328
+ merged.version = options.version ?? previousVersion ?? "latest";
329
+ Object.assign(merged, rest);
330
+ await writeFile2(configPath, `${JSON.stringify(merged, null, 2)}
331
+ `, "utf8");
332
+ const removedLockfile2 = projectId !== void 0 ? await removeLockfileForOtherProject(cwd, projectId) : false;
333
+ return {
334
+ removedLockfile: removedLockfile2,
335
+ configPath,
336
+ config: merged,
337
+ written: true,
338
+ updated: true,
339
+ previousProjectId,
340
+ previousVersion,
341
+ hasTokensFile,
342
+ isTypeScript
343
+ };
344
+ }
345
+ await writeFile2(configPath, `${JSON.stringify(config, null, 2)}
346
+ `, "utf8");
347
+ const removedLockfile = config.projectId !== void 0 ? await removeLockfileForOtherProject(cwd, config.projectId) : false;
348
+ return { configPath, config, written: true, updated: false, removedLockfile, hasTokensFile, isTypeScript };
349
+ }
350
+ async function readPackageScripts(cwd) {
351
+ try {
352
+ const raw = await readFile2(resolve3(cwd, "package.json"), "utf8");
353
+ const parsed = JSON.parse(raw);
354
+ return parsed.scripts ?? {};
355
+ } catch {
356
+ return {};
357
+ }
358
+ }
359
+
360
+ // src/node.ts
361
+ async function generateVariablesCss(options = {}) {
362
+ const resolved = resolveOptions(options);
363
+ let raw;
364
+ try {
365
+ raw = await readFile3(resolved.inputPath, "utf8");
366
+ } catch (err) {
367
+ throw new Error(
368
+ `bezel: could not read tokens file at "${resolved.inputPath}". A "design-tokens.json" file must exist at the project root.
369
+ ${err.message}`
370
+ );
371
+ }
372
+ let tokens;
373
+ try {
374
+ tokens = JSON.parse(raw);
375
+ } catch (err) {
376
+ throw new Error(`bezel: "${resolved.inputPath}" is not valid JSON.
377
+ ${err.message}`);
378
+ }
379
+ const css = emitCss(tokens, resolved);
380
+ if (resolved.write) {
381
+ await mkdir(dirname(resolved.variablesOutputPath), { recursive: true });
382
+ await writeFile3(resolved.variablesOutputPath, css, "utf8");
383
+ if (resolved.contextsOutput) {
384
+ const contextsModule = formatContextsModule(getContexts(tokens));
385
+ await mkdir(dirname(resolved.contextsOutput), { recursive: true });
386
+ await writeFile3(resolved.contextsOutput, contextsModule, "utf8");
387
+ }
388
+ if (resolved.fontsOutput) {
389
+ const fontsModule = formatFontsModule(getFonts(tokens));
390
+ await mkdir(dirname(resolved.fontsOutput), { recursive: true });
391
+ await writeFile3(resolved.fontsOutput, fontsModule, "utf8");
392
+ }
393
+ if (resolved.gitignore) {
394
+ await updateGitignore(resolved);
395
+ }
396
+ }
397
+ return css;
398
+ }
399
+ async function updateGitignore(resolved) {
400
+ const dir = dirname(resolved.gitignorePath);
401
+ const outputs = [
402
+ resolved.variablesOutputPath,
403
+ resolved.contextsOutput,
404
+ resolved.fontsOutput,
405
+ // The MCP's sync payload is a temporary hand-off file; the lockfile itself is committed.
406
+ resolve4(dirname(resolved.inputPath), SYNC_STAGING_PATH)
407
+ ].filter((p) => p !== null);
408
+ const entries = [];
409
+ for (const output of outputs) {
410
+ const rel = relative(dir, output);
411
+ if (rel === "" || rel.startsWith("..")) {
412
+ process.stderr.write(
413
+ `bezel: skipping .gitignore entry for "${output}" \u2014 outside ${resolved.gitignorePath}
414
+ `
415
+ );
416
+ continue;
417
+ }
418
+ entries.push(`/${rel.split(sep).join("/")}`);
419
+ }
420
+ if (entries.length === 0) return;
421
+ let existing = "";
422
+ try {
423
+ existing = await readFile3(resolved.gitignorePath, "utf8");
424
+ } catch {
425
+ }
426
+ const next = mergeGitignore(existing, entries);
427
+ if (next !== existing) {
428
+ await mkdir(dir, { recursive: true });
429
+ await writeFile3(resolved.gitignorePath, next, "utf8");
430
+ }
431
+ }
432
+
433
+ export {
434
+ resolveOptions,
435
+ mergeManagedBlock,
436
+ changedPaths,
437
+ diffTokens,
438
+ syncFromStaging,
439
+ removeLockfileForOtherProject,
440
+ initConfig,
441
+ readPackageScripts,
442
+ generateVariablesCss
443
+ };
444
+ //# sourceMappingURL=chunk-U3AGS46P.js.map