@octaviaflow/icon-build-helpers 1.0.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 (40) hide show
  1. package/README.md +50 -0
  2. package/package.json +55 -0
  3. package/src/builders/index.js +33 -0
  4. package/src/builders/plugins/virtual.js +51 -0
  5. package/src/builders/react/builder.js +313 -0
  6. package/src/builders/react/components/CarbonIcon.d.ts +18 -0
  7. package/src/builders/react/components/Icon.tsx +87 -0
  8. package/src/builders/react/next/babel.js +37 -0
  9. package/src/builders/react/next/convert.js +107 -0
  10. package/src/builders/react/next/templates.js +71 -0
  11. package/src/builders/react/next/typescript.js +87 -0
  12. package/src/builders/react/next.js +382 -0
  13. package/src/builders/svg.js +28 -0
  14. package/src/builders/vanilla.js +115 -0
  15. package/src/index.js +21 -0
  16. package/src/metadata/README.md +37 -0
  17. package/src/metadata/adapters/index.js +34 -0
  18. package/src/metadata/adapters/memory.js +55 -0
  19. package/src/metadata/adapters/yml.js +92 -0
  20. package/src/metadata/extension.js +92 -0
  21. package/src/metadata/extensions/assets.js +45 -0
  22. package/src/metadata/extensions/categories.js +141 -0
  23. package/src/metadata/extensions/deprecated.js +73 -0
  24. package/src/metadata/extensions/icons.js +152 -0
  25. package/src/metadata/extensions/index.js +48 -0
  26. package/src/metadata/extensions/module-info.js +154 -0
  27. package/src/metadata/extensions/module-name.js +54 -0
  28. package/src/metadata/extensions/output/getModuleName.js +52 -0
  29. package/src/metadata/extensions/output/index.js +251 -0
  30. package/src/metadata/extensions/output/optimizer.js +226 -0
  31. package/src/metadata/extensions/pictograms.js +85 -0
  32. package/src/metadata/index.js +178 -0
  33. package/src/metadata/migrations/2020-01-27.js +78 -0
  34. package/src/metadata/migrations/2020-02-17-remove-usage-fields.js +45 -0
  35. package/src/metadata/migrations/2020-02-17-update-pictogram-files.js +79 -0
  36. package/src/metadata/migrations/README.md +12 -0
  37. package/src/metadata/storage.js +68 -0
  38. package/src/metadata/validate.js +54 -0
  39. package/src/registry.js +158 -0
  40. package/src/tools.js +18 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+ /**
11
+ * Copyright IBM Corp. 2020, 2023
12
+ *
13
+ * This source code is licensed under the Apache-2.0 license found in the
14
+ * LICENSE file in the root directory of this source tree.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ /**
20
+ * Use the provided file adapter and directory information to load the given
21
+ * set of extensions.
22
+ * @param {Adapter} adapter
23
+ * @param {string} directory
24
+ * @param {Array<Extension>} [extensions]
25
+ * @returns {Array<Extension>}
26
+ */
27
+ function load(adapter, directory, extensions = []) {
28
+ return Promise.all(
29
+ extensions.map(async (extension) => {
30
+ // If computed, the extension has no file that has been persisted to disk
31
+ // so we don't have to load it.
32
+ if (extension.computed) {
33
+ return extension;
34
+ }
35
+
36
+ const data = await adapter.read(directory, extension.name);
37
+ return {
38
+ ...extension,
39
+ data,
40
+ };
41
+ })
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Use the provided file adapter and directory information to save the given
47
+ * set of extensions.
48
+ * @param {Adapter} adapter
49
+ * @param {string} directory
50
+ * @param {Array<Extension>} [extensions]
51
+ * @returns {Array<Extension>}
52
+ */
53
+ function save(adapter, directory, extensions = []) {
54
+ return Promise.all(
55
+ extensions.map((extension) => {
56
+ // If the extension is computed, there is nothing to persist to disk
57
+ if (extension.computed) {
58
+ return;
59
+ }
60
+ return adapter.write(directory, extension.name, extension.data);
61
+ })
62
+ );
63
+ }
64
+
65
+ module.exports = {
66
+ load,
67
+ save,
68
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+ /**
11
+ * Copyright IBM Corp. 2020, 2023
12
+ *
13
+ * This source code is licensed under the Apache-2.0 license found in the
14
+ * LICENSE file in the root directory of this source tree.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const { reporter } = require('@octaviaflow/cli-reporter');
20
+
21
+ /**
22
+ * Validate the given icons and extension metadata against the asset registry
23
+ * and the base icon schema, alongside running custom validators for each
24
+ * extension. This is a useful check to guarantee that icons exist in both the
25
+ * source directory of SVG assets and in metadata and corresponding extensions.
26
+ * @param {Registry} registry
27
+ * @param {Array<Extension>} [extensions]
28
+ * @returns {void}
29
+ */
30
+ function validate(registry, extensions = []) {
31
+ for (const extension of extensions) {
32
+ if (extension.schema) {
33
+ const { error, value } = extension.schema.validate(extension.data);
34
+ if (error) {
35
+ const failedAssets = error.details.map(({ path, message }) => ({
36
+ index: path[0],
37
+ message,
38
+ }));
39
+ reporter.error(`Unable to validate the ${extension.name} extension:`);
40
+ failedAssets.forEach((asset) => {
41
+ reporter.error(`Error: ${asset.message}`);
42
+ reporter.info(JSON.stringify(value[asset.index], null, 2));
43
+ });
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ if (extension.validate) {
49
+ extension.validate(registry, extension.data);
50
+ }
51
+ }
52
+ }
53
+
54
+ module.exports = validate;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+ /**
11
+ * Copyright IBM Corp. 2020, 2023
12
+ *
13
+ * This source code is licensed under the Apache-2.0 license found in the
14
+ * LICENSE file in the root directory of this source tree.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const fs = require('fs-extra');
20
+ const path = require('path');
21
+
22
+ /**
23
+ * A collection of icons built up from a source directory of .svg assets
24
+ * @typedef {Map<string, Icon>} Registry
25
+ */
26
+
27
+ /**
28
+ * An icon is defined by a unique name in "<name>.svg" that can have an optional
29
+ * namespace and has multiple assets that detail the assets available in a
30
+ * source directory of .svg assets
31
+ * @typedef {object} Icon
32
+ * @property {string} id
33
+ * @property {Array<string>} namespace
34
+ * @property {Array<Asset>} assets
35
+ */
36
+
37
+ /**
38
+ * An asset for an icon that details its size and filepath information
39
+ * @typedef {object} Asset
40
+ * @property {(number|('glyph'))} size
41
+ * @property {string} filepath
42
+ */
43
+
44
+ /**
45
+ * Create a registry of icons from the assets found within the given directory
46
+ * @param {string} directory
47
+ * @returns {Registry}
48
+ */
49
+ async function create(directory) {
50
+ const registry = new Map();
51
+ const queue = await getFilepathsFromDirectory(directory);
52
+
53
+ // Our queue is built up with filepaths that we need to process. Each
54
+ // filepath can either be a directory or an asset corresponding to an icon.
55
+ while (queue.length > 0) {
56
+ const filepath = queue.shift();
57
+ const stats = await fs.stat(filepath);
58
+
59
+ // If we encounter a directory, then we append all of the filepaths we've
60
+ // found to the queue to be processed
61
+ if (await stats.isDirectory()) {
62
+ const filepaths = await getFilepathsFromDirectory(filepath);
63
+ queue.push(...filepaths);
64
+ continue;
65
+ }
66
+
67
+ // When we have an asset for an icon, we need to build up information about
68
+ // it and correctly assign, or update, the entry for this icon in the
69
+ // registry.
70
+
71
+ // We want to build up an array of the relative path from the SVG folder to
72
+ // the asset that we've found. This relative path may contain size or
73
+ // namespace information.
74
+ const directories = path
75
+ .relative(directory, path.dirname(filepath))
76
+ .split(path.sep)
77
+ .filter(Boolean);
78
+
79
+ // Our namespace is generated from every directory that is not a size
80
+ const namespace = directories.filter((directory) => isNaN(directory));
81
+ const asset = {
82
+ id: path.basename(filepath, '.svg'),
83
+ filepath,
84
+ namespace,
85
+ };
86
+
87
+ // Our size folder is generated from the first directory that is a number
88
+ const sizeFolderName = directories.find((directory) => !isNaN(directory));
89
+ if (sizeFolderName) {
90
+ asset.size = parseInt(sizeFolderName, 10);
91
+ } else {
92
+ asset.size = 'glyph';
93
+ }
94
+
95
+ if (!registry.has(asset.id)) {
96
+ registry.set(asset.id, {
97
+ id: asset.id,
98
+ namespace: asset.namespace,
99
+ assets: [],
100
+ });
101
+ }
102
+
103
+ const entry = registry.get(asset.id);
104
+
105
+ // We have an invariant that all icons in a source SVG folder must have a
106
+ // unique name even if they are under different namespaces.
107
+ if (hash(entry.id, entry.namespace) !== hash(asset.id, asset.namespace)) {
108
+ const expected = entry.namespace.join(', ');
109
+ const actual = asset.namespace.join(', ');
110
+ throw new Error(
111
+ `Found namespace mismatch with asset ${asset.id}. Expected ` +
112
+ `[${expected}] but received [${actual}]. This likely means that ` +
113
+ `there is a duplicate asset in the source SVG folder`
114
+ );
115
+ }
116
+
117
+ entry.assets.push({
118
+ filepath: asset.filepath,
119
+ size: asset.size,
120
+ });
121
+ }
122
+
123
+ return registry;
124
+ }
125
+
126
+ /**
127
+ * Generate a hash with the basename and namespace of an asset to compare if two
128
+ * assets in the registry are equivalent
129
+ * @param {string} basename
130
+ * @param {Array<string>} [namespace]
131
+ * @returns {string}
132
+ */
133
+ function hash(basename, namespace = []) {
134
+ return [...namespace, basename].join('/');
135
+ }
136
+
137
+ const denylist = new Set(['.DS_Store']);
138
+
139
+ /**
140
+ * Get all the filepaths from the given directory that are not contained in a
141
+ * denylist.
142
+ * @param {string} directory
143
+ * @returns {Array<string>}
144
+ */
145
+ async function getFilepathsFromDirectory(directory) {
146
+ const files = await fs.readdir(directory);
147
+ return files
148
+ .filter((name) => {
149
+ return !denylist.has(name);
150
+ })
151
+ .map((filename) => {
152
+ return path.join(directory, filename);
153
+ });
154
+ }
155
+
156
+ module.exports = {
157
+ create,
158
+ };
package/src/tools.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright OctaviaFlow
3
+ * Author: Vishal Kumar
4
+ * Created: 11/November/2025
5
+ *
6
+ * This source code is licensed under the Apache-2.0 license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ async function flatMapAsync(source, mapFn) {
12
+ const results = await Promise.all(source.map(mapFn));
13
+ return results.reduce((acc, result) => acc.concat(result), []);
14
+ }
15
+
16
+ module.exports = {
17
+ flatMapAsync,
18
+ };