@nons-dev/uikit 0.1.6 → 0.1.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nons-dev/uikit",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "license": "BUSL-1.1",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,8 +19,11 @@
19
19
  },
20
20
  "files": [
21
21
  "dist",
22
+ "core/config",
22
23
  "generated/components.json",
24
+ "locales",
23
25
  "scripts/cli.mjs",
26
+ "scripts/vite-plugin-nons-config.ts",
24
27
  "readme.md"
25
28
  ],
26
29
  "exports": {
@@ -32,13 +35,17 @@
32
35
  "./style.css": {
33
36
  "import": "./dist/index.css",
34
37
  "default": "./dist/index.css"
38
+ },
39
+ "./plugin": {
40
+ "types": "./scripts/vite-plugin-nons-config.ts",
41
+ "import": "./scripts/vite-plugin-nons-config.ts",
42
+ "default": "./scripts/vite-plugin-nons-config.ts"
35
43
  }
36
44
  },
37
45
  "scripts": {
38
46
  "dev": "vite",
39
- "build": "npm run generate && vue-tsc -b && vite build && node scripts/copy-fonts.mjs",
40
- "preview": "vite preview",
41
- "generate": "tsx scripts/generate.ts"
47
+ "build": "vue-tsc -b && vite build && node scripts/copy-fonts.mjs",
48
+ "preview": "vite preview"
42
49
  },
43
50
  "peerDependencies": {
44
51
  "vue": "^3.5.0"
@@ -48,10 +55,8 @@
48
55
  "js-yaml": "^5.1.0"
49
56
  },
