From ea3f5548ca97e63cef9ffcf4175ee24b1f154d9a Mon Sep 17 00:00:00 2001 From: Alexandre Stahmer <47224540+astahmer@users.noreply.github.com> Date: Sun, 28 Jan 2024 19:25:41 +0100 Subject: [PATCH] feat: config validation (#2088) * feat: config validation * refactor: split config validation + add config.validation level * chore: cleanup * refactor: error handling and config validations * refactor: test file * refactor: validate breakpoint * refactor: types * refactor: some stuff --------- Co-authored-by: Segun Adebayo --- .changeset/mean-worms-rest.md | 16 + packages/cli/package.json | 1 - packages/cli/src/cli-main.ts | 4 +- .../config/__tests__/validate-config.test.ts | 397 ++++++++++++++++++ packages/config/package.json | 1 - packages/config/src/bundle-config.ts | 18 +- packages/config/src/diff-config.ts | 3 +- packages/config/src/find-config.ts | 25 +- packages/config/src/index.ts | 5 +- packages/config/src/is-panda-config.ts | 4 + packages/config/src/resolve-config.ts | 18 +- packages/config/src/types.ts | 25 ++ packages/config/src/validate-config.ts | 71 ++++ .../config/src/validation/get-final-paths.ts | 11 + .../src/validation/validate-artifact.ts | 19 + .../src/validation/validate-breakpoints.ts | 20 + .../src/validation/validate-condition.ts | 12 + .../src/validation/validate-patterns.ts | 10 + .../config/src/validation/validate-recipes.ts | 32 ++ .../validation/validate-token-references.ts | 36 ++ .../config/src/validation/validate-tokens.ts | 82 ++++ packages/core/package.json | 1 - packages/core/src/layers.ts | 3 +- packages/core/src/recipes.ts | 13 +- packages/core/src/rule-processor.ts | 3 +- packages/core/src/style-decoder.ts | 13 +- packages/core/src/style-encoder.ts | 3 +- packages/error/CHANGELOG.md | 152 ------- packages/error/package.json | 39 -- packages/error/src/index.ts | 41 -- packages/error/tsconfig.json | 4 - packages/extractor/package.json | 1 + packages/extractor/src/get-typeof-literal.ts | 3 +- packages/node/package.json | 1 - packages/node/src/builder.ts | 19 +- packages/parser/src/parser-result.ts | 4 +- packages/shared/src/error.ts | 24 ++ packages/shared/src/index.ts | 1 + packages/token-dictionary/src/utils.ts | 4 +- packages/types/src/config.ts | 11 +- pnpm-lock.yaml | 105 +---- 41 files changed, 870 insertions(+), 385 deletions(-) create mode 100644 .changeset/mean-worms-rest.md create mode 100644 packages/config/__tests__/validate-config.test.ts create mode 100644 packages/config/src/is-panda-config.ts create mode 100644 packages/config/src/validate-config.ts create mode 100644 packages/config/src/validation/get-final-paths.ts create mode 100644 packages/config/src/validation/validate-artifact.ts create mode 100644 packages/config/src/validation/validate-breakpoints.ts create mode 100644 packages/config/src/validation/validate-condition.ts create mode 100644 packages/config/src/validation/validate-patterns.ts create mode 100644 packages/config/src/validation/validate-recipes.ts create mode 100644 packages/config/src/validation/validate-token-references.ts create mode 100644 packages/config/src/validation/validate-tokens.ts delete mode 100644 packages/error/CHANGELOG.md delete mode 100644 packages/error/package.json delete mode 100644 packages/error/src/index.ts delete mode 100644 packages/error/tsconfig.json create mode 100644 packages/shared/src/error.ts diff --git a/.changeset/mean-worms-rest.md b/.changeset/mean-worms-rest.md new file mode 100644 index 000000000..63fc67a5e --- /dev/null +++ b/.changeset/mean-worms-rest.md @@ -0,0 +1,16 @@ +--- +'@pandacss/config': patch +--- + +Add config validation: + +- Check for duplicate between token & semanticTokens names +- Check for duplicate between recipes/patterns/slots names +- Check for token / semanticTokens paths (must end/contain 'value') +- Check for self/circular token references +- Check for missing tokens references +- Check for conditions selectors (must contain '&') +- Check for breakpoints units (must be the same) + +> You can set `validate: 'warn'` in your config to only warn about errors or set it to `none` to disable validation +> entirely. diff --git a/packages/cli/package.json b/packages/cli/package.json index d284388f5..08cd61de8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -58,7 +58,6 @@ "dependencies": { "@clack/prompts": "^0.6.3", "@pandacss/config": "workspace:*", - "@pandacss/error": "workspace:*", "@pandacss/logger": "workspace:*", "@pandacss/node": "workspace:*", "@pandacss/postcss": "workspace:*", diff --git a/packages/cli/src/cli-main.ts b/packages/cli/src/cli-main.ts index da3e0ef93..2b799e602 100644 --- a/packages/cli/src/cli-main.ts +++ b/packages/cli/src/cli-main.ts @@ -16,7 +16,7 @@ import { writeAnalyzeJSON, type CssGenOptions, } from '@pandacss/node' -import { compact } from '@pandacss/shared' +import { PandaError, compact } from '@pandacss/shared' import type { CssArtifactType } from '@pandacss/types' import { cac } from 'cac' import { join, resolve } from 'path' @@ -294,7 +294,7 @@ export async function main() { studio = require(studioPath) } catch (error) { logger.error('studio', error) - throw new Error("You need to install '@pandacss/studio' to use this command") + throw new PandaError('MISSING_STUDIO', "You need to install '@pandacss/studio' to use this command") } if (preview) { diff --git a/packages/config/__tests__/validate-config.test.ts b/packages/config/__tests__/validate-config.test.ts new file mode 100644 index 000000000..b891041a4 --- /dev/null +++ b/packages/config/__tests__/validate-config.test.ts @@ -0,0 +1,397 @@ +import type { UserConfig } from '@pandacss/types' +import { describe, expect, test } from 'vitest' +import { validateConfig } from '../src/validate-config' + +describe('validateConfig', () => { + test('should not throw when no errors', () => { + const config: Partial = { + theme: { + breakpoints: { + sm: '640px', + md: '768px', + lg: '1024px', + }, + tokens: { + colors: { + primary: { value: '#000' }, + secondary: { value: '#fff' }, + }, + fontSizes: { + sm: { value: '12px' }, + md: { value: '16px' }, + lg: { value: '20px' }, + }, + }, + semanticTokens: { + colors: { + bg: { value: '{colors.primary}' }, + }, + fontSizes: { + main: { value: '{fontSizes.md}' }, + }, + }, + }, + conditions: { + dark: '@media (prefers-color-scheme: dark)', + pinkTheme: '&[data-theme="pink"]', + }, + patterns: { + 'btn-primary': {}, + }, + } + + expect(() => validateConfig(config)).not.toThrow() + }) + + test('should throw with validation: error', () => { + const config: Partial = { + theme: { + breakpoints: { + sm: '640px', + md: '768px', + lg: '1024px', + }, + tokens: { + colors: { + primary: { value: '#000' }, + // @ts-expect-error should be an object with { value } + secondary: '#fff', + }, + fontSizes: { + sm: { value: '12px' }, + md: { value: '16px' }, + lg: { value: '20px' }, + }, + }, + semanticTokens: { + colors: { + bg: { value: '{colors.primary}' }, + }, + fontSizes: { + main: { value: '{fontSizes.md}' }, + }, + }, + }, + conditions: { + dark: '@media (prefers-color-scheme: dark)', + pinkTheme: '&[data-theme="pink"]', + }, + patterns: { + 'btn-primary': {}, + }, + } + + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [tokens]: Token paths must end with 'value': \`theme.tokens.colors.secondary\`]`, + ) + }) + + test('should not throw with validation: warn', () => { + const config: Partial = { + validation: 'warn', + theme: { + breakpoints: { + sm: '640px', + md: '768px', + lg: '1024px', + }, + tokens: { + colors: { + primary: { value: '#000' }, + // @ts-expect-error should be an object with { value } + secondary: '#fff', + }, + fontSizes: { + sm: { value: '12px' }, + md: { value: '16px' }, + lg: { value: '20px' }, + }, + }, + semanticTokens: { + colors: { + bg: { value: '{colors.primary}' }, + }, + fontSizes: { + main: { value: '{fontSizes.md}' }, + }, + }, + }, + conditions: { + dark: '@media (prefers-color-scheme: dark)', + pinkTheme: '&[data-theme="pink"]', + }, + patterns: { + 'btn-primary': {}, + }, + } + + expect(() => validateConfig(config)).not.toThrow() + }) + + test('should report errors on validation warn - altogether', () => { + const config: Partial = { + validation: 'warn', + theme: { + breakpoints: { + sm: '640em', // invalid unit + md: '768px', + lg: '1024px', + }, + tokens: { + colors: { + primary: { value: '#000' }, + // @ts-expect-error should be an object with { value } + secondary: '#fff', + group: { + with: { + nested: { + value: 'red', + }, + // @ts-expect-error should be an object with { value } + invalid: 'blue', + }, + }, + }, + fontSizes: { + sm: { value: '12px' }, + md: { value: '16px' }, + lg: { value: '20px' }, + }, + }, + semanticTokens: { + colors: { + bg: { value: '{colors.secondary}' }, + primary: { value: '{colors.bg}' }, // clashing name (already exists in tokens) + another: { + group: { + ok: { + value: '{colors.group.with.nested}', + }, + stillok: { + value: { + base: 'blue', + _hover: 'green', + _active: '{colors.doesntexist}', // invalid missing reference + }, + }, + // @ts-expect-error should be an object with { value } + invalid: '{colors.group.with.invalid}', + recursive: { + value: '{colors.another.circular}', // invalid circular reference + }, + missing: { + value: '{colors.missing}', // invalid missing reference + }, + }, + self: { + value: '{colors.another.self}', // invalid self reference + }, + circular: { + value: '{colors.another.group.recursive}', // invalid circular reference + }, + }, + }, + fontSizes: { + main: { value: '{fontSizes.md2}' }, // invalid reference + }, + }, + recipes: { + stack: { + // clashing name (already exists in patterns) + className: 'stack', + }, + }, + slotRecipes: { + 'btn-primary': { + // clashing name (already exists in patterns) + className: 'btn-primary', + slots: [], + }, + }, + }, + conditions: { + dark: '@media (prefers-color-scheme: dark)', + pinkTheme: '[data-theme="pink"]', // invalid selector (must contain '&') + }, + patterns: { + 'btn-primary': {}, + }, + } + + expect(validateConfig(config)).toMatchInlineSnapshot(` + Set { + "[breakpoints]: All breakpoints must use the same unit: \`640em, 768px, 1024px\`", + "[conditions]: Selectors should contain the \`&\` character: \`[data-theme="pink"]\`", + "[tokens]: Token paths must end with 'value': \`theme.tokens.colors.secondary\`", + "[tokens]: Token paths must end with 'value': \`theme.tokens.colors.group.with.invalid\`", + "[tokens]: Semantic token paths must contain 'value': \`theme.semanticTokens.colors.another.group.invalid\`", + "[tokens]: Missing token: \`colors.doesntexist\` used in \`config.semanticTokens.colors.another.group.stillok\`", + "[tokens]: Circular token reference: \`colors.another.group.recursive\` -> \`colors.another.circular\` -> ... -> \`colors.another.group.recursive\`", + "[tokens]: Missing token: \`colors.missing\` used in \`config.semanticTokens.colors.another.group.missing\`", + "[tokens]: Self token reference: \`colors.another.self\`", + "[tokens]: Circular token reference: \`colors.another.self\` -> \`colors.another.self\` -> ... -> \`colors.another.self\`", + "[tokens]: Circular token reference: \`colors.another.circular\` -> \`colors.another.group.recursive\` -> ... -> \`colors.another.circular\`", + "[tokens]: Missing token: \`fontSizes.md2\` used in \`config.semanticTokens.fontSizes.main\`", + "[tokens]: This token name is already used in \`config.theme.token\`: \`colors.primary.value\`", + "[recipes]: This recipe name is already used in \`config.patterns\`: btn-primary", + } + `) + }) + + test('should report error if breakpoints use different units', () => { + const config = { + theme: { + breakpoints: { + sm: '640em', // invalid unit + md: '768px', + lg: '1024px', + }, + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [breakpoints]: All breakpoints must use the same unit: \`640em, 768px, 1024px\`]`, + ) + }) + + test('should report error for condition selectors without & character', () => { + const config = { + conditions: { + pinkTheme: '[data-theme="pink"]', // invalid selector + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [conditions]: Selectors should contain the \`&\` character: \`[data-theme="pink"]\`]`, + ) + }) + + test('should report error for token paths not ending with value', () => { + const config: Partial = { + theme: { + tokens: { + colors: { + // @ts-expect-error should be an object with { value } + secondary: '#fff', + }, + }, + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [tokens]: Token paths must end with 'value': \`theme.tokens.colors.secondary\`]`, + ) + }) + + test('should report error for semantic token paths not containing value', () => { + const config: Partial = { + theme: { + semanticTokens: { + colors: { + // @ts-expect-error should be an object with { value } + invalid: '{colors.group.with.invalid}', + }, + }, + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [tokens]: Semantic token paths must contain 'value': \`theme.semanticTokens.colors.invalid\`]`, + ) + }) + + test('should report error for missing token references', () => { + const config = { + theme: { + semanticTokens: { + colors: { + another: { + group: { + stillok: { + value: { + _active: '{colors.doesntexist}', // invalid missing reference + }, + }, + }, + }, + }, + }, + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [tokens]: Missing token: \`colors.doesntexist\` used in \`config.semanticTokens.colors.another.group.stillok\`]`, + ) + }) + + test('should report error for circular token references', () => { + const config = { + theme: { + semanticTokens: { + colors: { + another: { + circular: { + value: '{colors.another.group.recursive}', // invalid circular reference + }, + }, + }, + }, + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [tokens]: Missing token: \`colors.another.group.recursive\` used in \`config.semanticTokens.colors.another.circular\`]`, + ) + }) + + test('should report error for self token references', () => { + const config = { + theme: { + semanticTokens: { + colors: { + another: { + self: { + value: '{colors.another.self}', // invalid self reference + }, + }, + }, + }, + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [tokens]: Self token reference: \`colors.another.self\`]`, + ) + }) + + test('should report error for clashing token names', () => { + const config = { + theme: { + tokens: { + colors: { + primary: { value: '#000' }, + }, + }, + semanticTokens: { + colors: { + primary: { value: '{colors.bg}' }, // clashing name + }, + }, + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [tokens]: Missing token: \`colors.bg\` used in \`config.semanticTokens.colors.primary\`]`, + ) + }) + + test('should report error for clashing recipe names', () => { + const config = { + theme: { + recipes: { + stack: { + className: 'stack', + }, + }, + }, + patterns: { + stack: {}, // clashing name + }, + } + expect(() => validateConfig(config)).toThrowErrorMatchingInlineSnapshot( + `[Error: [recipes]: This recipe name is already used in \`config.patterns\`: \`stack\`]`, + ) + }) +}) diff --git a/packages/config/package.json b/packages/config/package.json index 36e14c852..1a8f14c78 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -64,7 +64,6 @@ "dist" ], "dependencies": { - "@pandacss/error": "workspace:*", "@pandacss/logger": "workspace:*", "@pandacss/preset-base": "workspace:*", "@pandacss/preset-panda": "workspace:*", diff --git a/packages/config/src/bundle-config.ts b/packages/config/src/bundle-config.ts index a242ac37e..bd11a2bd1 100644 --- a/packages/config/src/bundle-config.ts +++ b/packages/config/src/bundle-config.ts @@ -1,15 +1,9 @@ -import { ConfigError, ConfigNotFoundError } from '@pandacss/error' import { logger } from '@pandacss/logger' +import { PandaError } from '@pandacss/shared' import type { Config } from '@pandacss/types' import { bundleNRequire } from 'bundle-n-require' import { findConfig } from './find-config' -import type { ConfigFileOptions } from './types' - -export interface BundleConfigResult { - config: T - dependencies: string[] - path: string -} +import type { BundleConfigResult, ConfigFileOptions } from './types' export async function bundle(filepath: string, cwd: string) { const { mod: config, dependencies } = await bundleNRequire(filepath, { @@ -27,20 +21,16 @@ export async function bundleConfig(options: ConfigFileOptions): Promise Config) -const runIfFn = (fn: ConfigOrFn): Config => (typeof fn === 'function' ? fn() : fn) -export { type DiffConfigResult } +const runIfFn = (fn: ConfigOrFn): Config => (typeof fn === 'function' ? fn() : fn) /** * Diff the two config objects and return the list of affected properties diff --git a/packages/config/src/find-config.ts b/packages/config/src/find-config.ts index c31f70f70..4ca6cf89b 100644 --- a/packages/config/src/find-config.ts +++ b/packages/config/src/find-config.ts @@ -1,15 +1,24 @@ +import { PandaError } from '@pandacss/shared' import findUp from 'escalade/sync' import { resolve } from 'path' +import { isPandaConfig } from './is-panda-config' import type { ConfigFileOptions } from './types' -const configs = ['.ts', '.js', '.mts', '.mjs', '.cts', '.cjs'] -const pandaConfigRegex = new RegExp(`panda.config(${configs.join('|')})$`) +export function findConfig(options: Partial): string { + const { cwd = process.cwd(), file } = options -const isPandaConfig = (file: string) => pandaConfigRegex.test(file) + if (file) { + return resolve(cwd, file) + } -export function findConfig({ cwd, file }: Partial): string | undefined { - cwd = cwd ?? process.cwd() - if (file) return resolve(cwd, file) - const result = findUp(cwd, (_dir, paths) => paths.find(isPandaConfig)) - return result ?? undefined + const configPath = findUp(cwd, (_dir, paths) => paths.find(isPandaConfig)) + + if (!configPath) { + throw new PandaError( + 'CONFIG_NOT_FOUND', + `Cannot find config file \`panda.config.{ts,js,mjs,mts}\`. Did you forget to run \`panda init\`?`, + ) + } + + return configPath } diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 454ef57ac..eca1d5e2a 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,8 +1,9 @@ -export { bundleConfig, type BundleConfigResult } from './bundle-config' -export { diffConfigs, type DiffConfigResult } from './diff-config' +export { bundleConfig } from './bundle-config' +export { diffConfigs } from './diff-config' export { findConfig } from './find-config' export { getConfigDependencies, type GetDepsOptions } from './get-mod-deps' export { getResolvedConfig } from './get-resolved-config' export { loadConfig } from './load-config' export { mergeConfigs } from './merge-config' export { convertTsPathsToRegexes } from './ts-config-paths' +export type { BundleConfigResult } from './types' diff --git a/packages/config/src/is-panda-config.ts b/packages/config/src/is-panda-config.ts new file mode 100644 index 000000000..e7493d560 --- /dev/null +++ b/packages/config/src/is-panda-config.ts @@ -0,0 +1,4 @@ +const configs = ['.ts', '.js', '.mts', '.mjs', '.cts', '.cjs'] +const pandaConfigRegex = new RegExp(`panda.config(${configs.join('|')})$`) + +export const isPandaConfig = (file: string) => pandaConfigRegex.test(file) diff --git a/packages/config/src/resolve-config.ts b/packages/config/src/resolve-config.ts index e4326ff85..aabd8e3af 100644 --- a/packages/config/src/resolve-config.ts +++ b/packages/config/src/resolve-config.ts @@ -1,8 +1,9 @@ import { parseJson, stringifyJson } from '@pandacss/shared' -import type { LoadConfigResult } from '@pandacss/types' -import { type BundleConfigResult } from './bundle-config' +import type { LoadConfigResult, UserConfig } from '@pandacss/types' import { getBundledPreset, presetBase, presetPanda } from './bundled-preset' import { getResolvedConfig } from './get-resolved-config' +import type { BundleConfigResult } from './types' +import { validateConfig } from './validate-config' /** * Resolve the final config (including presets) @@ -28,11 +29,18 @@ export async function resolveConfig(result: BundleConfigResult, cwd: string): Pr result.config.presets = Array.from(presets) - const mergedConfig = await getResolvedConfig(result.config, cwd) - const hooks = result.config.hooks ?? {} + const config = await getResolvedConfig(result.config, cwd) + + validateConfig(config as UserConfig) + + const { hooks = {} } = result.config + + const loadConfigResult = { + ...result, + config: config as any, + } as LoadConfigResult // This allows editing the config before the context is created - const loadConfigResult = { ...result, config: mergedConfig as any } as LoadConfigResult await hooks['config:resolved']?.({ conf: loadConfigResult }) const serialized = stringifyJson(loadConfigResult.config) diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 6b73f9b36..9cd4cd28f 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -1,4 +1,29 @@ +import type { Config } from '@pandacss/types' + +export interface AddError { + (scope: string, message: string): void +} + export interface ConfigFileOptions { cwd: string file?: string } + +export interface TokensData { + tokenNames: Set + semanticTokenNames: Set + valueAtPath: Map + refsByPath: Map> +} + +export interface ArtifactNames { + recipes: Set + slotRecipes: Set + patterns: Set +} + +export interface BundleConfigResult { + config: T + dependencies: string[] + path: string +} diff --git a/packages/config/src/validate-config.ts b/packages/config/src/validate-config.ts new file mode 100644 index 000000000..23c5bf860 --- /dev/null +++ b/packages/config/src/validate-config.ts @@ -0,0 +1,71 @@ +import { logger } from '@pandacss/logger' +import { PandaError } from '@pandacss/shared' +import type { UserConfig } from '@pandacss/types' +import type { ArtifactNames, TokensData } from './types' +import { validateArtifactNames } from './validation/validate-artifact' +import { validateBreakpoints } from './validation/validate-breakpoints' +import { validateConditions } from './validation/validate-condition' +import { validatePatterns } from './validation/validate-patterns' +import { validateRecipes } from './validation/validate-recipes' +import { validateTokens } from './validation/validate-tokens' + +/** + * Validate the config + * - Check for duplicate between token & semanticTokens names + * - Check for duplicate between recipes/patterns/slots names + * - Check for token / semanticTokens paths (must end/contain 'value') + * - Check for self/circular token references + * - Check for missing tokens references + * - Check for conditions selectors (must contain '&') + * - Check for breakpoints units (must be the same) + */ +export const validateConfig = (config: Partial) => { + if (config.validation === 'none') return + + const warnings = new Set() + + const addError = (scope: string, message: string) => { + if (config.validation === 'warn') { + warnings.add(`[${scope}]: ` + message) + } else { + throw new PandaError('CONFIG_ERROR', `[${scope}]: ` + message) + } + } + + validateBreakpoints(config.theme?.breakpoints, addError) + + validateConditions(config.conditions, addError) + + const artifacts: ArtifactNames = { + recipes: new Set(), + slotRecipes: new Set(), + patterns: new Set(), + } + + const tokens: TokensData = { + tokenNames: new Set(), + semanticTokenNames: new Set(), + valueAtPath: new Map(), + refsByPath: new Map(), + } + + if (config.theme) { + validateTokens({ config, tokens, addError }) + validateRecipes({ config, tokens, artifacts, addError }) + } + + validatePatterns(config.patterns, artifacts) + + validateArtifactNames(artifacts, addError) + + if (warnings.size) { + logger.warn( + 'config', + `⚠️ Invalid config:\n${Array.from(warnings) + .map((err) => '- ' + err) + .join('\n')}\n`, + ) + + return warnings + } +} diff --git a/packages/config/src/validation/get-final-paths.ts b/packages/config/src/validation/get-final-paths.ts new file mode 100644 index 000000000..8f3bf01a5 --- /dev/null +++ b/packages/config/src/validation/get-final-paths.ts @@ -0,0 +1,11 @@ +export const getFinalPaths = (paths: Set) => { + paths.forEach((path) => { + paths.forEach((potentialExtension) => { + if (potentialExtension.startsWith(path + '.')) { + paths.delete(path) + } + }) + }) + + return paths +} diff --git a/packages/config/src/validation/validate-artifact.ts b/packages/config/src/validation/validate-artifact.ts new file mode 100644 index 000000000..8dbbf8b8c --- /dev/null +++ b/packages/config/src/validation/validate-artifact.ts @@ -0,0 +1,19 @@ +import type { AddError, ArtifactNames } from '../types' + +export const validateArtifactNames = (names: ArtifactNames, addError: AddError) => { + names.recipes.forEach((recipeName) => { + if (names.slotRecipes.has(recipeName)) { + addError('recipes', `This recipe name is already used in \`config.theme.slotRecipes\`: ${recipeName}`) + } + + if (names.patterns.has(recipeName)) { + addError('recipes', `This recipe name is already used in \`config.patterns\`: \`${recipeName}\``) + } + }) + + names.slotRecipes.forEach((recipeName) => { + if (names.patterns.has(recipeName)) { + addError('recipes', `This recipe name is already used in \`config.patterns\`: ${recipeName}`) + } + }) +} diff --git a/packages/config/src/validation/validate-breakpoints.ts b/packages/config/src/validation/validate-breakpoints.ts new file mode 100644 index 000000000..5be7783d7 --- /dev/null +++ b/packages/config/src/validation/validate-breakpoints.ts @@ -0,0 +1,20 @@ +import { getUnit } from '@pandacss/shared' +import type { Theme } from '@pandacss/types' +import type { AddError } from '../types' + +export const validateBreakpoints = (breakpoints: Theme['breakpoints'], addError: AddError) => { + if (!breakpoints) return + + const units = new Set() + + const values = Object.values(breakpoints) + + for (const value of values) { + const unit = getUnit(value) ?? 'px' + units.add(unit) + } + + if (units.size > 1) { + addError('breakpoints', `All breakpoints must use the same unit: \`${values.join(', ')}\``) + } +} diff --git a/packages/config/src/validation/validate-condition.ts b/packages/config/src/validation/validate-condition.ts new file mode 100644 index 000000000..2ed74c2b4 --- /dev/null +++ b/packages/config/src/validation/validate-condition.ts @@ -0,0 +1,12 @@ +import type { Conditions } from '@pandacss/types' +import type { AddError } from '../types' + +export const validateConditions = (conditions: Conditions | undefined, addError: AddError) => { + if (!conditions) return + + Object.values(conditions).forEach((condition) => { + if (!condition.startsWith('@') && !condition.includes('&')) { + addError('conditions', `Selectors should contain the \`&\` character: \`${condition}\``) + } + }) +} diff --git a/packages/config/src/validation/validate-patterns.ts b/packages/config/src/validation/validate-patterns.ts new file mode 100644 index 000000000..53d695b58 --- /dev/null +++ b/packages/config/src/validation/validate-patterns.ts @@ -0,0 +1,10 @@ +import type { Patterns } from '@pandacss/types' +import type { ArtifactNames } from '../types' + +export const validatePatterns = (patterns: Patterns | undefined, names: ArtifactNames) => { + if (!patterns) return + + Object.keys(patterns).forEach((patternName) => { + names.patterns.add(patternName) + }) +} diff --git a/packages/config/src/validation/validate-recipes.ts b/packages/config/src/validation/validate-recipes.ts new file mode 100644 index 000000000..063bd36a7 --- /dev/null +++ b/packages/config/src/validation/validate-recipes.ts @@ -0,0 +1,32 @@ +import type { Config } from '@pandacss/types' +import type { AddError, ArtifactNames, TokensData } from '../types' + +interface Options { + config: Config + tokens: TokensData + artifacts: ArtifactNames + addError: AddError +} + +export const validateRecipes = (options: Options) => { + const { + config: { theme }, + artifacts, + } = options + + if (!theme) return + + if (theme.recipes) { + Object.keys(theme.recipes).forEach((recipeName) => { + artifacts.recipes.add(recipeName) + }) + } + + if (theme.slotRecipes) { + Object.keys(theme.slotRecipes).forEach((recipeName) => { + artifacts.slotRecipes.add(recipeName) + }) + } + + return artifacts +} diff --git a/packages/config/src/validation/validate-token-references.ts b/packages/config/src/validation/validate-token-references.ts new file mode 100644 index 000000000..e9b9ace2b --- /dev/null +++ b/packages/config/src/validation/validate-token-references.ts @@ -0,0 +1,36 @@ +import type { AddError } from '../types' + +export const validateTokenReferences = ( + valueAtPath: Map, + refsByPath: Map>, + addError: AddError, +) => { + refsByPath.forEach((refs, path) => { + if (refs.has(path)) { + addError('tokens', `Self token reference: \`${path}\``) + } + + const stack = [path] + + while (stack.length > 0) { + const current = stack.pop()! + const value = valueAtPath.get(current) + + if (!value) { + addError('tokens', `Missing token: \`${current}\` used in \`config.semanticTokens.${path}\``) + } + + const deps = refsByPath.get(current) + if (!deps) continue + + for (const transitiveDep of deps) { + if (path === transitiveDep) { + addError('tokens', `Circular token reference: \`${transitiveDep}\` -> \`${current}\` -> ... -> \`${path}\``) + break + } + + stack.push(transitiveDep) + } + } + }) +} diff --git a/packages/config/src/validation/validate-tokens.ts b/packages/config/src/validation/validate-tokens.ts new file mode 100644 index 000000000..f1f12fdfe --- /dev/null +++ b/packages/config/src/validation/validate-tokens.ts @@ -0,0 +1,82 @@ +import { traverse } from '@pandacss/shared' +import type { Config } from '@pandacss/types' +import type { AddError, TokensData } from '../types' +import { getFinalPaths } from './get-final-paths' +import { validateTokenReferences } from './validate-token-references' + +interface Options { + config: Config + tokens: TokensData + addError: AddError +} + +export const validateTokens = (options: Options) => { + const { + config: { theme }, + tokens, + addError, + } = options + + if (!theme) return + + const { tokenNames, semanticTokenNames, valueAtPath, refsByPath } = tokens + + if (theme.tokens) { + traverse(theme.tokens, (node) => { + if (node.depth >= 1) { + tokenNames.add(node.path) + valueAtPath.set(node.path, node.value) + } + }) + + const finalPaths = getFinalPaths(tokenNames) + + finalPaths.forEach((path) => { + if (!path.includes('value')) { + addError('tokens', `Token paths must end with 'value': \`theme.tokens.${path}\``) + } + + const atPath = valueAtPath.get(path) + if (typeof atPath === 'string' && atPath.startsWith('{')) { + refsByPath.set(path, new Set([])) + } + }) + } + + if (theme.semanticTokens) { + traverse(theme.semanticTokens, (node) => { + if (node.depth >= 1) { + semanticTokenNames.add(node.path) + valueAtPath.set(node.path, node.value) + + // Keep track of all token references + if (typeof node.value === 'string' && node.value.startsWith('{') && node.path.includes('value')) { + const tokenPath = node.path.split('.value')[0] + if (!refsByPath.has(tokenPath)) { + refsByPath.set(tokenPath, new Set()) + } + + const values = refsByPath.get(tokenPath)! + const tokenRef = node.value.slice(1, -1) + values.add(tokenRef) + } + } + }) + + const finalPaths = getFinalPaths(semanticTokenNames) + + finalPaths.forEach((path) => { + if (!path.includes('value')) { + addError('tokens', `Semantic token paths must contain 'value': \`theme.semanticTokens.${path}\``) + } + }) + + validateTokenReferences(valueAtPath, refsByPath, addError) + } + + semanticTokenNames.forEach((semanticTokenName) => { + if (tokenNames.has(semanticTokenName)) { + addError('tokens', `This token name is already used in \`config.theme.token\`: \`${semanticTokenName}\``) + } + }) +} diff --git a/packages/core/package.json b/packages/core/package.json index e9512d7c5..e80ea0f6f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -37,7 +37,6 @@ "access": "public" }, "dependencies": { - "@pandacss/error": "workspace:*", "@pandacss/is-valid-prop": "workspace:^", "@pandacss/logger": "workspace:*", "@pandacss/shared": "workspace:*", diff --git a/packages/core/src/layers.ts b/packages/core/src/layers.ts index a938aa4d3..9f799bd9f 100644 --- a/packages/core/src/layers.ts +++ b/packages/core/src/layers.ts @@ -1,3 +1,4 @@ +import { PandaError } from '@pandacss/shared' import type { CascadeLayer, CascadeLayers } from '@pandacss/types' import postcss, { AtRule, Root } from 'postcss' @@ -66,7 +67,7 @@ export class Layers { } default: - throw new Error(`Unknown layer: ${layer}`) + throw new PandaError('INVALID_LAYER', `Unknown layer: ${layer}`) } } diff --git a/packages/core/src/recipes.ts b/packages/core/src/recipes.ts index 4604f09a6..dd85b7a9a 100644 --- a/packages/core/src/recipes.ts +++ b/packages/core/src/recipes.ts @@ -1,4 +1,13 @@ -import { capitalize, createRegex, dashCase, getSlotRecipes, isObject, memo, splitProps } from '@pandacss/shared' +import { + PandaError, + capitalize, + createRegex, + dashCase, + getSlotRecipes, + isObject, + memo, + splitProps, +} from '@pandacss/shared' import type { ArtifactFilters, Dict, @@ -162,7 +171,7 @@ export class Recipes { getConfigOrThrow = memo((name: string) => { const config = this.getConfig(name) - if (!config) throw new Error(`Recipe "${name}" not found`) + if (!config) throw new PandaError('UNKNOWN_RECIPE', `Recipe "${name}" not found`) return config }) diff --git a/packages/core/src/rule-processor.ts b/packages/core/src/rule-processor.ts index 36c80b836..dc5b21aa8 100644 --- a/packages/core/src/rule-processor.ts +++ b/packages/core/src/rule-processor.ts @@ -1,4 +1,5 @@ import type { CssOptions, Stylesheet } from '@pandacss/core' +import { PandaError } from '@pandacss/shared' import type { AtomicRecipeRule, AtomicRule, @@ -25,7 +26,7 @@ export class RuleProcessor { getParamsOrThrow() { const isReady = Boolean(this.encoder && this.decoder && this.sheet) if (!isReady) { - throw new Error('RuleProcessor is missing params, please call `clone` first') + throw new PandaError('MISSING_PARAMS', 'RuleProcessor is missing params, please call `clone` first') } return { diff --git a/packages/core/src/style-decoder.ts b/packages/core/src/style-decoder.ts index e84d89faf..8656384fa 100644 --- a/packages/core/src/style-decoder.ts +++ b/packages/core/src/style-decoder.ts @@ -1,4 +1,13 @@ -import { deepSet, esc, getOrCreateSet, isImportant, markImportant, toHash, withoutImportant } from '@pandacss/shared' +import { + PandaError, + deepSet, + esc, + getOrCreateSet, + isImportant, + markImportant, + toHash, + withoutImportant, +} from '@pandacss/shared' import type { AtomicStyleResult, Dict, @@ -300,7 +309,7 @@ export class StyleDecoder { const recipeConfig = this.context.recipes.getConfigOrThrow(recipeName) if (!Recipes.isSlotRecipeConfig(recipeConfig)) { - throw new Error(`Recipe "${recipeName}" is not a slot recipe`) + throw new PandaError('UNKNOWN_RECIPE', `Recipe "${recipeName}" is not a slot recipe`) } const base: Dict = {} diff --git a/packages/core/src/style-encoder.ts b/packages/core/src/style-encoder.ts index f9e7aed7a..beb6e24ec 100644 --- a/packages/core/src/style-encoder.ts +++ b/packages/core/src/style-encoder.ts @@ -1,4 +1,5 @@ import { + PandaError, getOrCreateSet, getSlotRecipes, isObjectOrArray, @@ -280,7 +281,7 @@ export class StyleEncoder { const recipeConfig = this.context.recipes.getConfigOrThrow(recipeName) if (!Recipes.isSlotRecipeConfig(recipeConfig)) { - throw new Error(`Recipe "${recipeName}" is not a slot recipe`) + throw new PandaError('INVALID_RECIPE', `Recipe "${recipeName}" is not a slot recipe`) } const base: Dict = {} diff --git a/packages/error/CHANGELOG.md b/packages/error/CHANGELOG.md deleted file mode 100644 index 094b0f692..000000000 --- a/packages/error/CHANGELOG.md +++ /dev/null @@ -1,152 +0,0 @@ -# @pandacss/error - -## 0.28.0 - -## 0.27.3 - -## 0.27.2 - -## 0.27.1 - -## 0.27.0 - -### Minor Changes - -- 84304901: Improve performance, mostly for the CSS generation by removing a lot of `postcss` usage (and plugins). - - ## Public changes: - - - Introduce a new `config.lightningcss` option to use `lightningcss` (currently disabled by default) instead of - `postcss`. - - Add a new `config.browserslist` option to configure the browserslist used by `lightningcss`. - - Add a `--lightningcss` flag to the `panda` and `panda cssgen` command to use `lightningcss` instead of `postcss` for - this run. - - ## Internal changes: - - - `markImportant` fn from JS instead of walking through postcss AST nodes - - use a fork of `stitches` `stringify` function instead of `postcss-css-in-js` to write the CSS string from a JS - object - - only compute once `TokenDictionary` properties - - refactor `serializeStyle` to use the same code path as the rest of the pipeline with `StyleEncoder` / `StyleDecoder` - and rename it to `transformStyles` to better convey what it does - -## 0.26.2 - -## 0.26.1 - -## 0.26.0 - -## 0.25.0 - -## 0.24.2 - -## 0.24.1 - -## 0.24.0 - -## 0.23.0 - -## 0.22.1 - -## 0.22.0 - -## 0.21.0 - -## 0.20.1 - -## 0.20.0 - -## 0.19.0 - -## 0.18.3 - -## 0.18.2 - -## 0.18.1 - -## 0.18.0 - -## 0.17.5 - -## 0.17.4 - -## 0.17.3 - -## 0.17.2 - -## 0.17.1 - -## 0.17.0 - -## 0.16.0 - -## 0.15.5 - -## 0.15.4 - -## 0.15.3 - -## 0.15.2 - -## 0.15.1 - -## 0.15.0 - -## 0.14.0 - -## 0.13.1 - -### Patch Changes - -- d0fbc7cc: Allow `.mts` and `.cts` panda config extension - -## 0.13.0 - -## 0.12.2 - -## 0.12.1 - -## 0.12.0 - -## 0.11.1 - -## 0.11.0 - -## 0.10.0 - -## 0.9.0 - -## 0.8.0 - -## 0.7.0 - -## 0.6.0 - -## 0.5.1 - -## 0.5.0 - -## 0.4.0 - -## 0.3.2 - -## 0.3.1 - -### Patch Changes - -- efd79d83: Baseline release for the launch - -## 0.3.0 - -## 0.0.2 - -### Patch Changes - -- fb40fff2: Initial release of all packages - - - Internal AST parser for TS and TSX - - Support for defining presets in config - - Support for design tokens (core and semantic) - - Add `outExtension` key to config to allow file extension options for generated javascript. `.js` or `.mjs` - - Add `jsxElement` option to patterns, to allow specifying the jsx element rendered by the patterns. diff --git a/packages/error/package.json b/packages/error/package.json deleted file mode 100644 index 5069ac49d..000000000 --- a/packages/error/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@pandacss/error", - "version": "0.28.0", - "description": "Common error messages for css panda", - "author": "Segun Adebayo ", - "main": "dist/index.js", - "module": "dist/index.mjs", - "types": "dist/index.d.ts", - "sideEffects": false, - "homepage": "https://panda-css.com", - "repository": { - "type": "git", - "url": "git+https://github.com/chakra-ui/panda.git", - "directory": "packages/error" - }, - "publishConfig": { - "access": "public" - }, - "exports": { - ".": { - "source": "./src/index.ts", - "types": "./dist/index.d.ts", - "require": "./dist/index.js", - "import": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" - } - }, - "./package.json": "./package.json" - }, - "scripts": { - "build": "tsup src/index.ts --format=esm,cjs --dts", - "build-fast": "tsup src/index.ts --format=esm,cjs --no-dts", - "dev": "pnpm build-fast --watch" - }, - "files": [ - "dist" - ] -} diff --git a/packages/error/src/index.ts b/packages/error/src/index.ts deleted file mode 100644 index 6591a32af..000000000 --- a/packages/error/src/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -export class PandaError extends Error { - readonly code: string - readonly hint?: string - - constructor(code: string, message: string, opts?: { hint?: string }) { - super(message) - this.code = `ERR_PANDA_${code}` - this.hint = opts?.hint - } -} - -export class TokenError extends PandaError { - constructor(message: string) { - super('INVALID_TOKEN', message) - } -} - -export class ConfigNotFoundError extends PandaError { - constructor() { - const message = `Cannot find config file: panda.config.ts or panda.config.js/cjs/mjs/mts/cts. Did you forget to run \`panda init\`?` - super('NO_CONFIG', message) - } -} - -export class ConfigError extends PandaError { - constructor(message: string) { - super('CONFIG_ERROR', message) - } -} - -export class NotFoundError extends PandaError { - constructor({ name, type }: { name: string; type: string }) { - super('NOT_FOUND', `${type} not found: \`${name}\``) - } -} - -export class ConditionError extends PandaError { - constructor(message: string) { - super('CONDITION', message) - } -} diff --git a/packages/error/tsconfig.json b/packages/error/tsconfig.json deleted file mode 100644 index 262190bf7..000000000 --- a/packages/error/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.build.json", - "include": ["src"] -} diff --git a/packages/extractor/package.json b/packages/extractor/package.json index d8ee1ad99..09ab5bc09 100644 --- a/packages/extractor/package.json +++ b/packages/extractor/package.json @@ -41,6 +41,7 @@ "dist" ], "dependencies": { + "@pandacss/shared": "workspace:*", "ts-evaluator": "^1.1.0", "ts-morph": "19.0.0" } diff --git a/packages/extractor/src/get-typeof-literal.ts b/packages/extractor/src/get-typeof-literal.ts index 05b1bb286..3b1d27b08 100644 --- a/packages/extractor/src/get-typeof-literal.ts +++ b/packages/extractor/src/get-typeof-literal.ts @@ -1,3 +1,4 @@ +import { PandaError } from '@pandacss/shared' import type { LiteralKind } from './box-factory' import type { PrimitiveType } from './types' @@ -8,5 +9,5 @@ export const getTypeOfLiteral = (value: PrimitiveType | PrimitiveType[]): Litera if (typeof value === 'boolean') return 'boolean' if (value === null) return 'null' if (value === undefined) return 'undefined' - throw new Error(`Unexpected literal type: ${value as any}`) + throw new PandaError('UNKNOWN_TYPE', `Unexpected literal type: ${value as any}`) } diff --git a/packages/node/package.json b/packages/node/package.json index e3aa2e91c..d32d55d2d 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -39,7 +39,6 @@ "dependencies": { "@pandacss/config": "workspace:*", "@pandacss/core": "workspace:*", - "@pandacss/error": "workspace:*", "@pandacss/extractor": "workspace:*", "@pandacss/generator": "workspace:*", "@pandacss/logger": "workspace:*", diff --git a/packages/node/src/builder.ts b/packages/node/src/builder.ts index b0de44524..10d7fa2fd 100644 --- a/packages/node/src/builder.ts +++ b/packages/node/src/builder.ts @@ -1,7 +1,8 @@ -import { findConfig, getConfigDependencies, type DiffConfigResult } from '@pandacss/config' +import { findConfig, getConfigDependencies } from '@pandacss/config' import { optimizeCss } from '@pandacss/core' -import { ConfigNotFoundError } from '@pandacss/error' import { logger } from '@pandacss/logger' +import { PandaError } from '@pandacss/shared' +import type { DiffConfigResult } from '@pandacss/types' import { existsSync, statSync } from 'fs' import { normalize, resolve } from 'path' import type { Message, Root } from 'postcss' @@ -23,16 +24,6 @@ export class Builder { private affecteds: DiffConfigResult | undefined private configDependencies: Set = new Set() - getConfigPath = (cwd?: string) => { - const configPath = findConfig({ cwd }) - - if (!configPath) { - throw new ConfigNotFoundError() - } - - return configPath - } - setConfigDependencies(options: SetupContextOptions) { const tsOptions = this.context?.conf.tsOptions ?? { baseUrl: undefined, pathMappings: [] } const compilerOptions = this.context?.conf.tsconfig?.compilerOptions ?? {} @@ -52,7 +43,7 @@ export class Builder { setup = async (options: { configPath?: string; cwd?: string } = {}) => { logger.debug('builder', '🚧 Setup') - const configPath = options.configPath ?? this.getConfigPath(options.cwd) + const configPath = options.configPath ?? findConfig({ cwd: options.cwd }) this.setConfigDependencies({ configPath, cwd: options.cwd }) if (!this.context) { @@ -103,7 +94,7 @@ export class Builder { getContextOrThrow = (): PandaContext => { if (!this.context) { - throw new Error('context not loaded') + throw new PandaError('NO_CONTEXT', 'context not loaded') } return this.context } diff --git a/packages/parser/src/parser-result.ts b/packages/parser/src/parser-result.ts index 8d6be1b5d..87e15dd02 100644 --- a/packages/parser/src/parser-result.ts +++ b/packages/parser/src/parser-result.ts @@ -1,5 +1,5 @@ import type { ParserOptions } from '@pandacss/core' -import { getOrCreateSet } from '@pandacss/shared' +import { PandaError, getOrCreateSet } from '@pandacss/shared' import type { ParserResultInterface, ResultItem } from '@pandacss/types' export class ParserResult implements ParserResultInterface { @@ -37,7 +37,7 @@ export class ParserResult implements ParserResultInterface { this.setSva(result) break default: - throw new Error(`Unknown result type ${name}`) + throw new PandaError('UNKNOWN_TYPE', `Unknown result type ${name}`) } } diff --git a/packages/shared/src/error.ts b/packages/shared/src/error.ts new file mode 100644 index 000000000..b230f6773 --- /dev/null +++ b/packages/shared/src/error.ts @@ -0,0 +1,24 @@ +export type PandaErrorCode = + | 'CONFIG_NOT_FOUND' + | 'CONFIG_ERROR' + | 'NOT_FOUND' + | 'CONDITION' + | 'MISSING_STUDIO' + | 'INVALID_LAYER' + | 'UNKNOWN_RECIPE' + | 'INVALID_RECIPE' + | 'UNKNOWN_TYPE' + | 'MISSING_PARAMS' + | 'NO_CONTEXT' + | 'INVALID_TOKEN' + +export class PandaError extends Error { + readonly code: string + readonly hint?: string + + constructor(code: PandaErrorCode, message: string, opts?: { hint?: string }) { + super(message) + this.code = `ERR_PANDA_${code}` + this.hint = opts?.hint + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 37f7ece49..9e09e7da7 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -12,6 +12,7 @@ export * from './unit-conversion' export * from './css-var' export * from './deep-set' export * from './entries' +export * from './error' export * from './esc' export * from './flatten' export * from './get-or-create-set' diff --git a/packages/token-dictionary/src/utils.ts b/packages/token-dictionary/src/utils.ts index 409f70737..256007bbc 100644 --- a/packages/token-dictionary/src/utils.ts +++ b/packages/token-dictionary/src/utils.ts @@ -1,5 +1,5 @@ import { logger } from '@pandacss/logger' -import { esc, isObject } from '@pandacss/shared' +import { PandaError, esc, isObject } from '@pandacss/shared' import type { Token } from '@pandacss/types' /* ----------------------------------------------------------------------------- @@ -107,6 +107,6 @@ export const isToken = (value: any): value is Token => { export function assertTokenFormat(token: any): asserts token is Token { if (!isToken(token)) { - throw new Error(`Invalid token format: ${JSON.stringify(token)}`) + throw new PandaError('INVALID_TOKEN', `Invalid token format: ${JSON.stringify(token)}`) } } diff --git a/packages/types/src/config.ts b/packages/types/src/config.ts index f0e1b904c..a705fcf39 100644 --- a/packages/types/src/config.ts +++ b/packages/types/src/config.ts @@ -38,7 +38,7 @@ export interface StudioOptions { } } -interface Patterns { +export interface Patterns { [pattern: string]: PatternConfig } @@ -346,6 +346,15 @@ export interface Config * @default 'false' */ eject?: boolean + /** + * The validation strcictnesss to use when validating the config. + * - When set to 'none', no validation will be performed. + * - When set to 'warn', warnings will be logged when validation fails. + * - When set to 'error', errors will be thrown when validation fails. + * + * @default 'error' + */ + validation?: 'none' | 'warn' | 'error' } export interface Preset extends ExtendableOptions, PresetOptions {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42eedabd4..220d3e8cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,9 +106,6 @@ importers: '@pandacss/config': specifier: workspace:* version: link:../config - '@pandacss/error': - specifier: workspace:* - version: link:../error '@pandacss/logger': specifier: workspace:* version: link:../logger @@ -146,9 +143,6 @@ importers: packages/config: dependencies: - '@pandacss/error': - specifier: workspace:* - version: link:../error '@pandacss/logger': specifier: workspace:* version: link:../logger @@ -256,9 +250,6 @@ importers: packages/core: dependencies: - '@pandacss/error': - specifier: workspace:* - version: link:../error '@pandacss/is-valid-prop': specifier: workspace:^ version: link:../is-valid-prop @@ -324,10 +315,11 @@ importers: specifier: 4.6.8 version: 4.6.8 - packages/error: {} - packages/extractor: dependencies: + '@pandacss/shared': + specifier: workspace:* + version: link:../shared ts-evaluator: specifier: ^1.1.0 version: 1.1.0(typescript@5.3.3) @@ -435,9 +427,6 @@ importers: '@pandacss/core': specifier: workspace:* version: link:../core - '@pandacss/error': - specifier: workspace:* - version: link:../error '@pandacss/extractor': specifier: workspace:* version: link:../extractor @@ -1957,24 +1946,6 @@ packages: semver: 6.3.1 dev: true - /@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.23.5): - resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-member-expression-to-functions': 7.23.0 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.5) - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - semver: 6.3.1 - dev: true - /@babel/helper-create-class-features-plugin@7.23.5(@babel/core@7.22.5): resolution: {integrity: sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==} engines: {node: '>=6.9.0'} @@ -3044,16 +3015,6 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.23.5): - resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5): resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==} engines: {node: '>=6.9.0'} @@ -4465,19 +4426,6 @@ packages: '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.20) dev: true - /@babel/plugin-transform-typescript@7.22.15(@babel/core@7.23.5): - resolution: {integrity: sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.5) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.23.5) - dev: true - /@babel/plugin-transform-typescript@7.23.5(@babel/core@7.23.5): resolution: {integrity: sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA==} engines: {node: '>=6.9.0'} @@ -5073,7 +5021,6 @@ packages: '@babel/helper-string-parser': 7.23.4 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - dev: true /@base2/pretty-print-object@1.0.1: resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==} @@ -11941,9 +11888,9 @@ packages: vite: ^4.0.0 vue: ^3.0.0 dependencies: - '@babel/core': 7.23.5 - '@babel/plugin-transform-typescript': 7.22.15(@babel/core@7.23.5) - '@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.23.5) + '@babel/core': 7.22.20 + '@babel/plugin-transform-typescript': 7.22.15(@babel/core@7.22.20) + '@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.22.20) vite: 4.3.9(@types/node@20.4.5) vue: 3.3.4 transitivePeerDependencies: @@ -12065,25 +12012,6 @@ packages: - supports-color dev: true - /@vue/babel-plugin-jsx@1.1.5(@babel/core@7.23.5): - resolution: {integrity: sha512-nKs1/Bg9U1n3qSWnsHhCVQtAzI6aQXqua8j/bZrau8ywT1ilXQbK4FwEJGmU8fV7tcpuFvWmmN7TMmV1OBma1g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.5 - '@babel/helper-module-imports': 7.22.15 - '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5) - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.5 - '@babel/types': 7.23.5 - '@vue/babel-helper-vue-transform-on': 1.1.5 - camelcase: 6.3.0 - html-tags: 3.3.1 - svg-tags: 1.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /@vue/compiler-core@3.3.4: resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} dependencies: @@ -13433,6 +13361,13 @@ packages: dependencies: acorn: 8.11.2 + /acorn-import-assertions@1.9.0(acorn@8.11.3): + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.11.3 + /acorn-jsx@5.3.2(acorn@7.4.1): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -14162,7 +14097,7 @@ packages: '@babel/code-frame': 7.23.5 '@babel/parser': 7.23.5 '@babel/traverse': 7.23.5 - '@babel/types': 7.23.5 + '@babel/types': 7.23.9 eslint: 7.32.0 eslint-visitor-keys: 1.3.0 resolve: 1.22.8 @@ -25282,7 +25217,6 @@ packages: lilconfig: 3.0.0 postcss: 8.4.31 yaml: 2.3.4 - dev: true /postcss-load-config@4.0.2(postcss@8.4.32): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} @@ -25299,6 +25233,7 @@ packages: lilconfig: 3.0.0 postcss: 8.4.32 yaml: 2.3.4 + dev: true /postcss-loader@5.3.0(postcss@8.4.32)(webpack@5.87.0): resolution: {integrity: sha512-/+Z1RAmssdiSLgIZwnJHwBMnlABPgF7giYzTN2NOfr9D21IJZ4mQC1R2miwp80zno9M4zMD/umGI8cR+2EL5zw==} @@ -29303,7 +29238,7 @@ packages: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.32) + postcss-load-config: 4.0.2(postcss@8.4.31) resolve-from: 5.0.0 rollup: 4.7.0 source-map: 0.8.0-beta.0 @@ -30997,8 +30932,8 @@ packages: '@webassemblyjs/ast': 1.11.6 '@webassemblyjs/wasm-edit': 1.11.6 '@webassemblyjs/wasm-parser': 1.11.6 - acorn: 8.11.2 - acorn-import-assertions: 1.9.0(acorn@8.11.2) + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) browserslist: 4.22.2 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 @@ -31037,8 +30972,8 @@ packages: '@webassemblyjs/ast': 1.11.6 '@webassemblyjs/wasm-edit': 1.11.6 '@webassemblyjs/wasm-parser': 1.11.6 - acorn: 8.11.2 - acorn-import-assertions: 1.9.0(acorn@8.11.2) + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) browserslist: 4.22.2 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0