@fusion.dev/helix-icons 0.205.2 → 0.207.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.
package/README.md CHANGED
@@ -53,9 +53,9 @@ import { peak } from './icons'
53
53
  <helix-icon icon={peak} />
54
54
  ```
55
55
 
56
- Any set, not only ours. A subset of Phosphor larger than the 120 here is the
57
- same command against the package it resolves from, and `--suffix` is for a set
58
- that spells a weight into the file name:
56
+ The command reads any set of SVG files. More of Phosphor than this package
57
+ carries is the same command against the package it resolves from, and
58
+ `--suffix` is for a set that spells a weight into the file name:
59
59
 
60
60
  ```sh
61
61
  npx helix-icons build \
@@ -67,6 +67,40 @@ npx helix-icons build \
67
67
  so nothing can drop any of it: ask for it when you want names in markup, and
68
68
  leave it off when you import.
69
69
 
70
+ An `--out` path ending in `.js` or `.mjs` gets JavaScript, with the type in a
71
+ JSDoc comment. Any other path gets TypeScript.
72
+
73
+ ### From a script
74
+
75
+ The command is also a module, `@fusion.dev/helix-icons/build`, for a build
76
+ script of your own:
77
+
78
+ ```js
79
+ import { build } from '@fusion.dev/helix-icons/build'
80
+
81
+ const written = build({
82
+ out: 'src/icons.ts',
83
+ sources: [{ directory: 'brand-icons' }],
84
+ })
85
+ ```
86
+
87
+ `build` writes the module and returns how many icons it holds. Its options are
88
+ the command's flags:
89
+
90
+ - `sources`: where the SVG files are, searched in order, each as
91
+ `{ directory, suffix }`;
92
+ - `names`: the icons to build, and every icon in the sources when left out;
93
+ - `out`: where to write the module;
94
+ - `index`: whether to also write the record `<helix-icon name>` reads;
95
+ - `typeImport`: where the module imports `HelixIcon` from.
96
+
97
+ It throws where the command fails:
98
+
99
+ - a source that is not a directory;
100
+ - a name with no file, or with a file in two sources;
101
+ - sources with no SVG files, when `names` is left out;
102
+ - an `out` path ending in `.cjs`.
103
+
70
104
  Phosphor is MIT and its notice travels in `LICENSE` beside ours.
71
105
 