50
57
  "devDependencies": {
51
- "@types/js-yaml": "^4.0.9",
52
58
  "@types/node": "^26.0.1",
53
59
  "@vitejs/plugin-vue": "^5.2.3",
54
- "tsx": "^4.22.4",
55
60
  "typescript": "~5.7.3",
56
61
  "vite": "^6.3.3",
57
62
  "vite-plugin-dts": "^5.0.3",
package/readme.md CHANGED
@@ -2,50 +2,55 @@
2
2
 
3
3
  Schema-driven UI component library for the Nons platform — Vue 3, TypeScript, token-based design system.
4
4
 
5
- ## Architecture
5
+ ## Installation
6
6
 
7
- ```
8
- nons.config.yaml Single source of truth for theme, tokens, locale, direction, font
9
- core/ Schema types, component registry, schema renderer, runtime context
10
- ui/ Vue 3 components (primitives, composites, layouts, sections)
11
- generated/ Auto-generated files from nons.config.yaml (tokens.css, theme.css, config.ts, locales/)
12
- design-system/ CSS reset, global styles (imports generated/ tokens)
13
- playground/ Demo/explorer app for component development and testing
14
- plugins/ i18n, direction, permissions
15
- adapters/ Data provider layer (mock API, storage abstraction)
16
- locales/ Persian (fa) and English (en) translation YAML files
17
- scripts/ CLI tools (generate, init, component discovery)
7
+ ```bash
8
+ npm install @nons-dev/uikit
18
9
  ```
19
10
 
20
11
  ## Quick Start
21
12
 
22
13
  ```bash
23
- # Install
24
- npm install @nons-dev/uikit
25
-
26
- # Generate config file in your project root
14
+ # Generate config in your project root
27
15
  npx @nons-dev/uikit init
28
16
 
29
- # Edit nons.config.yaml to customize theme, tokens, locale, direction, font
17
+ # Edit nons.config.yaml
18
+ ```
30
19
 
31
- # Use in your app
32
- import { bootstrapRegistry, loadConfig } from '@nons-dev/uikit'
20
+ ```ts
21
+ import { bootstrapRegistry } from '@nons-dev/uikit'
33
22
  import '@nons-dev/uikit/style.css'
34
23
  ```
35
24
 
36
- ### Development (UI Kit only)
25
+ For token/theme/font customization, use the Nons Vite plugin in your project:
37
26
 
38
- ```bash
39
- npm install
40
- npm run dev # Start Vite dev server (playground app)
41
- npm run build # Generate tokens + type-check + library build
42
- npm run generate # Regenerate CSS/theme from nons.config.yaml
43
- npm run preview # Preview production playground build
27
+ ```ts
28
+ // vite.config.ts
29
+ import { defineConfig } from 'vite'
30
+ import vue from '@vitejs/plugin-vue'
31
+ import { nonsConfigPlugin } from '@nons-dev/uikit/plugin'
32
+
33
+ export default defineConfig({
34
+ plugins: [vue(), nonsConfigPlugin()],
35
+ })
36
+ ```
37
+
38
+ ## Architecture
39
+
40
+ ```
41
+ nons.config.yaml Single source of truth for theme, tokens, locale, direction, font
42
+ core/ Schema types, component registry, schema renderer, runtime context
43
+ ui/ Vue 3 components (primitives, composites, layouts, sections)
44
+ design-system/ CSS reset, global styles, token defaults (shipped in dist/)
45
+ plugins/ i18n, direction, permissions
46
+ adapters/ Data provider layer (mock API, storage abstraction)
47
+ locales/ Persian (fa) and English (en) translation JSON files
48
+ scripts/ CLI tool (nons) and Vite plugin
44
49
  ```
45
50
 
46
51
  ## Config System
47
52
 
48
- The `nons.config.yaml` file at your project root is the **single source of truth**:
53
+ `nons.config.yaml` at your project root is the **single source of truth**:
49
54
 
50
55
  ```yaml
51
56
  theme:
@@ -72,13 +77,11 @@ tokens:
72
77
  light:
73
78
  --color-primary: '#9FE870'
74
79
  --color-danger: '#F31260'
75
- # ... override any CSS custom property
76
80
  dark:
77
81
  --color-primary: '#9FE870'
78
82
  --color-danger: '#f31260'
79
83
  spacing:
80
84
  --space-4: 16px
81
- # ... typography, radius, shadows, z-index, opacity, button, alert
82
85
 
83
86
  theme-mappings:
84
87
  light:
@@ -89,61 +92,70 @@ theme-mappings:
89
92
  --text-primary: var(--color-text-default)
90
93
  ```
91
94
 
92
- Run `npx @nons-dev/uikit generate` to regenerate CSS variables from your config.
93
-
94
95
  ## CLI Commands
95
96
 
96
97
  ```bash
97
- npx @nons-dev/uikit init # Create nons.config.yaml in project root
98
- npx @nons-dev/uikit generate # Generate CSS from nons.config.yaml
98
+ npx @nons-dev/uikit init # Create nons.config.yaml + locales/ in project root
99
+ npx @nons-dev/uikit validate # Validate nons.config.yaml structure
99
100
  npx @nons-dev/uikit list # List all components
100
101
  npx @nons-dev/uikit show <name> # Show component details (props, events, slots)
101
102
  npx @nons-dev/uikit schema <name> # Show schema JSON for a component
102
103
  npx @nons-dev/uikit search <query> # Search components by name or description
103
104
  ```
104
105
 
105
- ## Customizing Colors & Tokens
106
+ ## Customizing Tokens & Theme
106
107
 
107
- Edit `nons.config.yaml` in your project root, then regenerate:
108
-
109
- ```bash
110
- npx @nons-dev/uikit generate
111
- ```
108
+ The default design tokens are built into `dist/index.css`. To override them:
112
109
 
113
- This produces `generated/tokens.css` (color schemes + all tokens) and `generated/theme.css` (semantic theme mappings). Import them in your app after the uikit CSS:
110
+ 1. Edit `nons.config.yaml` with your custom token values
111
+ 2. Use the **Nons Vite plugin** to generate custom CSS at build time:
114
112
 
115
113
  ```ts
116
- import '@nons-dev/uikit/style.css'
117
- import './generated/tokens.css'
118
- import './generated/theme.css'
114
+ // vite.config.ts
115
+ import { defineConfig } from 'vite'
116
+ import vue from '@vitejs/plugin-vue'
117
+ import { nonsConfigPlugin } from '@nons-dev/uikit/plugin'
118
+
119
+ export default defineConfig({
120
+ plugins: [vue(), nonsConfigPlugin()],
121
+ })
119
122
  ```
120
123
 
121
- The generated CSS uses `:root` / `[data-theme="dark"]` blocks your custom values override the defaults with the same specificity.
124
+ The plugin creates virtual modules (`virtual:nons-config` for JS access, `virtual:nons-config:style` for CSS overrides). Custom token values are injected with the same specificity as defaults.
122
125
 
123
- All visual values in the uikit reference CSS custom properties (`var(--color-primary)`), so changing a token in YAML propagates to every component.
126
+ All visual values in the uikit reference CSS custom properties (`var(--color-primary)`), so changing a token propagates to every component.
124
127
 
125
- ## Documentation
128
+ ## Development (UI Kit only)
126
129
 
127
- | Resource | Description |
128
- |----------|-------------|
129
- | [Style Guide](docs/STYLE-GUIDE.md) | Token-driven design, layout isolation, flat UI, component conventions |
130
- | [Component Contracts](docs/component-contracts/) | Single source of truth for all component APIs, schemas, and states |
130
+ ```bash
131
+ npm install
132
+ npm run dev # Start Vite dev server (playground app)
133
+ npm run build # Type-check + library build + copy fonts
134
+ npm run preview # Preview production playground build
135
+ ```
131
136
 
132
137
  ## Key Conventions
133
138
 
134
- - **Zero-hardcoding** — all visual values via CSS custom properties (`var(--...)`)
139
+ - **Zero-hardcoding** — all visual values via CSS custom properties
135
140
  - **Config-driven** — theme, tokens, direction, locale all from `nons.config.yaml`
136
141
  - **RTL-first** — `<html dir="rtl">`, logical properties (`inset-inline-start`, `margin-inline`)
137
- - **Schema-driven** — every component has a registry key, contract, schema example, and auto-generated metadata (53 components parsed from source)
142
+ - **Schema-driven** — every component has a registry key, contract, and metadata (53 components)
138
143
  - **modelValue** — all form inputs use `modelValue` prop + `update:modelValue` emit
139
144
  - **No external margins** — component spacing via parent layout `gap`
140
- - **Flat UI** — no elevation shadows; separation via themed borders (`--border-primary`)
145
+ - **Flat UI** — no elevation shadows; separation via themed borders
141
146
  - **TypeScript strict** — `strict: true`, `noUncheckedIndexedAccess`, `noUnusedLocals`
142
147
 
143
148
  ## Dependencies
144
149
 
145
- - Vue 3.5 (Composition API, `<script setup lang="ts">`)
150
+ - Vue 3.5 (Composition API)
146
151
  - `@lucide/vue` icons
147
152
  - Tabler Icons (CDN)
148
153
  - Plain CSS only (no SCSS/Sass)
149
- - Font files bundled in `dist/font/` (IRANSansX, Satoshi) — loaded via `@font-face` in generated CSS
154
+ - Font files bundled in `dist/font/` (IRANSansX, Satoshi)
155
+
156
+ ## Documentation
157
+
158
+ | Resource | Description |
159
+ |----------|-------------|
160
+ | [Style Guide](docs/STYLE-GUIDE.md) | Token-driven design, layout isolation, flat UI, component conventions |
161
+ | [Component Contracts](docs/component-contracts/) | Single source of truth for all component APIs, schemas, and states |
package/scripts/cli.mjs CHANGED
@@ -40,17 +40,6 @@ font:
40
40
  family: IRANSansX
41
41
  en:
42
42
  family: Satoshi
43
- # Faces loaded automatically from @nons-dev/uikit/dist/font/
44
- # Uncomment and customize src to use your own font files:
45
- # faces:
46
- # - family: Satoshi
47
- # weight: 400
48
- # src: fonts/Satoshi-Regular.woff2
49
- # format: woff2
50
- # - family: IRANSansX
51
- # weight: 400
52
- # src: fonts/IRANSansX-Regular.woff
53
- # format: woff
54
43
 
55
44
  tokens:
56
45
  colors:
@@ -204,6 +193,20 @@ theme-mappings:
204
193
  --overlay-bg: rgba(0, 0, 0, 0.6)
205
194
  `
206
195
 
196
+ const LOCALE_FA = JSON.stringify({
197
+ app: { name: 'نونز', footer: 'پنل مدیریت' },
198
+ nav: { dashboard: 'داشبورد', users: 'کاربران', stores: 'فروشگاه‌ها', products: 'محصولات', orders: 'سفارشات', chat: 'گفتگو', reports: 'گزارشات', stats: 'آمار', components: 'کامپوننت‌ها' },
199
+ table: { empty: 'هیچ داده‌ای یافت نشد.' },
200
+ loading: { text: 'در حال بارگذاری...' },
201
+ }, null, 2)
202
+
203
+ const LOCALE_EN = JSON.stringify({
204
+ app: { name: 'Nons', footer: 'Admin Panel' },
205
+ nav: { dashboard: 'Dashboard', users: 'Users', stores: 'Stores', products: 'Products', orders: 'Orders', chat: 'Chat', reports: 'Reports', stats: 'Stats', components: 'Components' },
206
+ table: { empty: 'No data found.' },
207
+ loading: { text: 'Loading...' },
208
+ }, null, 2)
209
+
207
210
  // ── Data ──
208
211
 
209
212
  function loadComponents() {
@@ -231,147 +234,82 @@ function searchComponents(data, query) {
231
234
  )
232
235
  }
233
236
 
234
- // ── Generate helpers ──
235
-
236
- function writeTokenBlock(tokens, indent) {
237
- return Object.entries(tokens || {}).map(([k, v]) => `${indent}${k}: ${v};`).join('\n')
238
- }
239
-
240
- function generateFontCSS(config) {
241
- const faces = config.font?.faces
242
- if (!faces || !faces.length) return ''
243
- const lines = []
244
- for (const face of faces) {
245
- lines.push('@font-face {')
246
- lines.push(` font-family: '${face.family}';`)
247
- lines.push(` src: url('./${face.src}') format('${face.format}');`)
248
- lines.push(` font-weight: ${face.weight};`)
249
- lines.push(' font-style: normal;')
250
- lines.push(' font-display: swap;')
251
- lines.push('}')
252
- lines.push('')
253
- }
254
- return lines.join('\n')
255
- }
256
-
257
- function generateTokensCSS(config) {
258
- const lines = []
259
- const fonts = generateFontCSS(config)
260
- if (fonts) lines.push(fonts)
261
-
262
- const t = config.tokens || {}
263
-
264
- // Light & dark color schemes
265
- const hasLight = t.colors?.light && Object.keys(t.colors.light).length
266
- const hasDark = t.colors?.dark && Object.keys(t.colors.dark).length
237
+ // ── Commands ──
267
238
 
268
- if (hasLight) {
269
- lines.push(':root {')
270
- lines.push(' color-scheme: light;')
271
- lines.push(writeTokenBlock(t.colors.light, ' '))
272
- lines.push('}')
273
- lines.push('')
239
+ function cmdInit() {
240
+ const configDest = path.join(CWD, 'nons.config.yaml')
241
+ if (fs.existsSync(configDest)) {
242
+ console.log(`${YELLOW}⚠${RESET} nons.config.yaml already exists — skipping.`)
243
+ return
274
244
  }
245
+ fs.writeFileSync(configDest, DEFAULT_CONFIG, 'utf-8')
246
+ console.log(`${GREEN}✓${RESET} nons.config.yaml created`)
275
247
 
276
- if (hasDark) {
277
- lines.push('[data-theme="dark"] {')
278
- lines.push(' color-scheme: dark;')
279
- lines.push(writeTokenBlock(t.colors.dark, ' '))
280
- lines.push('}')
281
- lines.push('')
248
+ const localesDir = path.join(CWD, 'locales')
249
+ if (!fs.existsSync(localesDir)) {
250
+ fs.mkdirSync(localesDir, { recursive: true })
251
+ console.log(`${GREEN}✓${RESET} locales/ directory created`)
282
252
  }
283
253
 
284
- // Flat token sections (no light/dark split)
285
- const flatSections = {
286
- spacing: 'Spacing', typography: 'Typography', radius: 'Border Radius',
287
- 'z-index': 'Z-Index', opacity: 'Opacity', button: 'Button', alert: 'Alert',
288
- }
289
- for (const [section, label] of Object.entries(flatSections)) {
290
- const tokens = t[section]
291
- if (!tokens || !Object.keys(tokens).length) continue
292
- lines.push(`/* ${label} */`)
293
- lines.push(':root {')
294
- lines.push(writeTokenBlock(tokens, ' '))
295
- lines.push('}')
296
- lines.push('')
254
+ const faPath = path.join(localesDir, 'fa.json')
255
+ if (!fs.existsSync(faPath)) {
256
+ fs.writeFileSync(faPath, LOCALE_FA, 'utf-8')
257
+ console.log(`${GREEN}✓${RESET} locales/fa.json created`)
297
258
  }
298
259
 
299
- // Shadows (light + dark)
300
- const hasShadowLight = t.shadows?.light && Object.keys(t.shadows.light).length
301
- const hasShadowDark = t.shadows?.dark && Object.keys(t.shadows.dark).length
302
- if (hasShadowLight) {
303
- lines.push(':root {')
304
- lines.push(' color-scheme: light;')
305
- lines.push(writeTokenBlock(t.shadows.light, ' '))
306
- lines.push('}')
307
- lines.push('')
308
- }
309
- if (hasShadowDark) {
310
- lines.push('[data-theme="dark"] {')
311
- lines.push(' color-scheme: dark;')
312
- lines.push(writeTokenBlock(t.shadows.dark, ' '))
313
- lines.push('}')
314
- lines.push('')
260
+ const enPath = path.join(localesDir, 'en.json')
261
+ if (!fs.existsSync(enPath)) {
262
+ fs.writeFileSync(enPath, LOCALE_EN, 'utf-8')
263
+ console.log(`${GREEN}✓${RESET} locales/en.json created`)
315
264
  }
316
265
 
317
- return lines.join('\n')
266
+ console.log(`\n${YELLOW}ℹ${RESET} Install a Vite plugin or adapter to load config automatically:`)
267
+ console.log(` npm install @nons-dev/vite-plugin\n`)
318
268
  }
319
269
 
320
- function generateThemeCSS(config) {
321
- const m = config['theme-mappings']
322
- if (!m) return ''
323
- const lines = []
324
-
325
- lines.push(':root {')
326
- lines.push(' color-scheme: light;')
327
- if (m.light) lines.push(writeTokenBlock(m.light, ' '))
328
- lines.push('}')
329
-
330
- lines.push('')
331
- lines.push('[data-theme="dark"] {')
332
- lines.push(' color-scheme: dark;')
333
- if (m.dark) lines.push(writeTokenBlock(m.dark, ' '))
334
- lines.push('}')
335
-
336
- return lines.join('\n')
337
- }
338
-
339
- // ── Commands ──
340
-
341
- function cmdInit() {
342
- const dest = path.join(CWD, 'nons.config.yaml')
343
- if (fs.existsSync(dest)) {
344
- console.log(`${YELLOW}⚠${RESET} nons.config.yaml already exists — skipping.`)
345
- return
346
- }
347
- fs.writeFileSync(dest, DEFAULT_CONFIG, 'utf-8')
348
- console.log(`${GREEN}✓${RESET} nons.config.yaml created in`, CWD)
349
- }
350
-
351
- function cmdGenerate() {
270
+ function cmdValidate() {
352
271
  const configPath = path.join(CWD, 'nons.config.yaml')
353
272
  if (!fs.existsSync(configPath)) {
354
273
  console.error(`${RED}✗${RESET} nons.config.yaml not found. Run \`nons init\` first.`)
