@weft-js/warp-icons 0.1.0 → 0.1.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/README.md +11 -4
- package/compile.mjs +267 -399
- package/package.json +12 -3
- package/plugin.mjs +33 -7
- package/src/action.tsx +7 -1
- package/src/all.tsx +4 -1
- package/src/compile-catalog.mjs +96 -0
- package/src/compile-closest.mjs +19 -0
- package/src/compile-names.mjs +33 -0
- package/src/compile-pascal.mjs +4 -0
- package/src/compile-read.mjs +227 -0
- package/src/compile-resolver.mjs +169 -0
- package/src/compile-rewrite-pack.mjs +39 -0
- package/src/compile-unwrap.mjs +19 -0
- package/src/gen/actions/bell.tsx +1 -1
- package/src/gen/actions/check.tsx +1 -1
- package/src/gen/actions/copy.tsx +1 -1
- package/src/gen/actions/refresh.tsx +1 -1
- package/src/gen/actions/trash.tsx +1 -1
- package/src/gen/all-accessibility-bus.tsx +274 -0
- package/src/gen/all-cake-cursor.tsx +284 -0
- package/src/gen/all-database-funnel.tsx +276 -0
- package/src/gen/all-gallery-logout.tsx +252 -0
- package/src/gen/all-magnet-puzzle.tsx +285 -0
- package/src/gen/all-qr-syringe.tsx +328 -0
- package/src/gen/all-tab-zoom.tsx +280 -0
- package/src/gen/all.tsx +8 -1959
- package/src/icon.tsx +16 -3
- package/src/icons.css +16 -3
- package/src/late.tsx +46 -12
- package/src/lookup.tsx +2 -1
- package/src/set.tsx +4 -1
- package/src/svg.tsx +23 -4
- package/src/tiers.css +1 -0
package/compile.mjs
CHANGED
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
// Names go through variables, conditionals, static lists and maps, imports and props handed down through components.
|
|
3
3
|
// A name it can't follow is legal: that use gets the pack, lazily. An unknown literal is an error. No Rust: the pass is
|
|
4
4
|
// a parse and a walk, so it runs the same in `vite build` and `weft check`.
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { closest } from './src/compile-closest.mjs'
|
|
6
|
+
import { title, pascal } from './src/compile-pascal.mjs'
|
|
7
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
8
|
+
import { extname, join, relative } from 'node:path'
|
|
9
|
+
import { actionList, list, findings as catalogFindings } from './src/compile-catalog.mjs'
|
|
10
|
+
import { namesModule } from './src/compile-names.mjs'
|
|
11
|
+
import { rewritePack } from './src/compile-rewrite-pack.mjs'
|
|
12
|
+
import { resolver } from './src/compile-resolver.mjs'
|
|
13
|
+
import { read } from './src/compile-read.mjs'
|
|
14
|
+
import { unwrap, literal } from './src/compile-unwrap.mjs'
|
|
15
|
+
export { closest } from './src/compile-closest.mjs'
|
|
16
|
+
export { pascal } from './src/compile-pascal.mjs'
|
|
17
|
+
export { actionList, list, packCost, publish } from './src/compile-catalog.mjs'
|
|
18
|
+
export { namesModule, trimTiers } from './src/compile-names.mjs'
|
|
9
19
|
|
|
10
20
|
/** warp's expression stops, in order. */
|
|
11
21
|
export const STOPS = ['quiet', 'balanced', 'vivid']
|
|
@@ -13,50 +23,53 @@ export const STOPS = ['quiet', 'balanced', 'vivid']
|
|
|
13
23
|
export const DEFAULT_STOP = 'balanced'
|
|
14
24
|
const SKIP = new Set(['node_modules', 'dist', '.weft', '.git', 'target', 'coverage'])
|
|
15
25
|
const CODE = /\.[cm]?[jt]sx?$/
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
const ICON_SOURCES = new Set([
|
|
27
|
+
'@weft-js/warp-icons/all',
|
|
28
|
+
'@weft-js/warp-icons/icon',
|
|
29
|
+
'@weft-js/warp',
|
|
30
|
+
])
|
|
19
31
|
|
|
20
|
-
const title = name => name.replace(/(^|-)(.)/g, (_, __, c) => c.toUpperCase())
|
|
21
|
-
/** The export a name and variant compile to: `check`, `solid` is `CheckIconSolid`. */
|
|
22
|
-
export const pascal = (name, variant = '') => `${title(name)}Icon${variant ? variant[0].toUpperCase() + variant.slice(1) : ''}`
|
|
23
32
|
/** The variant flags `<Icon>` takes. */
|
|
24
33
|
export const VARIANTS = ['solid']
|
|
25
34
|
/** Which variants an element asks for: '' is hollow, the default. A flag it can't read may be either. */
|
|
26
35
|
function variants(element) {
|
|
27
|
-
const out = new Set(),
|
|
36
|
+
const out = new Set(),
|
|
37
|
+
flags = element.attributes.filter(
|
|
38
|
+
(a) =>
|
|
39
|
+
a.type === 'JSXAttribute' && (VARIANTS.includes(a.name.name) || a.name.name === 'action'),
|
|
40
|
+
)
|
|
28
41
|
for (const a of flags) {
|
|
29
42
|
const v = a.value?.type === 'JSXExpressionContainer' ? unwrap(a.value.expression) : null
|
|
30
|
-
if (
|
|
43
|
+
if (
|
|
44
|
+
!a.value ||
|
|
45
|
+
v?.value === true ||
|
|
46
|
+
typeof a.value.value === 'string' ||
|
|
47
|
+
(typeof v?.value === 'string' && v.value)
|
|
48
|
+
)
|
|
49
|
+
return { set: new Set([a.name.name]), fixed: flags.length === 1 }
|
|
31
50
|
if (v?.value === false) continue
|
|
32
|
-
out.add(a.name.name)
|
|
51
|
+
out.add(a.name.name)
|
|
52
|
+
out.add('')
|
|
33
53
|
}
|
|
34
54
|
if (!out.size) out.add('')
|
|
35
55
|
return { set: out, fixed: out.size === 1 }
|
|
36
56
|
}
|
|
37
57
|
|
|
38
|
-
const line = (code, at) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let row = Array.from({ length: b.length + 1 }, (_, i) => i)
|
|
42
|
-
for (let i = 1; i <= a.length; i++) {
|
|
43
|
-
const next = [i]
|
|
44
|
-
for (let j = 1; j <= b.length; j++) next[j] = Math.min(next[j - 1] + 1, row[j] + 1, row[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
45
|
-
row = next
|
|
46
|
-
}
|
|
47
|
-
return row[b.length]
|
|
58
|
+
const line = (code, at) => {
|
|
59
|
+
const before = code.slice(0, at).split('\n')
|
|
60
|
+
return { line: before.length, column: before.at(-1).length + 1 }
|
|
48
61
|
}
|
|
49
|
-
/** The name nearest to a typo, for "did you mean": a prefix counts as one edit closer. */
|
|
50
|
-
export const closest = (name, names) => names.reduce((best, n) => { const d = distance(name, n) - (n.startsWith(name) ? 1 : 0); return d < best.d ? { n, d } : best }, { n: null, d: Infinity }).n
|
|
51
62
|
|
|
52
63
|
function sources(root) {
|
|
53
64
|
const out = []
|
|
54
|
-
const walk = dir => {
|
|
65
|
+
const walk = (dir) => {
|
|
55
66
|
for (const entry of readdirSync(dir)) {
|
|
56
67
|
if (SKIP.has(entry) || entry.startsWith('.')) continue
|
|
57
68
|
const p = join(dir, entry)
|
|
58
|
-
if (statSync(p).isDirectory()) {
|
|
59
|
-
|
|
69
|
+
if (statSync(p).isDirectory()) {
|
|
70
|
+
if (entry !== 'test' && entry !== 'tests' && entry !== '__tests__') walk(p)
|
|
71
|
+
} else if (CODE.test(entry) && !/\.(test|spec|d)\./.test(entry) && !/\.config\./.test(entry))
|
|
72
|
+
out.push(p)
|
|
60
73
|
else if (extname(entry) === '.html') out.push(p)
|
|
61
74
|
}
|
|
62
75
|
}
|
|
@@ -64,123 +77,26 @@ function sources(root) {
|
|
|
64
77
|
return out
|
|
65
78
|
}
|
|
66
79
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (extname(file) === '.html') {
|
|
82
|
-
for (const m of code.matchAll(/data-expression=["']?([a-z]+)/g)) unit.stops.push({ value: m[1] })
|
|
83
|
-
return unit
|
|
84
|
-
}
|
|
85
|
-
for (const m of code.matchAll(/setAttribute\(\s*['"]data-expression['"]\s*,\s*(?:['"]([a-z]+)['"])?/g)) unit.stops.push(m[1] ? { value: m[1] } : { dynamic: true, at: m.index })
|
|
86
|
-
if (/dataset\.expression\s*=/.test(code)) unit.stops.push({ dynamic: true, at: code.search(/dataset\.expression\s*=/) })
|
|
87
|
-
const ast = parse(file, code)
|
|
88
|
-
if (!ast) return unit
|
|
89
|
-
unit.ast = ast
|
|
90
|
-
const bind = (name, binding) => { if (!unit.bindings.has(name)) unit.bindings.set(name, []); unit.bindings.get(name).push(binding) }
|
|
91
|
-
const pattern = (id, make) => {
|
|
92
|
-
if (id?.type === 'Identifier') bind(id.name, make(null))
|
|
93
|
-
else if (id?.type === 'AssignmentPattern') pattern(id.left, make)
|
|
94
|
-
else if (id?.type === 'ObjectPattern') for (const p of id.properties) {
|
|
95
|
-
const key = p.key?.name ?? p.key?.value
|
|
96
|
-
if (key !== undefined) pattern(p.value, inner => make(inner ? [key, ...inner] : [key]))
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const component = (name, params) => {
|
|
100
|
-
if (!name || !/^[A-Z]/.test(name)) return
|
|
101
|
-
const first = params?.[0] ?? params?.items?.[0]
|
|
102
|
-
if (first?.type === 'Identifier') bind(first.name, { kind: 'props', comp: name })
|
|
103
|
-
else if (first) pattern(first, keys => ({ kind: 'prop', comp: name, key: keys?.[0] }))
|
|
104
|
-
}
|
|
105
|
-
const visit = node => {
|
|
106
|
-
if (!node || typeof node !== 'object') return
|
|
107
|
-
if (Array.isArray(node)) { for (const n of node) visit(n); return }
|
|
108
|
-
if (typeof node.type !== 'string') return
|
|
109
|
-
switch (node.type) {
|
|
110
|
-
case 'ImportDeclaration': {
|
|
111
|
-
const source = node.source.value
|
|
112
|
-
if (source === '@weft-js/warp' || source.startsWith('@weft-js/warp/')) unit.warp = true
|
|
113
|
-
const target = source.startsWith('.') ? moduleFile(file, source) : null
|
|
114
|
-
if (target) unit.related.push(target)
|
|
115
|
-
for (const s of node.specifiers ?? []) {
|
|
116
|
-
const imported = s.type === 'ImportNamespaceSpecifier' ? '*' : s.type === 'ImportDefaultSpecifier' ? 'default' : s.imported.name ?? s.imported.value
|
|
117
|
-
unit.imports.set(s.local.name, { source, imported, target })
|
|
118
|
-
}
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
case 'ExportNamedDeclaration':
|
|
122
|
-
if (node.source && node.source.value.startsWith('.')) { const t = moduleFile(file, node.source.value); if (t) { unit.related.push(t); for (const s of node.specifiers ?? []) unit.exports.set(s.exported.name, { reexport: t, name: s.local.name ?? s.local.value }) } }
|
|
123
|
-
break
|
|
124
|
-
case 'ExportAllDeclaration':
|
|
125
|
-
if (node.source.value.startsWith('.')) { const t = moduleFile(file, node.source.value); if (t) { unit.related.push(t); (unit.stars ??= []).push(t) } }
|
|
126
|
-
break
|
|
127
|
-
case 'VariableDeclarator':
|
|
128
|
-
if (node.init) {
|
|
129
|
-
pattern(node.id, keys => ({ kind: 'value', expr: node.init, keys }))
|
|
130
|
-
const fn = unwrap(node.init)
|
|
131
|
-
if (node.id.type === 'Identifier' && /Function/.test(fn.type)) component(node.id.name, fn.params)
|
|
132
|
-
}
|
|
133
|
-
break
|
|
134
|
-
case 'FunctionDeclaration':
|
|
135
|
-
component(node.id?.name, node.params)
|
|
136
|
-
break
|
|
137
|
-
case 'CallExpression': {
|
|
138
|
-
const callee = node.callee
|
|
139
|
-
if (callee.type === 'MemberExpression' && !callee.computed && ITERATORS.has(callee.property.name)) {
|
|
140
|
-
const fn = node.arguments[0] && unwrap(node.arguments[0])
|
|
141
|
-
if (fn && /Function/.test(fn.type)) pattern(fn.params?.[0] ?? fn.params?.items?.[0], keys => ({ kind: 'each', of: callee.object, keys }))
|
|
142
|
-
}
|
|
143
|
-
break
|
|
144
|
-
}
|
|
145
|
-
case 'MemberExpression':
|
|
146
|
-
if (node.object.type === 'Identifier') unit.members.push({ local: node.object.name, node })
|
|
147
|
-
break
|
|
148
|
-
case 'JSXMemberExpression':
|
|
149
|
-
if (node.object.type === 'JSXIdentifier') unit.members.push({ local: node.object.name, node, jsx: true })
|
|
150
|
-
break
|
|
151
|
-
case 'Identifier':
|
|
152
|
-
case 'JSXIdentifier':
|
|
153
|
-
unit.idents.push(node)
|
|
154
|
-
break
|
|
155
|
-
case 'JSXOpeningElement': {
|
|
156
|
-
const comp = node.name.type === 'JSXIdentifier' ? node.name.name : node.name.type === 'JSXMemberExpression' ? `${node.name.object.name}.${node.name.property.name}` : null
|
|
157
|
-
if (!comp) break
|
|
158
|
-
for (const attr of node.attributes) {
|
|
159
|
-
if (attr.type !== 'JSXAttribute' || attr.name.type !== 'JSXIdentifier') continue
|
|
160
|
-
const value = attr.value?.type === 'JSXExpressionContainer' ? attr.value.expression : attr.value
|
|
161
|
-
if (attr.name.name === 'data-expression' && value) unit.stops.push({ expr: value, at: attr.start })
|
|
162
|
-
if (value && /^[A-Z]/.test(comp)) unit.uses.push({ comp, prop: attr.name.name, expr: value, at: attr.start, end: attr.end, element: node })
|
|
163
|
-
}
|
|
164
|
-
break
|
|
165
|
-
}
|
|
166
|
-
case 'Property':
|
|
167
|
-
case 'ObjectProperty':
|
|
168
|
-
if (!node.computed && (node.key.name ?? node.key.value) === 'icon' && !node.method) unit.sites.push({ soft: true, expr: node.value, at: node.start })
|
|
169
|
-
break
|
|
170
|
-
}
|
|
171
|
-
for (const k of Object.keys(node)) { if (k !== 'type' && k !== 'start' && k !== 'end') visit(node[k]) }
|
|
172
|
-
}
|
|
173
|
-
visit(ast.body)
|
|
174
|
-
return unit
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const isSpecifier = (unit, id) => unit.ast.body.some(n => n.type === 'ImportDeclaration' && n.specifiers?.some(s => s.local === id || s.local.start === id.start))
|
|
178
|
-
const packLocals = unit => [...unit.imports].filter(([, i]) => i.source === '@weft-js/warp-icons' && (i.imported === 'default' || i.imported === '*')).map(([l]) => l)
|
|
179
|
-
const kebab = s => s.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`).replace(/^-/, '')
|
|
80
|
+
const isSpecifier = (unit, id) =>
|
|
81
|
+
unit.ast.body.some(
|
|
82
|
+
(n) =>
|
|
83
|
+
n.type === 'ImportDeclaration' &&
|
|
84
|
+
n.specifiers?.some((s) => s.local === id || s.local.start === id.start),
|
|
85
|
+
)
|
|
86
|
+
const packLocals = (unit) =>
|
|
87
|
+
[...unit.imports]
|
|
88
|
+
.filter(
|
|
89
|
+
([, i]) =>
|
|
90
|
+
i.source === '@weft-js/warp-icons' && (i.imported === 'default' || i.imported === '*'),
|
|
91
|
+
)
|
|
92
|
+
.map(([l]) => l)
|
|
93
|
+
const kebab = (s) => s.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`).replace(/^-/, '')
|
|
180
94
|
const TOOLS = new Set(['iconSet'])
|
|
181
95
|
/** `X`, `XHollow`, `XIcon`, `XIconSolid`: a pack member as a name and a variant. */
|
|
182
96
|
function member(id) {
|
|
183
|
-
const [, base, variant = ''] = /^(.+?)(Hollow|Solid|Action)?$/.exec(
|
|
97
|
+
const [, base, variant = ''] = /^(.+?)(Hollow|Solid|Action)?$/.exec(
|
|
98
|
+
id.replace(/Icon(?=$|Hollow$|Solid$|Action$)/, ''),
|
|
99
|
+
)
|
|
184
100
|
return { name: kebab(base), variant: variant === 'Hollow' ? '' : variant.toLowerCase() }
|
|
185
101
|
}
|
|
186
102
|
function packName(id, catalog, names, missing) {
|
|
@@ -189,234 +105,190 @@ function packName(id, catalog, names, missing) {
|
|
|
189
105
|
if (catalog.includes(name)) names.add(variant ? `${name}:${variant}` : name)
|
|
190
106
|
else missing()
|
|
191
107
|
}
|
|
192
|
-
const packIds = catalog => catalog.map(n => title(n))
|
|
193
|
-
|
|
194
|
-
const unwrap = node => { while (node && WRAPPERS.has(node.type)) node = node.expression; return node }
|
|
108
|
+
const packIds = (catalog) => catalog.map((n) => title(n))
|
|
195
109
|
|
|
196
|
-
|
|
197
|
-
export function analyze(root, { catalog }) {
|
|
110
|
+
function readUnits(root) {
|
|
198
111
|
const units = new Map()
|
|
199
112
|
const queue = sources(root)
|
|
200
113
|
while (queue.length) {
|
|
201
114
|
const file = queue.pop()
|
|
202
115
|
if (units.has(file)) continue
|
|
203
116
|
let code
|
|
204
|
-
try {
|
|
117
|
+
try {
|
|
118
|
+
code = readFileSync(file, 'utf8')
|
|
119
|
+
} catch {
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
205
122
|
const unit = read(file, code)
|
|
206
123
|
units.set(file, unit)
|
|
207
124
|
queue.push(...unit.related)
|
|
208
125
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const key = `${use.comp}.${use.prop}`
|
|
212
|
-
if (!usesOf.has(key)) usesOf.set(key, [])
|
|
213
|
-
usesOf.get(key).push({ unit: u, use })
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const empty = () => ({ values: new Set(), shapes: [], dynamic: null })
|
|
217
|
-
const merge = (into, r) => { for (const v of r.values) into.values.add(v); into.shapes.push(...r.shapes); into.dynamic ??= r.dynamic; return into }
|
|
218
|
-
const text = (unit, n) => unit.code.slice(n.start, n.end).slice(0, 60)
|
|
219
|
-
|
|
220
|
-
function exported(file, name, seen) {
|
|
221
|
-
const u = units.get(file)
|
|
222
|
-
if (!u) return { ...empty(), dynamic: `export ${name}` }
|
|
223
|
-
const re = u.exports.get(name)
|
|
224
|
-
if (re?.reexport) return exported(re.reexport, re.name, seen)
|
|
225
|
-
if (u.bindings.has(name)) return identifier(u, name, seen)
|
|
226
|
-
for (const star of u.stars ?? []) { const r = exported(star, name, seen); if (!r.dynamic) return r }
|
|
227
|
-
return { ...empty(), dynamic: `export ${name}` }
|
|
228
|
-
}
|
|
126
|
+
return units
|
|
127
|
+
}
|
|
229
128
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
return { ...empty(), dynamic: name }
|
|
129
|
+
/** Everything a build needs to know: the names, the stops, and where either escaped. */
|
|
130
|
+
export function analyze(root, { catalog }) {
|
|
131
|
+
const units = readUnits(root)
|
|
132
|
+
const usesOf = new Map()
|
|
133
|
+
for (const u of units.values())
|
|
134
|
+
for (const use of u.uses) {
|
|
135
|
+
const key = `${use.comp}.${use.prop}`
|
|
136
|
+
if (!usesOf.has(key)) usesOf.set(key, [])
|
|
137
|
+
usesOf.get(key).push({ unit: u, use })
|
|
240
138
|
}
|
|
241
|
-
const list = unit.bindings.get(name)
|
|
242
|
-
if (!list) return { ...empty(), dynamic: name }
|
|
243
|
-
const out = empty()
|
|
244
|
-
for (const b of list) merge(out, binding(unit, b, seen))
|
|
245
|
-
seen.delete(id)
|
|
246
|
-
return out
|
|
247
|
-
}
|
|
248
139
|
|
|
249
|
-
|
|
250
|
-
let cur = from
|
|
251
|
-
for (const key of keys ?? []) {
|
|
252
|
-
const next = empty(); next.dynamic = cur.dynamic
|
|
253
|
-
for (const { shape, unit: u } of cur.shapes) merge(next, property(u, shape, key, seen))
|
|
254
|
-
cur = next
|
|
255
|
-
}
|
|
256
|
-
return cur
|
|
257
|
-
}
|
|
140
|
+
const { expression } = resolver(units, usesOf)
|
|
258
141
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
142
|
+
const names = new Set(),
|
|
143
|
+
unknown = [],
|
|
144
|
+
dynamic = [],
|
|
145
|
+
escaped = [],
|
|
146
|
+
stops = new Set([DEFAULT_STOP]),
|
|
147
|
+
stopsDynamic = []
|
|
148
|
+
const known = new Set(catalog)
|
|
149
|
+
const where = (unit, at) => ({
|
|
150
|
+
file: relative(root, unit.file),
|
|
151
|
+
abs: unit.file,
|
|
152
|
+
...line(unit.code, at),
|
|
153
|
+
})
|
|
154
|
+
const site = createSite({ expression, known, names, unknown, dynamic, where, catalog })
|
|
155
|
+
const iconLocal = (unit, comp) => {
|
|
156
|
+
const imp = unit.imports.get(comp)
|
|
157
|
+
return imp && imp.imported === 'Icon' && ICON_SOURCES.has(imp.source)
|
|
269
158
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
for (const { unit, use } of usesOf.get(`${comp}.${prop}`) ?? []) merge(out, expression(unit, use.expr, seen))
|
|
274
|
-
return out
|
|
159
|
+
const warpLocal = (unit, comp) => {
|
|
160
|
+
const imp = unit.imports.get(comp.split('.')[0])
|
|
161
|
+
return imp && (imp.source === '@weft-js/warp' || imp.source.startsWith('@weft-js/warp/'))
|
|
275
162
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
163
|
+
const ctx = {
|
|
164
|
+
catalog,
|
|
165
|
+
names,
|
|
166
|
+
unknown,
|
|
167
|
+
dynamic,
|
|
168
|
+
escaped,
|
|
169
|
+
stops,
|
|
170
|
+
stopsDynamic,
|
|
171
|
+
known,
|
|
172
|
+
where,
|
|
173
|
+
site,
|
|
174
|
+
expression,
|
|
175
|
+
iconLocal,
|
|
176
|
+
warpLocal,
|
|
287
177
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return node.expressions.length ? { ...empty(), dynamic: text(unit, node) } : { ...empty(), values: new Set([node.quasis[0].value.cooked]) }
|
|
298
|
-
case 'ConditionalExpression':
|
|
299
|
-
return merge(expression(unit, node.consequent, seen), expression(unit, node.alternate, seen))
|
|
300
|
-
case 'LogicalExpression':
|
|
301
|
-
return merge(expression(unit, node.left, seen), expression(unit, node.right, seen))
|
|
302
|
-
case 'ArrayExpression':
|
|
303
|
-
case 'ObjectExpression':
|
|
304
|
-
return { ...empty(), shapes: [{ shape: node, unit }] }
|
|
305
|
-
case 'Identifier':
|
|
306
|
-
return identifier(unit, node.name, seen)
|
|
307
|
-
case 'ArrowFunctionExpression':
|
|
308
|
-
case 'FunctionExpression':
|
|
309
|
-
case 'JSXElement':
|
|
310
|
-
case 'JSXFragment':
|
|
311
|
-
return empty()
|
|
312
|
-
case 'MemberExpression': {
|
|
313
|
-
const object = unwrap(node.object)
|
|
314
|
-
const key = node.computed ? (unwrap(node.property)?.value ?? null) : node.property.name
|
|
315
|
-
if (object.type === 'Identifier') {
|
|
316
|
-
const props = unit.bindings.get(object.name)?.find(b => b.kind === 'props')
|
|
317
|
-
if (props && key) return forwarded(props.comp, key, seen)
|
|
318
|
-
const imp = unit.imports.get(object.name)
|
|
319
|
-
if (imp?.imported === '*' && imp.target && key) return exported(imp.target, key, seen)
|
|
320
|
-
}
|
|
321
|
-
const base = expression(unit, object, seen), out = empty()
|
|
322
|
-
out.dynamic = base.dynamic
|
|
323
|
-
for (const { shape, unit: u } of base.shapes) merge(out, property(u, shape, key, seen))
|
|
324
|
-
return out
|
|
325
|
-
}
|
|
326
|
-
case 'CallExpression': { // a list method hands back the list, or an item of it
|
|
327
|
-
const c = node.callee
|
|
328
|
-
if (c.type === 'MemberExpression' && !c.computed && ['filter', 'sort', 'toSorted', 'slice', 'reverse', 'concat'].includes(c.property.name)) return expression(unit, c.object, seen)
|
|
329
|
-
if (c.type === 'MemberExpression' && !c.computed && c.property.name === 'find') {
|
|
330
|
-
const base = expression(unit, c.object, seen), out = empty()
|
|
331
|
-
out.dynamic = base.dynamic
|
|
332
|
-
for (const { shape, unit: u } of base.shapes) merge(out, property(u, shape, null, seen))
|
|
333
|
-
return out
|
|
334
|
-
}
|
|
335
|
-
return { ...empty(), dynamic: text(unit, node) }
|
|
336
|
-
}
|
|
337
|
-
default:
|
|
338
|
-
return { ...empty(), dynamic: text(unit, node) }
|
|
339
|
-
}
|
|
178
|
+
for (const unit of units.values()) scanUnit(unit, ctx)
|
|
179
|
+
return {
|
|
180
|
+
names,
|
|
181
|
+
unknown,
|
|
182
|
+
dynamic,
|
|
183
|
+
escaped,
|
|
184
|
+
stops: stopsDynamic.length ? new Set(STOPS) : stops,
|
|
185
|
+
stopsDynamic,
|
|
186
|
+
files: units.size,
|
|
340
187
|
}
|
|
188
|
+
}
|
|
341
189
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const where = (unit, at) => ({ file: relative(root, unit.file), abs: unit.file, ...line(unit.code, at) })
|
|
345
|
-
const site = (unit, expr, at, soft, element) => {
|
|
190
|
+
function createSite({ expression, known, names, unknown, dynamic, where, catalog }) {
|
|
191
|
+
return (unit, expr, at, soft, element) => {
|
|
346
192
|
const r = expression(unit, expr)
|
|
347
193
|
const kinds = element ? variants(element).set : new Set([''])
|
|
348
194
|
for (const v of r.values) {
|
|
349
|
-
if (known.has(v))
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
195
|
+
if (known.has(v))
|
|
196
|
+
for (const k of kinds) {
|
|
197
|
+
if (k === 'action' && !actionList().includes(v))
|
|
198
|
+
unknown.push({
|
|
199
|
+
name: v,
|
|
200
|
+
why: actionMessage(v).slice(5),
|
|
201
|
+
...where(unit, unwrap(expr).start ?? at),
|
|
202
|
+
})
|
|
203
|
+
else names.add(k ? `${v}:${k}` : v)
|
|
204
|
+
}
|
|
205
|
+
else if (!soft)
|
|
206
|
+
unknown.push({
|
|
207
|
+
name: v,
|
|
208
|
+
suggest: closest(v, catalog),
|
|
209
|
+
...where(unit, unwrap(expr).start ?? at),
|
|
210
|
+
})
|
|
354
211
|
}
|
|
355
212
|
if (r.dynamic) dynamic.push({ why: r.dynamic, ...where(unit, at) })
|
|
356
213
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function scanUnit(unit, ctx) {
|
|
217
|
+
const { stops, stopsDynamic, known, where, site, expression, iconLocal, warpLocal } = ctx
|
|
218
|
+
for (const use of unit.uses) {
|
|
219
|
+
if (use.prop === 'name' && iconLocal(unit, use.comp))
|
|
220
|
+
site(unit, use.expr, use.at, false, use.element)
|
|
221
|
+
else if (use.prop === 'icon' && warpLocal(unit, use.comp)) {
|
|
222
|
+
// a literal becomes the icon itself (`rewrite`), so it needs no entry in every page's table
|
|
223
|
+
const v = literal(use.expr)
|
|
224
|
+
if (v !== undefined && known.has(v)) continue
|
|
225
|
+
site(unit, use.expr, use.at, false)
|
|
369
226
|
}
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
const own = unit.members.filter(m => m.local === local), inner = new Set(own.map(m => m.node.object))
|
|
373
|
-
for (const { node } of own) {
|
|
374
|
-
if (node.computed) {
|
|
375
|
-
const r = expression(unit, node.property)
|
|
376
|
-
for (const v of r.values) packName(v, catalog, names, () => unknown.push({ name: v, suggest: closest(v, packIds(catalog)), ...where(unit, node.start) }))
|
|
377
|
-
if (r.dynamic) dynamic.push({ why: r.dynamic, ...where(unit, node.start) })
|
|
378
|
-
} else packName(node.property.name, catalog, names, () => unknown.push({ name: node.property.name, suggest: closest(node.property.name, packIds(catalog)), ...where(unit, node.property.start) }))
|
|
379
|
-
}
|
|
380
|
-
for (const id of unit.idents) if (id.name === local && !inner.has(id) && !isSpecifier(unit, id)) escaped.push({ why: `${local} is used as a value`, ...where(unit, id.start) })
|
|
227
|
+
if (use.prop === 'tier') {
|
|
228
|
+
for (const v of expression(unit, use.expr).values) if (STOPS.includes(v)) stops.add(v)
|
|
381
229
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (
|
|
230
|
+
}
|
|
231
|
+
if (unit.warp) for (const s of unit.sites) site(unit, s.expr, s.at, true)
|
|
232
|
+
scanPack(unit, ctx)
|
|
233
|
+
for (const s of unit.stops) {
|
|
234
|
+
if (s.value) {
|
|
235
|
+
if (STOPS.includes(s.value)) stops.add(s.value)
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
if (s.dynamic) {
|
|
239
|
+
stopsDynamic.push(where(unit, s.at))
|
|
240
|
+
continue
|
|
388
241
|
}
|
|
242
|
+
const r = expression(unit, s.expr)
|
|
243
|
+
for (const v of r.values) if (STOPS.includes(v)) stops.add(v)
|
|
244
|
+
if (r.dynamic) stopsDynamic.push({ why: r.dynamic, ...where(unit, s.at) })
|
|
389
245
|
}
|
|
390
|
-
return { names, unknown, dynamic, escaped, stops: stopsDynamic.length ? new Set(STOPS) : stops, stopsDynamic, files: units.size }
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/** The module warp's name lookup becomes in a build: these names and no others, and the pack only if a name escaped. */
|
|
394
|
-
/** @param {Iterable<string>} names */
|
|
395
|
-
export function namesModule(names, { lazy = false, source } = {}) {
|
|
396
|
-
const list = [...names].sort()
|
|
397
|
-
const id = key => pascal(...key.split(':'))
|
|
398
|
-
const imports = list.length ? `import { ${list.map(id).join(', ')} } from '@weft-js/warp-icons'\n` : ''
|
|
399
|
-
const table = `const names = { ${list.map(n => `${JSON.stringify(n)}: ${id(n)}`).join(', ')} }\nconst key = (name, variant) => (variant ? name + ':' + variant : name)\n`
|
|
400
|
-
const packExport = `export const pack = id => { const [, b, v = ''] = /^(.+?)(Hollow|Solid|Action)?$/.exec(id.replace(/Icon(?=$|Hollow$|Solid$|Action$)/, '')); return resolve(b.replace(/[A-Z]/g, c => '-' + c.toLowerCase()).replace(/^-/, ''), v.toLowerCase()) }\n`
|
|
401
|
-
if (source) return `${imports}import { remote } from '@weft-js/warp-icons/late'\n${table}const get = remote(${JSON.stringify(source)})\nexport const resolve = (name, variant) => names[key(name, variant)] ?? get(name, variant)\n${packExport}`
|
|
402
|
-
if (lazy) return `${imports}import { late } from '@weft-js/warp-icons/late'\n${table}const load = () => import('@weft-js/warp-icons/all').then(m => m.lookup)\nexport const resolve = (name, variant) => names[key(name, variant)] ?? late(name, variant, load)\n${packExport}`
|
|
403
|
-
return `${imports}${table}export const resolve = (name, variant) => names[key(name, variant)]\n${packExport}`
|
|
404
246
|
}
|
|
405
247
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
248
|
+
function scanPack(unit, ctx) {
|
|
249
|
+
const { catalog, names, unknown, dynamic, escaped, where, expression } = ctx
|
|
250
|
+
for (const local of packLocals(unit)) {
|
|
251
|
+
const own = unit.members.filter((m) => m.local === local),
|
|
252
|
+
inner = new Set(own.map((m) => m.node.object))
|
|
253
|
+
for (const { node } of own) {
|
|
254
|
+
if (node.computed) {
|
|
255
|
+
const r = expression(unit, node.property)
|
|
256
|
+
for (const v of r.values)
|
|
257
|
+
packName(v, catalog, names, () =>
|
|
258
|
+
unknown.push({
|
|
259
|
+
name: v,
|
|
260
|
+
suggest: closest(v, packIds(catalog)),
|
|
261
|
+
...where(unit, node.start),
|
|
262
|
+
}),
|
|
263
|
+
)
|
|
264
|
+
if (r.dynamic) dynamic.push({ why: r.dynamic, ...where(unit, node.start) })
|
|
265
|
+
} else
|
|
266
|
+
packName(node.property.name, catalog, names, () =>
|
|
267
|
+
unknown.push({
|
|
268
|
+
name: node.property.name,
|
|
269
|
+
suggest: closest(node.property.name, packIds(catalog)),
|
|
270
|
+
...where(unit, node.property.start),
|
|
271
|
+
}),
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
for (const id of unit.idents)
|
|
275
|
+
if (id.name === local && !inner.has(id) && !isSpecifier(unit, id))
|
|
276
|
+
escaped.push({ why: `${local} is used as a value`, ...where(unit, id.start) })
|
|
277
|
+
}
|
|
415
278
|
}
|
|
416
279
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
280
|
+
// `Icon` alone is a small file over the names module, so this import doesn't bring the set
|
|
281
|
+
function iconOnlyImports(unit) {
|
|
282
|
+
const edits = []
|
|
283
|
+
for (const n of unit.ast.body)
|
|
284
|
+
if (
|
|
285
|
+
n.type === 'ImportDeclaration' &&
|
|
286
|
+
n.source.value === '@weft-js/warp-icons/all' &&
|
|
287
|
+
n.specifiers.length === 1 &&
|
|
288
|
+
n.specifiers[0].imported?.name === 'Icon'
|
|
289
|
+
)
|
|
290
|
+
edits.push({ start: n.source.start, end: n.source.end, text: "'@weft-js/warp-icons/icon'" })
|
|
291
|
+
return edits
|
|
420
292
|
}
|
|
421
293
|
|
|
422
294
|
/** `<Icon name="check">` becomes `<CheckIcon>`, the same output a named import gives, and `icon="check"` on a warp component takes `CheckIcon`. */
|
|
@@ -424,26 +296,38 @@ export function rewrite(code, file, catalog) {
|
|
|
424
296
|
if (!/@weft-js\/(warp-icons|warp)/.test(code)) return null
|
|
425
297
|
const unit = read(file, code)
|
|
426
298
|
if (!unit.ast) return null
|
|
427
|
-
const local = [...unit.imports].find(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
299
|
+
const local = [...unit.imports].find(
|
|
300
|
+
([, i]) => i.imported === 'Icon' && ICON_SOURCES.has(i.source),
|
|
301
|
+
)?.[0]
|
|
302
|
+
const edits = iconOnlyImports(unit),
|
|
303
|
+
added = new Set()
|
|
431
304
|
// the pack: a default import is the namespace, so a static member is an ordinary named import to the bundler
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
305
|
+
const lookups = rewritePack({
|
|
306
|
+
unit,
|
|
307
|
+
catalog,
|
|
308
|
+
code,
|
|
309
|
+
file,
|
|
310
|
+
edits,
|
|
311
|
+
packLocals,
|
|
312
|
+
packName,
|
|
313
|
+
unknownMessage,
|
|
314
|
+
packIds,
|
|
315
|
+
line,
|
|
316
|
+
})
|
|
442
317
|
for (const use of unit.uses) {
|
|
443
318
|
if (use.prop === 'icon') {
|
|
444
|
-
const v = literal(use.expr),
|
|
445
|
-
|
|
446
|
-
if (
|
|
319
|
+
const v = literal(use.expr),
|
|
320
|
+
imp = unit.imports.get(use.comp.split('.')[0])
|
|
321
|
+
if (
|
|
322
|
+
v === undefined ||
|
|
323
|
+
!imp ||
|
|
324
|
+
!(imp.source === '@weft-js/warp' || imp.source.startsWith('@weft-js/warp/'))
|
|
325
|
+
)
|
|
326
|
+
continue
|
|
327
|
+
if (catalog && !catalog.includes(v))
|
|
328
|
+
throw Object.assign(new Error(unknownMessage(v, catalog)), {
|
|
329
|
+
loc: { file, ...line(code, unwrap(use.expr).start) },
|
|
330
|
+
})
|
|
447
331
|
const name = pascal(v)
|
|
448
332
|
added.add(name)
|
|
449
333
|
edits.push({ start: use.at, end: use.end, text: `icon={${name}}` })
|
|
@@ -451,33 +335,45 @@ export function rewrite(code, file, catalog) {
|
|
|
451
335
|
}
|
|
452
336
|
if (!local || use.comp !== local || use.prop !== 'name') continue
|
|
453
337
|
const v = unwrap(use.expr)
|
|
454
|
-
if ((v.type !== 'Literal' && v.type !== 'StringLiteral') || typeof v.value !== 'string')
|
|
455
|
-
|
|
338
|
+
if ((v.type !== 'Literal' && v.type !== 'StringLiteral') || typeof v.value !== 'string')
|
|
339
|
+
continue
|
|
340
|
+
if (catalog && !catalog.includes(v.value))
|
|
341
|
+
throw Object.assign(new Error(unknownMessage(v.value, catalog)), {
|
|
342
|
+
loc: { file, ...line(code, v.start) },
|
|
343
|
+
})
|
|
456
344
|
if (!use.element.selfClosing) continue
|
|
457
345
|
const wanted = variants(use.element)
|
|
458
346
|
if (!wanted.fixed) continue
|
|
459
|
-
if (wanted.set.has('action') && !actionList().includes(v.value))
|
|
347
|
+
if (wanted.set.has('action') && !actionList().includes(v.value))
|
|
348
|
+
throw Object.assign(new Error(actionMessage(v.value)), {
|
|
349
|
+
loc: { file, ...line(code, v.start) },
|
|
350
|
+
})
|
|
460
351
|
const name = pascal(v.value, [...wanted.set][0])
|
|
461
352
|
added.add(name)
|
|
462
|
-
edits.push(
|
|
463
|
-
|
|
353
|
+
edits.push(
|
|
354
|
+
{ start: use.element.start, end: use.element.name.end, text: `<${name}` },
|
|
355
|
+
{ start: use.at, end: use.end, text: '' },
|
|
356
|
+
)
|
|
357
|
+
for (const a of use.element.attributes)
|
|
358
|
+
if (a.type === 'JSXAttribute' && VARIANTS.includes(a.name.name))
|
|
359
|
+
edits.push({ start: a.start, end: a.end, text: '' })
|
|
464
360
|
}
|
|
465
361
|
if (!edits.length) return null
|
|
466
362
|
let out = code
|
|
467
|
-
for (const e of edits.sort((a, b) => b.start - a.start))
|
|
468
|
-
|
|
363
|
+
for (const e of edits.sort((a, b) => b.start - a.start))
|
|
364
|
+
out = out.slice(0, e.start) + e.text + out.slice(e.end)
|
|
365
|
+
const head =
|
|
366
|
+
(added.size ? `import { ${[...added].join(', ')} } from '@weft-js/warp-icons'\n` : '') +
|
|
367
|
+
(lookups ? "import { pack as __icon } from '@weft-js/warp-icons/names'\n" : '')
|
|
469
368
|
return head + out
|
|
470
369
|
}
|
|
471
370
|
|
|
472
371
|
/** I001 for `action` on an icon that has none. */
|
|
473
|
-
export const actionMessage =
|
|
372
|
+
export const actionMessage = (name) =>
|
|
373
|
+
`I001 no action for icon '${name}', did you mean '${closest(name, actionList())}'? The action icons are ${actionList().join(', ')}.`
|
|
474
374
|
/** I001 for a name that isn't in the catalog. */
|
|
475
|
-
export const unknownMessage = (name, catalog) =>
|
|
476
|
-
|
|
477
|
-
/** The icons that have an action, read from the generated names. */
|
|
478
|
-
export const actionList = () => [...readFileSync(new URL('./src/gen/names.ts', import.meta.url), 'utf8').match(/const actions = (\[.*?\])/)[1].matchAll(/"([a-z0-9-]+)"/g)].map(m => m[1])
|
|
479
|
-
/** Every icon name, read from the generated names. */
|
|
480
|
-
export const list = () => [...new Set([...readFileSync(new URL('./src/gen/names.ts', import.meta.url), 'utf8').split('\n')[1].matchAll(/"([a-z0-9-]+)"/g)].map(m => m[1]))]
|
|
375
|
+
export const unknownMessage = (name, catalog) =>
|
|
376
|
+
`I001 no icon '${name}', did you mean '${closest(name, catalog)}'?`
|
|
481
377
|
|
|
482
378
|
/** The vite side, loaded by @weft-js/core/vite when @weft-js/warp-icons is in the build. */
|
|
483
379
|
export function pass({ root, command, tier, source }) {
|
|
@@ -485,40 +381,12 @@ export function pass({ root, command, tier, source }) {
|
|
|
485
381
|
const catalog = list()
|
|
486
382
|
const result = build ? analyze(root, { catalog }) : null
|
|
487
383
|
const lazy = !!result?.dynamic.length
|
|
488
|
-
const stops =
|
|
384
|
+
const stops =
|
|
385
|
+
tier && tier !== 'all' ? new Set([tier]) : result && !tier ? result.stops : new Set(STOPS)
|
|
489
386
|
return { catalog, result, lazy, stops, module: () => namesModule(result.names, { lazy, source }) }
|
|
490
387
|
}
|
|
491
388
|
|
|
492
|
-
/**
|
|
493
|
-
export function packCost() {
|
|
494
|
-
const code = readFileSync(new URL('./src/gen/all.tsx', import.meta.url), 'utf8')
|
|
495
|
-
return brotliCompressSync(Buffer.from(code), { params: { [constants.BROTLI_PARAM_QUALITY]: 5 } }).length
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/** `weft check`'s view: the same walk a build does, as findings. */
|
|
389
|
+
/** The icon findings for a build. */
|
|
499
390
|
export function findings(root) {
|
|
500
|
-
|
|
501
|
-
const kb = `${(packCost() / 1024).toFixed(0)} KB`
|
|
502
|
-
return [
|
|
503
|
-
...r.unknown.map(u => ({ code: 'I001', file: u.file, line: u.line, column: u.column, why: u.why ?? `no icon '${u.name}', did you mean '${u.suggest}'?` })),
|
|
504
|
-
...r.dynamic.map(d => ({ code: 'I002', file: d.file, line: d.line, column: d.column, why: `\`${d.why}\` isn't known until runtime, so this use loads the icon pack lazily (about ${kb} br, only on pages that reach it).` })),
|
|
505
|
-
...r.escaped.map(d => ({ code: 'I003', file: d.file, line: d.line, column: d.column, why: `${d.why}, so every icon ships with the page (about ${kb} br).` })),
|
|
506
|
-
]
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/** Every icon and variant as a static .svg, for a CDN or bucket. Writes files; nothing is uploaded. */
|
|
510
|
-
export async function publish(dir) {
|
|
511
|
-
const { load } = await import('./scripts/build.mjs')
|
|
512
|
-
const { forms } = await import('./scripts/forms.mjs')
|
|
513
|
-
const icons = await load()
|
|
514
|
-
mkdirSync(dir, { recursive: true })
|
|
515
|
-
const svg = html => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">${html}</svg>\n`
|
|
516
|
-
let files = 0
|
|
517
|
-
for (const [name, entry] of Object.entries(icons)) {
|
|
518
|
-
const f = forms(name, entry)
|
|
519
|
-
const all = { '': f.hollow.line + f.hollow.pixels, '.solid': f.solid.line + f.solid.pixels }
|
|
520
|
-
for (const [suffix, html] of Object.entries(all)) { writeFileSync(join(dir, `${name}${suffix}.svg`), svg(html)); files++ }
|
|
521
|
-
}
|
|
522
|
-
writeFileSync(join(dir, 'manifest.json'), JSON.stringify({ icons: Object.keys(icons), variants: ['', 'solid'] }) + '\n')
|
|
523
|
-
return { dir, files, icons: Object.keys(icons).length }
|
|
391
|
+
return catalogFindings(root, analyze)
|
|
524
392
|
}
|