piloti 1.0.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.md ADDED
@@ -0,0 +1,207 @@
1
+ # Piloti
2
+
3
+ A config-driven CSS framework following **CUBE CSS methodology**.
4
+
5
+ > *Piloti* (pilotis) — architectural columns that lift a building, providing the foundation. Just like pilotis support architecture, Piloti supports your design system.
6
+
7
+ ## Features
8
+
9
+ - šŸŽØ **Token-based** — Colors, spacing, typography from config
10
+ - ⚔ **Auto-generated utilities** — Build from your tokens
11
+ - šŸ“± **Responsive** — Breakpoint-prefixed classes (sm:, md:, lg:, xl:)
12
+ - 🧩 **CUBE CSS compositions** — Stack, cluster, flow
13
+ - šŸ”§ **Customizable** — Your config, your design system
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install piloti
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Create your config
24
+
25
+ Create `piloti.config.json` in your project root:
26
+
27
+ ```json
28
+ {
29
+ "settings": {
30
+ "basePixel": 16,
31
+ "breakpoints": {
32
+ "sm": "640px",
33
+ "md": "768px",
34
+ "lg": "1024px"
35
+ }
36
+ },
37
+ "tokens": {
38
+ "color": {
39
+ "primary": "#4f46e5",
40
+ "secondary": "#e5e7eb",
41
+ "accent": "#f59e0b"
42
+ },
43
+ "spacing": {
44
+ "0": "0px",
45
+ "1": "4px",
46
+ "2": "8px",
47
+ "4": "16px",
48
+ "8": "32px"
49
+ }
50
+ },
51
+ "utilities": [
52
+ {
53
+ "prefix": "p",
54
+ "property": "padding",
55
+ "fromToken": "spacing",
56
+ "values": ["0", "1", "2", "4", "8"],
57
+ "responsive": true
58
+ },
59
+ {
60
+ "prefix": "bg",
61
+ "property": "background-color",
62
+ "fromToken": "color",
63
+ "values": ["primary", "secondary", "accent"]
64
+ }
65
+ ]
66
+ }
67
+ ```
68
+
69
+ ### 2. Generate CSS
70
+
71
+ ```bash
72
+ npx piloti
73
+ ```
74
+
75
+ This creates `piloti-output/` with:
76
+ - `variables.css` — CSS custom properties
77
+ - `utilities.css` — Utility classes
78
+ - `responsive.css` — Responsive utilities
79
+ - `index.css` — Entry point
80
+
81
+ ### 3. Import in your project
82
+
83
+ ```css
84
+ @import url('./piloti-output/index.css');
85
+ ```
86
+
87
+ Or in JS:
88
+ ```js
89
+ import './piloti-output/index.css';
90
+ ```
91
+
92
+ ## CLI Options
93
+
94
+ ```bash
95
+ npx piloti [options]
96
+
97
+ Options:
98
+ -c, --config <path> Path to config file (default: ./piloti.config.json)
99
+ -o, --output <path> Output directory (default: ./piloti-output)
100
+ -h, --help Show help
101
+ ```
102
+
103
+ ### Examples
104
+
105
+ ```bash
106
+ # Use default config location
107
+ npx piloti
108
+
109
+ # Custom config
110
+ npx piloti --config ./tokens/design-system.json
111
+
112
+ # Custom output
113
+ npx piloti --output ./src/styles/generated
114
+ ```
115
+
116
+ ## Config Reference
117
+
118
+ ### Settings
119
+
120
+ ```json
121
+ {
122
+ "settings": {
123
+ "basePixel": 16,
124
+ "breakpoints": {
125
+ "sm": "640px",
126
+ "md": "768px",
127
+ "lg": "1024px",
128
+ "xl": "1280px"
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ### Tokens
135
+
136
+ ```json
137
+ {
138
+ "tokens": {
139
+ "color": { "primary": "#000", "accent": "#f00" },
140
+ "spacing": { "1": "4px", "2": "8px" },
141
+ "fontSize": { "sm": "14px", "base": "16px" },
142
+ "radius": { "sm": "2px", "full": "9999px" },
143
+ "shadow": { "md": "0 4px 6px rgba(0,0,0,0.1)" },
144
+ "lineHeight": { "tight": "1.25", "normal": "1.5" },
145
+ "zIndex": { "10": "10", "50": "50" }
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### Utilities
151
+
152
+ ```json
153
+ {
154
+ "utilities": [
155
+ {
156
+ "prefix": "p",
157
+ "property": "padding",
158
+ "fromToken": "spacing",
159
+ "values": ["1", "2", "4"],
160
+ "responsive": true
161
+ },
162
+ {
163
+ "prefix": "d",
164
+ "property": "display",
165
+ "values": ["flex", "grid", "block", "none"],
166
+ "responsive": true
167
+ },
168
+ {
169
+ "prefix": "justify",
170
+ "property": "justify-content",
171
+ "values": ["start", "center", "between"],
172
+ "valueMap": {
173
+ "start": "flex-start",
174
+ "center": "center",
175
+ "between": "space-between"
176
+ }
177
+ }
178
+ ]
179
+ }
180
+ ```
181
+
182
+ ## Generated Classes
183
+
184
+ With the default config, you get:
185
+
186
+ | Category | Examples |
187
+ |----------|----------|
188
+ | Display | `.d-flex`, `.d-grid`, `.d-none` |
189
+ | Flexbox | `.flex-row`, `.flex-col`, `.justify-center`, `.items-center` |
190
+ | Spacing | `.p-4`, `.m-2`, `.gap-4`, `.mx-auto` |
191
+ | Colors | `.text-primary`, `.bg-accent` |
192
+ | Typography | `.font-xl`, `.leading-tight` |
193
+ | Shadows | `.shadow-md`, `.shadow-lg` |
194
+ | Responsive | `.sm:d-flex`, `.md:w-1/2`, `.lg:gap-8` |
195
+
196
+ ## Using with the Base Framework
197
+
198
+ For compositions (stack, cluster, flow) and reset styles, also import:
199
+
200
+ ```css
201
+ @import url('node_modules/piloti/src/main.css');
202
+ @import url('./piloti-output/index.css'); /* Your custom tokens */
203
+ ```
204
+
205
+ ## License
206
+
207
+ MIT
package/bin/piloti.js ADDED
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // =============================================================================
7
+ // CLI ARGUMENT PARSING
8
+ // =============================================================================
9
+
10
+ const args = process.argv.slice(2);
11
+ let configPath = null;
12
+ let outputDir = null;
13
+
14
+ for (let i = 0; i < args.length; i++) {
15
+ if (args[i] === '--config' || args[i] === '-c') {
16
+ configPath = args[i + 1];
17
+ i++;
18
+ } else if (args[i] === '--output' || args[i] === '-o') {
19
+ outputDir = args[i + 1];
20
+ i++;
21
+ } else if (args[i] === '--help' || args[i] === '-h') {
22
+ console.log(`
23
+ Piloti - CSS Framework Build Tool
24
+
25
+ Usage:
26
+ npx piloti [options]
27
+
28
+ Options:
29
+ -c, --config <path> Path to custom config.json (default: ./piloti.config.json)
30
+ -o, --output <path> Output directory for generated CSS (default: ./piloti-output)
31
+ -h, --help Show this help message
32
+
33
+ Examples:
34
+ npx piloti
35
+ npx piloti --config ./my-tokens.json
36
+ npx piloti --config ./tokens.json --output ./styles/generated
37
+ `);
38
+ process.exit(0);
39
+ }
40
+ }
41
+
42
+ // Resolve paths
43
+ const packageDir = path.dirname(__dirname);
44
+ const cwd = process.cwd();
45
+
46
+ // Default config: look in cwd first, then fallback to package config
47
+ const defaultConfigPath = fs.existsSync(path.join(cwd, 'piloti.config.json'))
48
+ ? path.join(cwd, 'piloti.config.json')
49
+ : path.join(packageDir, 'config/config.json');
50
+
51
+ configPath = configPath
52
+ ? path.resolve(cwd, configPath)
53
+ : defaultConfigPath;
54
+
55
+ outputDir = outputDir
56
+ ? path.resolve(cwd, outputDir)
57
+ : path.join(cwd, 'piloti-output');
58
+
59
+ // Load config
60
+ if (!fs.existsSync(configPath)) {
61
+ console.error(`Error: Config file not found at ${configPath}`);
62
+ console.error('Create a piloti.config.json file or use --config to specify a path.');
63
+ process.exit(1);
64
+ }
65
+
66
+ const config = require(configPath);
67
+
68
+ console.log(`\nšŸ›ļø Piloti CSS Framework`);
69
+ console.log(` Config: ${configPath}`);
70
+ console.log(` Output: ${outputDir}\n`);
71
+
72
+ // =============================================================================
73
+ // ENSURE OUTPUT DIRECTORIES
74
+ // =============================================================================
75
+
76
+ [outputDir].forEach(dir => {
77
+ if (!fs.existsSync(dir)) {
78
+ fs.mkdirSync(dir, { recursive: true });
79
+ }
80
+ });
81
+
82
+ // =============================================================================
83
+ // HELPERS
84
+ // =============================================================================
85
+
86
+ /**
87
+ * Convert px to rem
88
+ */
89
+ const toRem = (value) => {
90
+ const basePixel = config.settings?.basePixel || 16;
91
+ if (typeof value === 'string' && value.endsWith('px')) {
92
+ const px = parseInt(value);
93
+ return px === 0 ? '0' : `${px / basePixel}rem`;
94
+ }
95
+ return value;
96
+ };
97
+
98
+ /**
99
+ * Sanitize class names (handles special characters like /)
100
+ */
101
+ const sanitizeClassName = (name) => {
102
+ return name.replace(/\//g, '\\/');
103
+ };
104
+
105
+ // =============================================================================
106
+ // GENERATE VARIABLES CSS
107
+ // =============================================================================
108
+
109
+ console.log('Generating CSS variables...');
110
+
111
+ let variablesContent = `/* ============================================
112
+ CSS Variables - Auto-generated
113
+ Config: ${path.basename(configPath)}
114
+ ============================================ */\n\n:root {\n`;
115
+
116
+ let variableCount = 0;
117
+
118
+ for (const [category, tokens] of Object.entries(config.tokens || {})) {
119
+ variablesContent += `\n /* ${category.charAt(0).toUpperCase() + category.slice(1)} */\n`;
120
+
121
+ for (const [key, value] of Object.entries(tokens)) {
122
+ const varName = `--${category}-${key}`;
123
+ const varValue = toRem(value);
124
+ variablesContent += ` ${varName}: ${varValue};\n`;
125
+ variableCount++;
126
+ }
127
+ }
128
+
129
+ variablesContent += '}\n';
130
+
131
+ fs.writeFileSync(path.join(outputDir, 'variables.css'), variablesContent);
132
+ console.log(` āœ“ ${variableCount} variables → variables.css`);
133
+
134
+ // =============================================================================
135
+ // GENERATE UTILITIES CSS
136
+ // =============================================================================
137
+
138
+ console.log('Generating utilities...');
139
+
140
+ let utilitiesContent = `/* ============================================
141
+ Utility Classes - Auto-generated
142
+ Config: ${path.basename(configPath)}
143
+ ============================================ */\n\n`;
144
+
145
+ let utilityCount = 0;
146
+ let warnings = [];
147
+
148
+ (config.utilities || []).forEach(util => {
149
+ utilitiesContent += `/* ${util.property} */\n`;
150
+
151
+ util.values.forEach(key => {
152
+ let valueStr;
153
+
154
+ if (util.fromToken) {
155
+ if (config.tokens[util.fromToken] && config.tokens[util.fromToken][key]) {
156
+ valueStr = `var(--${util.fromToken}-${key})`;
157
+ } else {
158
+ warnings.push(`Token '${key}' not found in '${util.fromToken}'`);
159
+ return;
160
+ }
161
+ } else if (util.valueMap && util.valueMap[key]) {
162
+ valueStr = util.valueMap[key];
163
+ } else {
164
+ valueStr = key;
165
+ }
166
+
167
+ const className = `.${util.prefix}-${sanitizeClassName(key)}`;
168
+ utilitiesContent += `${className} { ${util.property}: ${valueStr}; }\n`;
169
+ utilityCount++;
170
+ });
171
+
172
+ utilitiesContent += '\n';
173
+ });
174
+
175
+ fs.writeFileSync(path.join(outputDir, 'utilities.css'), utilitiesContent);
176
+ console.log(` āœ“ ${utilityCount} utilities → utilities.css`);
177
+
178
+ // =============================================================================
179
+ // GENERATE RESPONSIVE UTILITIES
180
+ // =============================================================================
181
+
182
+ const breakpoints = config.settings?.breakpoints || {};
183
+ let responsiveCount = 0;
184
+
185
+ if (Object.keys(breakpoints).length > 0) {
186
+ console.log('Generating responsive utilities...');
187
+
188
+ let responsiveContent = `/* ============================================
189
+ Responsive Utilities - Auto-generated
190
+ Config: ${path.basename(configPath)}
191
+ ============================================ */\n\n`;
192
+
193
+ for (const [bpName, bpValue] of Object.entries(breakpoints)) {
194
+ responsiveContent += `/* ${bpName}: (min-width: ${bpValue}) */\n`;
195
+ responsiveContent += `@media (min-width: ${bpValue}) {\n`;
196
+
197
+ (config.utilities || []).forEach(util => {
198
+ if (!util.responsive) return;
199
+
200
+ util.values.forEach(key => {
201
+ let valueStr;
202
+
203
+ if (util.fromToken) {
204
+ if (config.tokens[util.fromToken] && config.tokens[util.fromToken][key]) {
205
+ valueStr = `var(--${util.fromToken}-${key})`;
206
+ } else {
207
+ return;
208
+ }
209
+ } else if (util.valueMap && util.valueMap[key]) {
210
+ valueStr = util.valueMap[key];
211
+ } else {
212
+ valueStr = key;
213
+ }
214
+
215
+ const className = `.${bpName}\\:${util.prefix}-${sanitizeClassName(key)}`;
216
+ responsiveContent += ` ${className} { ${util.property}: ${valueStr}; }\n`;
217
+ responsiveCount++;
218
+ });
219
+ });
220
+
221
+ responsiveContent += '}\n\n';
222
+ }
223
+
224
+ fs.writeFileSync(path.join(outputDir, 'responsive.css'), responsiveContent);
225
+ console.log(` āœ“ ${responsiveCount} responsive utilities → responsive.css`);
226
+ }
227
+
228
+ // =============================================================================
229
+ // GENERATE MAIN ENTRY FILE
230
+ // =============================================================================
231
+
232
+ let mainContent = `/* Piloti CSS Framework - Generated Entry Point */\n`;
233
+ mainContent += `@import url('./variables.css');\n`;
234
+ mainContent += `@import url('./utilities.css');\n`;
235
+ if (responsiveCount > 0) {
236
+ mainContent += `@import url('./responsive.css');\n`;
237
+ }
238
+
239
+ fs.writeFileSync(path.join(outputDir, 'index.css'), mainContent);
240
+
241
+ // =============================================================================
242
+ // WARNINGS
243
+ // =============================================================================
244
+
245
+ if (warnings.length > 0) {
246
+ console.log('\nāš ļø Warnings:');
247
+ warnings.forEach(w => console.log(` - ${w}`));
248
+ }
249
+
250
+ // =============================================================================
251
+ // SUMMARY
252
+ // =============================================================================
253
+
254
+ console.log('\n' + '='.repeat(50));
255
+ console.log('āœ… Build complete!');
256
+ console.log('='.repeat(50));
257
+ console.log(` Variables: ${variableCount}`);
258
+ console.log(` Utilities: ${utilityCount}`);
259
+ console.log(` Responsive: ${responsiveCount}`);
260
+ console.log(` Total: ${variableCount + utilityCount + responsiveCount}`);
261
+ console.log('='.repeat(50));
262
+ console.log(`\nImport in your project:`);
263
+ console.log(` @import url('${path.relative(cwd, outputDir)}/index.css');`);
264
+ console.log('');