355
274
  process.exit(1)
356
275
  }
357
- const raw = fs.readFileSync(configPath, 'utf-8')
358
- const config = yaml.load(raw)
359
- const generatedDir = path.join(CWD, 'generated')
360
- if (!fs.existsSync(generatedDir)) fs.mkdirSync(generatedDir, { recursive: true })
361
-
362
- // tokens.css
363
- const tokensCSS = generateTokensCSS(config)
364
- fs.writeFileSync(path.join(generatedDir, 'tokens.css'), tokensCSS, 'utf-8')
365
- console.log(`${GREEN}✓${RESET} generated/tokens.css`)
366
-
367
- // theme.css
368
- const themeCSS = generateThemeCSS(config)
369
- if (themeCSS) {
370
- fs.writeFileSync(path.join(generatedDir, 'theme.css'), themeCSS, 'utf-8')
371
- console.log(`${GREEN}✓${RESET} generated/theme.css`)
372
- }
373
276
 
374
- console.log(`${YELLOW}ℹ${RESET} Import in your app:\n import './generated/tokens.css'\n import './generated/theme.css'`)
277
+ try {
278
+ const raw = fs.readFileSync(configPath, 'utf-8')
279
+ const config = yaml.load(raw)
280
+
281
+ const errors = []
282
+ const warnings = []
283
+
284
+ if (!config || typeof config !== 'object') {
285
+ errors.push('Config must be a non-null object')
286
+ } else {
287
+ const c = config
288
+ if (!c.theme || !c.theme.preset) errors.push('Missing theme.preset')
289
+ if (!c.brand || !c.brand.name) errors.push('Missing brand.name')
290
+ if (!c.direction || (c.direction.default !== 'rtl' && c.direction.default !== 'ltr')) errors.push('direction.default must be "rtl" or "ltr"')
291
+ if (!c.locale || !c.locale.default) errors.push('Missing locale.default')
292
+ if (!Array.isArray(c.locale?.supported) || c.locale.supported.length === 0) errors.push('locale.supported must be a non-empty array')
293
+ if (!c.tokens || typeof c.tokens !== 'object') warnings.push('No tokens section found — using defaults')
294
+ }
295
+
296
+ if (errors.length === 0) {
297
+ console.log(`\n${GREEN}✓${RESET} Config is valid\n`)
298
+ } else {
299
+ console.log(`\n${RED}✗${RESET} Config has errors:\n`)
300
+ for (const e of errors) console.log(` ${RED}•${RESET} ${e}`)
301
+ console.log('')
302
+ }
303
+ if (warnings.length) {
304
+ for (const w of warnings) console.log(` ${YELLOW}•${RESET} ${w}`)
305
+ console.log('')
306
+ }
307
+
308
+ if (errors.length) process.exit(1)
309
+ } catch (err) {
310
+ console.error(`\n${RED}✗${RESET} Invalid YAML: ${err.message}\n`)
311
+ process.exit(1)
312
+ }
375
313
  }
