joys-fonts 0.1.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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/bin/cli.js +250 -0
  4. package/fonts/joys-inter/joys-inter.css +47 -0
  5. package/fonts/joys-inter/woff2/joys-inter-300.woff2 +0 -0
  6. package/fonts/joys-inter/woff2/joys-inter-400.woff2 +0 -0
  7. package/fonts/joys-inter/woff2/joys-inter-500.woff2 +0 -0
  8. package/fonts/joys-inter/woff2/joys-inter-600.woff2 +0 -0
  9. package/fonts/joys-inter/woff2/joys-inter-700.woff2 +0 -0
  10. package/fonts/joys-inter/woff2/joys-inter-800.woff2 +0 -0
  11. package/fonts/joys-iran-sans-x/joys-iran-sans-x.css +201 -0
  12. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-100.woff2 +0 -0
  13. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-200.woff2 +0 -0
  14. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-300.woff2 +0 -0
  15. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-400.woff2 +0 -0
  16. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-500.woff2 +0 -0
  17. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-600.woff2 +0 -0
  18. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-700.woff2 +0 -0
  19. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-800.woff2 +0 -0
  20. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-900.woff2 +0 -0
  21. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-100.woff2 +0 -0
  22. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-200.woff2 +0 -0
  23. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-300.woff2 +0 -0
  24. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-400.woff2 +0 -0
  25. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-500.woff2 +0 -0
  26. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-600.woff2 +0 -0
  27. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-700.woff2 +0 -0
  28. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-800.woff2 +0 -0
  29. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-fanum-900.woff2 +0 -0
  30. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-100.woff2 +0 -0
  31. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-200.woff2 +0 -0
  32. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-300.woff2 +0 -0
  33. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-400.woff2 +0 -0
  34. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-500.woff2 +0 -0
  35. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-600.woff2 +0 -0
  36. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-700.woff2 +0 -0
  37. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-800.woff2 +0 -0
  38. package/fonts/joys-iran-sans-x/woff2/joys-iran-sans-x-noen-900.woff2 +0 -0
  39. package/fonts/joys-iran-yekan-x/joys-iran-yekan-x.css +81 -0
  40. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-100.woff2 +0 -0
  41. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-1000.woff2 +0 -0
  42. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-200.woff2 +0 -0
  43. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-300.woff2 +0 -0
  44. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-400.woff2 +0 -0
  45. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-500.woff2 +0 -0
  46. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-600.woff2 +0 -0
  47. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-700.woff2 +0 -0
  48. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-800.woff2 +0 -0
  49. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-900.woff2 +0 -0
  50. package/fonts/joys-iran-yekan-x/woff2/joys-iran-yekan-x-950.woff2 +0 -0
  51. package/fonts/joys-kalameh/joys-kalameh.css +201 -0
  52. package/fonts/joys-kalameh/woff2/joys-kalameh-100.woff2 +0 -0
  53. package/fonts/joys-kalameh/woff2/joys-kalameh-200.woff2 +0 -0
  54. package/fonts/joys-kalameh/woff2/joys-kalameh-300.woff2 +0 -0
  55. package/fonts/joys-kalameh/woff2/joys-kalameh-400.woff2 +0 -0
  56. package/fonts/joys-kalameh/woff2/joys-kalameh-500.woff2 +0 -0
  57. package/fonts/joys-kalameh/woff2/joys-kalameh-600.woff2 +0 -0
  58. package/fonts/joys-kalameh/woff2/joys-kalameh-700.woff2 +0 -0
  59. package/fonts/joys-kalameh/woff2/joys-kalameh-800.woff2 +0 -0
  60. package/fonts/joys-kalameh/woff2/joys-kalameh-900.woff2 +0 -0
  61. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-100.woff2 +0 -0
  62. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-200.woff2 +0 -0
  63. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-300.woff2 +0 -0
  64. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-400.woff2 +0 -0
  65. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-500.woff2 +0 -0
  66. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-600.woff2 +0 -0
  67. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-700.woff2 +0 -0
  68. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-800.woff2 +0 -0
  69. package/fonts/joys-kalameh/woff2/joys-kalameh-fanum-900.woff2 +0 -0
  70. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-100.woff2 +0 -0
  71. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-200.woff2 +0 -0
  72. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-300.woff2 +0 -0
  73. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-400.woff2 +0 -0
  74. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-500.woff2 +0 -0
  75. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-600.woff2 +0 -0
  76. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-700.woff2 +0 -0
  77. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-800.woff2 +0 -0
  78. package/fonts/joys-kalameh/woff2/joys-kalameh-noen-900.woff2 +0 -0
  79. package/fonts/joys-morabba/joys-morabba.css +77 -0
  80. package/fonts/joys-morabba/woff2/joys-morabba-200.woff2 +0 -0
  81. package/fonts/joys-morabba/woff2/joys-morabba-300.woff2 +0 -0
  82. package/fonts/joys-morabba/woff2/joys-morabba-400.woff2 +0 -0
  83. package/fonts/joys-morabba/woff2/joys-morabba-500.woff2 +0 -0
  84. package/fonts/joys-morabba/woff2/joys-morabba-600.woff2 +0 -0
  85. package/fonts/joys-morabba/woff2/joys-morabba-700.woff2 +0 -0
  86. package/fonts/joys-morabba/woff2/joys-morabba-800.woff2 +0 -0
  87. package/fonts/joys-morabba/woff2/joys-morabba-900.woff2 +0 -0
  88. package/fonts/joys-morabba/woff2/joys-morabba-950.woff2 +0 -0
  89. package/fonts/joys-vazir/joys-vazir.css +68 -0
  90. package/fonts/joys-vazir/woff2/joys-vazir-100.woff2 +0 -0
  91. package/fonts/joys-vazir/woff2/joys-vazir-200.woff2 +0 -0
  92. package/fonts/joys-vazir/woff2/joys-vazir-300.woff2 +0 -0
  93. package/fonts/joys-vazir/woff2/joys-vazir-400.woff2 +0 -0
  94. package/fonts/joys-vazir/woff2/joys-vazir-500.woff2 +0 -0
  95. package/fonts/joys-vazir/woff2/joys-vazir-600.woff2 +0 -0
  96. package/fonts/joys-vazir/woff2/joys-vazir-700.woff2 +0 -0
  97. package/fonts/joys-vazir/woff2/joys-vazir-800.woff2 +0 -0
  98. package/fonts/joys-vazir/woff2/joys-vazir-900.woff2 +0 -0
  99. package/fonts/joys-yekan-bakh/joys-yekan-bakh.css +48 -0
  100. package/fonts/joys-yekan-bakh/woff2/joys-yekan-bakh-fanum-vf.woff2 +0 -0
  101. package/fonts/joys-yekan-bakh/woff2/joys-yekan-bakh-noen-vf.woff2 +0 -0
  102. package/fonts/joys-yekan-bakh/woff2/joys-yekan-bakh-vf.woff2 +0 -0
  103. package/fonts.manifest.json +734 -0
  104. package/package.json +35 -0
  105. package/src/index.d.ts +42 -0
  106. package/src/index.js +189 -0
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "joys-fonts",
3
+ "version": "0.1.0",
4
+ "description": "Persian web fonts installer for Tailwind CSS projects",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
+ "bin": {
9
+ "joys-fonts": "bin/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "node generate-manifest.js",
13
+ "test": "node test/cli.test.js",
14
+ "deploy": "npm run build && git add . && (git diff --cached --quiet || git commit -m \"chore: update assets and manifest\") && git push"
15
+ },
16
+ "files": [
17
+ "bin",
18
+ "src",
19
+ "fonts",
20
+ "fonts.manifest.json"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "keywords": [
26
+ "fonts",
27
+ "persian",
28
+ "tailwind",
29
+ "tailwindcss",
30
+ "installer",
31
+ "cli"
32
+ ],
33
+ "author": "joys",
34
+ "license": "MIT"
35
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ export interface FontFile {
2
+ weight: string;
3
+ url: string;
4
+ }
5
+
6
+ export interface FontFamily {
7
+ family: string;
8
+ weights: number[] | null;
9
+ weight_range: [number, number] | null;
10
+ stretch_range: [string, string] | null;
11
+ files: FontFile[];
12
+ }
13
+
14
+ export interface FontClass {
15
+ class: string;
16
+ font_family: string | null;
17
+ is_base: boolean;
18
+ }
19
+
20
+ export interface FontEntry {
21
+ slug: string;
22
+ css: string;
23
+ families: FontFamily[];
24
+ classes: FontClass[];
25
+ }
26
+
27
+ export const manifest: FontEntry[];
28
+
29
+ export function detectTailwindVersion(): number;
30
+
31
+ export function patchTailwindV4(cssFilePath: string, destPath: string, fontEntry: FontEntry): void;
32
+
33
+ export function patchTailwindV3Config(configFilePath: string, fontEntry: FontEntry): void;
34
+
35
+ export interface InstallOptions {
36
+ slug: string;
37
+ destPath: string;
38
+ cssPath?: string;
39
+ tailwindVersion?: number;
40
+ }
41
+
42
+ export function installFont(options: InstallOptions): Promise<void>;
package/src/index.js ADDED
@@ -0,0 +1,189 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const pkgRoot = path.join(__dirname, '..');
7
+ const manifestPath = path.join(pkgRoot, 'fonts.manifest.json');
8
+
9
+ export const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
10
+
11
+ /**
12
+ * Automatically detects the Tailwind CSS version of the user's project
13
+ * @returns {number} 3 or 4
14
+ */
15
+ export function detectTailwindVersion() {
16
+ try {
17
+ if (fs.existsSync('package.json')) {
18
+ const userPkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
19
+ const deps = { ...userPkg.dependencies, ...userPkg.devDependencies };
20
+ if (deps.tailwindcss) {
21
+ const v = deps.tailwindcss.replace(/[^0-9.]/g, '');
22
+ if (v.startsWith('4')) return 4;
23
+ if (v.startsWith('3')) return 3;
24
+ }
25
+ }
26
+ } catch (_) {}
27
+
28
+ // Fallback check for config files (Tailwind v3 uses config files)
29
+ const v3Configs = ['tailwind.config.js', 'tailwind.config.ts', 'tailwind.config.cjs', 'tailwind.config.mjs'];
30
+ for (const config of v3Configs) {
31
+ if (fs.existsSync(config)) return 3;
32
+ }
33
+
34
+ // Default to v4
35
+ return 4;
36
+ }
37
+
38
+ /**
39
+ * Patches a Tailwind CSS v4 CSS file to inject imports and theme variables
40
+ * @param {string} cssFilePath
41
+ * @param {string} destPath
42
+ * @param {object} fontEntry
43
+ */
44
+ export function patchTailwindV4(cssFilePath, destPath, fontEntry) {
45
+ let cssContent = fs.readFileSync(cssFilePath, 'utf8');
46
+
47
+ // 1. Inject @import
48
+ const relativeCssPath = path.relative(path.dirname(cssFilePath), path.join(destPath, `${fontEntry.slug}.css`)).replace(/\\/g, '/');
49
+ const relativeImportStr = relativeCssPath.startsWith('.') ? relativeCssPath : `./${relativeCssPath}`;
50
+ const importStatement = `@import "${relativeImportStr}";`;
51
+
52
+ if (!cssContent.includes(importStatement)) {
53
+ cssContent = `${importStatement}\n${cssContent}`;
54
+ }
55
+
56
+ // 2. Inject @theme variables
57
+ const varsToInject = [];
58
+ for (const cls of fontEntry.classes) {
59
+ if (cls.font_family && cls.class.startsWith('font-')) {
60
+ const varName = `--font-${cls.class.substring(5)}`;
61
+ const varValue = `"${cls.font_family}", sans-serif`;
62
+ const varDecl = `${varName}: ${varValue};`;
63
+ if (!cssContent.includes(varName)) {
64
+ varsToInject.push(varDecl);
65
+ }
66
+ }
67
+ }
68
+
69
+ if (varsToInject.length > 0) {
70
+ const themeBlockRegex = /@theme\s*\{([^}]*)\}/s;
71
+ const themeMatch = cssContent.match(themeBlockRegex);
72
+
73
+ if (themeMatch) {
74
+ const fullThemeBlock = themeMatch[0];
75
+ const innerContent = themeMatch[1];
76
+ const newInnerContent = innerContent.trimEnd() + '\n ' + varsToInject.join('\n ') + '\n';
77
+ const updatedThemeBlock = `@theme {\n ${newInnerContent.trim()}\n}`;
78
+ cssContent = cssContent.replace(fullThemeBlock, updatedThemeBlock);
79
+ } else {
80
+ cssContent = cssContent.trimEnd() + `\n\n@theme {\n ${varsToInject.join('\n ')}\n}\n`;
81
+ }
82
+ }
83
+
84
+ fs.writeFileSync(cssFilePath, cssContent, 'utf8');
85
+ }
86
+
87
+ /**
88
+ * Patches a Tailwind CSS v3 JS/TS configuration file
89
+ * @param {string} configFilePath
90
+ * @param {object} fontEntry
91
+ */
92
+ export function patchTailwindV3Config(configFilePath, fontEntry) {
93
+ let configContent = fs.readFileSync(configFilePath, 'utf8');
94
+
95
+ const configLines = [];
96
+ for (const cls of fontEntry.classes) {
97
+ if (cls.font_family && cls.class.startsWith('font-')) {
98
+ const name = cls.class.substring(5);
99
+ const line = `'${name}': ['${cls.font_family}', 'sans-serif'],`;
100
+ if (!configContent.includes(`'${name}':`) && !configContent.includes(`"${name}":`)) {
101
+ configLines.push(line);
102
+ }
103
+ }
104
+ }
105
+
106
+ if (configLines.length === 0) return;
107
+
108
+ const fontFamilyRegex = /(fontFamily\s*:\s*\{)/;
109
+ if (fontFamilyRegex.test(configContent)) {
110
+ configContent = configContent.replace(fontFamilyRegex, `$1\n ${configLines.join('\n ')}`);
111
+ } else {
112
+ const extendRegex = /(extend\s*:\s*\{)/;
113
+ if (extendRegex.test(configContent)) {
114
+ configContent = configContent.replace(extendRegex, `$1\n fontFamily: {\n ${configLines.join('\n ')}\n },`);
115
+ } else {
116
+ const themeRegex = /(theme\s*:\s*\{)/;
117
+ if (themeRegex.test(configContent)) {
118
+ configContent = configContent.replace(themeRegex, `$1\n extend: {\n fontFamily: {\n ${configLines.join('\n ')}\n }\n },`);
119
+ } else {
120
+ const exportRegex = /(module\.exports\s*=\s*\{|export\s*default\s*\{)/;
121
+ if (exportRegex.test(configContent)) {
122
+ configContent = configContent.replace(exportRegex, `$1\n theme: {\n extend: {\n fontFamily: {\n ${configLines.join('\n ')}\n }\n }\n },`);
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ fs.writeFileSync(configFilePath, configContent, 'utf8');
129
+ }
130
+
131
+ /**
132
+ * Installs a font into the project and patches the configuration
133
+ * @param {object} options
134
+ * @param {string} options.slug - The font identifier (e.g. joys-vazir)
135
+ * @param {string} options.destPath - Destination folder path
136
+ * @param {string} [options.cssPath] - Main CSS stylesheet path
137
+ * @param {number} [options.tailwindVersion] - Tailwind CSS version (auto-detected if omitted)
138
+ */
139
+ export async function installFont(options) {
140
+ const { slug, destPath, cssPath } = options;
141
+
142
+ const fontEntry = manifest.find(f => f.slug === slug);
143
+ if (!fontEntry) {
144
+ throw new Error(`Font slug "${slug}" not found in manifest.`);
145
+ }
146
+
147
+ const srcDir = path.join(pkgRoot, 'fonts', slug);
148
+ if (!fs.existsSync(srcDir)) {
149
+ throw new Error(`Source font folder not found in package: ${srcDir}`);
150
+ }
151
+
152
+ // 1. Copy font assets
153
+ fs.mkdirSync(destPath, { recursive: true });
154
+ fs.cpSync(srcDir, destPath, { recursive: true });
155
+
156
+ // 2. Patch Tailwind configurations
157
+ if (cssPath && fs.existsSync(cssPath)) {
158
+ const version = options.tailwindVersion || detectTailwindVersion();
159
+
160
+ if (version === 4) {
161
+ patchTailwindV4(cssPath, destPath, fontEntry);
162
+ } else if (version === 3) {
163
+ // Inject CSS import first
164
+ let cssContent = fs.readFileSync(cssPath, 'utf8');
165
+ const relativeCssPath = path.relative(path.dirname(cssPath), path.join(destPath, `${slug}.css`)).replace(/\\/g, '/');
166
+ const relativeImportStr = relativeCssPath.startsWith('.') ? relativeCssPath : `./${relativeCssPath}`;
167
+ const importStatement = `@import "${relativeImportStr}";`;
168
+
169
+ if (!cssContent.includes(importStatement)) {
170
+ cssContent = `${importStatement}\n${cssContent}`;
171
+ fs.writeFileSync(cssPath, cssContent, 'utf8');
172
+ }
173
+
174
+ // Find tailwind.config file
175
+ const configFiles = ['tailwind.config.js', 'tailwind.config.ts', 'tailwind.config.cjs', 'tailwind.config.mjs'];
176
+ let configPath = null;
177
+ for (const config of configFiles) {
178
+ if (fs.existsSync(config)) {
179
+ configPath = path.resolve(config);
180
+ break;
181
+ }
182
+ }
183
+
184
+ if (configPath) {
185
+ patchTailwindV3Config(configPath, fontEntry);
186
+ }
187
+ }
188
+ }
189
+ }