@brewedby/ds 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,118 @@
1
+ # @brewedby/ds
2
+
3
+ > Fynd One Design System — CLI installer
4
+
5
+ Sets up design tokens, font declarations, icon library, and AI context (`CLAUDE.md`) in any Fynd project. Works with Astro, Next.js, Vite, React, and vanilla HTML.
6
+
7
+ ---
8
+
9
+ ## Usage
10
+
11
+ ### One-time install in a project (recommended)
12
+ ```bash
13
+ npx @brewedby/ds init
14
+ ```
15
+
16
+ ### Or install globally and run anywhere
17
+ ```bash
18
+ npm install -g @brewedby/ds
19
+ fynd-ds init
20
+ ```
21
+
22
+ ---
23
+
24
+ ## What it does
25
+
26
+ Runs an interactive setup that:
27
+
28
+ 1. **Detects** your framework and package manager automatically
29
+ 2. **Asks** where to place tokens, fonts, and whether to write `CLAUDE.md`
30
+ 3. **Writes** `fynd-tokens.css` to your styles directory (with font paths patched for your project structure)
31
+ 4. **Creates** a font directory with a `.gitkeep` placeholder for Fynd Sans files
32
+ 5. **Writes** `CLAUDE.md` at project root (AI context for Claude / Cursor)
33
+ 6. **Prepends** the token import to your global stylesheet
34
+ 7. **Installs** `@fontsource/inter-display`, `@fontsource/inter`, and `lucide-react` if you want them
35
+
36
+ ---
37
+
38
+ ## After install — the one manual step
39
+
40
+ Fynd Sans is a proprietary font and cannot be distributed via npm.
41
+ After running `fynd-ds init`, place the `.woff2` files in the fonts directory the installer created:
42
+
43
+ ```
44
+ FyndSans-Regular.woff2
45
+ FyndSans-Medium.woff2
46
+ FyndSans-SemiBold.woff2
47
+ FyndSans-Bold.woff2
48
+ ```
49
+
50
+ Obtain them from the design assets repo or contact **design@fynd.com**.
51
+
52
+ ---
53
+
54
+ ## Files installed
55
+
56
+ | File | Purpose |
57
+ |------|---------|
58
+ | `{stylesDir}/fynd-tokens.css` | All CSS custom properties — tokens, spacing, radius, component tokens |
59
+ | `FYND_DESIGN_SYSTEM.md` | Full design system reference doc |
60
+ | `CLAUDE.md` | AI context file (if opted in) — Claude and Cursor read this automatically |
61
+ | `.fynd-ds-readme.md` | Quick-reference for the installed configuration |
62
+ | `{fontsDir}/.gitkeep` | Placeholder for Fynd Sans font files |
63
+
64
+ ---
65
+
66
+ ## Token reference
67
+
68
+ After install, use CSS variables directly:
69
+
70
+ ```css
71
+ /* Typography */
72
+ font-family: var(--font-family--primary); /* Fynd Sans — headings */
73
+ font-family: var(--font-family--secondary); /* Inter Display — body */
74
+ font-family: var(--font-family--ui); /* Inter — buttons, nav */
75
+
76
+ /* Colors */
77
+ color: var(--text--title); /* #0e0e0e */
78
+ color: var(--text--subtext); /* #5b5c5d */
79
+ background: var(--blue--blue-fill); /* #f9fbff */
80
+
81
+ /* Spacing */
82
+ gap: var(--spacing--24); /* 24px */
83
+
84
+ /* Radius */
85
+ border-radius: var(--border-radius--pill); /* 250px — buttons */
86
+ border-radius: var(--border-radius--16); /* 16px — cards */
87
+ ```
88
+
89
+ ## Icons
90
+
91
+ ```bash
92
+ # Installed automatically if you opt in
93
+ npm install lucide-react
94
+ ```
95
+
96
+ ```jsx
97
+ import { ArrowRight, ShoppingBag } from 'lucide-react'
98
+ <ArrowRight size={16} strokeWidth={1.5} />
99
+ ```
100
+
101
+ Always use `strokeWidth={1.5}` — Fynd icon convention.
102
+
103
+ ---
104
+
105
+ ## Publishing to internal registry
106
+
107
+ ```bash
108
+ # Set your internal npm registry
109
+ npm config set @brewedby:registry https://your-registry.company.com
110
+
111
+ # Publish
112
+ npm publish
113
+ ```
114
+
115
+ Then any team member can run:
116
+ ```bash
117
+ npx @brewedby/ds init
118
+ ```
package/bin/fynd-ds.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const { run } = require('../lib/installer');
6
+
7
+ run().catch((err) => {
8
+ console.error(err.message);
9
+ process.exit(1);
10
+ });
@@ -0,0 +1,367 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+ const prompts = require('prompts');
7
+ const k = require('kleur');
8
+
9
+ const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
10
+
11
+ // ─────────────────────────────────────────────────────────────
12
+ // Helpers
13
+ // ─────────────────────────────────────────────────────────────
14
+
15
+ function exists(p) {
16
+ return fs.existsSync(p);
17
+ }
18
+
19
+ function ensureDir(p) {
20
+ fs.mkdirSync(p, { recursive: true });
21
+ }
22
+
23
+ function write(dest, content) {
24
+ ensureDir(path.dirname(dest));
25
+ fs.writeFileSync(dest, content, 'utf8');
26
+ }
27
+
28
+ function copy(src, dest) {
29
+ ensureDir(path.dirname(dest));
30
+ fs.copyFileSync(src, dest);
31
+ }
32
+
33
+ function readTemplate(name) {
34
+ return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf8');
35
+ }
36
+
37
+ function detectPackageManager(cwd) {
38
+ if (exists(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
39
+ if (exists(path.join(cwd, 'yarn.lock'))) return 'yarn';
40
+ if (exists(path.join(cwd, 'bun.lockb'))) return 'bun';
41
+ return 'npm';
42
+ }
43
+
44
+ function detectFramework(cwd) {
45
+ const pkg = path.join(cwd, 'package.json');
46
+ if (!exists(pkg)) return 'unknown';
47
+ const json = JSON.parse(fs.readFileSync(pkg, 'utf8'));
48
+ const deps = { ...json.dependencies, ...json.devDependencies };
49
+ if (deps['astro']) return 'astro';
50
+ if (deps['next']) return 'nextjs';
51
+ if (deps['vite']) return 'vite';
52
+ if (deps['react']) return 'react';
53
+ return 'vanilla';
54
+ }
55
+
56
+ function installDeps(packages, pm) {
57
+ const cmd = {
58
+ npm: `npm install --save-dev ${packages.join(' ')}`,
59
+ pnpm: `pnpm add -D ${packages.join(' ')}`,
60
+ yarn: `yarn add --dev ${packages.join(' ')}`,
61
+ bun: `bun add -d ${packages.join(' ')}`,
62
+ }[pm];
63
+ execSync(cmd, { stdio: 'inherit' });
64
+ }
65
+
66
+ // ─────────────────────────────────────────────────────────────
67
+ // Framework-specific paths
68
+ // ─────────────────────────────────────────────────────────────
69
+
70
+ const FRAMEWORK_CONFIG = {
71
+ astro: {
72
+ label: 'Astro',
73
+ tokensPath: 'src/styles/fynd-tokens.css',
74
+ fontsPath: 'public/fonts',
75
+ claudePath: 'CLAUDE.md',
76
+ globalsCss: 'src/styles/globals.css',
77
+ fontUrlBase: '/fonts',
78
+ },
79
+ nextjs: {
80
+ label: 'Next.js',
81
+ tokensPath: 'src/styles/fynd-tokens.css',
82
+ fontsPath: 'public/fonts',
83
+ claudePath: 'CLAUDE.md',
84
+ globalsCss: 'src/app/globals.css',
85
+ fontUrlBase: '/fonts',
86
+ },
87
+ vite: {
88
+ label: 'Vite / React',
89
+ tokensPath: 'src/styles/fynd-tokens.css',
90
+ fontsPath: 'src/assets/fonts',
91
+ claudePath: 'CLAUDE.md',
92
+ globalsCss: 'src/styles/globals.css',
93
+ fontUrlBase: '/assets/fonts',
94
+ },
95
+ react: {
96
+ label: 'React (CRA)',
97
+ tokensPath: 'src/styles/fynd-tokens.css',
98
+ fontsPath: 'public/fonts',
99
+ claudePath: 'CLAUDE.md',
100
+ globalsCss: 'src/index.css',
101
+ fontUrlBase: '/fonts',
102
+ },
103
+ vanilla: {
104
+ label: 'Vanilla HTML',
105
+ tokensPath: 'assets/css/fynd-tokens.css',
106
+ fontsPath: 'assets/fonts',
107
+ claudePath: 'CLAUDE.md',
108
+ globalsCss: null,
109
+ fontUrlBase: '/assets/fonts',
110
+ },
111
+ };
112
+
113
+ // ─────────────────────────────────────────────────────────────
114
+ // Tokens file — patch font URL to match project's asset path
115
+ // ─────────────────────────────────────────────────────────────
116
+
117
+ function patchTokensFontPaths(content, fontUrlBase) {
118
+ return content.replace(
119
+ /url\('\/assets\/fonts\/(FyndSans-[^']+)'\)/g,
120
+ `url('${fontUrlBase}/$1')`
121
+ );
122
+ }
123
+
124
+ // ─────────────────────────────────────────────────────────────
125
+ // Globals CSS import snippet
126
+ // ─────────────────────────────────────────────────────────────
127
+
128
+ function buildGlobalsSnippet(tokensPath, framework) {
129
+ const relativePath = tokensPath.startsWith('src/')
130
+ ? './' + path.basename(tokensPath)
131
+ : '../' + tokensPath;
132
+
133
+ const googleFonts = [
134
+ `/* Fynd One Design System */`,
135
+ `@import '${relativePath}'; /* tokens + Fynd Sans @font-face */`,
136
+ ``,
137
+ `/* Inter Display + Inter via @fontsource (run: npm install @fontsource/inter-display @fontsource/inter) */`,
138
+ `/* @import '@fontsource/inter-display/400.css'; */`,
139
+ `/* @import '@fontsource/inter-display/500.css'; */`,
140
+ `/* @import '@fontsource/inter-display/600.css'; */`,
141
+ `/* @import '@fontsource/inter-display/700.css'; */`,
142
+ `/* @import '@fontsource/inter/400.css'; */`,
143
+ `/* @import '@fontsource/inter/500.css'; */`,
144
+ `/* @import '@fontsource/inter/600.css'; */`,
145
+ ].join('\n');
146
+
147
+ return googleFonts;
148
+ }
149
+
150
+ // ─────────────────────────────────────────────────────────────
151
+ // .fynd-ds-readme.md — placed at project root, quick reference
152
+ // ─────────────────────────────────────────────────────────────
153
+
154
+ function buildProjectReadme(fw, config) {
155
+ return `# Fynd One Design System — Installed
156
+
157
+ **Framework:** ${config.label}
158
+ **Tokens:** \`${config.tokensPath}\`
159
+ **CLAUDE.md:** \`${config.claudePath}\`
160
+
161
+ ## Next steps
162
+
163
+ ### 1 — Add Fynd Sans font files
164
+ Place the following \`.woff2\` files in \`${config.fontsPath}/\`:
165
+ \`\`\`
166
+ ${config.fontsPath}/FyndSans-Regular.woff2
167
+ ${config.fontsPath}/FyndSans-Medium.woff2
168
+ ${config.fontsPath}/FyndSans-SemiBold.woff2
169
+ ${config.fontsPath}/FyndSans-Bold.woff2
170
+ \`\`\`
171
+ Get them from the design assets repo or contact design@fynd.com.
172
+
173
+ ### 2 — Install open-source fonts
174
+ \`\`\`bash
175
+ npm install @fontsource/inter-display @fontsource/inter
176
+ \`\`\`
177
+ Then uncomment the \`@fontsource\` import lines in your global stylesheet.
178
+
179
+ ### 3 — Install Lucide icons
180
+ \`\`\`bash
181
+ npm install lucide-react
182
+ \`\`\`
183
+
184
+ ### 4 — Import tokens in your global stylesheet
185
+ \`\`\`css
186
+ @import './fynd-tokens.css'; /* always first */
187
+ \`\`\`
188
+
189
+ Refer to \`${config.claudePath}\` for full component patterns, token reference, and dos/don'ts.
190
+ `;
191
+ }
192
+
193
+ // ─────────────────────────────────────────────────────────────
194
+ // Main run()
195
+ // ─────────────────────────────────────────────────────────────
196
+
197
+ async function run() {
198
+ const cwd = process.cwd();
199
+
200
+ console.log('');
201
+ console.log(k.bold().white(' Fynd One Design System') + k.dim(' — installer'));
202
+ console.log('');
203
+
204
+ const detectedFramework = detectFramework(cwd);
205
+ const detectedPm = detectPackageManager(cwd);
206
+
207
+ // ── Prompt 1: framework ──────────────────────────────────
208
+ const { framework } = await prompts({
209
+ type: 'select',
210
+ name: 'framework',
211
+ message: 'Which framework is this project using?',
212
+ choices: [
213
+ { title: 'Astro', value: 'astro', description: detectedFramework === 'astro' ? '(detected)' : '' },
214
+ { title: 'Next.js', value: 'nextjs', description: detectedFramework === 'nextjs' ? '(detected)' : '' },
215
+ { title: 'Vite / React', value: 'vite', description: detectedFramework === 'vite' ? '(detected)' : '' },
216
+ { title: 'React (CRA)', value: 'react', description: detectedFramework === 'react' ? '(detected)' : '' },
217
+ { title: 'Vanilla HTML', value: 'vanilla', description: '' },
218
+ ],
219
+ initial: ['astro','nextjs','vite','react','vanilla'].indexOf(detectedFramework) > -1
220
+ ? ['astro','nextjs','vite','react','vanilla'].indexOf(detectedFramework)
221
+ : 0,
222
+ }, { onCancel: () => process.exit(0) });
223
+
224
+ const config = FRAMEWORK_CONFIG[framework];
225
+
226
+ // ── Prompt 2: tokens destination ────────────────────────
227
+ const { tokensPath } = await prompts({
228
+ type: 'text',
229
+ name: 'tokensPath',
230
+ message: 'Where should fynd-tokens.css be placed?',
231
+ initial: config.tokensPath,
232
+ }, { onCancel: () => process.exit(0) });
233
+
234
+ // ── Prompt 3: font files destination ────────────────────
235
+ const { fontsPath } = await prompts({
236
+ type: 'text',
237
+ name: 'fontsPath',
238
+ message: 'Where will Fynd Sans .woff2 files live?',
239
+ initial: config.fontsPath,
240
+ hint: 'You\'ll drop the files here after install',
241
+ }, { onCancel: () => process.exit(0) });
242
+
243
+ // ── Prompt 4: CLAUDE.md ──────────────────────────────────
244
+ const { writeClaude } = await prompts({
245
+ type: 'confirm',
246
+ name: 'writeClaude',
247
+ message: 'Write CLAUDE.md at project root? (recommended for Claude / Cursor)',
248
+ initial: true,
249
+ }, { onCancel: () => process.exit(0) });
250
+
251
+ // ── Prompt 5: install @fontsource packages ───────────────
252
+ const { installFontsource } = await prompts({
253
+ type: 'confirm',
254
+ name: 'installFontsource',
255
+ message: `Install @fontsource/inter-display + @fontsource/inter via ${detectedPm}?`,
256
+ initial: true,
257
+ }, { onCancel: () => process.exit(0) });
258
+
259
+ // ── Prompt 6: install lucide-react ──────────────────────
260
+ const { installLucide } = await prompts({
261
+ type: 'confirm',
262
+ name: 'installLucide',
263
+ message: `Install lucide-react (icon library) via ${detectedPm}?`,
264
+ initial: framework !== 'vanilla',
265
+ }, { onCancel: () => process.exit(0) });
266
+
267
+ console.log('');
268
+
269
+ // ── Derive font URL base from fontsPath ──────────────────
270
+ // e.g. "public/fonts" → "/fonts", "src/assets/fonts" → "/assets/fonts"
271
+ let fontUrlBase = config.fontUrlBase;
272
+ if (fontsPath !== config.fontsPath) {
273
+ // User changed the path — try to infer URL base
274
+ const stripped = fontsPath.replace(/^public/, '').replace(/^src\//, '/');
275
+ fontUrlBase = stripped.startsWith('/') ? stripped : `/${stripped}`;
276
+ }
277
+
278
+ // ── Write fynd-tokens.css ────────────────────────────────
279
+ const step = (msg) => console.log(' ' + k.green('✔') + ' ' + msg);
280
+ const warn = (msg) => console.log(' ' + k.yellow('!') + ' ' + k.yellow(msg));
281
+
282
+ let tokensContent = readTemplate('fynd-tokens.css');
283
+ tokensContent = patchTokensFontPaths(tokensContent, fontUrlBase);
284
+ const tokensDest = path.join(cwd, tokensPath);
285
+ write(tokensDest, tokensContent);
286
+ step(`fynd-tokens.css → ${k.dim(tokensPath)}`);
287
+
288
+ // ── Write FYND_DESIGN_SYSTEM.md / CLAUDE.md ──────────────
289
+ const dsContent = readTemplate('FYND_DESIGN_SYSTEM.md');
290
+ if (writeClaude) {
291
+ const claudeDest = path.join(cwd, 'CLAUDE.md');
292
+ const alreadyExists = exists(claudeDest);
293
+ write(claudeDest, dsContent);
294
+ step(`CLAUDE.md → ${k.dim('CLAUDE.md')}${alreadyExists ? k.dim(' (overwritten)') : ''}`);
295
+ }
296
+ // Always write the full reference doc
297
+ write(path.join(cwd, 'FYND_DESIGN_SYSTEM.md'), dsContent);
298
+ step(`FYND_DESIGN_SYSTEM.md → ${k.dim('FYND_DESIGN_SYSTEM.md')}`);
299
+
300
+ // ── Create fonts directory with .gitkeep ─────────────────
301
+ const fontsDest = path.join(cwd, fontsPath);
302
+ ensureDir(fontsDest);
303
+ const gitkeep = path.join(fontsDest, '.gitkeep');
304
+ if (!exists(gitkeep)) {
305
+ write(gitkeep, '# Place FyndSans-*.woff2 font files here\n# Contact design@fynd.com to obtain them\n');
306
+ }
307
+ step(`Font directory ready → ${k.dim(fontsPath + '/')}`);
308
+
309
+ // ── Append import to globals CSS ─────────────────────────
310
+ if (config.globalsCss) {
311
+ const globalsPath = path.join(cwd, config.globalsCss);
312
+ const snippet = buildGlobalsSnippet(tokensPath, framework);
313
+ if (exists(globalsPath)) {
314
+ const existing = fs.readFileSync(globalsPath, 'utf8');
315
+ if (!existing.includes('fynd-tokens.css')) {
316
+ fs.writeFileSync(globalsPath, snippet + '\n\n' + existing, 'utf8');
317
+ step(`Import prepended → ${k.dim(config.globalsCss)}`);
318
+ } else {
319
+ warn(`Skipped globals.css — fynd-tokens.css already imported`);
320
+ }
321
+ } else {
322
+ write(globalsPath, snippet + '\n');
323
+ step(`Created ${k.dim(config.globalsCss)} with token import`);
324
+ }
325
+ }
326
+
327
+ // ── Write quick-reference readme ─────────────────────────
328
+ const projectReadme = buildProjectReadme(framework, {
329
+ ...config,
330
+ tokensPath,
331
+ fontsPath,
332
+ });
333
+ write(path.join(cwd, '.fynd-ds-readme.md'), projectReadme);
334
+ step(`.fynd-ds-readme.md → ${k.dim('.fynd-ds-readme.md')} (quick reference)`);
335
+
336
+ // ── Install packages ─────────────────────────────────────
337
+ const toInstall = [];
338
+ if (installFontsource) toInstall.push('@fontsource/inter-display', '@fontsource/inter');
339
+ if (installLucide) toInstall.push('lucide-react');
340
+
341
+ if (toInstall.length > 0) {
342
+ console.log('');
343
+ console.log(k.dim(' Installing packages...'));
344
+ try {
345
+ installDeps(toInstall, detectedPm);
346
+ step(`Installed: ${toInstall.join(', ')}`);
347
+ } catch {
348
+ warn(`Package install failed — run manually:`);
349
+ warn(` ${detectedPm} install ${toInstall.join(' ')}`);
350
+ }
351
+ }
352
+
353
+ // ── Summary ───────────────────────────────────────────────
354
+ console.log('');
355
+ console.log(k.bold().white(' Fynd DS installed') + k.green(' ✔'));
356
+ console.log('');
357
+ console.log(k.dim(' One manual step remaining:'));
358
+ console.log('');
359
+ console.log(` ${k.white('Drop Fynd Sans .woff2 files into:')} ${k.cyan(fontsPath + '/')}`);
360
+ console.log(` ${k.dim('FyndSans-Regular.woff2, FyndSans-Medium.woff2, FyndSans-SemiBold.woff2, FyndSans-Bold.woff2')}`);
361
+ console.log(` ${k.dim('Obtain from the design assets repo or contact design@fynd.com')}`);
362
+ console.log('');
363
+ console.log(` ${k.dim('Full reference: FYND_DESIGN_SYSTEM.md')}`);
364
+ console.log('');
365
+ }
366
+
367
+ module.exports = { run };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@brewedby/ds",
3
+ "version": "1.0.0",
4
+ "description": "Fynd One Design System installer — sets up tokens, fonts, icons and CLAUDE.md in any project",
5
+ "keywords": [
6
+ "fynd",
7
+ "design-system",
8
+ "cli",
9
+ "tokens",
10
+ "css"
11
+ ],
12
+ "license": "UNLICENSED",
13
+ "private": false,
14
+ "bin": {
15
+ "fynd-ds": "./bin/fynd-ds.js"
16
+ },
17
+ "files": [
18
+ "bin/",
19
+ "lib/",
20
+ "templates/"
21
+ ],
22
+ "scripts": {
23
+ "start": "node bin/fynd-ds.js"
24
+ },
25
+ "engines": {
26
+ "node": ">=16.0.0"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/fynd/ds-cli"
31
+ },
32
+ "devDependencies": {
33
+ "kleur": "^4.1.5",
34
+ "prompts": "^2.4.2"
35
+ }
36
+ }