@seyuna/postcss 1.0.0-canary.12 → 1.0.0-canary.13

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 CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.0.0-canary.13](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.12...v1.0.0-canary.13) (2025-10-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * safely clone nodes and replace {name} placeholders ([8a68ee6](https://github.com/seyuna-corp/seyuna-postcss/commit/8a68ee6f1ade947639151bdbf365c102aa439d37))
7
+
1
8
  # [1.0.0-canary.12](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.11...v1.0.0-canary.12) (2025-09-15)
2
9
 
3
10
 
@@ -1,34 +1,9 @@
1
1
  import { AtRule } from "postcss";
2
2
  /**
3
- * Custom PostCSS plugin handler for `@each-standard-color` at-rules.
4
- *
5
- * Example usage:
6
- *
7
- * @each-standard-color {
8
- * color: white;
9
- * }
10
- *
11
- * Will generate:
12
- *
13
- * .alpha { color: white; }
14
- * .beta { color: white; }
15
- * .gamma { color: white; }
16
- * ...
3
+ * Custom PostCSS handler for @each-standard-color
17
4
  */
18
5
  export declare function eachStandardColor(atRule: AtRule): void;
19
6
  /**
20
- * Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
21
- *
22
- * Example usage:
23
- *
24
- * @each-fixed-color {
25
- * color: white;
26
- * }
27
- *
28
- * Will generate:
29
- *
30
- * .primary { color: white; }
31
- * .secondary { color: white; }
32
- * ...
7
+ * Custom PostCSS handler for @each-fixed-color
33
8
  */
34
9
  export declare function eachFixedColor(atRule: AtRule): void;
@@ -10,44 +10,20 @@ const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const color_1 = require("../functions/color");
12
12
  /**
13
- * Custom PostCSS plugin handler for `@each-standard-color` at-rules.
14
- *
15
- * Example usage:
16
- *
17
- * @each-standard-color {
18
- * color: white;
19
- * }
20
- *
21
- * Will generate:
22
- *
23
- * .alpha { color: white; }
24
- * .beta { color: white; }
25
- * .gamma { color: white; }
26
- * ...
13
+ * Helper: safely clone nodes and replace {name} placeholders,
14
+ * evaluate fc() and sc() calls.
27
15
  */
28
- function eachStandardColor(atRule) {
29
- // Read seyuna.json from project root
30
- const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
31
- const fileContents = fs_1.default.readFileSync(jsonPath, "utf-8");
32
- const data = JSON.parse(fileContents);
33
- const hues = data.ui.theme.hues;
34
- const hueNamesSet = new Set(Object.keys(hues));
35
- // Guard against atRule.nodes being undefined
36
- const nodes = atRule.nodes ?? [];
37
- const generatedRules = [];
38
- // Helper to clone nodes and replace {name} placeholder
39
- const cloneNodesWithName = (name, nodeList = nodes) => nodeList.map((node) => {
16
+ function cloneNodesWithName(nodeList, name) {
17
+ return nodeList.flatMap((node) => {
40
18
  const cloned = node.clone();
41
19
  if (cloned.type === "decl") {
42
20
  const decl = cloned;
43
- // First replace {name} placeholders
44
21
  let value = decl.value.replace(/\{name\}/g, name);
45
- // Detect sc(...) or fc(...) calls and evaluate them
46
22
  if (/sc\(/.test(value)) {
47
23
  const args = value
48
24
  .match(/sc\(([^)]*)\)/)?.[1]
49
25
  .split(",")
50
- .map((s) => s.trim().replace(/\{name\}/g, name));
26
+ .map((s) => s.trim());
51
27
  if (args)
52
28
  value = (0, color_1.sc)(...args);
53
29
  }
@@ -55,106 +31,65 @@ function eachStandardColor(atRule) {
55
31
  const args = value
56
32
  .match(/fc\(([^)]*)\)/)?.[1]
57
33
  .split(",")
58
- .map((s) => s.trim().replace(/\{name\}/g, name));
34
+ .map((s) => s.trim());
59
35
  if (args)
60
36
  value = (0, color_1.fc)(...args);
61
37
  }
62
38
  decl.value = value;
39
+ return decl;
63
40
  }
64
- else if (cloned.type === "rule") {
41
+ if (cloned.type === "rule") {
65
42
  const rule = cloned;
43
+ if (!rule.selector)
44
+ return [];
66
45
  rule.selector = rule.selector.replace(/\{name\}/g, name);
67
- rule.nodes = cloneNodesWithName(name, rule.nodes || []);
46
+ rule.nodes = cloneNodesWithName(rule.nodes || [], name);
47
+ return rule;
68
48
  }
69
- else if (cloned.type === "atrule") {
49
+ if (cloned.type === "atrule") {
70
50
  cloned.params = cloned.params.replace(/\{name\}/g, name);
71
- cloned.nodes = cloneNodesWithName(name, cloned.nodes || []);
51
+ cloned.nodes = cloneNodesWithName(cloned.nodes || [], name);
52
+ return cloned.nodes.length ? cloned : [];
72
53
  }
73
- return cloned;
54
+ return [];
74
55
  });
75
- // Generate rules for each hue
76
- for (const hueName of hueNamesSet) {
56
+ }
57
+ /**
58
+ * Custom PostCSS handler for @each-standard-color
59
+ */
60
+ function eachStandardColor(atRule) {
61
+ const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
62
+ const data = JSON.parse(fs_1.default.readFileSync(jsonPath, "utf-8"));
63
+ const hues = data.ui.theme.hues;
64
+ const hueNames = Object.keys(hues);
65
+ const nodes = atRule.nodes ?? [];
66
+ const generatedRules = [];
67
+ for (const hueName of hueNames) {
77
68
  const rule = new postcss_1.Rule({ selector: `.${hueName}` });
78
- cloneNodesWithName(hueName).forEach((n) => rule.append(n));
79
- generatedRules.push(rule);
69
+ cloneNodesWithName(nodes, hueName).forEach((n) => rule.append(n));
70
+ if (rule.nodes.length)
71
+ generatedRules.push(rule);
80
72
  }
81
- // Replace the original @each-seyuna-color at-rule with all the generated rules
82
73
  atRule.replaceWith(...generatedRules);
83
74
  }
84
75
  /**
85
- * Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
86
- *
87
- * Example usage:
88
- *
89
- * @each-fixed-color {
90
- * color: white;
91
- * }
92
- *
93
- * Will generate:
94
- *
95
- * .primary { color: white; }
96
- * .secondary { color: white; }
97
- * ...
76
+ * Custom PostCSS handler for @each-fixed-color
98
77
  */
99
78
  function eachFixedColor(atRule) {
100
- // Read seyuna.json from project root
101
79
  const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
102
- const fileContents = fs_1.default.readFileSync(jsonPath, "utf-8");
103
- const data = JSON.parse(fileContents);
80
+ const data = JSON.parse(fs_1.default.readFileSync(jsonPath, "utf-8"));
104
81
  const light_colors = data.ui.theme.light.colors;
105
82
  const dark_colors = data.ui.theme.dark.colors;
106
- const lightColorNamesSet = new Set(Object.keys(light_colors));
107
- const darkColorNamesSet = new Set(Object.keys(dark_colors));
108
- const mergedColorNamesSet = new Set([
109
- ...lightColorNamesSet,
110
- ...darkColorNamesSet,
111
- ]);
112
- // Guard against atRule.nodes being undefined
83
+ const mergedNames = [
84
+ ...new Set([...Object.keys(light_colors), ...Object.keys(dark_colors)]),
85
+ ];
113
86
  const nodes = atRule.nodes ?? [];
114
87
  const generatedRules = [];
115
- // Helper to clone nodes and replace {name} placeholder
116
- const cloneNodesWithName = (name, nodeList = nodes) => nodeList.map((node) => {
117
- const cloned = node.clone();
118
- if (cloned.type === "decl") {
119
- const decl = cloned;
120
- // First replace {name} placeholders
121
- let value = decl.value.replace(/\{name\}/g, name);
122
- // Detect sc(...) or fc(...) calls and evaluate them
123
- if (/sc\(/.test(value)) {
124
- const args = value
125
- .match(/sc\(([^)]*)\)/)?.[1]
126
- .split(",")
127
- .map((s) => s.trim().replace(/\{name\}/g, name));
128
- if (args)
129
- value = (0, color_1.sc)(...args);
130
- }
131
- if (/fc\(/.test(value)) {
132
- const args = value
133
- .match(/fc\(([^)]*)\)/)?.[1]
134
- .split(",")
135
- .map((s) => s.trim().replace(/\{name\}/g, name));
136
- if (args)
137
- value = (0, color_1.fc)(...args);
138
- }
139
- decl.value = value;
140
- }
141
- else if (cloned.type === "rule") {
142
- const rule = cloned;
143
- rule.selector = rule.selector.replace(/\{name\}/g, name);
144
- rule.nodes = cloneNodesWithName(name, rule.nodes || []);
145
- }
146
- else if (cloned.type === "atrule") {
147
- cloned.params = cloned.params.replace(/\{name\}/g, name);
148
- cloned.nodes = cloneNodesWithName(name, cloned.nodes || []);
149
- }
150
- return cloned;
151
- });
152
- // Generate rules for mergedColorNamesSet
153
- for (const colorName of mergedColorNamesSet) {
88
+ for (const colorName of mergedNames) {
154
89
  const rule = new postcss_1.Rule({ selector: `.${colorName}` });
155
- cloneNodesWithName(colorName).forEach((n) => rule.append(n));
156
- generatedRules.push(rule);
90
+ cloneNodesWithName(nodes, colorName).forEach((n) => rule.append(n));
91
+ if (rule.nodes.length)
92
+ generatedRules.push(rule);
157
93
  }
158
- // Replace the original @each-seyuna-color at-rule with all the generated rules
159
94
  atRule.replaceWith(...generatedRules);
160
95
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seyuna/postcss",
3
- "version": "1.0.0-canary.12",
3
+ "version": "1.0.0-canary.13",
4
4
  "description": "Seyuna UI's postcss plugin",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -5,179 +5,96 @@ import { SeyunaConfig } from "../types";
5
5
  import { fc, sc } from "../functions/color";
6
6
 
7
7
  /**
8
- * Custom PostCSS plugin handler for `@each-standard-color` at-rules.
9
- *
10
- * Example usage:
11
- *
12
- * @each-standard-color {
13
- * color: white;
14
- * }
15
- *
16
- * Will generate:
17
- *
18
- * .alpha { color: white; }
19
- * .beta { color: white; }
20
- * .gamma { color: white; }
21
- * ...
8
+ * Helper: safely clone nodes and replace {name} placeholders,
9
+ * evaluate fc() and sc() calls.
10
+ */
11
+ function cloneNodesWithName(nodeList: ChildNode[], name: string): ChildNode[] {
12
+ return nodeList.flatMap((node) => {
13
+ const cloned = node.clone();
14
+
15
+ if (cloned.type === "decl") {
16
+ const decl = cloned as Declaration;
17
+ let value = decl.value.replace(/\{name\}/g, name);
18
+
19
+ if (/sc\(/.test(value)) {
20
+ const args = value
21
+ .match(/sc\(([^)]*)\)/)?.[1]
22
+ .split(",")
23
+ .map((s) => s.trim());
24
+ if (args) value = sc(...(args as [string, string?, string?, string?]));
25
+ }
26
+
27
+ if (/fc\(/.test(value)) {
28
+ const args = value
29
+ .match(/fc\(([^)]*)\)/)?.[1]
30
+ .split(",")
31
+ .map((s) => s.trim());
32
+ if (args) value = fc(...(args as [string, string?, string?, string?]));
33
+ }
34
+
35
+ decl.value = value;
36
+ return decl;
37
+ }
38
+
39
+ if (cloned.type === "rule") {
40
+ const rule = cloned as Rule;
41
+ if (!rule.selector) return [];
42
+ rule.selector = rule.selector.replace(/\{name\}/g, name);
43
+ rule.nodes = cloneNodesWithName(rule.nodes || [], name);
44
+ return rule;
45
+ }
46
+
47
+ if (cloned.type === "atrule") {
48
+ cloned.params = cloned.params.replace(/\{name\}/g, name);
49
+ cloned.nodes = cloneNodesWithName(cloned.nodes || [], name);
50
+ return cloned.nodes.length ? cloned : [];
51
+ }
52
+
53
+ return [];
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Custom PostCSS handler for @each-standard-color
22
59
  */
23
60
  export function eachStandardColor(atRule: AtRule) {
24
- // Read seyuna.json from project root
25
61
  const jsonPath = path.resolve(process.cwd(), "seyuna.json");
26
- const fileContents = fs.readFileSync(jsonPath, "utf-8");
27
- const data: SeyunaConfig = JSON.parse(fileContents);
62
+ const data: SeyunaConfig = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
28
63
  const hues = data.ui.theme.hues;
29
- const hueNamesSet = new Set(Object.keys(hues));
64
+ const hueNames = Object.keys(hues);
30
65
 
31
- // Guard against atRule.nodes being undefined
32
66
  const nodes = atRule.nodes ?? [];
33
-
34
67
  const generatedRules: Rule[] = [];
35
68
 
36
- // Helper to clone nodes and replace {name} placeholder
37
- const cloneNodesWithName = (
38
- name: string,
39
- nodeList: ChildNode[] = nodes
40
- ): ChildNode[] =>
41
- nodeList.map((node) => {
42
- const cloned = node.clone();
43
-
44
- if (cloned.type === "decl") {
45
- const decl = cloned as Declaration;
46
-
47
- // First replace {name} placeholders
48
- let value = decl.value.replace(/\{name\}/g, name);
49
-
50
- // Detect sc(...) or fc(...) calls and evaluate them
51
- if (/sc\(/.test(value)) {
52
- const args = value
53
- .match(/sc\(([^)]*)\)/)?.[1]
54
- .split(",")
55
- .map((s: string) => s.trim().replace(/\{name\}/g, name));
56
- if (args)
57
- value = sc(...(args as [string, string?, string?, string?]));
58
- }
59
-
60
- if (/fc\(/.test(value)) {
61
- const args = value
62
- .match(/fc\(([^)]*)\)/)?.[1]
63
- .split(",")
64
- .map((s: string) => s.trim().replace(/\{name\}/g, name));
65
- if (args)
66
- value = fc(...(args as [string, string?, string?, string?]));
67
- }
68
-
69
- decl.value = value;
70
- } else if (cloned.type === "rule") {
71
- const rule = cloned as Rule;
72
- rule.selector = rule.selector.replace(/\{name\}/g, name);
73
- rule.nodes = cloneNodesWithName(name, rule.nodes || []);
74
- } else if (cloned.type === "atrule") {
75
- cloned.params = cloned.params.replace(/\{name\}/g, name);
76
- cloned.nodes = cloneNodesWithName(name, cloned.nodes || []);
77
- }
78
-
79
- return cloned;
80
- });
81
-
82
- // Generate rules for each hue
83
- for (const hueName of hueNamesSet) {
69
+ for (const hueName of hueNames) {
84
70
  const rule = new Rule({ selector: `.${hueName}` });
85
- cloneNodesWithName(hueName).forEach((n) => rule.append(n));
86
- generatedRules.push(rule);
71
+ cloneNodesWithName(nodes, hueName).forEach((n) => rule.append(n));
72
+ if (rule.nodes.length) generatedRules.push(rule);
87
73
  }
88
74
 
89
- // Replace the original @each-seyuna-color at-rule with all the generated rules
90
75
  atRule.replaceWith(...generatedRules);
91
76
  }
92
77
 
93
78
  /**
94
- * Custom PostCSS plugin handler for `@each-fixed-color` at-rules.
95
- *
96
- * Example usage:
97
- *
98
- * @each-fixed-color {
99
- * color: white;
100
- * }
101
- *
102
- * Will generate:
103
- *
104
- * .primary { color: white; }
105
- * .secondary { color: white; }
106
- * ...
79
+ * Custom PostCSS handler for @each-fixed-color
107
80
  */
108
81
  export function eachFixedColor(atRule: AtRule) {
109
- // Read seyuna.json from project root
110
82
  const jsonPath = path.resolve(process.cwd(), "seyuna.json");
111
- const fileContents = fs.readFileSync(jsonPath, "utf-8");
112
- const data: SeyunaConfig = JSON.parse(fileContents);
83
+ const data: SeyunaConfig = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
113
84
  const light_colors = data.ui.theme.light.colors;
114
85
  const dark_colors = data.ui.theme.dark.colors;
115
- const lightColorNamesSet = new Set(Object.keys(light_colors));
116
- const darkColorNamesSet = new Set(Object.keys(dark_colors));
117
-
118
- const mergedColorNamesSet = new Set([
119
- ...lightColorNamesSet,
120
- ...darkColorNamesSet,
121
- ]);
86
+ const mergedNames = [
87
+ ...new Set([...Object.keys(light_colors), ...Object.keys(dark_colors)]),
88
+ ];
122
89
 
123
- // Guard against atRule.nodes being undefined
124
90
  const nodes = atRule.nodes ?? [];
125
-
126
91
  const generatedRules: Rule[] = [];
127
92
 
128
- // Helper to clone nodes and replace {name} placeholder
129
- const cloneNodesWithName = (
130
- name: string,
131
- nodeList: ChildNode[] = nodes
132
- ): ChildNode[] =>
133
- nodeList.map((node) => {
134
- const cloned = node.clone();
135
-
136
- if (cloned.type === "decl") {
137
- const decl = cloned as Declaration;
138
-
139
- // First replace {name} placeholders
140
- let value = decl.value.replace(/\{name\}/g, name);
141
-
142
- // Detect sc(...) or fc(...) calls and evaluate them
143
- if (/sc\(/.test(value)) {
144
- const args = value
145
- .match(/sc\(([^)]*)\)/)?.[1]
146
- .split(",")
147
- .map((s: string) => s.trim().replace(/\{name\}/g, name));
148
- if (args)
149
- value = sc(...(args as [string, string?, string?, string?]));
150
- }
151
-
152
- if (/fc\(/.test(value)) {
153
- const args = value
154
- .match(/fc\(([^)]*)\)/)?.[1]
155
- .split(",")
156
- .map((s: string) => s.trim().replace(/\{name\}/g, name));
157
- if (args)
158
- value = fc(...(args as [string, string?, string?, string?]));
159
- }
160
-
161
- decl.value = value;
162
- } else if (cloned.type === "rule") {
163
- const rule = cloned as Rule;
164
- rule.selector = rule.selector.replace(/\{name\}/g, name);
165
- rule.nodes = cloneNodesWithName(name, rule.nodes || []);
166
- } else if (cloned.type === "atrule") {
167
- cloned.params = cloned.params.replace(/\{name\}/g, name);
168
- cloned.nodes = cloneNodesWithName(name, cloned.nodes || []);
169
- }
170
-
171
- return cloned;
172
- });
173
-
174
- // Generate rules for mergedColorNamesSet
175
- for (const colorName of mergedColorNamesSet) {
93
+ for (const colorName of mergedNames) {
176
94
  const rule = new Rule({ selector: `.${colorName}` });
177
- cloneNodesWithName(colorName).forEach((n) => rule.append(n));
178
- generatedRules.push(rule);
95
+ cloneNodesWithName(nodes, colorName).forEach((n) => rule.append(n));
96
+ if (rule.nodes.length) generatedRules.push(rule);
179
97
  }
180
98
 
181
- // Replace the original @each-seyuna-color at-rule with all the generated rules
182
99
  atRule.replaceWith(...generatedRules);
183
100
  }