@hiscovega/grisso 1.0.4 → 1.0.6

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/lib/build.d.ts CHANGED
@@ -5,6 +5,8 @@ export interface BuildOptions {
5
5
  content?: string[];
6
6
  /** Minificar el CSS de salida (default: true) */
7
7
  minify?: boolean;
8
+ /** Patrones de clases protegidas del tree-shaking (se mergean con config.safelist) */
9
+ safelist?: (string | RegExp)[];
8
10
  }
9
11
  /**
10
12
  * API principal de Grisso — genera, purga y optimiza CSS
package/lib/build.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import { generateCSS } from "./index.js";
2
2
  import { optimizeCSS } from "./optimize.js";
3
3
  import { purgeCSS } from "./purge.js";
4
+ import { resolveConfig } from "./resolve-config.js";
5
+ /** Convierte strings a RegExp, deja RegExp intactos */
6
+ function toRegExpArray(list) {
7
+ if (!list)
8
+ return [];
9
+ return list.map((item) => (item instanceof RegExp ? item : new RegExp(item)));
10
+ }
4
11
  /**
5
12
  * API principal de Grisso — genera, purga y optimiza CSS
6
13
  * sin depender de PostCSS.
@@ -16,12 +23,18 @@ import { purgeCSS } from "./purge.js";
16
23
  * });
17
24
  */
