CherryHQ/cherry-studio · error · Error

[theme-contract] unsupported @import syntax: ${importValue}

Error message

[theme-contract] unsupported @import syntax: ${importValue}

What it means

Thrown by the theme contract validator when an @import statement in a CSS file uses a syntax that is neither a quoted string (e.g., @import 'file.css'; or @import "file.css";) nor a url() function (e.g., @import url('file.css');). The extractImports function matches @import followed by a quoted string or url(...) and throws for any other format. This enforces a consistent import style and catches malformed @import statements.

Source

Thrown at packages/ui/scripts/validate-theme-contract.ts:102

  return [...value.matchAll(/var\(\s*(--[^\s,)]+)/g)].map((match) => {
    const name = match[1]
    if (!CUSTOM_PROPERTY_NAME_PATTERN.test(name)) {
      throw new Error(`[theme-contract] ${sourceName} references invalid custom property ${name}`)
    }
    return name
  })
}

function extractImports(source: string): string[] {
  return [...stripComments(source).matchAll(/@import\s+([^;]+);/g)].map((match) => {
    const importValue = match[1].trim()
    const stringMatch = importValue.match(/^(['"])([^'"]+)\1$/)
    if (stringMatch) return stringMatch[2]

    const urlMatch = importValue.match(/^url\(\s*(?:(['"])([^'"]+)\1|([^'")\s][^)]*?))\s*\)$/)
    if (urlMatch) return (urlMatch[2] ?? urlMatch[3]).trim()

    throw new Error(`[theme-contract] unsupported @import syntax: ${importValue}`)
  })
}

function assertUnique(label: string, values: readonly string[]): void {
  if (new Set(values).size !== values.length) {
    throw new Error(`[theme-contract] ${label} contains duplicate names`)
  }
}

function assertSurfacePairs(
  label: string,
  pairs: ReadonlyArray<readonly [surface: string, foreground: string]>,
  variableNames: Set<string>
): void {
  const surfaces = new Set<string>()

  for (const [surface, foreground] of pairs) {
    if (surface === foreground || surfaces.has(surface)) {

View on GitHub (pinned to 726446b54c)

Solutions

  1. Rewrite the @import to use a quoted string: @import 'path/to/file.css';.
  2. If using url() syntax, ensure it's properly formatted: @import url('path/to/file.css');.
  3. If the @import has qualifiers (layer, supports), move them outside the string so the path itself is quoted: @import 'file.css' layer(theme);.
  4. Run the validator to confirm: npx tsx packages/ui/scripts/validate-theme-contract.ts.

Example fix

/* before — unquoted import */
@import tokens/colors.css;

/* after — quoted string */
@import 'tokens/colors.css';
Defensive patterns

Strategy: validation

Validate before calling

// Validate @import syntax in CSS before committing
import { readFileSync } from 'node:fs'

function checkImports(cssPath: string): void {
  const source = readFileSync(cssPath, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
  const imports = [...source.matchAll(/@import\s+([^;]+);/g)]
  for (const match of imports) {
    const value = match[1].trim()
    const isString = /^(['"])([^'"]+)\1$/.test(value)
    const isUrl = /^url\(\s*(?:(['"])([^'"]+)\1|([^'")\s][^)]*?))\s*\)$/.test(value)
    if (!isString && !isUrl) {
      throw new Error(`${cssPath}: unsupported @import syntax: ${value}`)
    }
  }
}

Prevention

When it happens

Trigger: A CSS file contains an @import with an unquoted bare path (e.g., @import file.css;), an @import with media queries written inline without proper wrapping, or a syntax that doesn't match the string or url() patterns. The validator processes every @import \s+([^;]+); match and rejects unparseable values.

Common situations: A developer writes @import without quotes (@import tokens/colors.css;). A CSS minifier or formatter rewrites @import into an unexpected format. An @import with a layer() or supports() qualifier that shifts the string/url match. A merge conflict artifact producing a broken @import.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/4d62fc0fe02af158. Report an issue: GitHub.