mod-build 4.0.83 → 4.0.84

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.84
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.83
4
8
 
5
9
  - Updated `grab-jsdoc` task to use correct resource path for mod-form
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mod-build",
3
- "version": "4.0.83",
3
+ "version": "4.0.84",
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,23 +1,32 @@
1
1
  import fs from 'fs';
2
+ import path from 'path';
2
3
 
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'];
4
+ const baseFoldersToDelete = ['./src/resources', './src/accessible-components', './src/shared-components', './src/.tmp', './src/temp', './public/resources', './jsdoc-types'];
4
5
 
5
6
  const filesToDelete = ['./modform.jsdoc.js'];
6
7
 
7
- foldersToDelete.forEach(async (folder) => {
8
+ // We will want to delete all downloaded fonts (which will be all folders but ./public/fonts/branded/*)
9
+ const fontBaseFolder = './public/fonts';
10
+ const fontFoldersToDelete = fs.existsSync(fontBaseFolder)
11
+ ? fs.readdirSync(fontBaseFolder, { withFileTypes: true }).filter(dirent => dirent.isDirectory() && dirent.name !== 'branded').map(dirent => path.join(fontBaseFolder, dirent.name))
12
+ : [];
13
+
14
+ const foldersToDelete = [...baseFoldersToDelete, ...fontFoldersToDelete];
15
+
16
+ await Promise.all(foldersToDelete.map(async (folder) => {
8
17
  try {
9
18
  await fs.promises.rm(folder, { recursive: true });
10
19
  console.log(`Folder ${folder} deleted successfully`);
11
20
  } catch (err) {
12
21
  // fail silently
13
22
  }
14
- });
23
+ }));
15
24
 
16
- filesToDelete.forEach(async (file) => {
25
+ await Promise.all(filesToDelete.map(async (file) => {
17
26
  try {
18
27
  await fs.promises.rm(file);
19
28
  console.log(`File ${file} deleted successfully`);
20
29
  } catch (err) {
21
30
  // fail silently
22
31
  }
23
- });
32
+ }));
@@ -4,94 +4,145 @@ 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
-
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
- });
14
+ const finished = promisify(stream.finished);
30
15
 
31
16
  const axiosInstance = axios.create();
32
17
  responseInterceptor(axiosInstance);
33
18
 
