nobadfonts-cli 1.1.5 → 1.1.7

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/index.js CHANGED
@@ -133,7 +133,10 @@ async function addFont(fontName) {
133
133
  }
134
134
 
135
135
  if (mainFileDownloaded) {
136
- cssContent.push(`
136
+ // Only add main variant if "regular" is NOT present in variants to avoid duplicates
137
+ const hasRegular = font.font_variants && font.font_variants.some(v => v.variant_name.toLowerCase().includes('regular'));
138
+ if (!hasRegular) {
139
+ cssContent.push(`
137
140
  @font-face {
138
141
  font-family: '${font.name}';
139
142
  src: ${mainCssSrc.join(',\n ')};
@@ -142,6 +145,7 @@ async function addFont(fontName) {
142
145
  font-display: swap;
143
146
  }
144
147
  `);
148
+ }
145
149
  }
146
150
 
147
151
  // Download Variants
@@ -160,12 +164,15 @@ async function addFont(fontName) {
160
164
  let style = 'normal';
161
165
  const nameLower = variant.variant_name.toLowerCase();
162
166
 
163
- if (nameLower.includes('bold')) weight = 700;
164
- if (nameLower.includes('light')) weight = 300;
165
- if (nameLower.includes('medium')) weight = 500;
166
- if (nameLower.includes('black')) weight = 900;
167
167
  if (nameLower.includes('thin')) weight = 100;
168
- if (nameLower.includes('extra')) weight = 800; // heuristic
168
+ else if (nameLower.includes('extra light') || nameLower.includes('extralight')) weight = 200;
169
+ else if (nameLower.includes('light')) weight = 300;
170
+ else if (nameLower.includes('regular')) weight = 400;
171
+ else if (nameLower.includes('medium')) weight = 500;
172
+ else if (nameLower.includes('semi bold') || nameLower.includes('semibold')) weight = 600;
173
+ else if (nameLower.includes('extra bold') || nameLower.includes('extrabold')) weight = 800;
174
+ else if (nameLower.includes('bold')) weight = 700;
175
+ else if (nameLower.includes('black')) weight = 900;
169
176
 
170
177
  if (nameLower.includes('italic')) style = 'italic';
171
178
 
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "nobadfonts-cli",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "CLI tool to instantly install fonts from NoBadFonts",
5
5
  "files": [
6
- "bin/nobadfonts.js",
6
+ "index.js",
7
7
  "package.json",
8
8
  "README.md",
9
9
  "PUBLISHING.md"
10
10
  ],
11
11
  "preferGlobal": true,
12
12
  "bin": {
13
- "nobadfonts-cli": "bin/nobadfonts.js",
14
- "nobadfonts": "bin/nobadfonts.js"
13
+ "nobadfonts-cli": "index.js",
14
+ "nobadfonts": "index.js"
15
15
  },
16
16
  "dependencies": {
17
17
  "chalk": "^4.1.2",
package/bin/nobadfonts.js DELETED
@@ -1,272 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fetch from 'node-fetch';
4
- import fs from 'fs-extra';
5
- import path from 'path';
6
- import chalk from 'chalk';
7
- import ora from 'ora';
8
- import { fileURLToPath } from 'url';
9
-
10
- const SUPABASE_URL = 'https://wcegdxhvgwbeskaidlxr.supabase.co';
11
- const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndjZWdkeGh2Z3diZXNrYWlkbHhyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njk1MjI3OTQsImV4cCI6MjA4NTA5ODc5NH0.P_JY0RF6wVdPCDfWLlcor5l1CP3g4bLE5y4JWmZVOig';
12
-
13
- const args = process.argv.slice(2);
14
- const command = args[0];
15
- const targetFont = args[1];
16
-
17
- if (!command) {
18
- console.log(chalk.yellow('Usage: npx nobadfonts-cli add <font-name>'));
19
- process.exit(1);
20
- }
21
-
22
- if (command === 'add') {
23
- if (!targetFont) {
24
- console.log(chalk.red('Please specify a font name. Example: npx nobadfonts-cli add "Inter"'));
25
- process.exit(1);
26
- }
27
- addFont(targetFont);
28
- } else if (command === 'list') {
29
- listFonts();
30
- } else {
31
- console.log(chalk.red(`Unknown command: ${command}`));
32
- console.log(chalk.yellow('Available commands: add <font>, list'));
33
- process.exit(1);
34
- }
35
-
36
- async function listFonts() {
37
- const spinner = ora('Fetching available fonts...').start();
38
- try {
39
- const res = await fetch(`${SUPABASE_URL}/rest/v1/fonts?select=name,category&limit=50&order=downloads.desc`, {
40
- headers: {
41
- 'apikey': SUPABASE_ANON_KEY,
42
- 'Authorization': `Bearer ${SUPABASE_ANON_KEY}`
43
- }
44
- });
45
- if (!res.ok) throw new Error(res.statusText);
46
- const data = await res.json();
47
- spinner.stop();
48
-
49
- console.log(chalk.bold.cyan('\nAvailable Fonts (Top 50):'));
50
- data.forEach(f => {
51
- console.log(`${chalk.green('•')} ${chalk.white(f.name)} ${chalk.gray(`(${f.category})`)}`);
52
- });
53
- console.log('\nTo install: ' + chalk.yellow('npx nobadfonts-cli add "Font Name"'));
54
-
55
- } catch (e) {
56
- spinner.fail('Error fetching fonts: ' + e.message);
57
- }
58
- }
59
-
60
- async function addFont(fontName) {
61
- const spinner = ora(`Searching for font: ${fontName}...`).start();
62
-
63
- try {
64
- // Search for font by name or slug
65
- const searchUrl = `${SUPABASE_URL}/rest/v1/fonts?or=(name.ilike.*${fontName}*,slug.ilike.*${fontName}*)&select=*,font_variants(*)&limit=1`;
66
-
67
- const response = await fetch(searchUrl, {
68
- headers: {
69
- 'apikey': SUPABASE_ANON_KEY,
70
- 'Authorization': `Bearer ${SUPABASE_ANON_KEY}`
71
- }
72
- });
73
-
74
- if (!response.ok) {
75
- throw new Error(`Failed to fetch font data: ${response.statusText}`);
76
- }
77
-
78
- const data = await response.json();
79
-
80
- if (!data || data.length === 0) {
81
- spinner.fail(chalk.red(`Font "${fontName}" not found.`));
82
- return;
83
- }
84
-
85
- const font = data[0];
86
- spinner.succeed(chalk.green(`Found font: ${font.name}`));
87
-
88
- // download files
89
- const downloadDir = path.join(process.cwd(), 'public', 'fonts', font.slug);
90
- await fs.ensureDir(downloadDir);
91
-
92
- const cssContent = [];
93
- const cssPath = path.join(process.cwd(), 'src', 'index.css'); // Default assumption, maybe configure later? Or creates dedicated file.
94
- let imports = '';
95
-
96
- // Function to download file
97
- const downloadFile = async (url, filename) => {
98
- if (!url) return null;
99
- try {
100
- const res = await fetch(url);
101
- if (!res.ok) throw new Error(`Failed to download ${url}`);
102
- const buffer = await res.buffer();
103
- const filePath = path.join(downloadDir, filename);
104
- await fs.writeFile(filePath, buffer);
105
- return filePath;
106
- } catch (e) {
107
- console.error(chalk.red(`Error downloading ${filename}: ${e.message}`));
108
- return null;
109
- }
110
- };
111
-
112
- spinner.start('Downloading font files...');
113
-
114
- // Download Main Font
115
- // We prefer woff2 -> woff -> ttf -> otf
116
- const mainFiles = [
117
- { url: font.woff2_url, name: `${font.slug}.woff2`, format: 'woff2' },
118
- { url: font.woff_url, name: `${font.slug}.woff`, format: 'woff' },
119
- { url: font.ttf_url, name: `${font.slug}.ttf`, format: 'truetype' },
120
- { url: font.otf_url, name: `${font.slug}.otf`, format: 'opentype' }
121
- ];
122
-
123
- let mainFileDownloaded = false;
124
- let mainCssSrc = [];
125
-
126
- // Prioritize formats for main font
127
- for (const f of mainFiles) {
128
- if (f.url) {
129
- await downloadFile(f.url, f.name);
130
- mainCssSrc.push(`url('/fonts/${font.slug}/${f.name}') format('${f.format}')`);
131
- mainFileDownloaded = true;
132
- }
133
- }
134
-
135
- if (mainFileDownloaded) {
136
- cssContent.push(`
137
- @font-face {
138
- font-family: '${font.name}';
139
- src: ${mainCssSrc.join(',\n ')};
140
- font-weight: normal;
141
- font-style: normal;
142
- font-display: swap;
143
- }
144
- `);
145
- }
146
-
147
- // Download Variants
148
- if (font.font_variants && font.font_variants.length > 0) {
149
- for (const variant of font.font_variants) {
150
- const vFiles = [
151
- { url: variant.woff2_url, name: `${font.slug}-${variant.variant_name}.woff2`, format: 'woff2' },
152
- { url: variant.woff_url, name: `${font.slug}-${variant.variant_name}.woff`, format: 'woff' },
153
- { url: variant.ttf_url, name: `${font.slug}-${variant.variant_name}.ttf`, format: 'truetype' },
154
- { url: variant.otf_url, name: `${font.slug}-${variant.variant_name}.otf`, format: 'opentype' }
155
- ];
156
-
157
- let vCssSrc = [];
158
- // Determine weight/style from name (simple heuristic)
159
- let weight = 400;
160
- let style = 'normal';
161
- const nameLower = variant.variant_name.toLowerCase();
162
-
163
- if (nameLower.includes('bold')) weight = 700;
164
- if (nameLower.includes('light')) weight = 300;
165
- if (nameLower.includes('medium')) weight = 500;
166
- if (nameLower.includes('black')) weight = 900;
167
- if (nameLower.includes('thin')) weight = 100;
168
- if (nameLower.includes('extra')) weight = 800; // heuristic
169
-
170
- if (nameLower.includes('italic')) style = 'italic';
171
-
172
- for (const f of vFiles) {
173
- if (f.url) {
174
- await downloadFile(f.url, f.name);
175
- vCssSrc.push(`url('/fonts/${font.slug}/${f.name}') format('${f.format}')`);
176
- }
177
- }
178
-
179
- if (vCssSrc.length > 0) {
180
- cssContent.push(`
181
- @font-face {
182
- font-family: '${font.name}';
183
- src: ${vCssSrc.join(',\n ')};
184
- font-weight: ${weight};
185
- font-style: ${style};
186
- font-display: swap;
187
- }
188
- `);
189
- }
190
- }
191
- }
192
-
193
- spinner.succeed('Files downloaded successfully.');
194
-
195
- // Write CSS
196
- // Create specific CSS file
197
- const outputCssPath = path.join(process.cwd(), 'src', 'fonts.css');
198
-
199
- // Check if file exists to append or create
200
- // If we append, we check if font-family is already defined to avoid duplicates?
201
- // For simplicity, just append.
202
-
203
- spinner.start('Updating CSS...');
204
-
205
- let existingCss = '';
206
- if (await fs.pathExists(outputCssPath)) {
207
- existingCss = await fs.readFile(outputCssPath, 'utf8');
208
- }
209
-
210
- const newCssBlock = `
211
- /* Font: ${font.name} (Added via NoBadFonts CLI) */
212
- ${cssContent.join('\n')}
213
- `;
214
-
215
- if (!existingCss.includes(`Font: ${font.name}`)) {
216
- await fs.outputFile(outputCssPath, existingCss + newCssBlock);
217
- spinner.succeed(chalk.green(`Updated src/fonts.css with @font-face definitions.`));
218
- } else {
219
- spinner.info(chalk.yellow(`Font ${font.name} definitions already exist in src/fonts.css`));
220
- }
221
-
222
- // Update src/index.css with @theme variable
223
- // This assumes Tailwind 4 mechanism as seen in user's index.css
224
- const indexCssPath = path.join(process.cwd(), 'src', 'index.css');
225
- if (await fs.pathExists(indexCssPath)) {
226
- let indexCss = await fs.readFile(indexCssPath, 'utf8');
227
- // Simple heuristic to name the variable: kebab-case of slug
228
- const variableName = `--font-${font.slug}`;
229
- const variableValue = `"${font.name}", sans-serif;`; // Defaulting to sans-serif for now, could infer from category
230
-
231
- // Check if @theme exists
232
- if (indexCss.includes('@theme {')) {
233
- if (!indexCss.includes(variableName)) {
234
- // Insert inside @theme { ... }
235
- // We find the last closing brace of @theme block? Or just append to the start?
236
- // Safer to append at the end of the block.
237
- // Regex to find @theme block content is tricky.
238
- // Let's try simple string manipulation.
239
- const themeStart = indexCss.indexOf('@theme {');
240
- // We assume the block ends with a closing brace at some indentation level.
241
- // This is fragile but works for simple files.
242
- // Alternative: Insert after `@theme {`
243
- const insertPos = themeStart + '@theme {'.length;
244
- const newEntry = `\n ${variableName}: ${variableValue}`;
245
- indexCss = indexCss.slice(0, insertPos) + newEntry + indexCss.slice(insertPos);
246
-
247
- await fs.writeFile(indexCssPath, indexCss);
248
- spinner.succeed(chalk.green(`Added ${variableName} to src/index.css @theme block.`));
249
- } else {
250
- spinner.info(chalk.yellow(`Variable ${variableName} already exists in src/index.css`));
251
- }
252
- } else {
253
- // Create @theme block if missing (unlikely for this project but good for others)
254
- const newThemeBlock = `
255
- @theme {
256
- ${variableName}: ${variableValue}
257
- }
258
- `;
259
- await fs.appendFile(indexCssPath, newThemeBlock);
260
- spinner.succeed(chalk.green(`Created @theme block in src/index.css with ${variableName}.`));
261
- }
262
- }
263
-
264
- console.log(chalk.blue(`\nSuccess! To use the font:`));
265
- console.log(chalk.cyan(`1. In CSS: font-family: var(--font-${font.slug});`));
266
- console.log(chalk.cyan(`2. In Tailwind: class="font-${font.slug}"`));
267
- console.log(chalk.gray(`(Make sure to import './fonts.css' in your main execution file)`));
268
-
269
- } catch (error) {
270
- spinner.fail(chalk.red(`Error: ${error.message}`));
271
- }
272
- }