zark-design 1.0.0 → 3.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/bin/cli.js +364 -90
- package/package.json +2 -2
- package/templates/README.md.hbs +264 -0
- package/templates/_shared/ASSETS-README.md.hbs +39 -0
- package/templates/_shared/tokens.css.hbs +202 -0
- package/templates/_shared/tokens.js.hbs +34 -0
- package/templates/html/components.css +740 -0
- package/templates/html/index.html +135 -0
- package/templates/html/showcase.html +3550 -0
- package/templates/jsx/App.example.jsx +229 -0
- package/templates/jsx/components/AlertCritical.jsx +43 -0
- package/templates/jsx/components/Avatar.jsx +41 -0
- package/templates/jsx/components/Badge.jsx +12 -0
- package/templates/jsx/components/Banner.jsx +42 -0
- package/templates/jsx/components/Button.jsx +43 -0
- package/templates/jsx/components/Chip.jsx +28 -0
- package/templates/jsx/components/CodeBlock.jsx +42 -0
- package/templates/jsx/components/EmptyState.jsx +27 -0
- package/templates/jsx/components/Funnel.jsx +55 -0
- package/templates/jsx/components/Input.jsx +53 -0
- package/templates/jsx/components/KanbanColumn.jsx +51 -0
- package/templates/jsx/components/Kbd.jsx +11 -0
- package/templates/jsx/components/LeadCard.jsx +79 -0
- package/templates/jsx/components/Modal.jsx +57 -0
- package/templates/jsx/components/Panel.jsx +25 -0
- package/templates/jsx/components/Section.jsx +28 -0
- package/templates/jsx/components/Segmented.jsx +26 -0
- package/templates/jsx/components/Sidebar.jsx +49 -0
- package/templates/jsx/components/Spec.jsx +19 -0
- package/templates/jsx/components/StatCard.jsx +44 -0
- package/templates/jsx/components/TableActions.jsx +34 -0
- package/templates/jsx/components/Tag.jsx +21 -0
- package/templates/jsx/components/TagDot.jsx +26 -0
- package/templates/jsx/components/Toast.jsx +25 -0
- package/templates/jsx/components/Toggle.jsx +29 -0
- package/templates/jsx/components.css +740 -0
- package/templates/{icons.jsx → jsx/icons.jsx} +20 -9
- package/templates/jsx/index.js +31 -0
- package/templates/presets/zark/preset.json +26 -0
- package/templates/REFERENCE.md +0 -376
- package/templates/SHOWCASE.html +0 -254
- package/templates/brand.jsx +0 -89
- package/templates/components.jsx +0 -385
- package/templates/design-canvas.jsx +0 -789
- package/templates/foundations.jsx +0 -363
- package/templates/layouts.jsx +0 -232
- package/templates/patterns.jsx +0 -268
- package/templates/primitives.jsx +0 -306
- package/templates/tokens.css +0 -306
- package/templates/visual-references/icon-zark.png +0 -0
- package/templates/visual-references/logo-zark-principal.png +0 -0
- package/templates/visual-references/pasted-1777605750385-0.png +0 -0
- package/templates/visual-references/pasted-1777605766298-0.png +0 -0
- package/templates/visual-references/pasted-1777605775820-0.png +0 -0
- package/templates/visual-references/pasted-1777605789833-0.png +0 -0
- package/templates/visual-references/pasted-1777605802420-0.png +0 -0
- package/templates/visual-references/pasted-1777605812470-0.png +0 -0
- package/templates/visual-references/pasted-1777605817688-0.png +0 -0
- package/templates/visual-references/pasted-1777605828485-0.png +0 -0
- package/templates/visual-references/pasted-1777605837137-0.png +0 -0
- package/templates/visual-references/pasted-1777605849789-0.png +0 -0
- package/templates/visual-references/pasted-1777605864942-0.png +0 -0
- package/templates/visual-references/pasted-1777605877920-0.png +0 -0
- package/templates/visual-references/pasted-1777605897353-0.png +0 -0
- /package/templates/{assets/zark-logo.png → presets/zark/assets/logo-zark-laranja.png} +0 -0
- /package/templates/{assets → presets/zark/assets}/zark-icon.png +0 -0
package/bin/cli.js
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
import { promises as fs } from 'node:fs';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import readline from 'node:readline';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
8
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const TEMPLATES_DIR = path.resolve(__dirname, '../templates');
|
|
11
|
+
const PRESETS_DIR = path.join(TEMPLATES_DIR, 'presets');
|
|
12
|
+
const SHARED_DIR = path.join(TEMPLATES_DIR, '_shared');
|
|
10
13
|
const PKG_PATH = path.resolve(__dirname, '../package.json');
|
|
11
14
|
|
|
12
15
|
const c = {
|
|
@@ -27,146 +30,417 @@ function banner(title) {
|
|
|
27
30
|
|
|
28
31
|
async function getVersion() {
|
|
29
32
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
return JSON.parse(await fs.readFile(PKG_PATH, 'utf-8')).version;
|
|
34
|
+
} catch { return 'unknown'; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Color utils: hex ↔ HSL, palette generation ──────────────────────────
|
|
38
|
+
|
|
39
|
+
function hexToRgb(hex) {
|
|
40
|
+
const h = hex.replace('#', '').trim();
|
|
41
|
+
const s = h.length === 3 ? h.split('').map(x => x + x).join('') : h;
|
|
42
|
+
if (s.length !== 6 || !/^[0-9a-f]{6}$/i.test(s)) {
|
|
43
|
+
throw new Error(`Hex inválido: "${hex}" (esperado #RRGGBB ou #RGB)`);
|
|
34
44
|
}
|
|
45
|
+
return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
|
|
35
46
|
}
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
function rgbToHex(r, g, b) {
|
|
49
|
+
const cl = (v) => Math.max(0, Math.min(255, Math.round(v)));
|
|
50
|
+
return '#' + [cl(r), cl(g), cl(b)].map(v => v.toString(16).padStart(2, '0')).join('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function rgbToHsl(r, g, b) {
|
|
54
|
+
r /= 255; g /= 255; b /= 255;
|
|
55
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
56
|
+
let h = 0, s = 0; const l = (max + min) / 2;
|
|
57
|
+
if (max !== min) {
|
|
58
|
+
const d = max - min;
|
|
59
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
60
|
+
switch (max) {
|
|
61
|
+
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
|
62
|
+
case g: h = (b - r) / d + 2; break;
|
|
63
|
+
case b: h = (r - g) / d + 4; break;
|
|
47
64
|
}
|
|
65
|
+
h /= 6;
|
|
48
66
|
}
|
|
67
|
+
return [h * 360, s * 100, l * 100];
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
function hslToRgb(h, s, l) {
|
|
71
|
+
h /= 360; s /= 100; l /= 100;
|
|
72
|
+
if (s === 0) { const v = l * 255; return [v, v, v]; }
|
|
73
|
+
const hue2rgb = (p, q, t) => {
|
|
74
|
+
if (t < 0) t += 1; if (t > 1) t -= 1;
|
|
75
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
76
|
+
if (t < 1 / 2) return q;
|
|
77
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
78
|
+
return p;
|
|
79
|
+
};
|
|
80
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
81
|
+
const p = 2 * l - q;
|
|
82
|
+
return [hue2rgb(p, q, h + 1 / 3) * 255, hue2rgb(p, q, h) * 255, hue2rgb(p, q, h - 1 / 3) * 255];
|
|
83
|
+
}
|
|
53
84
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
85
|
+
// Gera escala 50→900 a partir de uma cor base (assumida como 500/primary).
|
|
86
|
+
// Mantém o matiz, varia luminosidade e ajusta saturação por tier.
|
|
87
|
+
function generatePalette(baseHex) {
|
|
88
|
+
const [h, s, l] = rgbToHsl(...hexToRgb(baseHex));
|
|
89
|
+
// Tabela de target lightness por tier; saturação ajustada nos extremos
|
|
90
|
+
const tiers = [
|
|
91
|
+
{ k: 50, L: 95, sScale: 0.55 },
|
|
92
|
+
{ k: 100, L: 88, sScale: 0.70 },
|
|
93
|
+
{ k: 200, L: 78, sScale: 0.85 },
|
|
94
|
+
{ k: 300, L: 68, sScale: 0.95 },
|
|
95
|
+
{ k: 400, L: 58, sScale: 1.00 },
|
|
96
|
+
{ k: 500, L: Math.round(l), sScale: 1.00 }, // mantém a cor base
|
|
97
|
+
{ k: 600, L: Math.max(l - 8, 38), sScale: 1.00 },
|
|
98
|
+
{ k: 700, L: Math.max(l - 18, 28), sScale: 1.00 },
|
|
99
|
+
{ k: 800, L: Math.max(l - 28, 20), sScale: 0.95 },
|
|
100
|
+
{ k: 900, L: Math.max(l - 38, 12), sScale: 0.85 },
|
|
57
101
|
];
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const t of tiers) {
|
|
104
|
+
if (t.k === 500) { out[500] = baseHex.toLowerCase(); continue; }
|
|
105
|
+
const tS = Math.min(100, s * t.sScale);
|
|
106
|
+
const [r, g, b] = hslToRgb(h, tS, t.L);
|
|
107
|
+
out[t.k] = rgbToHex(r, g, b);
|
|
108
|
+
}
|
|
109
|
+
// Rings (rgba)
|
|
110
|
+
const [r5, g5, b5] = hexToRgb(out[500]);
|
|
111
|
+
const [r4, g4, b4] = hexToRgb(out[400]);
|
|
112
|
+
out.ring = `rgba(${r5}, ${g5}, ${b5}, 0.20)`;
|
|
113
|
+
out.ringSoft = `rgba(${r5}, ${g5}, ${b5}, 0.12)`;
|
|
114
|
+
out.ringDark = `rgba(${r4}, ${g4}, ${b4}, 0.30)`;
|
|
115
|
+
out.ringSoftDark = `rgba(${r4}, ${g4}, ${b4}, 0.16)`;
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
58
118
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
119
|
+
// ─── Template engine: tiny Handlebars subset ─────────────────────────────
|
|
120
|
+
// Suporta {{var}}, {{var.nested}}, {{#if x}}...{{/if}}, {{#if x}}A{{else}}B{{/if}}
|
|
121
|
+
|
|
122
|
+
function renderTemplate(src, ctx) {
|
|
123
|
+
const get = (path) => path.split('.').reduce((o, k) => (o == null ? undefined : o[k]), ctx);
|
|
124
|
+
// Process {{#if cond}}...{{else}}...{{/if}} blocks (no nesting needed for our cases)
|
|
125
|
+
let out = src;
|
|
126
|
+
let prev;
|
|
127
|
+
do {
|
|
128
|
+
prev = out;
|
|
129
|
+
out = out.replace(
|
|
130
|
+
/\{\{#if\s+([\w.]+)\s*\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g,
|
|
131
|
+
(_, cond, a, b) => {
|
|
132
|
+
const v = get(cond);
|
|
133
|
+
return v ? a : (b || '');
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
} while (out !== prev);
|
|
137
|
+
// Replace {{var}}
|
|
138
|
+
out = out.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, key) => {
|
|
139
|
+
const v = get(key);
|
|
140
|
+
return v == null ? '' : String(v);
|
|
141
|
+
});
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── FS helpers ──────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
async function copyDir(src, dest, ctx, opts = {}) {
|
|
148
|
+
await fs.mkdir(dest, { recursive: true });
|
|
149
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
150
|
+
for (const e of entries) {
|
|
151
|
+
const sp = path.join(src, e.name);
|
|
152
|
+
const dp = path.join(dest, e.name);
|
|
153
|
+
if (e.isDirectory()) await copyDir(sp, dp, ctx, opts);
|
|
154
|
+
else if (e.isFile()) {
|
|
155
|
+
if (sp.endsWith('.hbs')) {
|
|
156
|
+
const content = await fs.readFile(sp, 'utf-8');
|
|
157
|
+
const rendered = renderTemplate(content, ctx);
|
|
158
|
+
const finalPath = dp.replace(/\.hbs$/, '');
|
|
159
|
+
await fs.writeFile(finalPath, rendered, 'utf-8');
|
|
160
|
+
opts.written?.push(path.relative(opts.targetRoot, finalPath));
|
|
161
|
+
} else {
|
|
162
|
+
await fs.copyFile(sp, dp);
|
|
163
|
+
opts.written?.push(path.relative(opts.targetRoot, dp));
|
|
69
164
|
}
|
|
70
|
-
await fs.writeFile(realFile, current + note, 'utf-8');
|
|
71
|
-
return realFile + ' (nota adicionada)';
|
|
72
|
-
} catch {
|
|
73
|
-
// continue
|
|
74
165
|
}
|
|
75
166
|
}
|
|
76
|
-
return null;
|
|
77
167
|
}
|
|
78
168
|
|
|
169
|
+
// ─── Prompts (readline nativo) ───────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
function makePrompt() {
|
|
172
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
173
|
+
return {
|
|
174
|
+
ask: (q, def) => new Promise(resolve => {
|
|
175
|
+
const prompt = def ? ` ${q} ${c.dim(`(${def})`)} ` : ` ${q} `;
|
|
176
|
+
rl.question(prompt, (a) => resolve(a.trim() || def || ''));
|
|
177
|
+
}),
|
|
178
|
+
yn: (q, def = true) => new Promise(resolve => {
|
|
179
|
+
const d = def ? 'S/n' : 's/N';
|
|
180
|
+
rl.question(` ${q} ${c.dim(`(${d})`)} `, (a) => {
|
|
181
|
+
const v = a.trim().toLowerCase();
|
|
182
|
+
if (v === '') return resolve(def);
|
|
183
|
+
resolve(v.startsWith('s') || v.startsWith('y'));
|
|
184
|
+
});
|
|
185
|
+
}),
|
|
186
|
+
close: () => rl.close(),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function slugify(s) {
|
|
191
|
+
return s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '')
|
|
192
|
+
.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ─── Help + List ─────────────────────────────────────────────────────────
|
|
196
|
+
|
|
79
197
|
function printHelp(version) {
|
|
80
198
|
banner(`zark-design v${version}`);
|
|
81
199
|
console.log(` ${c.bold('Comandos:')}\n`);
|
|
82
|
-
console.log(` ${c.green('init')} ${c.dim('[
|
|
83
|
-
console.log(` ${c.green('
|
|
84
|
-
console.log(` ${c.green('
|
|
85
|
-
console.log(`
|
|
86
|
-
console.log(`
|
|
87
|
-
console.log(`
|
|
88
|
-
console.log(`
|
|
89
|
-
console.log(` ${c.bold('
|
|
90
|
-
console.log(`
|
|
91
|
-
console.log(`
|
|
92
|
-
console.log(`
|
|
200
|
+
console.log(` ${c.green('init')} ${c.dim('[flags]')} cria design system (preset ou custom)`);
|
|
201
|
+
console.log(` ${c.green('presets')} lista presets disponíveis`);
|
|
202
|
+
console.log(` ${c.green('help')} mostra esta ajuda`);
|
|
203
|
+
console.log(` ${c.green('version')} mostra a versão\n`);
|
|
204
|
+
console.log(` ${c.bold('Modos:')}`);
|
|
205
|
+
console.log(` ${c.green('preset ZARK')} paleta + logos ZARK prontos (Liquid Lava #F56F10)`);
|
|
206
|
+
console.log(` ${c.green('custom')} sua cor primária + suas logos\n`);
|
|
207
|
+
console.log(` ${c.bold('Uso interativo:')}`);
|
|
208
|
+
console.log(` npx zark-design init ${c.dim('# pergunta tudo')}`);
|
|
209
|
+
console.log(` npx zark-design init --preset zark ${c.dim('# usa ZARK direto')}`);
|
|
210
|
+
console.log(` npx zark-design init --preset zark --yes ${c.dim('# sem confirms')}\n`);
|
|
211
|
+
console.log(` ${c.bold('Uso custom (flags):')}`);
|
|
212
|
+
console.log(` npx zark-design init --name "ALLSEC" --primary "#E74C3C" --yes`);
|
|
213
|
+
console.log(` npx zark-design init --palette ./minha-paleta.json --yes\n`);
|
|
214
|
+
console.log(` ${c.bold('Output:')}`);
|
|
215
|
+
console.log(` design-system/ ${c.dim('# default; mude com [pasta]')}`);
|
|
216
|
+
console.log(` ├── README.md`);
|
|
217
|
+
console.log(` ├── html/ (HTML puro)`);
|
|
218
|
+
console.log(` ├── jsx/ (React)`);
|
|
219
|
+
console.log(` └── assets/ (logos)\n`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function listPresets() {
|
|
223
|
+
banner('Presets disponíveis');
|
|
224
|
+
const entries = await fs.readdir(PRESETS_DIR, { withFileTypes: true });
|
|
225
|
+
for (const e of entries) {
|
|
226
|
+
if (!e.isDirectory()) continue;
|
|
227
|
+
const cfgPath = path.join(PRESETS_DIR, e.name, 'preset.json');
|
|
228
|
+
if (existsSync(cfgPath)) {
|
|
229
|
+
const cfg = JSON.parse(await fs.readFile(cfgPath, 'utf-8'));
|
|
230
|
+
console.log(` ${c.green('•')} ${c.bold(e.name.padEnd(10))} ${c.dim(`${cfg.name} · brand ${cfg.brand['500']}`)}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
console.log('');
|
|
234
|
+
console.log(c.dim(' Uso: npx zark-design init --preset <nome>'));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Init logic ──────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
function parseArgs(argv) {
|
|
240
|
+
const flags = {}; const positional = [];
|
|
241
|
+
for (let i = 0; i < argv.length; i++) {
|
|
242
|
+
const a = argv[i];
|
|
243
|
+
if (a.startsWith('--')) {
|
|
244
|
+
const key = a.slice(2);
|
|
245
|
+
const next = argv[i + 1];
|
|
246
|
+
if (next === undefined || next.startsWith('--')) { flags[key] = true; }
|
|
247
|
+
else { flags[key] = next; i++; }
|
|
248
|
+
} else positional.push(a);
|
|
249
|
+
}
|
|
250
|
+
return { flags, positional };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function loadPreset(name) {
|
|
254
|
+
const cfgPath = path.join(PRESETS_DIR, name, 'preset.json');
|
|
255
|
+
if (!existsSync(cfgPath)) {
|
|
256
|
+
const available = (await fs.readdir(PRESETS_DIR)).filter(x => !x.startsWith('.'));
|
|
257
|
+
throw new Error(`Preset "${name}" não existe. Disponíveis: ${available.join(', ')}`);
|
|
258
|
+
}
|
|
259
|
+
return JSON.parse(await fs.readFile(cfgPath, 'utf-8'));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function loadCustomPalette(jsonPath) {
|
|
263
|
+
const data = JSON.parse(await fs.readFile(jsonPath, 'utf-8'));
|
|
264
|
+
if (!data.brand || !data.brand['500']) {
|
|
265
|
+
throw new Error('Paleta JSON precisa ter pelo menos { "name": ..., "brand": { "500": "#..." } }');
|
|
266
|
+
}
|
|
267
|
+
// Se faltam tiers, gera os ausentes a partir do 500
|
|
268
|
+
const filled = generatePalette(data.brand['500']);
|
|
269
|
+
data.brand = { ...filled, ...data.brand };
|
|
270
|
+
data.name = data.name || 'Custom';
|
|
271
|
+
data.slug = data.slug || slugify(data.name);
|
|
272
|
+
data.fonts = data.fonts || { ui: 'Inter', mono: 'JetBrains Mono', display: 'Space Grotesk' };
|
|
273
|
+
return data;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function buildCtx({ flags, prompt }) {
|
|
277
|
+
// 1) Preset path
|
|
278
|
+
if (flags.preset) {
|
|
279
|
+
const preset = await loadPreset(flags.preset);
|
|
280
|
+
if (!flags.yes && !prompt) return preset;
|
|
281
|
+
return preset;
|
|
282
|
+
}
|
|
283
|
+
// 2) Palette JSON path
|
|
284
|
+
if (flags.palette) {
|
|
285
|
+
const data = await loadCustomPalette(path.resolve(flags.palette));
|
|
286
|
+
data.preset = null;
|
|
287
|
+
return data;
|
|
288
|
+
}
|
|
289
|
+
// 3) Flags directos (custom não-interativo)
|
|
290
|
+
if (flags.name || flags.primary) {
|
|
291
|
+
const name = flags.name || 'Custom';
|
|
292
|
+
const slug = flags.slug || slugify(name);
|
|
293
|
+
const primary = flags.primary;
|
|
294
|
+
if (!primary) throw new Error('--primary <hex> é obrigatório quando usa --name sem --palette');
|
|
295
|
+
return {
|
|
296
|
+
name, slug, preset: null,
|
|
297
|
+
brand: generatePalette(primary),
|
|
298
|
+
fonts: { ui: 'Inter', mono: 'JetBrains Mono', display: 'Space Grotesk' },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// 4) Interativo
|
|
302
|
+
if (!prompt) throw new Error('Modo interativo precisa de TTY. Use --preset zark ou --primary <hex>.');
|
|
303
|
+
console.log(c.dim(' Pressione Enter pra usar o default em parênteses.\n'));
|
|
304
|
+
const usePreset = await prompt.yn('Usar preset ZARK pronto?', true);
|
|
305
|
+
if (usePreset) return await loadPreset('zark');
|
|
306
|
+
|
|
307
|
+
const name = await prompt.ask('Nome do projeto/empresa:', 'Custom');
|
|
308
|
+
const slug = await prompt.ask('Slug (lowercase, sem espaços):', slugify(name));
|
|
309
|
+
const primary = await prompt.ask('Cor primária (hex, ex: #E74C3C):');
|
|
310
|
+
if (!primary) throw new Error('Cor primária é obrigatória.');
|
|
311
|
+
const palette = generatePalette(primary);
|
|
312
|
+
return {
|
|
313
|
+
name, slug, preset: null,
|
|
314
|
+
brand: palette,
|
|
315
|
+
fonts: { ui: 'Inter', mono: 'JetBrains Mono', display: 'Space Grotesk' },
|
|
316
|
+
};
|
|
93
317
|
}
|
|
94
318
|
|
|
95
319
|
async function runInit(args) {
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const target = path.resolve(positional[0] ?? './design-system');
|
|
320
|
+
const { flags, positional } = parseArgs(args);
|
|
321
|
+
const target = path.resolve(positional[0] || './design-system');
|
|
99
322
|
const designDirName = path.basename(target);
|
|
100
|
-
const
|
|
323
|
+
const force = !!flags.force;
|
|
324
|
+
const yes = !!flags.yes;
|
|
101
325
|
|
|
102
326
|
banner('🎨 zark-design — install');
|
|
103
327
|
|
|
104
328
|
if (existsSync(target)) {
|
|
105
329
|
if (!force) {
|
|
106
|
-
console.error(c.red(`✗ Pasta '${target}' já existe.`));
|
|
107
|
-
console.error(c.dim(` Use --force pra sobrescrever, ou escolha outro nome.`));
|
|
330
|
+
console.error(c.red(`✗ Pasta '${target}' já existe. Use --force pra sobrescrever.`));
|
|
108
331
|
process.exit(1);
|
|
109
332
|
}
|
|
110
333
|
console.log(c.yellow(`⚠ Sobrescrevendo ${target} (--force)`));
|
|
111
334
|
await fs.rm(target, { recursive: true, force: true });
|
|
112
335
|
}
|
|
113
336
|
|
|
114
|
-
|
|
115
|
-
|
|
337
|
+
// Build context (preset OR custom)
|
|
338
|
+
const interactive = !yes && !flags.preset && !flags.palette && !flags.name && process.stdin.isTTY;
|
|
339
|
+
const prompt = interactive ? makePrompt() : null;
|
|
340
|
+
let ctx;
|
|
341
|
+
try {
|
|
342
|
+
ctx = await buildCtx({ flags, prompt });
|
|
343
|
+
} finally { prompt?.close(); }
|
|
116
344
|
|
|
117
|
-
|
|
345
|
+
// Defaults
|
|
346
|
+
ctx.date = new Date().toISOString().slice(0, 10);
|
|
347
|
+
ctx.nameDesc = ctx.preset === 'zark'
|
|
348
|
+
? 'Sistema de design para apps e sistemas internos da ZARK'
|
|
349
|
+
: `Sistema de design para apps e sistemas do(a) **${ctx.name}**`;
|
|
118
350
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
await walk(target);
|
|
351
|
+
console.log(c.dim(' Contexto:'));
|
|
352
|
+
console.log(c.dim(` Nome: ${ctx.name} (slug: ${ctx.slug})`));
|
|
353
|
+
console.log(c.dim(` Modo: ${ctx.preset ? 'preset ' + ctx.preset : 'custom'}`));
|
|
354
|
+
console.log(c.dim(` Brand: ${ctx.brand['500']} (paleta 50→900 gerada)`));
|
|
355
|
+
console.log(c.dim(` Fontes: ${ctx.fonts.ui} / ${ctx.fonts.mono} / ${ctx.fonts.display}`));
|
|
356
|
+
console.log(c.dim(` Destino: ${target}\n`));
|
|
128
357
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
358
|
+
const written = [];
|
|
359
|
+
const copyOpts = { targetRoot: target, written };
|
|
132
360
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
361
|
+
// 1. Copia html/ e jsx/ (estáticos, com substituição de .hbs)
|
|
362
|
+
await copyDir(path.join(TEMPLATES_DIR, 'html'), path.join(target, 'html'), ctx, copyOpts);
|
|
363
|
+
await copyDir(path.join(TEMPLATES_DIR, 'jsx'), path.join(target, 'jsx'), ctx, copyOpts);
|
|
364
|
+
|
|
365
|
+
// 2. Renderiza tokens.css.hbs e tokens.js.hbs em html/ e jsx/
|
|
366
|
+
const tokensCss = renderTemplate(await fs.readFile(path.join(SHARED_DIR, 'tokens.css.hbs'), 'utf-8'), ctx);
|
|
367
|
+
await fs.writeFile(path.join(target, 'html/tokens.css'), tokensCss);
|
|
368
|
+
await fs.writeFile(path.join(target, 'jsx/tokens.css'), tokensCss);
|
|
369
|
+
written.push('html/tokens.css', 'jsx/tokens.css');
|
|
370
|
+
const tokensJs = renderTemplate(await fs.readFile(path.join(SHARED_DIR, 'tokens.js.hbs'), 'utf-8'), ctx);
|
|
371
|
+
await fs.writeFile(path.join(target, 'jsx/tokens.js'), tokensJs);
|
|
372
|
+
written.push('jsx/tokens.js');
|
|
373
|
+
|
|
374
|
+
// 3. README.md
|
|
375
|
+
const readme = renderTemplate(await fs.readFile(path.join(TEMPLATES_DIR, 'README.md.hbs'), 'utf-8'), ctx);
|
|
376
|
+
await fs.writeFile(path.join(target, 'README.md'), readme);
|
|
377
|
+
written.push('README.md');
|
|
378
|
+
|
|
379
|
+
// 4. Assets
|
|
380
|
+
await fs.mkdir(path.join(target, 'assets'), { recursive: true });
|
|
381
|
+
if (ctx.preset && existsSync(path.join(PRESETS_DIR, ctx.preset, 'assets'))) {
|
|
382
|
+
// Preset traz assets prontos
|
|
383
|
+
await copyDir(path.join(PRESETS_DIR, ctx.preset, 'assets'), path.join(target, 'assets'), ctx, copyOpts);
|
|
137
384
|
} else {
|
|
138
|
-
|
|
385
|
+
// Custom: pasta vazia + README instrutivo
|
|
386
|
+
const assetsReadme = renderTemplate(await fs.readFile(path.join(SHARED_DIR, 'ASSETS-README.md.hbs'), 'utf-8'), ctx);
|
|
387
|
+
await fs.writeFile(path.join(target, 'assets/README.md'), assetsReadme);
|
|
388
|
+
written.push('assets/README.md (pasta pra você colar seus logos)');
|
|
139
389
|
}
|
|
140
390
|
|
|
391
|
+
// Log
|
|
392
|
+
for (const f of written.sort()) console.log(` ${c.green('✓')} ${f}`);
|
|
393
|
+
|
|
394
|
+
// Auto-note em CLAUDE.md / .ai-context/CONTEXT.md se existir
|
|
141
395
|
console.log('');
|
|
142
|
-
|
|
396
|
+
const noteResult = await appendNoteToContextFiles(path.dirname(target), designDirName, ctx);
|
|
397
|
+
if (noteResult) console.log(` ${c.green('✓')} ${noteResult}`);
|
|
398
|
+
else console.log(c.dim(` (sem CLAUDE.md ou .ai-context/CONTEXT.md no projeto — sem auto-nota)`));
|
|
399
|
+
|
|
400
|
+
console.log('');
|
|
401
|
+
console.log(c.green(`✓ Design system "${ctx.name}" instalado em ${designDirName}/`));
|
|
143
402
|
console.log('');
|
|
144
403
|
console.log(c.bold('Próximo passo:'));
|
|
145
|
-
|
|
146
|
-
|
|
404
|
+
if (!ctx.preset) {
|
|
405
|
+
console.log(` ${c.yellow('•')} Cole seus logos em ${designDirName}/assets/ (veja assets/README.md)`);
|
|
406
|
+
}
|
|
407
|
+
console.log(` ${c.dim('•')} Diga pra IA: "use o design system em ${designDirName}/ como referência (leia README.md)"`);
|
|
408
|
+
console.log(` ${c.dim('•')} Abra ${designDirName}/html/showcase.html no browser pra ver tudo renderizado.`);
|
|
147
409
|
}
|
|
148
410
|
|
|
411
|
+
async function appendNoteToContextFiles(targetRoot, designDirName, ctx) {
|
|
412
|
+
const note = `\n\n## Design system\nEste projeto usa o **${ctx.name} Design System** (gerado por \`zark-design\`${ctx.preset ? `, preset ${ctx.preset}` : ', modo custom'}). Antes de qualquer trabalho de UI/frontend, leia \`${designDirName}/README.md\`.\n\n2 implementações lado a lado consumindo \`tokens.css\` + \`components.css\` idênticos:\n- \`${designDirName}/html/\` — HTML puro (Blade/Django/Rails/HTML)\n- \`${designDirName}/jsx/\` — React (24 componentes individuais)\n\nVue/Svelte/Solid: use \`tokens.css\` + \`components.css\` direto, adapte os JSX mantendo as classes.\n\nCor de marca: \`var(--brand-500)\` = ${ctx.brand['500']}. Abra \`${designDirName}/html/showcase.html\` no browser pra inspeção visual.\n`;
|
|
413
|
+
const candidates = [
|
|
414
|
+
path.join(targetRoot, '.ai-context', 'CONTEXT.md'),
|
|
415
|
+
path.join(targetRoot, 'CLAUDE.md'),
|
|
416
|
+
];
|
|
417
|
+
for (const file of candidates) {
|
|
418
|
+
if (!existsSync(file)) continue;
|
|
419
|
+
try {
|
|
420
|
+
const stat = await fs.lstat(file);
|
|
421
|
+
const realFile = stat.isSymbolicLink()
|
|
422
|
+
? path.resolve(path.dirname(file), await fs.readlink(file)) : file;
|
|
423
|
+
const current = await fs.readFile(realFile, 'utf-8');
|
|
424
|
+
if (current.includes('Design System') && current.includes('design-system')) return realFile + ' (já tem nota — pulando)';
|
|
425
|
+
await fs.writeFile(realFile, current + note, 'utf-8');
|
|
426
|
+
return realFile + ' (nota adicionada)';
|
|
427
|
+
} catch { /* continue */ }
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ─── Main ────────────────────────────────────────────────────────────────
|
|
433
|
+
|
|
149
434
|
async function main() {
|
|
150
435
|
const argv = process.argv.slice(2);
|
|
151
|
-
const cmd = argv[0]
|
|
436
|
+
const cmd = argv[0] || 'help';
|
|
152
437
|
const version = await getVersion();
|
|
153
|
-
|
|
154
438
|
try {
|
|
155
439
|
switch (cmd) {
|
|
156
|
-
case 'init':
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
case '
|
|
160
|
-
case '--version':
|
|
161
|
-
case '-v':
|
|
162
|
-
console.log(version);
|
|
163
|
-
return;
|
|
164
|
-
case 'help':
|
|
165
|
-
case '--help':
|
|
166
|
-
case '-h':
|
|
167
|
-
default:
|
|
168
|
-
printHelp(version);
|
|
169
|
-
return;
|
|
440
|
+
case 'init': return await runInit(argv.slice(1));
|
|
441
|
+
case 'presets': return await listPresets();
|
|
442
|
+
case 'version': case '--version': case '-v': return console.log(version);
|
|
443
|
+
case 'help': case '--help': case '-h': default: return printHelp(version);
|
|
170
444
|
}
|
|
171
445
|
} catch (e) {
|
|
172
446
|
console.error(c.red(`✗ ${e.message ?? e}`));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zark-design",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Design system scaffolder seguindo método ZARK — `npx zark-design init` em modo preset (ZARK pronto) ou custom (cor e logos suas, paleta 50→900 gerada). HTML + JSX side-by-side. Dark mode nativo. Sem dependência runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"zark-design": "bin/cli.js"
|