@seyuna/postcss 1.0.0-canary.27 → 1.0.0-canary.29
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/CHANGELOG.md +14 -0
- package/README.md +171 -135
- package/dist/at-rules/config.d.ts +6 -0
- package/dist/at-rules/config.js +71 -0
- package/dist/at-rules/import.js +6 -11
- package/dist/at-rules/index.js +2 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +29 -74
- package/dist/types.d.ts +0 -2
- package/package.json +1 -1
- package/src/at-rules/config.ts +78 -0
- package/src/at-rules/import.ts +6 -12
- package/src/at-rules/index.ts +2 -0
- package/src/config.ts +45 -86
- package/src/types.ts +0 -2
- package/tests/plugin.test.ts +138 -76
package/dist/config.js
CHANGED
|
@@ -1,87 +1,42 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
const DEFAULT_OPTIONS = {
|
|
4
|
-
configPath:
|
|
5
|
-
modeAttribute:
|
|
2
|
+
configPath: "seyuna.json",
|
|
3
|
+
modeAttribute: "data-mode",
|
|
6
4
|
strict: false,
|
|
7
5
|
config: undefined,
|
|
8
6
|
functions: undefined,
|
|
9
7
|
};
|
|
10
|
-
let cachedConfig = null;
|
|
11
|
-
let cachedConfigPath = null;
|
|
12
8
|
export function loadConfig(options = {}) {
|
|
13
9
|
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
14
10
|
if (mergedOptions.config) {
|
|
15
11
|
return { config: mergedOptions.config, options: mergedOptions };
|
|
16
12
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
config: { ui: { theme: { hues: {}, light: { colors: {} }, dark: { colors: {} } } } },
|
|
44
|
-
options: mergedOptions,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
13
|
+
// Return base empty config - we'll populate it from CSS
|
|
14
|
+
return {
|
|
15
|
+
config: {
|
|
16
|
+
ui: {
|
|
17
|
+
theme: {
|
|
18
|
+
hues: {},
|
|
19
|
+
colors: {},
|
|
20
|
+
light: {
|
|
21
|
+
chroma: 0,
|
|
22
|
+
lightness: 0,
|
|
23
|
+
background: { lightness: 0, chroma: 0, hue: 0 },
|
|
24
|
+
text: { lightness: 0, chroma: 0, hue: 0 },
|
|
25
|
+
colors: {},
|
|
26
|
+
},
|
|
27
|
+
dark: {
|
|
28
|
+
chroma: 0,
|
|
29
|
+
lightness: 0,
|
|
30
|
+
background: { lightness: 0, chroma: 0, hue: 0 },
|
|
31
|
+
text: { lightness: 0, chroma: 0, hue: 0 },
|
|
32
|
+
colors: {},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
options: mergedOptions,
|
|
38
|
+
};
|
|
47
39
|
}
|
|
48
40
|
export async function loadConfigAsync(options = {}) {
|
|
49
|
-
|
|
50
|
-
if (mergedOptions.config) {
|
|
51
|
-
return { config: mergedOptions.config, options: mergedOptions };
|
|
52
|
-
}
|
|
53
|
-
const configPath = path.resolve(process.cwd(), mergedOptions.configPath);
|
|
54
|
-
// Cache config if it's the same path
|
|
55
|
-
if (cachedConfig && cachedConfigPath === configPath) {
|
|
56
|
-
return { config: cachedConfig, options: mergedOptions };
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
try {
|
|
60
|
-
await fs.promises.access(configPath);
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
if (mergedOptions.strict) {
|
|
64
|
-
throw new Error(`Seyuna config not found at ${configPath}`);
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
config: { ui: { theme: { hues: {}, light: { colors: {} }, dark: { colors: {} } } } },
|
|
68
|
-
options: mergedOptions,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
const content = await fs.promises.readFile(configPath, 'utf-8');
|
|
72
|
-
const data = JSON.parse(content);
|
|
73
|
-
cachedConfig = data;
|
|
74
|
-
cachedConfigPath = configPath;
|
|
75
|
-
return { config: data, options: mergedOptions };
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
if (mergedOptions.strict) {
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
console.warn(`[Seyuna PostCSS] Warning: Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
82
|
-
return {
|
|
83
|
-
config: { ui: { theme: { hues: {}, light: { colors: {} }, dark: { colors: {} } } } },
|
|
84
|
-
options: mergedOptions,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
41
|
+
return loadConfig(options);
|
|
87
42
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -34,14 +34,12 @@ export type Mode = "system" | "light" | "dark";
|
|
|
34
34
|
*/
|
|
35
35
|
export interface UI {
|
|
36
36
|
theme: Theme;
|
|
37
|
-
mode: Mode;
|
|
38
37
|
output_dir?: string;
|
|
39
38
|
}
|
|
40
39
|
/**
|
|
41
40
|
* Root configuration structure for a Seyuna project.
|
|
42
41
|
*/
|
|
43
42
|
export interface SeyunaConfig {
|
|
44
|
-
license?: string;
|
|
45
43
|
ui?: UI;
|
|
46
44
|
}
|
|
47
45
|
export type FunctionMap = Record<string, (context: PluginContext, ...args: string[]) => string>;
|
package/package.json
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { PluginContext } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handler for @config "seyuna"
|
|
5
|
+
* Parses the configuration defined directly in CSS.
|
|
6
|
+
*/
|
|
7
|
+
export function handleConfig(atRule: any, context: PluginContext) {
|
|
8
|
+
const params = atRule.params.replace(/['"]/g, "");
|
|
9
|
+
|
|
10
|
+
if (params === "seyuna") {
|
|
11
|
+
const { config } = context;
|
|
12
|
+
if (!config.ui) {
|
|
13
|
+
config.ui = {
|
|
14
|
+
theme: {
|
|
15
|
+
hues: {},
|
|
16
|
+
colors: {},
|
|
17
|
+
light: {
|
|
18
|
+
chroma: 0,
|
|
19
|
+
lightness: 0,
|
|
20
|
+
background: { lightness: 0, chroma: 0, hue: 0 },
|
|
21
|
+
text: { lightness: 0, chroma: 0, hue: 0 },
|
|
22
|
+
colors: {},
|
|
23
|
+
},
|
|
24
|
+
dark: {
|
|
25
|
+
chroma: 0,
|
|
26
|
+
lightness: 0,
|
|
27
|
+
background: { lightness: 0, chroma: 0, hue: 0 },
|
|
28
|
+
text: { lightness: 0, chroma: 0, hue: 0 },
|
|
29
|
+
colors: {},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const theme = config.ui.theme;
|
|
36
|
+
|
|
37
|
+
atRule.walkDecls((decl: any) => {
|
|
38
|
+
const prop = decl.prop;
|
|
39
|
+
const value = decl.value;
|
|
40
|
+
|
|
41
|
+
// Hues: --hue-alpha: 0;
|
|
42
|
+
if (prop.startsWith("--hue-")) {
|
|
43
|
+
const name = prop.replace("--hue-", "");
|
|
44
|
+
theme.hues[name] = parseFloat(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Fixed Colors: --color-primary: 0.66 0.26 240;
|
|
48
|
+
else if (prop.startsWith("--color-")) {
|
|
49
|
+
const name = prop.replace("--color-", "");
|
|
50
|
+
const [l, c, h] = value.split(/\s+/).map(parseFloat);
|
|
51
|
+
theme.colors[name] = { lightness: l, chroma: c, hue: h };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Theme Palettes: --light-lightness: 0.66;
|
|
55
|
+
else if (prop.startsWith("--light-") || prop.startsWith("--dark-")) {
|
|
56
|
+
const isLight = prop.startsWith("--light-");
|
|
57
|
+
const palette = isLight ? theme.light : theme.dark;
|
|
58
|
+
const key = prop.replace(isLight ? "--light-" : "--dark-", "");
|
|
59
|
+
|
|
60
|
+
if (key === "lightness") palette.lightness = parseFloat(value);
|
|
61
|
+
else if (key === "chroma") palette.chroma = parseFloat(value);
|
|
62
|
+
else if (key === "background" || key === "text") {
|
|
63
|
+
const [l, c, h] = value.split(/\s+/).map(parseFloat);
|
|
64
|
+
(palette as any)[key] = { lightness: l, chroma: c, hue: h };
|
|
65
|
+
} else {
|
|
66
|
+
// Custom colors in palette: --light-surface: 1 0 0;
|
|
67
|
+
// Also supports overrides like --light-color-primary: 0.8 0.1 240;
|
|
68
|
+
const cleanKey = key.replace(/^color-/, "");
|
|
69
|
+
const [l, c, h] = value.split(/\s+/).map(parseFloat);
|
|
70
|
+
palette.colors[cleanKey] = { lightness: l, chroma: c, hue: h };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Remove the at-rule from CSS output
|
|
76
|
+
atRule.remove();
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/at-rules/import.ts
CHANGED
|
@@ -10,10 +10,6 @@ import postcss from "postcss";
|
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = path.dirname(__filename);
|
|
12
12
|
|
|
13
|
-
const STANDARD_HUES = [
|
|
14
|
-
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega'
|
|
15
|
-
];
|
|
16
|
-
|
|
17
13
|
/**
|
|
18
14
|
* Handler for @import "seyuna"
|
|
19
15
|
* Injects the core Seyuna Design System variables and base styles
|
|
@@ -56,14 +52,12 @@ export function handleImport(atRule: any, context: PluginContext) {
|
|
|
56
52
|
// 2. Global Hues and Base Colors (:root)
|
|
57
53
|
const rootRule = new Rule({ selector: ":root", source: atRule.source });
|
|
58
54
|
|
|
59
|
-
// Process all hues from config
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(value) }));
|
|
66
|
-
});
|
|
55
|
+
// Process all hues from config
|
|
56
|
+
if (theme.hues) {
|
|
57
|
+
for (const [name, value] of Object.entries(theme.hues)) {
|
|
58
|
+
rootRule.append(new Declaration({ prop: `--${name}-hue`, value: String(value) }));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
67
61
|
|
|
68
62
|
// Add shared colors from theme.colors
|
|
69
63
|
if (theme.colors) {
|
package/src/at-rules/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { eachStandardColor, eachFixedColor } from "./color.js";
|
|
|
3
3
|
import container from "./container.js";
|
|
4
4
|
import { light, dark } from "./color-scheme.js";
|
|
5
5
|
import { handleImport } from "./import.js";
|
|
6
|
+
import { handleConfig } from "./config.js";
|
|
6
7
|
import { PluginContext } from "../types.js";
|
|
7
8
|
|
|
8
9
|
// Each handler has a name (matches the at-rule) and the function
|
|
@@ -13,6 +14,7 @@ export interface AtRuleHandler {
|
|
|
13
14
|
|
|
14
15
|
// Ordered array ensures execution order
|
|
15
16
|
export const atRuleHandlers: AtRuleHandler[] = [
|
|
17
|
+
{ name: "config", handler: handleConfig },
|
|
16
18
|
{ name: "import", handler: handleImport },
|
|
17
19
|
{ name: "each-standard-color", handler: eachStandardColor },
|
|
18
20
|
{ name: "each-fixed-color", handler: eachFixedColor },
|
package/src/config.ts
CHANGED
|
@@ -1,104 +1,63 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import {
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {
|
|
4
|
+
SeyunaConfig,
|
|
5
|
+
PluginOptions,
|
|
6
|
+
PluginContext,
|
|
7
|
+
FunctionMap,
|
|
8
|
+
} from "./types.js";
|
|
4
9
|
|
|
5
10
|
// Re-export types for backwards compatibility
|
|
6
|
-
export type { PluginOptions, PluginContext, FunctionMap } from
|
|
11
|
+
export type { PluginOptions, PluginContext, FunctionMap } from "./types.js";
|
|
7
12
|
|
|
8
13
|
const DEFAULT_OPTIONS: Required<PluginOptions> = {
|
|
9
|
-
configPath:
|
|
10
|
-
modeAttribute:
|
|
14
|
+
configPath: "seyuna.json",
|
|
15
|
+
modeAttribute: "data-mode",
|
|
11
16
|
strict: false,
|
|
12
17
|
config: undefined as any,
|
|
13
18
|
functions: undefined as any,
|
|
14
19
|
};
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
export function loadConfig(options: PluginOptions = {}): {
|
|
22
|
+
config: SeyunaConfig;
|
|
23
|
+
options: Required<PluginOptions>;
|
|
24
|
+
} {
|
|
20
25
|
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
21
26
|
|
|
22
27
|
if (mergedOptions.config) {
|
|
23
28
|
return { config: mergedOptions.config, options: mergedOptions };
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
console.warn(`[Seyuna PostCSS] Warning: Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
-
return {
|
|
55
|
-
config: { ui: { theme: { hues: {}, light: { colors: {} }, dark: { colors: {} } } } } as any,
|
|
56
|
-
options: mergedOptions,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
31
|
+
// Return base empty config - we'll populate it from CSS
|
|
32
|
+
return {
|
|
33
|
+
config: {
|
|
34
|
+
ui: {
|
|
35
|
+
theme: {
|
|
36
|
+
hues: {},
|
|
37
|
+
colors: {},
|
|
38
|
+
light: {
|
|
39
|
+
chroma: 0,
|
|
40
|
+
lightness: 0,
|
|
41
|
+
background: { lightness: 0, chroma: 0, hue: 0 },
|
|
42
|
+
text: { lightness: 0, chroma: 0, hue: 0 },
|
|
43
|
+
colors: {},
|
|
44
|
+
},
|
|
45
|
+
dark: {
|
|
46
|
+
chroma: 0,
|
|
47
|
+
lightness: 0,
|
|
48
|
+
background: { lightness: 0, chroma: 0, hue: 0 },
|
|
49
|
+
text: { lightness: 0, chroma: 0, hue: 0 },
|
|
50
|
+
colors: {},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
} as any,
|
|
55
|
+
options: mergedOptions,
|
|
56
|
+
};
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
export async function loadConfigAsync(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return { config: mergedOptions.config, options: mergedOptions };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const configPath = path.resolve(process.cwd(), mergedOptions.configPath);
|
|
69
|
-
|
|
70
|
-
// Cache config if it's the same path
|
|
71
|
-
if (cachedConfig && cachedConfigPath === configPath) {
|
|
72
|
-
return { config: cachedConfig, options: mergedOptions };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
try {
|
|
77
|
-
await fs.promises.access(configPath);
|
|
78
|
-
} catch {
|
|
79
|
-
if (mergedOptions.strict) {
|
|
80
|
-
throw new Error(`Seyuna config not found at ${configPath}`);
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
config: { ui: { theme: { hues: {}, light: { colors: {} }, dark: { colors: {} } } } } as any,
|
|
84
|
-
options: mergedOptions,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const content = await fs.promises.readFile(configPath, 'utf-8');
|
|
89
|
-
const data = JSON.parse(content);
|
|
90
|
-
cachedConfig = data;
|
|
91
|
-
cachedConfigPath = configPath;
|
|
92
|
-
|
|
93
|
-
return { config: data, options: mergedOptions };
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (mergedOptions.strict) {
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
console.warn(`[Seyuna PostCSS] Warning: Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
99
|
-
return {
|
|
100
|
-
config: { ui: { theme: { hues: {}, light: { colors: {} }, dark: { colors: {} } } } } as any,
|
|
101
|
-
options: mergedOptions,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
59
|
+
export async function loadConfigAsync(
|
|
60
|
+
options: PluginOptions = {},
|
|
61
|
+
): Promise<{ config: SeyunaConfig; options: Required<PluginOptions> }> {
|
|
62
|
+
return loadConfig(options);
|
|
104
63
|
}
|
package/src/types.ts
CHANGED
|
@@ -41,7 +41,6 @@ export type Mode = "system" | "light" | "dark";
|
|
|
41
41
|
*/
|
|
42
42
|
export interface UI {
|
|
43
43
|
theme: Theme;
|
|
44
|
-
mode: Mode;
|
|
45
44
|
output_dir?: string;
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -49,7 +48,6 @@ export interface UI {
|
|
|
49
48
|
* Root configuration structure for a Seyuna project.
|
|
50
49
|
*/
|
|
51
50
|
export interface SeyunaConfig {
|
|
52
|
-
license?: string;
|
|
53
51
|
ui?: UI;
|
|
54
52
|
}
|
|
55
53
|
|