34
- const streamFontToDestination = (defaultSettings, fontPath) => {
35
- const finished = promisify(stream.finished);
19
+ const getFontPath = (...parts) => path.join(
20
+ defaultSettings.publicFolder,
21
+ defaultSettings.fontsSubfolder,
22
+ ...parts
23
+ );
36
24
 
37
- return new Promise(resolve => {
38
- const filePath = `${defaultSettings.publicFolder}/${defaultSettings.fontsSubfolder}/${fontPath}`;
39
- const folderPath = filePath.split('/').slice(0, -1).join('/');
25
+ const directoryHasItems = (dirPath) => {
26
+ if (!fs.existsSync(dirPath)) {
27
+ return false;
28
+ }
29
+ return fs.readdirSync(dirPath).length > 0;
30
+ };
40
31
 
41
- // Create directory if it doesn't exist
42
- if (!fs.existsSync(folderPath)) {
43
- fs.mkdirSync(folderPath, { recursive: true });
32
+ const getFontDownloadUrls = async (fontName) => {
33
+ try {
34
+ const url = `https://gwfh.mranftl.com/api/fonts/${fontName}?subsets=latin`;
35
+ const response = await axiosInstance.get(url);
36
+ const fontData = response.data;
37
+
38
+ if (!fontData?.variants) {
39
+ throw new Error('Invalid font data received');
44
40
  }
45
-
46
- // if file exists, do not create it again
47
- if (fs.existsSync(filePath)) {
48
- 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 => {
58
- if (resp.status !== 200) {
59
- throw new Error(`${resp.status}: Error while fetching ${options.url}`);
41
+
42
+ const downloadUrls = {};
43
+ const targetWeights = ['400', '700'];
44
+
45
+ fontData.variants.forEach(variant => {
46
+ if (variant.fontStyle === 'normal' && targetWeights.includes(variant.fontWeight)) {
47
+ const variantName = variant.fontWeight === '400' ? 'regular' : 'bold';
48
+ downloadUrls[variantName] = downloadUrls[variantName] || {};
49
+ if (variant.ttf) {
50
+ downloadUrls[variantName].ttf = variant.ttf;
60
51
  }
61
- console.log(`${filePath} copied...`);
62
- resp.data.pipe(writer);
63
- return finished(writer);
64
- }).then(() => {
65
- resolve();
66
- }).catch(error => {
67
- console.error(error);
68
- throw new Error(`${error?.response?.statusText} [${error?.status}]: Error while fetching ${options.url}`);
69
- });
52
+ if (variant.woff2) {
53
+ downloadUrls[variantName].woff2 = variant.woff2;
54
+ }
55
+ }
56
+ });
57
+
58
+ return downloadUrls;
59
+ } catch (error) {
60
+ console.error(`Error fetching font data for ${fontName}:`, error.message);
61
+ throw error;
62
+ }
63
+ };
64
+
65
+ const downloadFontFile = async (fontName, variant, extension, downloadUrl) => {
66
+ const fileName = `${fontName}-${variant}.${extension}`;
67
+ const filePath = getFontPath(fontName, fileName);
68
+ const folderPath = path.dirname(filePath);
69
+
70
+ if (!fs.existsSync(folderPath)) {
71
+ fs.mkdirSync(folderPath, { recursive: true });
72
+ }
73
+
74
+ if (fs.existsSync(filePath)) {
75
+ console.log(`${filePath} already exists, skipping...`);
76
+ return;
77
+ }
78
+
79
+ try {
80
+ const resp = await axiosInstance({
81
+ url: downloadUrl,
82
+ method: 'get',
83
+ responseType: 'stream'
84
+ });
85
+
86
+ if (resp.status !== 200) {
87
+ throw new Error(`${resp.status}: Error while fetching ${downloadUrl}`);
70
88
  }
71
- });
89
+
90
+ console.log(`Downloading ${filePath}...`);
91
+ const writer = createWriteStream(filePath);
92
+ resp.data.pipe(writer);
93
+ await finished(writer);
94
+ console.log(`${filePath} downloaded successfully`);
95
+ } catch (error) {
96
+ console.error(`Error downloading ${filePath} from ${downloadUrl}:`, error.message);
97
+ throw new Error(`Failed to download ${filePath}: ${error.message}`);
98
+ }
72
99
  };
73
100
 
74
- const updateGitignore = async () => {
75
- const fontEntries = fontNames.map(fontName =>
76
- `${defaultSettings.publicFolder}/${defaultSettings.fontsSubfolder}/${fontName}/`
77
- );
101
+ const downloadFont = async (fontName) => {
102
+ const fontDir = getFontPath(fontName);
103
+
104
+ if (directoryHasItems(fontDir)) {
105
+ console.log(`Font directory ${fontDir} already has items, skipping ${fontName}...`);
106
+ return;
107
+ }
108
+
109
+ console.log(`Downloading font: ${fontName}`);
110
+
111
+ const downloadUrls = await getFontDownloadUrls(fontName);
78
112
 
79
- await addFilesToGitignore(fontEntries, 'Global fonts');
113
+ if (!downloadUrls || Object.keys(downloadUrls).length === 0) {
114
+ throw new Error(`Could not find download URLs for ${fontName}`);
115
+ }
116
+
117
+ await Promise.all(
118
+ fontVariants.flatMap(variant =>
119
+ fontExtensions.map(ext => {
120
+ const url = downloadUrls[variant]?.[ext];
121
+ if (!url) {
122
+ console.warn(`Warning: No ${ext} URL found for ${fontName} ${variant}, skipping...`);
123
+ return Promise.resolve();
124
+ }
125
+ return downloadFontFile(fontName, variant, ext, url);
126
+ })
127
+ )
128
+ );
80
129
  };
81
130
 
82
- export default function() {
83
- const fontsPath = `${defaultSettings.publicFolder}/${defaultSettings.fontsSubfolder}`;
131
+ const updateGitignore = async (fontNames) => {
132
+ const fontEntries = fontNames.map(fontName => `${getFontPath(fontName)}/`);
133
+ await addFilesToGitignore(fontEntries, 'Downloaded fonts');
134
+ };
135
+
136
+ // If a site has a branded font that is outside of this API - we should add it to /public/fonts/branded/{fontName}/*
137
+ export default async function(config) {
138
+ // Download fonts from config.fontsToDownload or will default to ['roboto']
139
+ const fontNames = config?.fontsToDownload?.length > 0 ? config.fontsToDownload : ['roboto'];
140
+
141
+ const fontsPath = getFontPath();
84
142
  if (!fs.existsSync(fontsPath)) {
85
143
  fs.mkdirSync(fontsPath, { recursive: true });
86
144
  }
87
145
 
88
- // Download all font files
89
- const fontPromises = fontFiles.map(fontPath => {
90
- return streamFontToDestination(defaultSettings, fontPath);
91
- });
92
-
93
- return Promise.all(fontPromises).then(async () => {
94
- // Update .gitignore after fonts are downloaded
95
- await updateGitignore();
96
- });
97
- }
146
+ await Promise.all(fontNames.map(downloadFont));
147
+ await updateGitignore(fontNames);
148
+ }
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
+ await grabGlobalFonts(config);
17
17
  await grabB2BData(config);
18
18
  await grabCdn(config);
19
19
  await getDefaultTradeQuestions(config);