@codenhub/styles 0.7.0 → 0.9.0
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/README.es.md +9 -8
- package/README.md +8 -7
- package/README.pt-BR.md +9 -8
- package/dist/aesthetics/chunky-tile.css +1 -1
- package/dist/aesthetics/cyber.css +1 -1
- package/dist/aesthetics/glass.css +1 -1
- package/dist/aesthetics/index.css +2 -2
- package/dist/aesthetics/neobrutalism.css +1 -1
- package/dist/aesthetics/pixel.css +1 -1
- package/dist/aesthetics/sketch.css +1 -1
- package/dist/cli.js +358 -0
- package/dist/components.css +50 -2
- package/dist/index.css +50 -2
- package/dist/index.d.ts +1 -0
- package/dist/native.css +50 -2
- package/dist/palette.css +1 -1
- package/dist/purge/classes.json +1 -0
- package/dist/purge/index.css +50 -0
- package/dist/theme.css +2 -2
- package/docs/accessibility.md +46 -27
- package/docs/changelog/0.8.0.md +70 -0
- package/docs/changelog/0.9.0.md +40 -0
- package/docs/changelog/index.md +2 -0
- package/docs/es/accessibility.md +47 -28
- package/docs/es/index.md +4 -3
- package/docs/es/integrating/index.md +38 -10
- package/docs/es/integrating/tailwind.md +79 -8
- package/docs/es/integrating/without-a-bundler.md +157 -5
- package/docs/es/setup.md +43 -34
- package/docs/es/usage/aesthetics.md +20 -14
- package/docs/es/usage/buttons.md +6 -2
- package/docs/es/usage/composing.md +6 -2
- package/docs/es/usage/content-and-layout.md +10 -4
- package/docs/es/usage/customizing.md +46 -26
- package/docs/es/usage/feedback.md +5 -1
- package/docs/es/usage/forms.md +30 -5
- package/docs/es/usage/index.md +7 -3
- package/docs/es/usage/presets.md +92 -0
- package/docs/es/usage/solo.md +4 -2
- package/docs/es/usage/theming.md +29 -16
- package/docs/es/usage/tooltips.md +2 -2
- package/docs/index.md +3 -2
- package/docs/integrating/index.md +38 -10
- package/docs/integrating/tailwind.md +78 -7
- package/docs/integrating/without-a-bundler.md +156 -4
- package/docs/pt-BR/accessibility.md +47 -28
- package/docs/pt-BR/index.md +4 -3
- package/docs/pt-BR/integrating/index.md +38 -10
- package/docs/pt-BR/integrating/tailwind.md +79 -8
- package/docs/pt-BR/integrating/without-a-bundler.md +157 -5
- package/docs/pt-BR/setup.md +43 -34
- package/docs/pt-BR/usage/aesthetics.md +20 -14
- package/docs/pt-BR/usage/buttons.md +6 -2
- package/docs/pt-BR/usage/composing.md +6 -2
- package/docs/pt-BR/usage/content-and-layout.md +10 -4
- package/docs/pt-BR/usage/customizing.md +46 -26
- package/docs/pt-BR/usage/feedback.md +5 -1
- package/docs/pt-BR/usage/forms.md +30 -5
- package/docs/pt-BR/usage/index.md +7 -3
- package/docs/pt-BR/usage/presets.md +92 -0
- package/docs/pt-BR/usage/solo.md +4 -2
- package/docs/pt-BR/usage/theming.md +22 -9
- package/docs/pt-BR/usage/tooltips.md +2 -2
- package/docs/setup.md +31 -22
- package/docs/usage/aesthetics.md +19 -13
- package/docs/usage/buttons.md +5 -1
- package/docs/usage/composing.md +5 -1
- package/docs/usage/content-and-layout.md +9 -3
- package/docs/usage/customizing.md +45 -25
- package/docs/usage/feedback.md +4 -0
- package/docs/usage/forms.md +29 -4
- package/docs/usage/index.md +6 -2
- package/docs/usage/presets.md +91 -0
- package/docs/usage/solo.md +3 -1
- package/docs/usage/theming.md +21 -8
- package/docs/usage/tooltips.md +1 -1
- package/llms-full.txt +695 -134
- package/llms.txt +3 -2
- package/package.json +117 -65
- package/{dist/tw → tw}/aesthetics/chunky-tile.css +43 -20
- package/{dist/tw → tw}/aesthetics/cyber.css +19 -25
- package/{dist/tw → tw}/aesthetics/glass.css +110 -36
- package/{dist/tw → tw}/aesthetics/neobrutalism.css +41 -21
- package/{dist/tw → tw}/aesthetics/pixel.css +3 -0
- package/tw/aesthetics/sketch.css +435 -0
- package/{dist/tw → tw}/box.css +109 -26
- package/tw/components/button.css +11 -0
- package/tw/components/button.rules.css +187 -0
- package/tw/components/feedback.css +11 -0
- package/{dist/tw/components/feedback.css → tw/components/feedback.rules.css} +156 -33
- package/tw/components/form.css +11 -0
- package/{dist/tw/components/form.css → tw/components/form.rules.css} +584 -141
- package/tw/components/index.css +16 -0
- package/tw/components/rules.css +41 -0
- package/tw/components/surface.css +11 -0
- package/tw/components/surface.rules.css +225 -0
- package/tw/components/tooltip.css +11 -0
- package/{dist/tw/components/tooltip.css → tw/components/tooltip.rules.css} +82 -61
- package/{dist/tw → tw}/corner.css +18 -12
- package/{dist/tw → tw}/elevation.css +27 -21
- package/{dist/tw → tw}/index.css +11 -9
- package/tw/index.d.ts +1 -0
- package/tw/intent.css +312 -0
- package/tw/modifiers.css +127 -0
- package/tw/native.css +323 -0
- package/tw/presentation.css +139 -0
- package/tw/reset.css +10 -0
- package/{dist/tw/reset.css → tw/reset.rules.css} +7 -8
- package/tw/size.css +34 -0
- package/tw/surface.css +9 -0
- package/tw/surface.rules.css +212 -0
- package/{dist/tw → tw}/theme.css +93 -29
- package/tw/typography.css +8 -0
- package/{dist/tw/typography.css → tw/typography.rules.css} +0 -3
- package/{dist/tw → tw}/utilities/content.css +248 -108
- package/tw/utilities/index.css +11 -0
- package/tw/utilities/layout.css +210 -0
- package/tw/utilities/rules.css +8 -0
- package/dist/tw/aesthetics/sketch.css +0 -398
- package/dist/tw/components/button.css +0 -139
- package/dist/tw/components/index.css +0 -35
- package/dist/tw/components/surface.css +0 -184
- package/dist/tw/intent.css +0 -229
- package/dist/tw/native.css +0 -203
- package/dist/tw/palette.css +0 -460
- package/dist/tw/presentation.css +0 -68
- package/dist/tw/surface.css +0 -61
- package/dist/tw/utilities/index.css +0 -6
- package/dist/tw/utilities/layout.css +0 -144
- /package/{dist/tw → tw}/aesthetics/index.css +0 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
// src/cli/purge.ts
|
|
8
|
+
const GROUPS = /* @__PURE__ */ new Set([
|
|
9
|
+
"@media",
|
|
10
|
+
"@supports",
|
|
11
|
+
"@layer",
|
|
12
|
+
"@container",
|
|
13
|
+
"@scope",
|
|
14
|
+
"@starting-style"
|
|
15
|
+
]);
|
|
16
|
+
const IDENT = /^(?:\\[0-9a-fA-F]{1,6}\s?|\\.|[\w-]|\P{ASCII})+/u;
|
|
17
|
+
const ANY_OF = /* @__PURE__ */ new Set([
|
|
18
|
+
"is",
|
|
19
|
+
"where",
|
|
20
|
+
"has"
|
|
21
|
+
]);
|
|
22
|
+
/** Returns the index just past the string or comment that starts at `index`, or `index` when none does. */
|
|
23
|
+
function skipInert(css, index) {
|
|
24
|
+
const char = css[index];
|
|
25
|
+
if (char === "\"" || char === "'") {
|
|
26
|
+
for (let i = index + 1; i < css.length; i++) if (css[i] === "\\") i++;
|
|
27
|
+
else if (css[i] === char) return i + 1;
|
|
28
|
+
return css.length;
|
|
29
|
+
}
|
|
30
|
+
if (char === "/" && css[index + 1] === "*") {
|
|
31
|
+
const end = css.indexOf("*/", index + 2);
|
|
32
|
+
return end === -1 ? css.length : end + 2;
|
|
33
|
+
}
|
|
34
|
+
return index;
|
|
35
|
+
}
|
|
36
|
+
/** Returns the index of the first `stop` character at bracket depth zero from `from`, or `end` when there is none. */
|
|
37
|
+
function findTop(css, from, end, stop) {
|
|
38
|
+
let depth = 0;
|
|
39
|
+
for (let i = from; i < end; i++) {
|
|
40
|
+
const next = skipInert(css, i);
|
|
41
|
+
if (next !== i) {
|
|
42
|
+
i = next - 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const char = css[i];
|
|
46
|
+
if (char === "\\") i++;
|
|
47
|
+
else if (depth === 0 && stop.includes(char)) return i;
|
|
48
|
+
else if (char === "(" || char === "[" || char === "{") depth++;
|
|
49
|
+
else if (char === ")" || char === "]" || char === "}") depth--;
|
|
50
|
+
}
|
|
51
|
+
return end;
|
|
52
|
+
}
|
|
53
|
+
function splitTop(text, separator) {
|
|
54
|
+
const parts = [];
|
|
55
|
+
let from = 0;
|
|
56
|
+
while (from <= text.length) {
|
|
57
|
+
const at = findTop(text, from, text.length, separator);
|
|
58
|
+
parts.push(text.slice(from, at));
|
|
59
|
+
from = at + 1;
|
|
60
|
+
}
|
|
61
|
+
return parts;
|
|
62
|
+
}
|
|
63
|
+
function parse(css, start = 0, end = css.length) {
|
|
64
|
+
const nodes = [];
|
|
65
|
+
let index = start;
|
|
66
|
+
while (index < end) {
|
|
67
|
+
let comment = index;
|
|
68
|
+
while (comment < end && /\s/.test(css[comment])) comment++;
|
|
69
|
+
if (css.startsWith("/*", comment)) {
|
|
70
|
+
const after = skipInert(css, comment);
|
|
71
|
+
nodes.push({
|
|
72
|
+
kind: "statement",
|
|
73
|
+
text: css.slice(index, after)
|
|
74
|
+
});
|
|
75
|
+
index = after;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const open = findTop(css, index, end, "{;");
|
|
79
|
+
if (open >= end || css[open] === ";") {
|
|
80
|
+
nodes.push({
|
|
81
|
+
kind: "statement",
|
|
82
|
+
text: css.slice(index, Math.min(open + 1, end))
|
|
83
|
+
});
|
|
84
|
+
index = open + 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const close = findTop(css, open + 1, end, "}");
|
|
88
|
+
const prelude = css.slice(index, open);
|
|
89
|
+
const atRule = /^\s*(@[\w-]+)/.exec(prelude)?.[1];
|
|
90
|
+
if (atRule !== void 0 && GROUPS.has(atRule)) nodes.push({
|
|
91
|
+
kind: "group",
|
|
92
|
+
prelude,
|
|
93
|
+
children: parse(css, open + 1, close)
|
|
94
|
+
});
|
|
95
|
+
else nodes.push({
|
|
96
|
+
kind: "block",
|
|
97
|
+
prelude,
|
|
98
|
+
body: css.slice(open + 1, close),
|
|
99
|
+
isRule: atRule === void 0
|
|
100
|
+
});
|
|
101
|
+
index = close + 1;
|
|
102
|
+
}
|
|
103
|
+
return nodes;
|
|
104
|
+
}
|
|
105
|
+
function serialize(nodes) {
|
|
106
|
+
let css = "";
|
|
107
|
+
for (const node of nodes) if (node.kind === "statement") css += node.text;
|
|
108
|
+
else if (node.kind === "group") css += `${node.prelude}{${serialize(node.children)}}`;
|
|
109
|
+
else css += `${node.prelude}{${node.body}}`;
|
|
110
|
+
return css;
|
|
111
|
+
}
|
|
112
|
+
/** Writes the tree one declaration to a line, for `--no-minify`. */
|
|
113
|
+
function format(nodes, indent = "") {
|
|
114
|
+
let css = "";
|
|
115
|
+
for (const node of nodes) if (node.kind === "statement") css += `${indent}${node.text.trim()}\n`;
|
|
116
|
+
else if (node.kind === "group") css += `${indent}${node.prelude.trim()} {\n${format(node.children, `${indent} `)}${indent}}\n`;
|
|
117
|
+
else if (node.isRule) {
|
|
118
|
+
const selectors = splitTop(node.prelude, ",").map((selector) => selector.trim());
|
|
119
|
+
const declarations = splitTop(node.body, ";").filter((declaration) => declaration.trim() !== "");
|
|
120
|
+
css += `${indent}${selectors.join(`,\n${indent}`)} {\n`;
|
|
121
|
+
css += declarations.map((declaration) => `${indent} ${declaration.trim()};\n`).join("");
|
|
122
|
+
css += `${indent}}\n`;
|
|
123
|
+
} else css += `${indent}${node.prelude.trim()} { ${node.body} }\n`;
|
|
124
|
+
return css;
|
|
125
|
+
}
|
|
126
|
+
function unescapeIdent(raw) {
|
|
127
|
+
return raw.replace(/\\([0-9a-fA-F]{1,6})\s?|\\(.)/g, (_, hex, char) => {
|
|
128
|
+
if (hex === void 0) return char;
|
|
129
|
+
const code = Number.parseInt(hex, 16);
|
|
130
|
+
return code === 0 || code >= 55296 && code <= 57343 || code > 1114111 ? "�" : String.fromCodePoint(code);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Says whether a selector can match in markup whose classes are all in `used`.
|
|
135
|
+
* Every class the selector requires has to be there. A class inside `:not()` is
|
|
136
|
+
* never required, and of the selectors inside `:is()`, `:where()`, and `:has()`
|
|
137
|
+
* one is enough. `onClass`, when given, is told every class name it reads.
|
|
138
|
+
*/
|
|
139
|
+
function canMatch(selector, used, onClass) {
|
|
140
|
+
let isMatch = true;
|
|
141
|
+
for (let i = 0; i < selector.length; i++) {
|
|
142
|
+
const next = skipInert(selector, i);
|
|
143
|
+
if (next !== i) {
|
|
144
|
+
i = next - 1;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const char = selector[i];
|
|
148
|
+
if (char === "\\") i++;
|
|
149
|
+
else if (char === "[") i = findTop(selector, i + 1, selector.length, "]");
|
|
150
|
+
else if (char === ".") {
|
|
151
|
+
const raw = IDENT.exec(selector.slice(i + 1))?.[0];
|
|
152
|
+
if (raw !== void 0) {
|
|
153
|
+
const name = unescapeIdent(raw);
|
|
154
|
+
onClass?.(name);
|
|
155
|
+
isMatch &&= used.has(name);
|
|
156
|
+
i += raw.length;
|
|
157
|
+
}
|
|
158
|
+
} else if (char === ":") {
|
|
159
|
+
const pseudo = /^::?([\w-]+)\(/.exec(selector.slice(i));
|
|
160
|
+
if (pseudo !== null) {
|
|
161
|
+
const open = i + pseudo[0].length;
|
|
162
|
+
const close = findTop(selector, open, selector.length, ")");
|
|
163
|
+
const branches = splitTop(selector.slice(open, close), ",");
|
|
164
|
+
if (ANY_OF.has(pseudo[1])) {
|
|
165
|
+
const results = branches.map((branch) => canMatch(branch, used, onClass));
|
|
166
|
+
isMatch &&= results.includes(true);
|
|
167
|
+
} else if (onClass !== void 0) for (const branch of branches) canMatch(branch, used, onClass);
|
|
168
|
+
i = close;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!isMatch && onClass === void 0) return false;
|
|
172
|
+
}
|
|
173
|
+
return isMatch;
|
|
174
|
+
}
|
|
175
|
+
function isLayer(node) {
|
|
176
|
+
return node.kind === "group" && /^\s*@layer\b/.test(node.prelude);
|
|
177
|
+
}
|
|
178
|
+
function isEmptyLayer(node) {
|
|
179
|
+
return node.kind === "group" && isLayer(node) && node.children.every(isEmptyLayer);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns the tree without the rules that cannot match `used`. A selector list
|
|
183
|
+
* keeps only its selectors that can. A `@layer` block emptied this way stays,
|
|
184
|
+
* because where it first stands fixes the layer's place in the cascade, unless
|
|
185
|
+
* it is all that is left of a `@media` or `@supports` block: those hold a
|
|
186
|
+
* variant's rules, which come after the layer's first block.
|
|
187
|
+
*/
|
|
188
|
+
function purge(nodes, used) {
|
|
189
|
+
const kept = [];
|
|
190
|
+
for (const node of nodes) if (node.kind === "group") {
|
|
191
|
+
const children = purge(node.children, used);
|
|
192
|
+
if (isLayer(node) ? true : children.some((child) => !isEmptyLayer(child))) kept.push({
|
|
193
|
+
...node,
|
|
194
|
+
children
|
|
195
|
+
});
|
|
196
|
+
} else if (node.kind === "block" && node.isRule) {
|
|
197
|
+
const selectors = splitTop(node.prelude, ",").filter((selector) => canMatch(selector, used));
|
|
198
|
+
if (selectors.length > 0) kept.push({
|
|
199
|
+
...node,
|
|
200
|
+
prelude: selectors.join(",")
|
|
201
|
+
});
|
|
202
|
+
} else kept.push(node);
|
|
203
|
+
return kept;
|
|
204
|
+
}
|
|
205
|
+
// src/cli/run.ts
|
|
206
|
+
const BIN = "chs";
|
|
207
|
+
const MIN_NODE_MAJOR = 24;
|
|
208
|
+
const AESTHETIC_EXPORT = /^\.\/aesthetics\/([^/]+)$/;
|
|
209
|
+
const PURGE_SOURCE = "dist/purge/index.css";
|
|
210
|
+
const PURGE_CLASSES = "dist/purge/classes.json";
|
|
211
|
+
const USAGE = `Usage: ${BIN} build --content <glob> [--content <glob> ...] --output <file> [options]
|
|
212
|
+
|
|
213
|
+
Builds a StyleKit stylesheet holding only the classes your templates use.
|
|
214
|
+
|
|
215
|
+
Options:
|
|
216
|
+
-c, --content <glob> Files to scan for class names, relative to the current
|
|
217
|
+
directory. Repeat for more globs. Quote each glob.
|
|
218
|
+
-o, --output <file> Stylesheet to write. Parent directories are created.
|
|
219
|
+
--aesthetic <name> Add an aesthetic: {names}. Repeatable.
|
|
220
|
+
--no-minify Write readable CSS instead of minified CSS.
|
|
221
|
+
--help Show this help.
|
|
222
|
+
--version Show the version.`;
|
|
223
|
+
async function run(argv, io) {
|
|
224
|
+
try {
|
|
225
|
+
const parsed = parseCommandLine(argv);
|
|
226
|
+
if (parsed === "help") io.stdout(USAGE.replace("{names}", readAestheticNames().join(", ")));
|
|
227
|
+
else if (parsed === "version") io.stdout(readManifest().version);
|
|
228
|
+
else {
|
|
229
|
+
const output = await build(parsed, io);
|
|
230
|
+
const { size } = await fs.stat(output);
|
|
231
|
+
io.stdout(`${output} ${size} bytes`);
|
|
232
|
+
}
|
|
233
|
+
return 0;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
io.stderr(`${BIN}: ${error instanceof Error ? error.message : String(error)}`);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function parseCommandLine(argv) {
|
|
240
|
+
const { values, positionals } = parseFlags(argv);
|
|
241
|
+
if (values.help) return "help";
|
|
242
|
+
if (values.version) return "version";
|
|
243
|
+
const [command, ...extra] = positionals;
|
|
244
|
+
if (command === void 0) throw new Error(`missing command; run "${BIN} build --content <glob> --output <file>".`);
|
|
245
|
+
if (command !== "build") throw new Error(`unknown command "${command}"; the only command is "build".`);
|
|
246
|
+
if (extra.length > 0) throw new Error(`unexpected argument "${extra[0]}"; quote each --content glob so the shell does not expand it, as in --content "app/views/**/*.erb".`);
|
|
247
|
+
if (!values.content?.length) throw new Error(`missing --content; name the templates to scan, as in --content "app/views/**/*.erb".`);
|
|
248
|
+
const output = values.output?.trim();
|
|
249
|
+
if (!output) throw new Error("missing --output; name the stylesheet to write, as in --output public/app.css.");
|
|
250
|
+
return {
|
|
251
|
+
contentGlobs: values.content,
|
|
252
|
+
output,
|
|
253
|
+
aesthetics: [...new Set(values.aesthetic ?? [])],
|
|
254
|
+
shouldMinify: !values["no-minify"]
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function parseFlags(argv) {
|
|
258
|
+
try {
|
|
259
|
+
return parseArgs({
|
|
260
|
+
args: [...argv],
|
|
261
|
+
allowPositionals: true,
|
|
262
|
+
options: {
|
|
263
|
+
content: {
|
|
264
|
+
type: "string",
|
|
265
|
+
short: "c",
|
|
266
|
+
multiple: true
|
|
267
|
+
},
|
|
268
|
+
output: {
|
|
269
|
+
type: "string",
|
|
270
|
+
short: "o"
|
|
271
|
+
},
|
|
272
|
+
aesthetic: {
|
|
273
|
+
type: "string",
|
|
274
|
+
multiple: true
|
|
275
|
+
},
|
|
276
|
+
"no-minify": { type: "boolean" },
|
|
277
|
+
help: { type: "boolean" },
|
|
278
|
+
version: { type: "boolean" }
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const flag = error instanceof Error ? /'([^']+)'/.exec(error.message)?.[1] : void 0;
|
|
283
|
+
if (flag === void 0) throw error;
|
|
284
|
+
const code = error.code;
|
|
285
|
+
throw new Error(code === "ERR_PARSE_ARGS_UNKNOWN_OPTION" ? `unknown option "${flag}"; run "${BIN} --help" for the options.` : `option "${/--[\w-]+/.exec(flag)?.[0] ?? flag}" is missing its value or does not take one; run "${BIN} --help" for the options.`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function build(options, io) {
|
|
289
|
+
assertNodeVersion(io.nodeVersion);
|
|
290
|
+
const validAesthetics = readAestheticNames();
|
|
291
|
+
const unknown = options.aesthetics.find((name) => !validAesthetics.includes(name));
|
|
292
|
+
if (unknown !== void 0) throw new Error(`unknown aesthetic "${unknown}"; choose from ${validAesthetics.join(", ")}.`);
|
|
293
|
+
const files = new Set((await Promise.all(options.contentGlobs.map((pattern) => findFiles(pattern, io.cwd)))).flat());
|
|
294
|
+
const contents = [];
|
|
295
|
+
for (const file of files) contents.push(await fs.readFile(file, "utf8"));
|
|
296
|
+
const droppable = new Set(JSON.parse(await readPackageFile(PURGE_CLASSES)));
|
|
297
|
+
const found = new Set(contents.flatMap((content) => findClasses(content, droppable)));
|
|
298
|
+
const used = { has: (name) => found.has(name) || !droppable.has(name) };
|
|
299
|
+
const stylesheets = await Promise.all([PURGE_SOURCE, ...options.aesthetics.map((name) => resolveExport(`./aesthetics/${name}`))].map(readPackageFile));
|
|
300
|
+
const write = options.shouldMinify ? serialize : format;
|
|
301
|
+
const css = stylesheets.map((stylesheet) => write(purge(parse(stylesheet), used))).join("");
|
|
302
|
+
const output = path.resolve(io.cwd, options.output);
|
|
303
|
+
const temporary = `${output}.${process.pid}.tmp`;
|
|
304
|
+
try {
|
|
305
|
+
await fs.mkdir(path.dirname(output), { recursive: true });
|
|
306
|
+
await fs.writeFile(temporary, css);
|
|
307
|
+
await fs.rename(temporary, output);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
await fs.rm(temporary, { force: true }).catch(() => void 0);
|
|
310
|
+
throw new Error(`cannot write the stylesheet: ${error instanceof Error ? error.message : String(error)}`);
|
|
311
|
+
}
|
|
312
|
+
return output;
|
|
313
|
+
}
|
|
314
|
+
/** Returns the files a `--content` glob matches, as absolute paths, and fails when there are none. */
|
|
315
|
+
async function findFiles(pattern, cwd) {
|
|
316
|
+
const files = [];
|
|
317
|
+
for await (const entry of fs.glob(pattern, {
|
|
318
|
+
cwd,
|
|
319
|
+
withFileTypes: true
|
|
320
|
+
})) if (entry.isFile()) files.push(path.resolve(entry.parentPath, entry.name));
|
|
321
|
+
if (files.length === 0) throw new Error(`--content "${pattern}" matches no file in ${cwd}; check the path, or run from the project root.`);
|
|
322
|
+
return files;
|
|
323
|
+
}
|
|
324
|
+
function findClasses(content, known) {
|
|
325
|
+
return content.split(/[^\w:-]+/).flatMap((word) => known.has(word) ? [word] : word.split(":")).filter((word) => known.has(word));
|
|
326
|
+
}
|
|
327
|
+
function readAestheticNames() {
|
|
328
|
+
const { exports } = readManifest();
|
|
329
|
+
return Object.keys(exports).map((subpath) => AESTHETIC_EXPORT.exec(subpath)?.[1]).filter((name) => name !== void 0).sort();
|
|
330
|
+
}
|
|
331
|
+
/** Returns the nearest folder at or above `from` that holds a `package.json`. */
|
|
332
|
+
function findPackageRoot(from) {
|
|
333
|
+
for (let dir = from;; dir = path.dirname(dir)) {
|
|
334
|
+
if (existsSync(path.join(dir, "package.json"))) return dir;
|
|
335
|
+
if (path.dirname(dir) === dir) throw new Error(`cannot find the package.json of @codenhub/styles above ${from}.`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const packageRoot = findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
339
|
+
function readManifest() {
|
|
340
|
+
return JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
341
|
+
}
|
|
342
|
+
function resolveExport(subpath) {
|
|
343
|
+
return readManifest().exports[subpath].import;
|
|
344
|
+
}
|
|
345
|
+
function readPackageFile(relativePath) {
|
|
346
|
+
return fs.readFile(path.resolve(packageRoot, relativePath), "utf8");
|
|
347
|
+
}
|
|
348
|
+
function assertNodeVersion(nodeVersion) {
|
|
349
|
+
if (Number.parseInt(nodeVersion, 10) < MIN_NODE_MAJOR) throw new Error(`this command needs Node.js ${MIN_NODE_MAJOR} or newer; this is Node.js ${nodeVersion}.`);
|
|
350
|
+
}
|
|
351
|
+
// src/cli/cli.ts
|
|
352
|
+
process.exitCode = await run(process.argv.slice(2), {
|
|
353
|
+
cwd: process.cwd(),
|
|
354
|
+
nodeVersion: process.versions.node,
|
|
355
|
+
stdout: (line) => process.stdout.write(`${line}\n`),
|
|
356
|
+
stderr: (line) => process.stderr.write(`${line}\n`)
|
|
357
|
+
});
|
|
358
|
+
export {};
|