@stratixlabs/core 1.7.1

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.
@@ -0,0 +1,521 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const args = process.argv.slice(2);
7
+ const command = args[0];
8
+
9
+ if (!command || (command !== 'init' && command !== 'generate' && command !== 'figma')) {
10
+ console.log('Usage: @stratixlabs/core <command>');
11
+ console.log('Commands:');
12
+ console.log(' init Initialize a complete Substrata project with tokens, scripts, and examples');
13
+ console.log(' generate Generate tokens.json from source');
14
+ console.log(' figma Figma-related automation (sync, apply-preview)');
15
+ process.exit(1);
16
+ }
17
+
18
+ if (command === 'init') {
19
+ console.log('šŸš€ Initializing Substrata...\n');
20
+
21
+ const cwd = process.cwd();
22
+ const configPath = path.join(cwd, 'substrata.config.js');
23
+ const tokensDir = path.join(cwd, 'src/tokens');
24
+ const consumptionDir = path.join(cwd, 'src/consumption');
25
+ const scriptsDir = path.join(cwd, 'scripts');
26
+ const srcDir = path.join(cwd, 'src');
27
+
28
+ // 1. Create config file
29
+ if (fs.existsSync(configPath)) {
30
+ console.log('āš ļø substrata.config.js already exists.');
31
+ } else {
32
+ const defaultConfig = `module.exports = {
33
+ tokens: './src/tokens',
34
+ output: './tokens.json'
35
+ };
36
+ `;
37
+ fs.writeFileSync(configPath, defaultConfig);
38
+ console.log('āœ… Created substrata.config.js');
39
+ }
40
+
41
+ // 2. Create directory structure
42
+ if (!fs.existsSync(tokensDir)) {
43
+ fs.mkdirSync(tokensDir, { recursive: true });
44
+ console.log('āœ… Created src/tokens/ directory');
45
+ }
46
+ if (!fs.existsSync(consumptionDir)) {
47
+ fs.mkdirSync(consumptionDir, { recursive: true });
48
+ console.log('āœ… Created src/consumption/ directory');
49
+ }
50
+ if (!fs.existsSync(scriptsDir)) {
51
+ fs.mkdirSync(scriptsDir, { recursive: true });
52
+ console.log('āœ… Created scripts/ directory');
53
+ }
54
+
55
+ // 3. Create all token files
56
+ const tokens = {
57
+ 'colors.css': `:root {
58
+
59
+ /* ==============================
60
+ Colors
61
+ ============================== */
62
+
63
+ /* Neutrals */
64
+ --neutral-0: #ffffff;
65
+ --neutral-50: #f8fafc;
66
+ --neutral-100: #f1f5f9;
67
+ --neutral-200: #e2e8f0;
68
+ --neutral-300: #cbd5e1;
69
+ --neutral-400: #94a3b8;
70
+ --neutral-500: #64748b;
71
+ --neutral-600: #475569;
72
+ --neutral-700: #334155;
73
+ --neutral-800: #1e293b;
74
+ --neutral-900: #0f172a;
75
+
76
+ /* Brand */
77
+ --brand-300: #93c5fd;
78
+ --brand-500: #3b82f6;
79
+ --brand-700: #1d4ed8;
80
+
81
+ /* Feedback */
82
+ --color-success: #22c55e;
83
+ --color-warning: #eab308;
84
+ --color-danger: #ef4444;
85
+ }
86
+ `,
87
+ 'spacing.css': `:root {
88
+
89
+ /* ==============================
90
+ Spacing
91
+ ============================== */
92
+
93
+ --space-1: 0.25rem;
94
+ --space-2: 0.5rem;
95
+ --space-3: 0.75rem;
96
+ --space-4: 1rem;
97
+ --space-5: 1.5rem;
98
+ --space-6: 2rem;
99
+
100
+ }
101
+ `,
102
+ 'typography.css': `:root {
103
+ /* ==============================
104
+ Typography
105
+ ============================== */
106
+
107
+ --font-family-base: system-ui, -apple-system, BlinkMacSystemFont,
108
+ "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
109
+
110
+ --font-size-xs: 0.75rem;
111
+ --font-size-sm: 0.875rem;
112
+ --font-size-md: 1rem;
113
+ --font-size-lg: 1.25rem;
114
+ --font-size-xl: 1.5rem;
115
+ --font-size-2xl: 2rem;
116
+
117
+ --font-weight-regular: 400;
118
+ --font-weight-medium: 500;
119
+ --font-weight-semibold: 600;
120
+ --font-weight-bold: 700;
121
+
122
+ --line-height-tight: 1.25;
123
+ --line-height-normal: 1.5;
124
+ --line-height-relaxed: 1.75;
125
+ }
126
+ `,
127
+ 'radius-and-borders.css': `:root {
128
+
129
+ /* ==============================
130
+ Radius & Borders
131
+ ============================== */
132
+
133
+ --radius-sm: 0.375rem;
134
+ --radius-md: 0.5rem;
135
+ --radius-lg: 0.75rem;
136
+
137
+ --border-width: 1px;
138
+ --border-color: var(--neutral-200);
139
+
140
+ }
141
+ `,
142
+ 'elevation.css': `:root {
143
+
144
+ /* ==============================
145
+ Shadows
146
+ ============================== */
147
+
148
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
149
+ --shadow-md: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
150
+ --shadow-lg: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
151
+ }
152
+ `,
153
+ 'motion.css': `:root {
154
+
155
+ /* ==============================
156
+ Motion
157
+ ============================== */
158
+
159
+ --motion-fast: 200ms;
160
+ --motion-normal: 300ms;
161
+ --motion-slow: 400ms;
162
+
163
+ }
164
+ `,
165
+ 'opacity.css': `:root {
166
+
167
+ /* ==============================
168
+ Opacity
169
+ ============================== */
170
+
171
+ --opacity-0: 0;
172
+ --opacity-20: 0.2;
173
+ --opacity-40: 0.4;
174
+ --opacity-60: 0.6;
175
+ --opacity-80: 0.8;
176
+ --opacity-100: 1;
177
+
178
+ }
179
+ `,
180
+ 'breakpoints.css': `:root {
181
+
182
+ /* ==============================
183
+ Breakpoints
184
+ ============================== */
185
+
186
+ --breakpoint-sm: 640px;
187
+ --breakpoint-md: 768px;
188
+ --breakpoint-lg: 1024px;
189
+ }
190
+ `,
191
+ 'semantic-aliases.css': `:root {
192
+
193
+ /* ==============================
194
+ Semantic Aliases
195
+ ============================== */
196
+
197
+ --color-bg: var(--neutral-50);
198
+ --color-surface: var(--neutral-50);
199
+ --color-text-primary: var(--neutral-900);
200
+ --color-text-secondary: var(--neutral-700);
201
+ --color-border: var(--neutral-200);
202
+
203
+ --color-brand-primary: var(--brand-500);
204
+ --color-brand-hover: var(--brand-700);
205
+ --color-text-inverse: var(--neutral-0);
206
+
207
+ }
208
+ `
209
+ };
210
+
211
+ console.log('');
212
+ for (const [file, content] of Object.entries(tokens)) {
213
+ const filePath = path.join(tokensDir, file);
214
+ if (!fs.existsSync(filePath)) {
215
+ fs.writeFileSync(filePath, content);
216
+ console.log(`āœ… Created src/tokens/${file}`);
217
+ }
218
+ }
219
+
220
+ // 4. Create substrata.css (imports all tokens)
221
+ const substrataCssPath = path.join(srcDir, 'substrata.css');
222
+ const substrataCss = `@import "tokens/typography.css";
223
+ @import "tokens/spacing.css";
224
+ @import "tokens/colors.css";
225
+ @import "tokens/radius-and-borders.css";
226
+ @import "tokens/elevation.css";
227
+ @import "tokens/motion.css";
228
+ @import "tokens/opacity.css";
229
+ @import "tokens/breakpoints.css";
230
+ @import "tokens/semantic-aliases.css";
231
+
232
+ @import "./base.css";
233
+ `;
234
+ if (!fs.existsSync(substrataCssPath)) {
235
+ if (!fs.existsSync(srcDir)) fs.mkdirSync(srcDir, { recursive: true });
236
+ fs.writeFileSync(substrataCssPath, substrataCss);
237
+ console.log('\nāœ… Created src/substrata.css');
238
+ }
239
+
240
+ // 5. Create base.css
241
+ const baseCssPath = path.join(srcDir, 'base.css');
242
+ const baseCss = `body {
243
+ font-family: var(--font-family-base);
244
+ font-size: var(--font-size-md);
245
+ font-weight: var(--font-weight-regular);
246
+ line-height: var(--line-height-normal);
247
+ color: var(--neutral-900);
248
+ background-color: var(--neutral-0);
249
+ }
250
+
251
+ /* ==============================
252
+ Typography
253
+ ============================== */
254
+
255
+ h1 {
256
+ font-size: var(--font-size-2xl);
257
+ font-weight: var(--font-weight-bold);
258
+ line-height: var(--line-height-tight);
259
+ margin-bottom: var(--space-3);
260
+ }
261
+
262
+ h2 {
263
+ font-size: var(--font-size-xl);
264
+ font-weight: var(--font-weight-semibold);
265
+ line-height: var(--line-height-normal);
266
+ margin-bottom: var(--space-3);
267
+ margin-top: 40px;
268
+ }
269
+
270
+ h3 {
271
+ font-size: var(--font-size-lg);
272
+ font-weight: var(--font-weight-semibold);
273
+ margin-bottom: var(--space-3);
274
+ margin-top: 40px;
275
+ }
276
+
277
+ h4 {
278
+ font-size: var(--font-size-md);
279
+ font-weight: var(--font-weight-medium);
280
+ margin-bottom: var(--space-3);
281
+ }
282
+ `;
283
+ if (!fs.existsSync(baseCssPath)) {
284
+ fs.writeFileSync(baseCssPath, baseCss);
285
+ console.log('āœ… Created src/base.css');
286
+ }
287
+
288
+ // 6. Create consumption examples
289
+ const consumptionExamples = {
290
+ 'plain.css': `/*
291
+ Plain CSS Consumption Example
292
+ Import Substrata tokens and use them directly in your styles.
293
+ */
294
+
295
+ @import "@stratixlabs/core/substrata.css";
296
+
297
+ .card {
298
+ background: var(--color-surface);
299
+ border: var(--border-width) solid var(--color-border);
300
+ border-radius: var(--radius-md);
301
+ padding: var(--space-4);
302
+ box-shadow: var(--shadow-sm);
303
+ }
304
+
305
+ .button {
306
+ padding: var(--space-2) var(--space-4);
307
+ background: var(--color-brand-primary);
308
+ color: var(--color-text-inverse);
309
+ border-radius: var(--radius-sm);
310
+ font-weight: var(--font-weight-medium);
311
+ transition: background var(--motion-normal);
312
+ }
313
+
314
+ .button:hover {
315
+ background: var(--color-brand-hover);
316
+ }
317
+ `,
318
+ 'styled-components.js': `/*
319
+ CSS-in-JS Consumption Example (styled-components / emotion)
320
+ Use Substrata tokens in template literals via CSS variables.
321
+ */
322
+
323
+ import styled from 'styled-components';
324
+
325
+ export const Card = styled.div\`
326
+ padding: var(--space-4);
327
+ background: var(--color-surface);
328
+ color: var(--color-text-primary);
329
+ border-radius: var(--radius-md);
330
+ box-shadow: var(--shadow-sm);
331
+ \`;
332
+
333
+ export const Button = styled.button\`
334
+ padding: var(--space-2) var(--space-4);
335
+ background: var(--color-brand-primary);
336
+ color: var(--color-text-inverse);
337
+ font-family: var(--font-family-base);
338
+ border-radius: var(--radius-sm);
339
+ border: none;
340
+ cursor: pointer;
341
+ transition: background var(--motion-normal);
342
+
343
+ &:hover {
344
+ background: var(--color-brand-hover);
345
+ }
346
+ \`;
347
+ `,
348
+ 'tailwind.config.js': `/*
349
+ Tailwind CSS Consumption Example
350
+ Map Substrata tokens to Tailwind's theme configuration.
351
+ */
352
+
353
+ /** @type {import('tailwindcss').Config} */
354
+ module.exports = {
355
+ content: ["./src/**/*.{html,js}"],
356
+ theme: {
357
+ extend: {
358
+ colors: {
359
+ brand: {
360
+ 300: "var(--brand-300)",
361
+ 500: "var(--brand-500)",
362
+ 700: "var(--brand-700)",
363
+ },
364
+ neutral: {
365
+ 0: "var(--neutral-0)",
366
+ 50: "var(--neutral-50)",
367
+ 100: "var(--neutral-100)",
368
+ 200: "var(--neutral-200)",
369
+ 300: "var(--neutral-300)",
370
+ 400: "var(--neutral-400)",
371
+ 500: "var(--neutral-500)",
372
+ 600: "var(--neutral-600)",
373
+ 700: "var(--neutral-700)",
374
+ 800: "var(--neutral-800)",
375
+ 900: "var(--neutral-900)",
376
+ },
377
+ surface: "var(--color-surface)",
378
+ text: "var(--color-text-primary)",
379
+ },
380
+ spacing: {
381
+ 1: "var(--space-1)",
382
+ 2: "var(--space-2)",
383
+ 3: "var(--space-3)",
384
+ 4: "var(--space-4)",
385
+ 5: "var(--space-5)",
386
+ 6: "var(--space-6)",
387
+ },
388
+ borderRadius: {
389
+ sm: "var(--radius-sm)",
390
+ md: "var(--radius-md)",
391
+ lg: "var(--radius-lg)",
392
+ },
393
+ fontFamily: {
394
+ body: "var(--font-family-base)",
395
+ heading: "var(--font-family-heading)",
396
+ },
397
+ },
398
+ },
399
+ plugins: [],
400
+ };
401
+ `
402
+ };
403
+
404
+ console.log('');
405
+ for (const [file, content] of Object.entries(consumptionExamples)) {
406
+ const filePath = path.join(consumptionDir, file);
407
+ if (!fs.existsSync(filePath)) {
408
+ fs.writeFileSync(filePath, content);
409
+ console.log(`āœ… Created src/consumption/${file}`);
410
+ }
411
+ }
412
+
413
+ // 7. Create validation scripts
414
+ const scripts = {
415
+ 'generate-tokens.js': fs.readFileSync(path.join(__dirname, '../scripts/generate-tokens.js'), 'utf8'),
416
+ 'validate-tokens.js': fs.readFileSync(path.join(__dirname, '../scripts/validate-tokens.js'), 'utf8'),
417
+ 'lint-hardcoded.js': fs.readFileSync(path.join(__dirname, '../scripts/lint-hardcoded.js'), 'utf8'),
418
+ 'figma-sync.js': fs.readFileSync(path.join(__dirname, '../scripts/figma-sync.js'), 'utf8')
419
+ };
420
+
421
+ console.log('');
422
+ for (const [file, content] of Object.entries(scripts)) {
423
+ const filePath = path.join(scriptsDir, file);
424
+ if (!fs.existsSync(filePath)) {
425
+ fs.writeFileSync(filePath, content);
426
+ console.log(`āœ… Created scripts/${file}`);
427
+ }
428
+ }
429
+
430
+ // 8. Add scripts to package.json
431
+ const pkgPath = path.join(cwd, 'package.json');
432
+ if (fs.existsSync(pkgPath)) {
433
+ try {
434
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
435
+ if (!pkg.scripts) pkg.scripts = {};
436
+
437
+ let updated = false;
438
+ if (!pkg.scripts['build:tokens']) {
439
+ pkg.scripts['build:tokens'] = 'node scripts/generate-tokens.js';
440
+ updated = true;
441
+ }
442
+ if (!pkg.scripts['test:tokens']) {
443
+ pkg.scripts['test:tokens'] = 'node scripts/validate-tokens.js';
444
+ updated = true;
445
+ }
446
+ if (!pkg.scripts['lint:tokens']) {
447
+ pkg.scripts['lint:tokens'] = 'node scripts/lint-hardcoded.js';
448
+ updated = true;
449
+ }
450
+ if (!pkg.scripts['sync:figma']) {
451
+ pkg.scripts['sync:figma'] = 'node scripts/figma-sync.js';
452
+ updated = true;
453
+ }
454
+
455
+ if (updated) {
456
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
457
+ console.log('\nāœ… Added npm scripts to package.json');
458
+ }
459
+ } catch (e) {
460
+ console.log('\nāš ļø Could not update package.json (invalid JSON or permissions)');
461
+ }
462
+ }
463
+
464
+ console.log('\n✨ Substrata is ready!\n');
465
+ console.log('šŸ“‚ Generated structure:');
466
+ console.log(' ā”œā”€ā”€ substrata.config.js');
467
+ console.log(' ā”œā”€ā”€ src/');
468
+ console.log(' │ ā”œā”€ā”€ tokens/ (9 token files)');
469
+ console.log(' │ ā”œā”€ā”€ consumption/ (3 framework examples)');
470
+ console.log(' │ ā”œā”€ā”€ substrata.css (main entry point)');
471
+ console.log(' │ └── base.css');
472
+ console.log(' └── scripts/ (3 automation scripts)\n');
473
+ console.log('ā–¶ļø Next steps:');
474
+ console.log(' 1. Import src/substrata.css in your app');
475
+ console.log(' 2. Run: npm run build:tokens');
476
+ console.log(' 3. Check src/consumption/ for framework integration examples\n');
477
+ }
478
+
479
+ if (command === 'generate') {
480
+ console.log('Generating tokens...');
481
+ try {
482
+ const scriptPath = path.join(__dirname, '../scripts/generate-tokens.js');
483
+
484
+ if (fs.existsSync(scriptPath)) {
485
+ require(scriptPath);
486
+ } else {
487
+ console.error('āŒ Could not find generation script at:', scriptPath);
488
+ process.exit(1);
489
+ }
490
+ } catch (error) {
491
+ console.error('āŒ Error during generation:', error);
492
+ process.exit(1);
493
+ }
494
+ }
495
+
496
+ if (command === 'figma') {
497
+ const subcommand = args[1];
498
+
499
+ if (!subcommand || subcommand !== 'apply-preview') {
500
+ console.log('Usage: @stratixlabs/core figma <subcommand>');
501
+ console.log('Subcommands:');
502
+ console.log(' apply-preview Apply figma-push-preview.json to Figma via Variables REST API');
503
+ process.exit(1);
504
+ }
505
+
506
+ const scriptPath = path.join(__dirname, '../scripts/figma-sync.js');
507
+
508
+ if (!fs.existsSync(scriptPath)) {
509
+ console.error('āŒ Could not find Figma script at:', scriptPath);
510
+ process.exit(1);
511
+ }
512
+
513
+ process.env.FIGMA_SYNC_DIRECTION = 'apply';
514
+
515
+ try {
516
+ require(scriptPath);
517
+ } catch (error) {
518
+ console.error('āŒ Error applying Figma push preview:', error);
519
+ process.exit(1);
520
+ }
521
+ }
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ const tokens = require('./tokens.json');
2
+
3
+ module.exports = tokens;
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@stratixlabs/core",
3
+ "version": "1.7.1",
4
+ "description": "...",
5
+ "bin": {
6
+ "substrata": "bin/substrata.js"
7
+ },
8
+ "main": "index.js",
9
+ "scripts": {
10
+ "test": "npm run test:tokens",
11
+ "build:tokens": "node scripts/generate-tokens.js",
12
+ "build:css-from-tokens": "node scripts/tokens-to-css.js",
13
+ "build:types": "node scripts/generate-dts-from-tokens.js",
14
+ "test:tokens": "node scripts/validate-tokens.js",
15
+ "lint:tokens": "node scripts/lint-hardcoded.js",
16
+ "lint:code": "node scripts/lint-code.js",
17
+ "detect:breaking": "node scripts/detect-breaking-changes.js",
18
+ "sync:figma": "node scripts/figma-sync.js"
19
+ },
20
+ "keywords": [
21
+ "stratix",
22
+ "design-tokens",
23
+ "design-system",
24
+ "infra",
25
+ "platform"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/mikaelcarrara/stratix"
30
+ },
31
+ "author": "Mikael Carrara",
32
+ "license": "MIT",
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "files": [
37
+ "bin",
38
+ "scripts",
39
+ "src",
40
+ "index.js",
41
+ "tokens.json",
42
+ "substrata.d.ts",
43
+ "README.md",
44
+ "substrata.config.js"
45
+ ],
46
+ "devDependencies": {
47
+ "ajv": "^8.12.0"
48
+ }
49
+ }