@massimo-cassandro/create-favicons 1.2.0 → 1.3.1

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.mjs CHANGED
@@ -9,12 +9,13 @@ import * as path from 'path';
9
9
 
10
10
  import chalk from 'chalk';
11
11
 
12
+ import { parseParams } from './src/parse-params.mjs';
13
+ import { default_params } from './src/default-params.mjs';
12
14
  import { createFavicons } from './src/create-favicons.mjs';
13
- import { defaults } from './src/defaults.mjs';
14
15
  import { init } from './src/init.mjs';
15
16
 
16
17
  // nome del file di configurazione, se utilizzato
17
- const cfg_filename = 'create-favicons-cfg.mjs';
18
+ const config_filename = 'create-favicons-cfg.mjs';
18
19
 
19
20
  try {
20
21
 
@@ -25,39 +26,39 @@ try {
25
26
 
26
27
  } else {
27
28
 
28
- let params = {};
29
+ let work_dir;
29
30
  const dir_param_idx = process.argv.findIndex(el => /^--dir/.test(el) );
30
31
 
31
32
  if(dir_param_idx !== -1) {
32
- [, params.work_dir] = process.argv[dir_param_idx].split('=');
33
+ [, work_dir] = process.argv[dir_param_idx].split('=');
33
34
 
34
35
  } else {
35
- params.work_dir = './';
36
+ work_dir = './';
36
37
  }
37
38
 
38
- if(fs.existsSync(path.resolve(params.work_dir, cfg_filename))) {
39
+ work_dir = path.resolve(process.cwd(), work_dir) ;
39
40
 
40
- import(path.resolve(params.work_dir, cfg_filename))
41
- .then((custom_params) => {
42
41
 
43
- if(Array.isArray(custom_params.default)) {
42
+ if(fs.existsSync(path.resolve(work_dir, config_filename))) {
44
43
 
45
- custom_params.default.forEach(item => {
46
- createFavicons({ ...defaults, ...params, ...item });
47
- });
44
+ import(path.resolve(work_dir, config_filename))
45
+ .then((config_params) => {
46
+
47
+ const imported_params = [...(Array.isArray(config_params.default)? config_params.default : [config_params.default])];
48
+
49
+ imported_params.forEach(params_item => {
50
+ createFavicons(parseParams(params_item, work_dir));
51
+ });
48
52
 
49
- } else {
50
- createFavicons({ ...defaults, ...params, ...custom_params.default });
51
- }
52
53
  });
53
54
 
54
- } else if(fs.existsSync(path.resolve(params.work_dir, defaults.src_img))) {
55
+ } else if(default_params.src_img && fs.existsSync(path.resolve(work_dir, default_params.src_img))) {
55
56
 
56
- createFavicons({...defaults, ...params});
57
+ createFavicons(parseParams(null, work_dir));
57
58
 
58
59
  } else {
59
60
 
60
- throw `'${cfg_filename}' e '${defaults.src_img}' non presenti`;
61
+ throw new Error( `'${config_filename}' e '${defaults.src_img}' non presenti`);
61
62
  }
62
63
 
63
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/create-favicons",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Favicon files builder",
5
5
  "bin": {
6
6
  "create-favicons": "./index.mjs"
@@ -42,8 +42,8 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "chalk": "^5.3.0",
45
- "sharp": "^0.32.6",
46
- "svgo": "^3.2.0",
47
- "to-ico": "^1.1.5"
45
+ "sharp": "^0.33.2",
46
+ "sharp-ico": "^0.1.5",
47
+ "svgo": "^3.2.0"
48
48
  }
49
49
  }
package/readme.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  Crea i file favicons come descritto in [How to Favicon in 2024](https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs).
4
4
 
5
- Utilizzo:
5
+ I file da elaborare devono essere in formato SVG, mentre quelli generati sono in formato SVG (favicon), PNG (apple-touch-icon e altri file per android), e ICO (altro favicon per compatibilità con i browser meno recenti). Vengono inoltre genrati i file `manifest.webmanifest` e uno snippet html, opzionale, con i tag `link` per l'inserimento degli elementi generati.
6
+
7
+ Le immagini sono generate con [Sharp](https://sharp.pixelplumbing.com/), [SVGO](https://github.com/svg/svgo) e [sharp-ico](https://github.com/ssnangua/sharp-ico).
8
+
9
+ ## Utilizzo
6
10
 
7
11
  ```bash
8
12
  npx create-favicons [--dir=./path/to/dir]
@@ -32,7 +36,7 @@ const params = [{ /* ... */ }];
32
36
  export default params;
33
37
  ```
34
38
 
35
- `params` può essere un ogetto o un array. In quest'ultimo caso, ogni elemento dell'array corrisponde ad un diverso set di favicons.
39
+ `params` può essere un oggetto o un array. In quest'ultimo caso, ogni elemento dell'array corrisponde ad un diverso set di favicons.
36
40
 
37
41
  Per creare un file di cfg di esempio **nella directory corrente** (con tutti i valori di default e la loro descrizione),
38
42
  utilizzare il comando:
@@ -1,186 +1,36 @@
1
1
  /* eslint-disable no-console */
2
2
 
3
- import * as fs from 'fs';
4
- import * as path from 'path';
3
+ // import * as fs from 'fs';
4
+ // import * as path from 'path';
5
5
  import chalk from 'chalk';
6
6
 
7
- import sharp from 'sharp';
8
- import toIco from 'to-ico';
9
- import { optimize } from 'svgo';
10
-
11
- import { printFrame } from './print-frame.mjs';
12
- import { remove_homedir_string } from './remove-homedir-string.mjs';
7
+ import { createIco } from './create-ico.mjs';
8
+ import { createPng } from './create-png.mjs';
9
+ import { createSvg } from './create-svg.mjs';
10
+ import { createWebmanifest } from './create-webmanifest.mjs';
11
+ import { createSnippet } from './create-snippet.mjs';
12
+ import { printResult } from './print-result.mjs';
13
13
 
14
14
 
15
15
  export function createFavicons(params) {
16
16
 
17
- // const files = [];
18
-
19
17
  try {
20
18
 
21
- const output_dir = path.resolve(params.work_dir, params.output_dir);
22
-
23
- if (!fs.existsSync(output_dir)){
24
- fs.mkdirSync(output_dir);
25
- }
19
+ console.error(chalk.green('Creating favicons...'));
26
20
 
27
-
28
- // https://sharp.pixelplumbing.com/api-constructor
29
- // https://sharp.pixelplumbing.com/api-output#png
30
21
  Promise.all([
31
- ['apple-touch-icon.png', 180],
32
- ['icon-192.png', 192],
33
- ['icon-512.png', 512]
34
- ].map(size => {
35
- sharp(path.resolve(params.work_dir, params.src_img))
36
- .resize({ width: size[1], fit: 'inside' })
37
- .png(params.png_parameters)
38
- // .then(info => console.log(info))
39
- // .toFile(`${output_dir}/${size[0]}`)
40
- .toBuffer()
41
- .then(bufferData => {
42
- fs.writeFileSync(`${output_dir}/${size[0]}`, bufferData);
43
-
44
- const stats = fs.statSync(`${output_dir}/${size[0]}`);
45
- // console.log(`${size[0]}: ${(stats.size / 1024).toFixed(2)} kb`);
46
- // files.push(`${size[0]}: ${(stats.size / 1024).toFixed(2)} kb`);
47
-
48
- })
49
- .catch(err => { throw err; });
50
-
51
- }));
52
-
53
- // favicon.ico
54
- // https://github.com/kevva/to-ico
55
- // alternativa: https://github.com/steambap/png-to-ico
56
-
57
- // const ico_source = fs.readFileSync(`${output_dir}/apple-touch-icon.png`);
58
-
59
- sharp(path.resolve(params.work_dir, params.small_src_img?? params.src_img))
60
- .png()
61
- // .then(info => console.log(info))
62
- .toBuffer()
63
- .then(bufferData => {
64
-
65
- toIco([bufferData], {
66
- sizes: [16, 32],
67
- resize: true
68
- }).then( result => {
69
- fs.writeFileSync(`${output_dir}/favicon.ico`, result);
70
- });
22
+ createSvg(params),
23
+ createPng(params),
24
+ createIco(params),
25
+ createWebmanifest(params),
26
+ createSnippet(params),
27
+ ])
28
+ .then(result => {
29
+ printResult(params);
71
30
 
72
31
  })
73
- .catch(err => { throw err; });
74
-
75
-
76
- // favicon.svg (SVGO)
77
- // TODO add extra config ???
78
- const svgString = fs.readFileSync(path.resolve(params.work_dir, params.small_src_img?? params.src_img), 'utf8');
79
- const svg_result = optimize(svgString, {
80
- multipass: true,
81
- });
82
- fs.writeFileSync(`${output_dir}/favicon.svg`, svg_result.data, {force: true});
83
-
84
- // web manifest
85
- const manifest = {
86
- icons: [192, 512].map( size => {
87
- return { src: `./icon-${size}.png`, type: 'image/png', sizes: `${size}x${size}` };
88
- }),
89
- ...(params.webmanifest_extra?? {})
90
- };
91
-
92
- fs.writeFileSync(`${output_dir}/manifest.webmanifest`, JSON.stringify(manifest, null, ' '));
93
-
94
-
95
- const snippet_path = params.snippet_path? path.resolve(params.work_dir, params.snippet_path) : output_dir;
96
-
97
- // snippet
98
- if(params.snippet_name || params.snippet_target_file) {
99
-
100
- if (!params.snippet_target_file && !fs.existsSync(snippet_path)){
101
- fs.mkdirSync(snippet_path);
102
- }
103
-
104
- params.snippet_language = params.snippet_language.toLowerCase();
105
-
106
- const cache_buster = params.add_cache_buster? `?_=${Date.now()}` : '',
107
- create_href = nome_file => params.href_template.replace('%_file_name_%', nome_file)
108
- .replace('%_cache_buster_%', cache_buster);
109
-
110
-
111
- let snippet_content = `<link rel="icon" href="${create_href('favicon.ico')}" sizes="any">\n` +
112
- `<link rel="icon" href="${create_href('favicon.svg')}" type="image/svg+xml">\n` +
113
- `<link rel="apple-touch-icon" href="${create_href('apple-touch-icon.png')}">\n` +
114
- `<link rel="manifest" href="${create_href('manifest.webmanifest')}">`;
115
-
116
- if (params.snippet_language === 'pug') {
117
- snippet_content = snippet_content.replace(/<link (.*?)>/g, 'link($1)');
118
- }
119
-
120
- snippet_content = params.snippet_template.replace('%_link_tags_%', snippet_content);
121
-
122
- if (params.snippet_target_file) {
123
-
124
- params.snippet_target_file = path.resolve(params.work_dir, params.snippet_target_file);
125
-
126
- if (fs.existsSync(params.snippet_target_file)) {
127
- const targetFileContent = fs.readFileSync(path.resolve(params.snippet_target_file), 'utf8'),
128
- regexp = /<!-- ?favicon-snippet-start ?-->(.*?)<!-- ?favicon-snippet-end ?-->/mis;
129
-
130
- fs.writeFileSync(params.snippet_target_file,
131
- targetFileContent.replace(regexp,
132
- `<!-- favicon-snippet-start -->\n${snippet_content}\n<!-- favicon-snippet-end -->`
133
- )
134
- );
135
-
136
- } else {
137
- throw `Il file '${params.snippet_target_file}' non esiste`;
138
- }
139
-
140
- } else {
141
-
142
- fs.writeFileSync(
143
- `${snippet_path}/${params.snippet_name}`,
144
- snippet_content
145
- );
146
-
147
- }
148
- }
149
-
150
- // print result
151
- let extra_strings = [];
152
-
153
- if(snippet_path !== output_dir && params.snippet_name !== null) {
154
- extra_strings = [
155
- {string: ''},
156
- {string: `Il file snippet '${params.snippet_name}' è stato salvato nella directory:`, color: 'green'},
157
- {string: remove_homedir_string(snippet_path), color: 'yellow'},
158
- ];
159
-
160
- } else if(params.snippet_name === null) {
161
- extra_strings = [
162
- {string: ''},
163
- {string: 'Il file snippet non è stato generato', color: 'magentaBright'},
164
- ];
165
- }
166
-
167
-
168
- printFrame({
169
- strings: [
170
- {string: '** Creazione favicons completata **', color: 'bgGreen'},
171
- {string: ''},
172
- {string: 'I file generati sono nella directory:', color: 'green'},
173
- {string: remove_homedir_string(output_dir), color: 'yellow'},
174
- // {string: ''},
175
- // {string: '*' + files.join('\n'), color: 'yellow'},
176
- ...extra_strings
177
- ],
178
- frameColor: 'green',
179
- frameType: 'single'
180
- });
181
32
 
182
33
  } catch(err) {
183
-
184
34
  console.error(chalk.bgRed(` ${err} `));
185
35
  }
186
36
 
@@ -0,0 +1,32 @@
1
+ import * as fs from 'fs';
2
+ import sharp from 'sharp';
3
+ import toIco from 'to-ico';
4
+
5
+
6
+ // https://github.com/kevva/to-ico
7
+ // alternativa: https://github.com/steambap/png-to-ico
8
+
9
+ // const ico_source = fs.readFileSync(`${output_dir}/apple-touch-icon.png`);
10
+
11
+ export async function createIco(params) {
12
+
13
+ await sharp(params.small_src_img?? params.src_img)
14
+ // .png()
15
+ // .then(info => console.log(info))
16
+ .toBuffer()
17
+ .then(bufferData => {
18
+
19
+ toIco([bufferData], {
20
+ sizes: [16, 32],
21
+ resize: true
22
+ }).then( async result => {
23
+ await fs.promises.writeFile(`${params.output_dir}/favicon.ico`, result, {
24
+ encoding: "utf8",
25
+ flag: "w",
26
+ mode: 0o666
27
+ });
28
+ });
29
+
30
+ })
31
+ .catch(err => { throw new Error( err + ' (createIco)'); });
32
+ }
@@ -0,0 +1,18 @@
1
+ import sharp from 'sharp';
2
+ import ico from 'sharp-ico';
3
+
4
+ // https://github.com/ssnangua/sharp-ico
5
+
6
+ export async function createIco(params) {
7
+
8
+ ico.sharpsToIco(
9
+ [sharp(params.small_src_img?? params.src_img)],
10
+ `${params.output_dir}/favicon.ico`,
11
+ {
12
+ sizes: [16, 32],
13
+ // sizes: "default", // equal to [256, 128, 64, 48, 32, 24, 16]
14
+ resizeOptions: {}, // sharp resize optinos
15
+ }
16
+ );
17
+
18
+ }
@@ -0,0 +1,21 @@
1
+ import sharp from 'sharp';
2
+
3
+ export async function createPng(params) {
4
+
5
+ // https://sharp.pixelplumbing.com/api-constructor
6
+ // https://sharp.pixelplumbing.com/api-output#png
7
+ Promise.all([
8
+ ['apple-touch-icon.png', 180],
9
+ ['icon-192.png', 192],
10
+ ['icon-512.png', 512]
11
+ ].map(file => {
12
+ sharp(params.src_img)
13
+ .resize({ width: file[1], fit: 'inside' })
14
+ .png(params.png_parameters)
15
+ .toFile(`${params.output_dir}/${file[0]}`)
16
+
17
+ }))
18
+ .catch(err => { throw new Error( err ); });
19
+
20
+
21
+ }
@@ -0,0 +1,50 @@
1
+ import * as fs from 'fs';
2
+
3
+ export async function createSnippet(params) {
4
+
5
+
6
+ if(params.create_snippet) {
7
+
8
+
9
+ const cache_buster = params.add_cache_buster? `?_=${Date.now()}` : '',
10
+ create_href = nome_file => params.href_template.replace('%_file_name_%', nome_file)
11
+ .replace('%_cache_buster_%', cache_buster);
12
+
13
+
14
+ let snippet_content = `<link rel="icon" href="${create_href('favicon.ico')}" sizes="32x32">\n` +
15
+ `<link rel="icon" href="${create_href('favicon.svg')}" type="image/svg+xml">\n` +
16
+ `<link rel="apple-touch-icon" href="${create_href('apple-touch-icon.png')}">\n` +
17
+ `<link rel="manifest" href="${create_href('manifest.webmanifest')}">`;
18
+
19
+ if (params.snippet_language === 'pug') {
20
+ snippet_content = snippet_content.replace(/<link (.*?)>/g, 'link($1)');
21
+ }
22
+
23
+ snippet_content = params.snippet_template.replace('%_link_tags_%', snippet_content);
24
+
25
+ if (params.snippet_target_file) {
26
+
27
+ if (fs.existsSync(params.snippet_target_file)) {
28
+ const targetFileContent = fs.readFileSync(params.snippet_target_file, 'utf8'),
29
+ regexp = /<!-- ?favicon-snippet-start ?-->(.*?)<!-- ?favicon-snippet-end ?-->/mis;
30
+
31
+ fs.promises.writeFile(params.snippet_target_file,
32
+ targetFileContent.replace(regexp,
33
+ `<!-- favicon-snippet-start -->\n${snippet_content}\n<!-- favicon-snippet-end -->`
34
+ )
35
+ );
36
+
37
+ } else {
38
+ throw new Error( `Il file '${params.snippet_target_file}' non esiste` );
39
+ }
40
+
41
+ } else {
42
+
43
+ fs.promises.writeFile(
44
+ `${params.snippet_path}/${params.snippet_name}`,
45
+ snippet_content
46
+ );
47
+
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,15 @@
1
+ import { optimize } from 'svgo';
2
+ import * as fs from 'fs/promises';
3
+
4
+ export async function createSvg(params) {
5
+
6
+ // TODO add extra config ???
7
+ fs.readFile(params.small_src_img?? params.src_img, { encoding: 'utf8' })
8
+ .then(svgString => {
9
+
10
+ const svg_result = optimize(svgString, { multipass: true});
11
+
12
+ fs.writeFile(`${params.output_dir}/favicon.svg`, svg_result.data);
13
+
14
+ });
15
+ }
@@ -0,0 +1,14 @@
1
+ import * as fs from 'fs/promises';
2
+
3
+ export async function createWebmanifest(params) {
4
+ // web manifest
5
+ const manifest = {
6
+ icons: [192, 512].map( size => {
7
+ return { src: `./icon-${size}.png`, type: 'image/png', sizes: `${size}x${size}` };
8
+ }),
9
+ ...(params.webmanifest_extra?? {})
10
+ };
11
+
12
+ fs.writeFile(`${params.output_dir}/manifest.webmanifest`, JSON.stringify(manifest, null, ' '));
13
+
14
+ }
@@ -1,4 +1,4 @@
1
- export const defaults = {
1
+ export const default_params = {
2
2
 
3
3
  // delimitazione porzione da copiare nel file init
4
4
  /*** INIT START ***/
package/src/init.mjs CHANGED
@@ -22,7 +22,7 @@ export function init() {
22
22
 
23
23
  const start_string = '/*** INIT START ***/',
24
24
  end_string = '/*** INIT END ***/',
25
- init_start_text = fs.readFileSync(src_dir +'./init-start.txt', 'utf8');
25
+ init_start_text = fs.readFileSync(src_dir +'./init-start-text.txt', 'utf8');
26
26
 
27
27
 
28
28
 
@@ -0,0 +1,48 @@
1
+ import { default_params } from './default-params.mjs';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+
5
+ export function parseParams(config_params = null, work_dir = process.cwd()) {
6
+
7
+ try {
8
+
9
+ const params = {...default_params, ...(config_params??{})}
10
+ params.work_dir = work_dir;
11
+
12
+ [
13
+ 'src_img',
14
+ 'small_src_img',
15
+ 'snippet_path',
16
+ 'snippet_target_file',
17
+ 'output_dir'
18
+ ].forEach(item =>
19
+ params[item] = params[item]? path.resolve(params.work_dir, params[item]) : null
20
+ );
21
+
22
+ // creazione dir output se non esiste
23
+ if (!fs.existsSync(params.output_dir)){
24
+ fs.mkdirSync(params.output_dir);
25
+ }
26
+
27
+ // definizione snippet_path e creazione dir se necessario
28
+ params.create_snippet = params.snippet_name || params.snippet_target_file;
29
+
30
+ if(params.create_snippet) {
31
+
32
+ params.snippet_path = params.snippet_path?? params.output_dir;
33
+
34
+ if (!params.snippet_target_file && !fs.existsSync(params.snippet_path )){
35
+ fs.mkdirSync(params.snippet_path );
36
+ }
37
+
38
+ params.snippet_language = params.snippet_language.toLowerCase();
39
+ }
40
+
41
+
42
+ return params;
43
+
44
+ } catch(e) {
45
+ console.error( e ); // eslint-disable-line
46
+ }
47
+
48
+ }
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-console */
2
2
  import chalk from 'chalk';
3
3
 
4
+ // https://github.com/chalk/chalk
4
5
  export function printFrame(options) {
5
6
 
6
7
  /*
@@ -0,0 +1,61 @@
1
+ import { printFrame } from './print-frame.mjs';
2
+ import { remove_homedir_string } from './remove-homedir-string.mjs';
3
+ import * as fs from 'fs';
4
+
5
+
6
+ // https://github.com/chalk/chalk
7
+ export function printResult(params) {
8
+
9
+ function printSize(size) {
10
+ if(size > 1024) {
11
+ return `${(size / 1024).toFixed(2)} KB`;
12
+ } else {
13
+ return `${size} B`;
14
+
15
+ }
16
+ }
17
+
18
+ // print result
19
+ let extra_strings = [];
20
+
21
+ // immagini generate e dimensioni
22
+ [
23
+ 'apple-touch-icon.png',
24
+ 'favicon.ico',
25
+ 'favicon.svg',
26
+ 'icon-192.png',
27
+ 'icon-512.png',
28
+ 'manifest.webmanifest'
29
+ ].forEach(item => {
30
+ const stats = fs.statSync(`${params.output_dir}/${item}`);
31
+ extra_strings.push({string: `${item}: ${printSize(stats.size)}`, color: 'greenBright'});
32
+ });
33
+
34
+ if(params.snippet_path !== params.output_dir && params.snippet_name !== null) {
35
+ extra_strings.push(
36
+ {string: ''},
37
+ {string: `Il file snippet '${params.snippet_name}' è stato salvato nella directory:`, color: 'green'},
38
+ {string: remove_homedir_string(params.snippet_path ), color: 'yellow'},
39
+ );
40
+
41
+ } else if(params.snippet_name === null) {
42
+ extra_strings.push(
43
+ {string: ''},
44
+ {string: 'Il file snippet non è stato generato', color: 'magentaBright'},
45
+ );
46
+ }
47
+
48
+
49
+ printFrame({
50
+ strings: [
51
+ {string: '** Creazione favicons completata **', color: 'bgGreen'},
52
+ {string: ''},
53
+ {string: 'I file generati sono nella directory:', color: 'green'},
54
+ {string: remove_homedir_string(params.output_dir), color: 'yellow'},
55
+ {string: ''},
56
+ ...extra_strings
57
+ ],
58
+ frameColor: 'green',
59
+ frameType: 'single'
60
+ });
61
+ }
File without changes