376
314
 
377
315
  function cmdList() {
@@ -493,8 +431,8 @@ function cmdSearch(query) {
493
431
  function printHelp() {
494
432
  console.log(`\n${BOLD}${CYAN}Nons UIKit CLI${RESET}\n`)
495
433
  console.log(`${BOLD}Commands:${RESET}\n`)
496
- console.log(` ${GREEN}init${RESET} Create nons.config.yaml in current directory`)
497
- console.log(` ${GREEN}generate${RESET} Generate tokens.css + theme.css from config`)
434
+ console.log(` ${GREEN}init${RESET} Create nons.config.yaml + locales/ in current directory`)
435
+ console.log(` ${GREEN}validate${RESET} Validate nons.config.yaml`)
498
436
  console.log(` ${GREEN}list${RESET} List all components`)
499
437
  console.log(` ${GREEN}show${RESET} ${DIM}<name>${RESET} Show full detail for a component`)
500
438
  console.log(` ${GREEN}schema${RESET} ${DIM}<name>${RESET} Show schema JSON for a component`)
@@ -502,7 +440,7 @@ function printHelp() {
502
440
  console.log(` ${GREEN}help${RESET} Show this help\n`)
503
441
  console.log(`${BOLD}Usage:${RESET}\n`)
504
442
  console.log(` ${DIM}npx @nons-dev/uikit init${RESET}`)
505
- console.log(` ${DIM}npx @nons-dev/uikit generate${RESET}`)
443
+ console.log(` ${DIM}npx @nons-dev/uikit validate${RESET}`)
506
444
  console.log(` ${DIM}npx @nons-dev/uikit list${RESET}`)
507
445
  console.log(` ${DIM}npx @nons-dev/uikit show Button${RESET}`)
508
446
  console.log(` ${DIM}npx @nons-dev/uikit schema Input${RESET}`)
@@ -516,7 +454,7 @@ const param = process.argv[3]
516
454
 
517
455
  switch (command) {
518
456
  case 'init': cmdInit(); break
519
- case 'generate': cmdGenerate(); break
457
+ case 'validate': cmdValidate(); break
520
458
  case 'list': cmdList(); break
521
459
  case 'show': param ? cmdShow(param) : (console.error(`${RED}✗${RESET} Missing component name.`), process.exit(1)); break
522
460
  case 'schema': param ? cmdSchema(param) : (console.error(`${RED}✗${RESET} Missing component name.`), process.exit(1)); break
@@ -0,0 +1,76 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+ import * as yaml from 'js-yaml'
4
+ import type { Plugin, ResolvedConfig } from 'vite'
5
+ import type { RawConfig } from '../core/config/types'
6
+ import { generateFullCSS } from '../core/config/parser'
7
+
8
+ const CONFIG_VIRTUAL = 'virtual:nons-config'
9
+ const CONFIG_CSS_VIRTUAL = 'virtual:nons-config:style'
10
+ const RESOLVED_CONFIG_VIRTUAL = '\0' + CONFIG_VIRTUAL
11
+ const RESOLVED_CONFIG_CSS_VIRTUAL = '\0' + CONFIG_CSS_VIRTUAL
12
+
13
+ let loadedConfig: RawConfig | null = null
14
+
15
+ export function loadConfigFromYaml(filePath: string): RawConfig {
16
+ const raw = fs.readFileSync(filePath, 'utf-8')
17
+ const config = yaml.load(raw) as RawConfig
18
+ loadedConfig = config
19
+ return config
20
+ }
21
+
22
+ export function getLoadedConfig(): RawConfig | null {
23
+ return loadedConfig
24
+ }
25
+
26
+ export function nonsConfigPlugin(configPath?: string): Plugin {
27
+ const resolvedPath = configPath || path.resolve(process.cwd(), 'nons.config.yaml')
28
+
29
+ return {
30
+ name: 'nons-config',
31
+
32
+ configResolved(_config: ResolvedConfig) {
33
+ if (fs.existsSync(resolvedPath)) {
34
+ loadConfigFromYaml(resolvedPath)
35
+ }
36
+ },
37
+
38
+ resolveId(id: string) {
39
+ if (id === CONFIG_VIRTUAL) return RESOLVED_CONFIG_VIRTUAL
40
+ if (id === CONFIG_CSS_VIRTUAL) return RESOLVED_CONFIG_CSS_VIRTUAL
41
+ return null
42
+ },
43
+
44
+ load(id: string) {
45
+ if (id === RESOLVED_CONFIG_VIRTUAL) {
46
+ if (!loadedConfig) {
47
+ this.error('nons.config.yaml not found at ' + resolvedPath)
48
+ return
49
+ }
50
+ const runtimeConfig = {
51
+ theme: loadedConfig.theme,
52
+ brand: loadedConfig.brand,
53
+ direction: loadedConfig.direction,
54
+ locale: loadedConfig.locale,
55
+ font: {
56
+ fa: loadedConfig.font?.fa,
57
+ en: loadedConfig.font?.en,
58
+ },
59
+ }
60
+ return `export const nonsConfig = ${JSON.stringify(runtimeConfig, null, 2)}`
61
+ }
62
+
63
+ if (id === RESOLVED_CONFIG_CSS_VIRTUAL) {
64
+ if (!loadedConfig) {
65
+ this.error('nons.config.yaml not found at ' + resolvedPath)
66
+ return
67
+ }
68
+ return generateFullCSS(loadedConfig)
69
+ }
70
+
71
+ return null
72
+ },
73
+ }
74
+ }
75
+
76
+