18
25
  export async function buildCSS(options = {}) {
19
- const { config, content, minify = true } = options;
26
+ const { config, content, minify = true, safelist: optsSafelist } = options;
20
27
  // 1. Generar CSS raw desde los generators
21
28
  let css = await generateCSS(config);
22
29
  // 2. Tree-shaking si hay rutas de contenido
23
30
  if (content && content.length > 0) {
24
- css = await purgeCSS(css, { content });
31
+ // Resolver config para extraer safelist configurada
32
+ const resolved = await resolveConfig(config);
33
+ const mergedSafelist = [
34
+ ...toRegExpArray(resolved.safelist),
35
+ ...toRegExpArray(optsSafelist),
36
+ ];
37
+ css = await purgeCSS(css, { content, safelist: mergedSafelist });
25
38
  }
26
39
  // 3. Optimizar (nesting, autoprefixer, minificación)
27
40
  css = await optimizeCSS(css, { minify });
package/lib/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/lib/cli.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { parseArgs } from "node:util";
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ /** Lee la versión desde package.json */
8
+ function getVersion() {
9
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../package.json"), "utf8"));
10
+ return pkg.version;
11
+ }
12
+ /** Texto de ayuda general */
13
+ function printHelp() {
14
+ const version = getVersion();
15
+ console.log(`grisso v${version} — Griddo CSS utility class library
16
+
17
+ Uso:
18
+ grisso <comando> [opciones]
19
+
20
+ Comandos:
21
+ build Genera CSS de utilidades
22
+ tokens Genera scaffold de tokens (custom properties)
23
+
24
+ Opciones globales:
25
+ --help, -h Muestra esta ayuda
26
+ --version, -v Muestra la versión`);
27
+ }
28
+ /** Texto de ayuda del comando build */
29
+ function printBuildHelp() {
30
+ console.log(`grisso build — Genera CSS de utilidades
31
+
32
+ Uso:
33
+ grisso build [opciones]
34
+
35
+ Opciones:
36
+ --config <ruta> Ruta a grisso.config.mjs
37
+ --content <glob> Globs para tree-shaking (repetible)
38
+ --safelist <regex> Patrones de clases a preservar (repetible)
39
+ --output <ruta> Archivo de salida (sin --output → stdout)
40
+ --no-minify Deshabilitar minificación
41
+ --help, -h Muestra esta ayuda
42
+
43
+ Ejemplos:
44
+ grisso build # CSS completo a stdout
45
+ grisso build --output dist/grisso.css # Escribir a archivo
46
+ grisso build --content "src/**/*.tsx" --output out.css # Tree-shaking
47
+ grisso build --safelist "^p-" --safelist "^m-" # Proteger clases
48
+ grisso build --no-minify # Sin minificar`);
49
+ }
50
+ /** Comando build: genera CSS */
51
+ async function runBuild(argv) {
52
+ const { values } = parseArgs({
53
+ args: argv,
54
+ options: {
55
+ config: { type: "string" },
56
+ content: { type: "string", multiple: true },
57
+ safelist: { type: "string", multiple: true },
58
+ output: { type: "string" },
59
+ minify: { type: "boolean", default: true },
60
+ help: { type: "boolean", short: "h", default: false },
61
+ },
62
+ strict: true,
63
+ allowNegative: true,
64
+ });
65
+ if (values.help) {
66
+ printBuildHelp();
67
+ return;
68
+ }
69
+ // Import dinámico para que --help sea instantáneo
70
+ const { buildCSS } = await import("./build.js");
71
+ const content = values.content && values.content.length > 0 ? values.content : undefined;
72
+ const safelist = values.safelist && values.safelist.length > 0 ? values.safelist : undefined;
73
+ if (values.output) {
74
+ const label = content
75
+ ? "Generando CSS con tree-shaking..."
76
+ : "Generando CSS...";
77
+ console.error(label);
78
+ }
79
+ const css = await buildCSS({
80
+ config: values.config,
81
+ content,
82
+ minify: values.minify,
83
+ safelist,
84
+ });
85
+ if (values.output) {
86
+ const outputPath = path.resolve(values.output);
87
+ const dir = path.dirname(outputPath);
88
+ if (!fs.existsSync(dir)) {
89
+ fs.mkdirSync(dir, { recursive: true });
90
+ }
91
+ fs.writeFileSync(outputPath, css);
92
+ const kb = (Buffer.byteLength(css, "utf8") / 1024).toFixed(1);
93
+ console.error(`Generado ${path.relative(process.cwd(), outputPath)} (${kb} KB)`);
94
+ }
95
+ else {
96
+ process.stdout.write(css);
97
+ }
98
+ }
99
+ /** Texto de ayuda del comando tokens */
100
+ function printTokensHelp() {
101
+ console.log(`grisso tokens — Genera scaffold de tokens (custom properties)
102
+
103
+ Uso:
104
+ grisso tokens [opciones]
105
+
106
+ Opciones:
107
+ --config <ruta> Ruta a grisso.config.mjs
108
+ --format <fmt> Formato de salida: css (default) o json
109
+ --output <ruta> Archivo de salida (sin --output → stdout)
110
+ --help, -h Muestra esta ayuda
111
+
112
+ Ejemplos:
113
+ grisso tokens # Scaffold CSS a stdout
114
+ grisso tokens --output tokens.css # Escribir a archivo
115
+ grisso tokens --config grisso.config.mjs # Usa config custom
116
+ grisso tokens --format json # Config como JSON`);
117
+ }
118
+ /** Comando tokens: genera scaffold de tokens */
119
+ async function runTokens(argv) {
120
+ const { values } = parseArgs({
121
+ args: argv,
122
+ options: {
123
+ config: { type: "string" },
124
+ format: { type: "string" },
125
+ output: { type: "string" },
126
+ help: { type: "boolean", short: "h", default: false },
127
+ },
128
+ strict: true,
129
+ });
130
+ if (values.help) {
131
+ printTokensHelp();
132
+ return;
133
+ }
134
+ const format = values.format;
135
+ if (format && format !== "css" && format !== "json") {
136
+ console.error(`Formato no soportado: ${format} (usa "css" o "json")`);
137
+ process.exit(1);
138
+ }
139
+ // Import dinámico para que --help sea instantáneo
140
+ const { extractTokens } = await import("./tokens.js");
141
+ const output = await extractTokens({ config: values.config, format });
142
+ if (values.output) {
143
+ const outputPath = path.resolve(values.output);
144
+ const dir = path.dirname(outputPath);
145
+ if (!fs.existsSync(dir)) {
146
+ fs.mkdirSync(dir, { recursive: true });
147
+ }
148
+ fs.writeFileSync(outputPath, output);
149
+ const kb = (Buffer.byteLength(output, "utf8") / 1024).toFixed(1);
150
+ console.error(`Generado ${path.relative(process.cwd(), outputPath)} (${kb} KB)`);
151
+ }
152
+ else {
153
+ process.stdout.write(output);
154
+ }
155
+ }
156
+ /** Punto de entrada */
157
+ async function main() {
158
+ const { values, positionals } = parseArgs({
159
+ args: process.argv.slice(2),
160
+ options: {
161
+ help: { type: "boolean", short: "h", default: false },
162
+ version: { type: "boolean", short: "v", default: false },
163
+ },
164
+ strict: false,
165
+ allowPositionals: true,
166
+ });
167
+ if (values.version) {
168
+ console.log(getVersion());
169
+ return;
170
+ }
171
+ const command = positionals[0];
172
+ if (values.help && !command) {
173
+ printHelp();
174
+ return;
175
+ }
176
+ if (!command) {
177
+ printHelp();
178
+ process.exit(1);
179
+ }
180
+ switch (command) {
181
+ case "build":
182
+ // Pasar solo los args después del comando
183
+ await runBuild(process.argv.slice(3));
184
+ break;
185
+ case "tokens":
186
+ await runTokens(process.argv.slice(3));
187
+ break;
188
+ default:
189
+ console.error(`Comando desconocido: ${command}`);
190
+ printHelp();
191
+ process.exit(1);
192
+ }
193
+ }
194
+ main().catch((err) => {
195
+ console.error(err);
196
+ process.exit(1);
197
+ });
package/lib/defaults.js CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  const defaults = {
6
6
  columns: 12,
7
+ safelist: [],
7
8
  breakpoints: {
8
9
  tablet: "(min-width: 700px)",
9
10
  desktop: "(min-width: 1024px)",
@@ -1,8 +1,4 @@
1
1
  import type { Breakpoints, Declarations, TokenMap } from "./types.js";
2
- /**
3
- * Generadores de clases CSS utility.
4
- * Replican los mixins grisso_simple_class, grisso_complex_class de SCSS.
5
- */
6
2
  /**
7
3
  * Genera una clase simple con variantes responsive.
8
4
  * Equivalente al mixin grisso_simple_class.
package/lib/generators.js CHANGED
@@ -2,14 +2,19 @@
2
2
  * Generadores de clases CSS utility.
3
3
  * Replican los mixins grisso_simple_class, grisso_complex_class de SCSS.
4
4
  */
5
+ /** Escapa caracteres especiales en classNames para selectores CSS válidos. */
6
+ function escapeCSS(name) {
7
+ return name.replace(/\//g, "\\/");
8
+ }
5
9
  /**
6
10
  * Genera una clase simple con variantes responsive.
7
11
  * Equivalente al mixin grisso_simple_class.
8
12
  */
9
13
  export function simpleClass(className, property, value, breakpoints) {
10
- let css = `.${className} { ${property}: ${value}; }\n`;
14
+ const escaped = escapeCSS(className);
15
+ let css = `.${escaped} { ${property}: ${value}; }\n`;
11
16
  for (const [bp, mq] of Object.entries(breakpoints)) {
12
- css += `@media ${mq} { .${bp}-${className} { ${property}: ${value}; } }\n`;
17
+ css += `@media ${mq} { .${bp}-${escaped} { ${property}: ${value}; } }\n`;
13
18
  }
14
19
  return css;
15
20
  }
@@ -21,12 +26,13 @@ export function complexClass(prefix, properties, tokens, breakpoints) {
21
26
  const props = Array.isArray(properties) ? properties : [properties];
22
27
  let css = "";
23
28
  for (const [name, value] of Object.entries(tokens)) {
29
+ const escaped = escapeCSS(`${prefix}${name}`);
24
30
  for (const prop of props) {
25
- css += `.${prefix}${name} { ${prop}: ${value}; }\n`;
31
+ css += `.${escaped} { ${prop}: ${value}; }\n`;
26
32
  }
27
33
  for (const [bp, mq] of Object.entries(breakpoints)) {
28
34
  for (const prop of props) {
29
- css += `@media ${mq} { .${bp}-${prefix}${name} { ${prop}: ${value}; } }\n`;
35
+ css += `@media ${mq} { .${bp}-${escaped} { ${prop}: ${value}; } }\n`;
30
36
  }
31
37
  }
32
38
  }
@@ -37,13 +43,14 @@ export function complexClass(prefix, properties, tokens, breakpoints) {
37
43
  * Para casos especiales: divide >*+*, truncate, smoothing, outline-none, etc.
38
44
  */
39
45
  export function customClass(className, declarations, breakpoints, selectorSuffix) {
46
+ const escaped = escapeCSS(className);
40
47
  const suffix = selectorSuffix || "";
41
48
  const decls = Object.entries(declarations)
42
49
  .map(([p, v]) => `${p}: ${v}`)
43
50
  .join("; ");
44
- let css = `.${className}${suffix} { ${decls}; }\n`;
51
+ let css = `.${escaped}${suffix} { ${decls}; }\n`;
45
52
  for (const [bp, mq] of Object.entries(breakpoints)) {
46
- css += `@media ${mq} { .${bp}-${className}${suffix} { ${decls}; } }\n`;
53
+ css += `@media ${mq} { .${bp}-${escaped}${suffix} { ${decls}; } }\n`;
47
54
  }
48
55
  return css;
49
56
  }
@@ -19,6 +19,13 @@ export default function borders(config) {
19
19
  };
20
20
  // border-width
21
21
  css += complexClass("border-", "border-width", borderWidth, breakpoints);
22
+ // border-{side}-width
23
+ css += complexClass("border-t-", "border-top-width", borderWidth, breakpoints);
24
+ css += complexClass("border-r-", "border-right-width", borderWidth, breakpoints);
25
+ css += complexClass("border-b-", "border-bottom-width", borderWidth, breakpoints);
26
+ css += complexClass("border-l-", "border-left-width", borderWidth, breakpoints);
27
+ // border-color
28
+ css += complexClass("border-", "border-color", extendedColors, breakpoints);
22
29
  // border-style
23
30
  css += complexClass("border-", "border-style", borderStyle, breakpoints);
24
31
  // divide-color
@@ -1,4 +1,4 @@
1
- import { complexClass, simpleClass } from "../generators.js";
1
+ import { complexClass, customClass, simpleClass } from "../generators.js";
2
2
  export default function layout(config) {
3
3
  const { columns, breakpoints, spacing } = config;
4
4
  let css = "";
@@ -31,8 +31,6 @@ export default function layout(config) {
31
31
  for (let i = 1; i <= columns; i++) {
32
32
  css += simpleClass(`columns-${i}`, "columns", String(i), breakpoints);
33
33
  }
34
- // columns (spacing tokens)
35
- css += complexClass("columns-", "columns", spacing, breakpoints);
36
34
  // float
37
35
  const float = {
38
36
  left: "left",
@@ -98,14 +96,14 @@ export default function layout(config) {
98
96
  const zIndex = {
99
97
  auto: "auto",
100
98
  "0": "0",
101
- "1": "10",
99
+ "10": "10",
102
100
  "20": "20",
103
101
  "30": "30",
104
102
  "40": "40",
105
103
  "50": "50",
106
104
  };
107
105
  const zIndexNeg = {
108
- "1": "-10",
106
+ "10": "-10",
109
107
  "20": "-20",
110
108
  "30": "-30",
111
109
  "40": "-40",
@@ -114,13 +112,19 @@ export default function layout(config) {
114
112
  css += complexClass("z-", "z-index", zIndex, breakpoints);
115
113
  css += complexClass("-z-", "z-index", zIndexNeg, breakpoints);
116
114
  // top/right/bottom/left
117
- css += simpleClass("top-0", "top", "0", breakpoints);
118
- css += simpleClass("right-0", "right", "0", breakpoints);
119
- css += simpleClass("bottom-0", "bottom", "0", breakpoints);
120
- css += simpleClass("left-0", "left", "0", breakpoints);
121
115
  css += complexClass("top-", "top", spacing, breakpoints);
122
116
  css += complexClass("right-", "right", spacing, breakpoints);
123
117
  css += complexClass("bottom-", "bottom", spacing, breakpoints);
124
118
  css += complexClass("left-", "left", spacing, breakpoints);
119
+ // inset (top + right + bottom + left)
120
+ css += complexClass("inset-", "inset", spacing, breakpoints);
121
+ // inset-x (left + right)
122
+ for (const [key, value] of Object.entries(spacing)) {
123
+ css += customClass(`inset-x-${key}`, { left: value, right: value }, breakpoints);
124
+ }
125
+ // inset-y (top + bottom)
126
+ for (const [key, value] of Object.entries(spacing)) {
127
+ css += customClass(`inset-y-${key}`, { top: value, bottom: value }, breakpoints);
128
+ }
125
129
  return css;
126
130
  }
@@ -1,7 +1,35 @@
1
1
  import { complexClass, simpleClass } from "../generators.js";
2
2
  import { fractionPercent } from "../utils.js";
3
+ const fractions = [
4
+ [1, 2],
5
+ [1, 3],
6
+ [2, 3],
7
+ [1, 4],
8
+ [2, 4],
9
+ [3, 4],
10
+ [1, 5],
11
+ [2, 5],
12
+ [3, 5],
13
+ [4, 5],
14
+ [1, 6],
15
+ [2, 6],
16
+ [3, 6],
17
+ [4, 6],
18
+ [5, 6],
19
+ [1, 12],
20
+ [2, 12],
21
+ [3, 12],
22
+ [4, 12],
23
+ [5, 12],
24
+ [6, 12],
25
+ [7, 12],
26
+ [8, 12],
27
+ [9, 12],
28
+ [10, 12],
29
+ [11, 12],
30
+ ];
3
31
  export default function sizing(config) {
4
- const { columns, breakpoints } = config;
32
+ const { breakpoints } = config;
5
33
  let css = "";
6
34
  // width
7
35
  const width = {
@@ -14,8 +42,8 @@ export default function sizing(config) {
14
42
  fit: "fit-content",
15
43
  };
16
44
  css += complexClass("w-", "width", width, breakpoints);
17
- for (let i = 1; i <= columns; i++) {
18
- css += simpleClass(`w-${i}`, "width", fractionPercent(i, columns), breakpoints);
45
+ for (const [n, d] of fractions) {
46
+ css += simpleClass(`w-${n}/${d}`, "width", fractionPercent(n, d), breakpoints);
19
47
  }
20
48
  css += complexClass("min-w-", "min-width", width, breakpoints);
21
49
  css += complexClass("max-w-", "max-width", width, breakpoints);
@@ -30,8 +58,8 @@ export default function sizing(config) {
30
58
  fit: "fit-content",
31
59
  };
32
60
  css += complexClass("h-", "height", height, breakpoints);
33
- for (let i = 1; i <= columns; i++) {
34
- css += simpleClass(`h-${i}`, "height", fractionPercent(i, columns), breakpoints);
61
+ for (const [n, d] of fractions) {
62
+ css += simpleClass(`h-${n}/${d}`, "height", fractionPercent(n, d), breakpoints);
35
63
  }
36
64
  css += complexClass("min-h-", "min-height", height, breakpoints);
37
65
  css += complexClass("max-h-", "max-height", height, breakpoints);
@@ -83,7 +83,7 @@ export default function typography(config) {
83
83
  const fontWeight = {
84
84
  thin: "100",
85
85
  extralight: "200",
86
- light: "200",
86
+ light: "300",
87
87
  normal: "400",
88
88
  medium: "500",
89
89
  semibold: "600",
@@ -93,7 +93,14 @@ export default function typography(config) {
93
93
  };
94
94
  css += complexClass("font-", "font-weight", fontWeight, breakpoints);
95
95
  // letter-spacing
96
- const letterSpacing = omit(spacing, "auto");
96
+ const letterSpacing = {
97
+ tighter: "-0.05em",
98
+ tight: "-0.025em",
99
+ normal: "0em",
100
+ wide: "0.025em",
101
+ wider: "0.05em",
102
+ widest: "0.1em",
103
+ };
97
104
  css += complexClass("tracking-", "letter-spacing", letterSpacing, breakpoints);
98
105
  // line-height
99
106
  const lineHeight = omit(spacing, "auto", "zero");
@@ -101,7 +108,7 @@ export default function typography(config) {
101
108
  ...lineHeight,
102
109
  none: "1",
103
110
  tight: "1.25",
104
- snug: "1.3755",
111
+ snug: "1.375",
105
112
  normal: "1.5",
106
113
  relaxed: "1.625",
107
114
  loose: "2",
package/lib/purge.js CHANGED
@@ -11,8 +11,8 @@ export async function purgeCSS(css, options) {
11
11
  css: [{ raw: css }],
12
12
  extractors: [
13
13
  {
14
- // Extractor para uso directo en HTML/JSX: class="flex gap-md"
15
- extractor: (content) => content.match(/[A-Za-z0-9_-]+/g) || [],
14
+ // Extractor para uso directo en HTML/JSX: class="flex gap-md w-1/2"
15
+ extractor: (content) => content.match(/[A-Za-z0-9_/.-]+/g) || [],
16
16
  extensions: ["html", "jsx", "tsx", "js", "ts"],
17
17
  },
18
18
  {
@@ -32,7 +32,7 @@ export async function purgeCSS(css, options) {
32
32
  },
33
33
  ],
34
34
  safelist: {
35
- greedy: [/^bg-/, ...(options.safelist ?? [])],
35
+ greedy: options.safelist ?? [],
36
36
  },
37
37
  });
38
38
  return result ? result.css : css;
@@ -1,7 +1,6 @@
1
1
  import type { GrissoConfig } from "./types.js";
2
2
  /**
3
3
  * Resuelve la configuración de Grisso.
4
- * Patrón Tailwind v3:
5
4
  * - Top-level keys reemplazan los defaults completamente
6
5
  * - `extend` mergea superficialmente con defaults
7
6
  */
@@ -3,7 +3,6 @@ import { pathToFileURL } from "node:url";
3
3
  import defaults from "./defaults.js";
4
4
  /**
5
5
  * Resuelve la configuración de Grisso.
6
- * Patrón Tailwind v3:
7
6
  * - Top-level keys reemplazan los defaults completamente
8
7
  * - `extend` mergea superficialmente con defaults
9
8
  */
@@ -36,7 +35,11 @@ export async function resolveConfig(configPath) {
36
35
  if (extend && typeof extend === "object" && !Array.isArray(extend)) {
37
36
  for (const [key, value] of Object.entries(extend)) {
38
37
  const current = config[key];
39
- if (key in config &&
38
+ if (key in config && Array.isArray(current) && Array.isArray(value)) {
39
+ // Arrays se concatenan (e.g. safelist)
40
+ config[key] = [...current, ...value];
41
+ }
42
+ else if (key in config &&
40
43
  typeof current === "object" &&
41
44
  current !== null &&
42
45
  !Array.isArray(current)) {
@@ -0,0 +1,14 @@
1
+ export interface TokensOptions {
2
+ /** Ruta a grisso.config.mjs del consumidor */
3
+ config?: string;
4
+ /** Formato de salida: CSS scaffold o JSON (default: "css") */
5
+ format?: "css" | "json";
6
+ }
7
+ /**
8
+ * Extrae tokens de la config resuelta y genera un scaffold CSS o JSON.
9
+ *
10
+ * @example
11
+ * const css = await extractTokens();
12
+ * const json = await extractTokens({ format: "json" });
13
+ */
14
+ export declare function extractTokens(options?: TokensOptions): Promise<string>;