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 +13 -6
- package/package.json +4 -4
- package/bin/nobadfonts.js +0 -272
package/index.js
CHANGED
|
@@ -133,7 +133,10 @@ async function addFont(fontName) {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (mainFileDownloaded) {
|
|
136
|
-
|
|
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 =
|
|
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.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "CLI tool to instantly install fonts from NoBadFonts",
|
|
5
5
|
"files": [
|
|
6
|
-
"
|
|
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": "
|
|
14
|
-
"nobadfonts": "
|
|
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
|
-
}
|