72
106
  [Documentation](https://fusion.dev/helix)
package/dist/build.d.ts CHANGED
@@ -40,7 +40,13 @@ export interface BuildOptions {
40
40
  * pointing at their own folder of drawings usually means.
41
41
  */
42
42
  names?: string[];
43
- /** Where to write the module. */
43
+ /**
44
+ * Where to write the module, creating its directory where there is none.
45
+ *
46
+ * A `.js` or `.mjs` path gets JavaScript, with the type in a JSDoc comment.
47
+ * Any other gets TypeScript, and a `.cjs` path is refused, the module being
48
+ * an ES module.
49
+ */
44
50
  out: string;
45
51
  sources: IconSource[];
46
52
  /**
@@ -59,6 +65,8 @@ export interface BuildOptions {
59
65
  /**
60
66
  * Writes the module, and says how many glyphs are in it.
61
67
  *
62
- * @throws where a name resolves to no file, or to more than one.
68
+ * @throws where a source is not a directory, where a name resolves to no file
69
+ * or to more than one, where the sources hold no glyph and no names were given,
70
+ * and where `out` is a `.cjs` file.
63
71
  */
64
72
  export declare function build(options: BuildOptions): number;
package/dist/build.js CHANGED
@@ -1,5 +1,5 @@
1
- import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
3
  import { optimize } from 'svgo';
4
4
  const SVGO = {
5
5
  multipass: true,
@@ -9,9 +9,6 @@ const camel = (slug) => slug.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCa
9
9
  const namesIn = (sources) => {
10
10
  const found = new Set();
11
11
  for (const { directory, suffix = '' } of sources) {
12
- if (!existsSync(directory)) {
13
- continue;
14
- }
15
12
  for (const file of readdirSync(directory)) {
16
13
  if (!file.endsWith('.svg')) {
17
14
  continue;
@@ -51,23 +48,35 @@ const drawn = (slug, sources) => {
51
48
  };
52
49
  export function build(options) {
53
50
  const { index = false, out, sources, typeImport = '@fusion.dev/helix-icons' } = options;
51
+ if (out.endsWith('.cjs')) {
52
+ throw new Error(`icons: ${out} is a CommonJS file, and the module is an ES module. Name it .js, .mjs, or .ts.`);
53
+ }
54
+ for (const { directory } of sources) {
55
+ if (!existsSync(directory) || !statSync(directory).isDirectory()) {
56
+ throw new Error(`icons: no directory at ${directory}`);
57
+ }
58
+ }
54
59
  const names = options.names ?? namesIn(sources);
60
+ if (options.names === undefined && names.length === 0) {
61
+ const where = sources
62
+ .map(({ directory, suffix = '' }) => suffix === '' ? directory : `${directory} (named *${suffix}.svg)`)
63
+ .join(', ');
64
+ throw new Error(`icons: no SVG files in ${where}`);
65
+ }
55
66
  const entries = names.map((slug) => drawn(slug, sources));
67
+ const script = /\.m?js$/.test(out);
68
+ const icon = script ? `import('${typeImport}').HelixIcon` : 'HelixIcon';
69
+ const declared = (name, type) => script ? `/** @type {${type}} */\nexport const ${name} =` : `export const ${name}: ${type} =`;
56
70
  const body = entries
57
- .map(({ camel: name, content, slug, viewBox }) => `export const ${name}: HelixIcon = {\n content: '${content.replace(/'/g, "\\'")}',\n name: '${slug}',\n viewBox: '${viewBox}',\n}`)
71
+ .map(({ camel: name, content, slug, viewBox }) => `${declared(name, icon)} {\n content: '${content.replace(/'/g, "\\'")}',\n name: '${slug}',\n viewBox: '${viewBox}',\n}`)
58
72
  .join('\n\n');
59
73
  const record = entries.map(({ camel: name, slug }) => ` '${slug}': ${name},`).join('\n');
60
- writeFileSync(out, `// Generated by @fusion.dev/helix-icons. Do not edit.
61
- import type { HelixIcon } from '${typeImport}'
62
-
63
- ${body}
64
- ${index
65
- ? `
66
- /** Every icon, by name, for lookup from a \`name\` attribute. */
67
- export const icons: Record<string, HelixIcon> = {
68
- ${record}
69
- }
70
- `
71
- : ''}`);
74
+ const lookup = 'Every icon, by name, for lookup from a `name` attribute.';
75
+ const indexed = script
76
+ ? `/**\n * ${lookup}\n *\n * @type {Record<string, ${icon}>}\n */\nexport const icons =`
77
+ : `/** ${lookup} */\nexport const icons: Record<string, HelixIcon> =`;
78
+ const head = script ? '' : `import type { HelixIcon } from '${typeImport}'\n`;
79
+ mkdirSync(dirname(out), { recursive: true });
80
+ writeFileSync(out, `// Generated by @fusion.dev/helix-icons. Do not edit.\n${head}\n${body}\n${index ? `\n${indexed} {\n${record}\n}\n` : ''}`);
72
81
  return entries.length;
73
82
  }
package/dist/cli.js CHANGED
@@ -9,61 +9,81 @@ const USAGE = `helix-icons build --source <dir> [--suffix <text>] [--list <file>
9
9
  before it: --source …/bold --suffix -bold reads caret-down-bold.svg.
10
10
  --list <file> Names to build, one per line, # for a comment. Default: every
11
11
  glyph the sources hold.
12
- --out <file> Where to write the module. Default: icons.generated.ts
12
+ --out <file> Where to write the module: JavaScript for a .js or .mjs file,
13
+ TypeScript otherwise. Default: icons.generated.ts
13
14
  --index Also write the record \`<helix-icon name>\` reads. It names every
14
15
  glyph, so nothing can drop any of it: for markup, not for imports.
15
16
  --type-import <s> Where the module imports \`HelixIcon\` from.
16
- Default: @fusion.dev/helix-icons`;
17
+ Default: @fusion.dev/helix-icons
18
+ -h, --help Print this.`;
17
19
  const after = (argv, at) => {
18
20
  const value = argv[at + 1];
19
21
  return value === undefined || value.startsWith('--') ? undefined : value;
20
22
  };
23
+ const refuse = (message, usage = false) => {
24
+ console.error(`helix-icons: ${message}`);
25
+ if (usage) {
26
+ console.error(`\n${USAGE}`);
27
+ }
28
+ return 1;
29
+ };
21
30
  export function main(argv) {
22
- if (argv[0] !== 'build' || argv.includes('--help')) {
31
+ if (argv.includes('--help') || argv.includes('-h')) {
23
32
  console.log(USAGE);
24
- return argv[0] === 'build' ? 0 : 1;
33
+ return 0;
34
+ }
35
+ if (argv[0] !== 'build') {
36
+ return refuse(argv[0] === undefined ? 'needs a command' : `${argv[0]} is not a command`, true);
25
37
  }
26
38
  const sources = [];
27
39
  let list;
28
40
  let out = 'icons.generated.ts';
29
41
  let index = false;
30
42
  let typeImport;
31
- for (const [at, flag] of argv.entries()) {
43
+ for (let at = 1; at < argv.length; at += 1) {
44
+ const flag = argv[at];
45
+ if (flag === '--index') {
46
+ index = true;
47
+ continue;
48
+ }
49
+ const value = after(argv, at);
32
50
  switch (flag) {
33
- case '--source': {
34
- const directory = after(argv, at);
35
- if (directory === undefined) {
36
- console.error('helix-icons: --source needs a directory');
37
- return 1;
51
+ case '--source':
52
+ if (value === undefined) {
53
+ return refuse('--source needs a directory');
38
54
  }
39
- sources.push({ directory: resolve(directory) });
55
+ sources.push({ directory: resolve(value) });
40
56
  break;
41
- }
42
57
  case '--suffix': {
43
- const suffix = after(argv, at);
44
58
  const last = sources.at(-1);
45
- if (suffix === undefined || last === undefined) {
46
- console.error('helix-icons: --suffix names what a file carries, after the --source it is for');
47
- return 1;
59
+ if (value === undefined || last === undefined) {
60
+ return refuse('--suffix names what a file carries, after the --source it is for');
48
61
  }
49
- last.suffix = suffix;
62
+ last.suffix = value;
50
63
  break;
51
64
  }
52
65
  case '--list':
53
- list = after(argv, at);
66
+ if (value === undefined) {
67
+ return refuse('--list needs a file');
68
+ }
69
+ list = value;
54
70
  break;
55
71
  case '--out':
56
- out = after(argv, at) ?? out;
57
- break;
58
- case '--index':
59
- index = true;
72
+ if (value === undefined) {
73
+ return refuse('--out needs a file');
74
+ }
75
+ out = value;
60
76
  break;
61
77
  case '--type-import':
62
- typeImport = after(argv, at);
78
+ if (value === undefined) {
79
+ return refuse('--type-import needs the module to import the type from');
80
+ }
81
+ typeImport = value;
63
82
  break;
64
83
  default:
65
- break;
84
+ return refuse(`${flag} is not an option of build`, true);
66
85
  }
86
+ at += 1;
67
87
  }
68
88
  if (sources.length === 0) {
69
89
  console.error('helix-icons: needs at least one --source\n');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-icons",
3
3
  "description": "Helix icon set.",
4
- "version": "0.205.2",
4
+ "version": "0.207.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -25,6 +25,7 @@
25
25
  "types": "./dist/build.d.ts",
26
26
  "default": "./dist/build.js"
27
27
  },
28
+ "./package.json": "./package.json",
28
29
  "./regular": {
29
30
  "types": "./dist/regular.d.ts",
30
31
  "default": "./dist/regular.js"