mod-build 4.0.82 → 4.0.83-beta.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.83
4
+
5
+ - Updated `grab-global-fonts` task to grab fonts from `gwfh.mranftl.com/api/fonts` -- will default to `Roboto` unless defined in `siteconfig.js` = `fontsToDownload: ['font-name']`
6
+
3
7
  ## 4.0.82
4
8
 
5
9
  - Added `grab-global-fonts` task to grab Roboto + Montserrat font files from mod-site
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mod-build",
3
- "version": "4.0.82",
3
+ "version": "4.0.83-beta.2",
4
4
  "description": "Share components for S3 sites.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -2,7 +2,8 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
4
  /**
5
- * Adds entries to .gitignore file (always appends to bottom)
5
+ * Adds entries to .gitignore file
6
+ * If a section with the same comment exists, adds entries below it; otherwise, appends to bottom
6
7
  * @param {string[]} entries - Array of entries to add to .gitignore
7
8
  * @param {string} comment - Optional comment to add before the entries
8
9
  * @returns {Promise<void>}
@@ -27,17 +28,33 @@ export default async function addFilesToGitignore(entries, comment) {
27
28
  return;
28
29
  }
29
30
 
30
- // Build content to append with proper spacing
31
- let contentToAppend = content.trim()
32
- ? (content.endsWith('\n') ? '\n' : '\n\n')
33
- : '';
34
-
31
+ // If comment exists, find the section and insert below it
35
32
  if (comment) {
36
- contentToAppend += `# ${comment}\n`;
33
+ const commentLine = `# ${comment}`;
34
+ const lines = content.split('\n');
35
+ const sectionIndex = lines.findIndex(line => line.trim() === commentLine);
36
+
37
+ if (sectionIndex !== -1) {
38
+ let insertIndex = sectionIndex + 1;
39
+ let lastNonBlankIndex = sectionIndex;
40
+
41
+ while (insertIndex < lines.length && !lines[insertIndex].trim().startsWith('#')) {
42
+ if (lines[insertIndex].trim() !== '') {
43
+ lastNonBlankIndex = insertIndex;
44
+ }
45
+ insertIndex++;
46
+ }
47
+
48
+ lines.splice(lastNonBlankIndex + 1, 0, ...entriesToAppend);
49
+ await fs.promises.writeFile(gitignorePath, lines.join('\n') + '\n', 'utf8');
50
+ console.log(`Added to .gitignore ${comment} section: ${entriesToAppend.join(', ')}`);
51
+ return;
52
+ }
37
53
  }
38
- contentToAppend += entriesToAppend.join('\n') + '\n';
39
54
 
40
- await fs.promises.appendFile(gitignorePath, contentToAppend, 'utf8');
41
- console.log(`Added to .gitignore: ${entriesToAppend.join(', ')}`);
42
- }
55
+ const separator = content.trim() ? (content.endsWith('\n') ? '\n' : '\n\n') : '';
56
+ const newContent = comment ? `${separator}# ${comment}\n${entriesToAppend.join('\n')}\n` : `${separator}${entriesToAppend.join('\n')}\n`;
43
57
 
58
+ await fs.promises.appendFile(gitignorePath, newContent, 'utf8');
59
+ console.log(`Added to .gitignore: ${entriesToAppend.join(', ')}`);
60
+ }
package/tasks/clean.js CHANGED
@@ -1,9 +1,17 @@
1
1
  import fs from 'fs';
2
2
 
3
- const foldersToDelete = ['./src/resources', './src/accessible-components', './src/shared-components', './src/.tmp', './src/temp', './public/resources', './jsdoc-types', './public/fonts/montserrat', './public/fonts/roboto'];
3
+ const baseFoldersToDelete = ['./src/resources', './src/accessible-components', './src/shared-components', './src/.tmp', './src/temp', './public/resources', './jsdoc-types'];
4
4
 
5
5
  const filesToDelete = ['./modform.jsdoc.js'];
6
6
 
7
+ // We will want to delete all downloaded fonts (which will be all folders but ./public/fonts/branded/*)
8
+ const fontBaseFolder = './public/fonts';
9
+ const fontFoldersToDelete = fs.readdirSync(fontBaseFolder, { withFileTypes: true })
10
+ .filter(dirent => dirent.isDirectory() && dirent.name !== 'branded')
11
+ .map(dirent => path.join(fontBaseFolder, dirent.name));
12
+
13
+ const foldersToDelete = [...baseFoldersToDelete, ...fontFoldersToDelete];
14
+
7
15
  foldersToDelete.forEach(async (folder) => {
8
16
  try {
9
17
  await fs.promises.rm(folder, { recursive: true });
@@ -4,74 +4,147 @@ import { createWriteStream } from 'node:fs';
4
4
  import * as stream from 'node:stream';
5
5
  import { promisify } from 'node:util';
6
6
  import fs from 'node:fs';
7
+ import path from 'node:path';
7
8
  import { responseInterceptor } from '../src/scripts/retry-axios.js';
8
9
  import addFilesToGitignore from './add-files-to-gitignore.js';
9
10
 
10
- const resourcePath = 'quote/resources/mod-site/fonts';
11
-
12
- // Global font names
13
- const fontNames = [
14
- 'roboto',
15
- 'montserrat'
16
- ];
17
-
18
11
  // Font variants and extensions to download for each font
19
12
  const fontVariants = ['regular', 'bold'];
20
13
  const fontExtensions = ['ttf', 'woff2'];
21
14
 
22
- // Generate array of all font files needed for download
23
- const fontFiles = fontNames.flatMap(fontName => {
24
- return fontVariants.flatMap(variant => {
25
- return fontExtensions.map(ext => {
26
- return `${fontName}/${fontName}-${variant}.${ext}`;
27
- });
28
- });
29
- });
30
-
31
15
  const axiosInstance = axios.create();
32
16
  responseInterceptor(axiosInstance);
33
17
 
34
- const streamFontToDestination = (defaultSettings, fontPath) => {
18
+ const directoryHasItems = (dirPath) => {
19
+ if (!fs.existsSync(dirPath)) {
20
+ return false;
21
+ }
22
+ const items = fs.readdirSync(dirPath);
23
+ return items.length > 0;
24
+ };
25
+
26
+ const getFontDownloadUrls = async (fontName) => {
27
+ try {
28
+ const url = `https://gwfh.mranftl.com/api/fonts/${fontName}?subsets=latin`;
29
+ const response = await axiosInstance.get(url);
30
+ const fontData = response.data;
31
+
32
+ if (!fontData || !fontData.variants) {
33
+ throw new Error('Invalid font data received');
34
+ }
35
+
36
+ const downloadUrls = {};
37
+
38
+ const targetWeights = ['400', '700'];
39
+
40
+ fontData.variants.forEach(variant => {
41
+ if (variant.fontStyle === 'normal' && targetWeights.includes(variant.fontWeight)) {
42
+ const variantName = variant.fontWeight === '400' ? 'regular' : 'bold';
43
+
44
+ if (!downloadUrls[variantName]) {
45
+ downloadUrls[variantName] = {};
46
+ }
47
+
48
+ if (variant.ttf) {
49
+ downloadUrls[variantName].ttf = variant.ttf;
50
+ }
51
+ if (variant.woff2) {
52
+ downloadUrls[variantName].woff2 = variant.woff2;
53
+ }
54
+ }
55
+ });
56
+
57
+ return downloadUrls;
58
+ } catch (error) {
59
+ console.error(`Error fetching font data for ${fontName}:`, error.message);
60
+ throw error;
61
+ }
62
+ };
63
+
64
+ const downloadFontFile = async (defaultSettings, fontName, variant, extension, downloadUrl) => {
35
65
  const finished = promisify(stream.finished);
36
66
 
37
- return new Promise(resolve => {
38
- const filePath = `${defaultSettings.publicFolder}/${defaultSettings.fontsSubfolder}/${fontPath}`;
39
- const folderPath = filePath.split('/').slice(0, -1).join('/');
67
+ return new Promise((resolve, reject) => {
68
+ const fileName = `${fontName}-${variant}.${extension}`;
69
+ const filePath = path.join(
70
+ defaultSettings.publicFolder,
71
+ defaultSettings.fontsSubfolder,
72
+ fontName,
73
+ fileName
74
+ );
75
+ const folderPath = path.dirname(filePath);
40
76
 
41
- // Create directory if it doesn't exist
42
77
  if (!fs.existsSync(folderPath)) {
43
78
  fs.mkdirSync(folderPath, { recursive: true });
44
79
  }
45
80
 
46
- // if file exists, do not create it again
47
81
  if (fs.existsSync(filePath)) {
82
+ console.log(`${filePath} already exists, skipping...`);
48
83
  resolve();
49
- } else {
50
- const writer = createWriteStream(filePath);
51
- const options = {
52
- url: `https://${defaultSettings.nodeEnv}/${resourcePath}/${fontPath}`,
53
- method: 'get',
54
- responseType: 'stream'
55
- };
56
-
57
- axios(options).then(resp => {
84
+ return;
85
+ }
86
+
87
+ const writer = createWriteStream(filePath);
88
+
89
+ axiosInstance({
90
+ url: downloadUrl,
91
+ method: 'get',
92
+ responseType: 'stream'
93
+ })
94
+ .then(resp => {
58
95
  if (resp.status !== 200) {
59
- throw new Error(`${resp.status}: Error while fetching ${options.url}`);
96
+ throw new Error(`${resp.status}: Error while fetching ${downloadUrl}`);
60
97
  }
61
- console.log(`${filePath} copied...`);
98
+ console.log(`Downloading ${filePath}...`);
62
99
  resp.data.pipe(writer);
63
100
  return finished(writer);
64
- }).then(() => {
101
+ })
102
+ .then(() => {
103
+ console.log(`${filePath} downloaded successfully`);
65
104
  resolve();
66
- }).catch(error => {
67
- console.error(error);
68
- throw new Error(`${error?.response?.statusText} [${error?.status}]: Error while fetching ${options.url}`);
105
+ })
106
+ .catch(error => {
107
+ console.error(`Error downloading ${filePath} from ${downloadUrl}:`, error.message);
108
+ reject(new Error(`Failed to download ${filePath}: ${error.message}`));
69
109
  });
70
- }
71
110
  });
72
111
  };
73
112
 
74
- const updateGitignore = async () => {
113
+ const downloadFont = async (defaultSettings, fontName) => {
114
+ const fontDir = path.join(
115
+ defaultSettings.publicFolder,
116
+ defaultSettings.fontsSubfolder,
117
+ fontName
118
+ );
119
+
120
+ if (directoryHasItems(fontDir)) {
121
+ console.log(`Font directory ${fontDir} already has items, skipping ${fontName}...`);
122
+ return;
123
+ }
124
+
125
+ console.log(`Downloading font: ${fontName}`);
126
+
127
+ const downloadUrls = await getFontDownloadUrls(fontName);
128
+
129
+ if (!downloadUrls || Object.keys(downloadUrls).length === 0) {
130
+ throw new Error(`Could not find download URLs for ${fontName}`);
131
+ }
132
+
133
+ const downloadPromises = fontVariants.flatMap(variant => {
134
+ return fontExtensions.map(ext => {
135
+ const url = downloadUrls[variant]?.[ext];
136
+ if (!url) {
137
+ console.warn(`Warning: No ${ext} URL found for ${fontName} ${variant}, skipping...`);
138
+ return Promise.resolve();
139
+ }
140
+ return downloadFontFile(defaultSettings, fontName, variant, ext, url);
141
+ });
142
+ });
143
+
144
+ await Promise.all(downloadPromises);
145
+ };
146
+
147
+ const updateGitignore = async (defaultSettings, fontNames) => {
75
148
  const fontEntries = fontNames.map(fontName =>
76
149
  `${defaultSettings.publicFolder}/${defaultSettings.fontsSubfolder}/${fontName}/`
77
150
  );
@@ -79,19 +152,23 @@ const updateGitignore = async () => {
79
152
  await addFilesToGitignore(fontEntries, 'Global fonts');
80
153
  };
81
154
 
82
- export default function() {
83
- const fontsPath = `${defaultSettings.publicFolder}/${defaultSettings.fontsSubfolder}`;
155
+ // If a site has a branded font that is outside of this API - we should add it to /public/fonts/branded/{fontName}/*
156
+ export default function(config) {
157
+ // Download fonts from config.fontsToDownload or will default to ['roboto']
158
+ const fontNames = config?.fontsToDownload && Array.isArray(config.fontsToDownload) && config.fontsToDownload.length > 0
159
+ ? config.fontsToDownload
160
+ : ['roboto'];
161
+
162
+ const fontsPath = path.join(defaultSettings.publicFolder, defaultSettings.fontsSubfolder);
84
163
  if (!fs.existsSync(fontsPath)) {
85
164
  fs.mkdirSync(fontsPath, { recursive: true });
86
165
  }
87
166
 
88
- // Download all font files
89
- const fontPromises = fontFiles.map(fontPath => {
90
- return streamFontToDestination(defaultSettings, fontPath);
167
+ const fontPromises = fontNames.map(fontName => {
168
+ return downloadFont(defaultSettings, fontName);
91
169
  });
92
170
 
93
171
  return Promise.all(fontPromises).then(async () => {
94
- // Update .gitignore after fonts are downloaded
95
- await updateGitignore();
172
+ await updateGitignore(defaultSettings, fontNames);
96
173
  });
97
- }
174
+ }
package/tasks/serve.js CHANGED
@@ -13,7 +13,7 @@ import { createStylelintFile, updateConfig } from '../src/scripts/plugins.js';
13
13
  export async function startModBuild(config) {
14
14
  addEditorConfig();
15
15
  createStylelintFile();
16
- grabGlobalFonts();
16
+ grabGlobalFonts(config);
17
17
  await grabB2BData(config);
18
18
  await grabCdn(config);
19
19
  await getDefaultTradeQuestions(config);