@jirawatpyk/aura-react 5.8.0 → 5.9.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 +27 -0
- package/dist/aura.bundle.js +1485 -1085
- package/dist/cjs/index.cjs +1485 -1085
- package/dist/esm/Accordion.js +2 -1
- package/dist/esm/Alert.js +2 -1
- package/dist/esm/AppShell.js +2 -1
- package/dist/esm/Breadcrumb.js +2 -1
- package/dist/esm/Button.js +2 -1
- package/dist/esm/Checkbox.js +2 -1
- package/dist/esm/Combobox.js +7 -6
- package/dist/esm/Command.js +3 -2
- package/dist/esm/DataTable.js +35 -13
- package/dist/esm/DatePicker.js +8 -7
- package/dist/esm/Dialog.js +38 -5
- package/dist/esm/Field.js +2 -1
- package/dist/esm/FileUpload.js +4 -3
- package/dist/esm/FilterBar.js +3 -2
- package/dist/esm/FormErrorSummary.js +2 -1
- package/dist/esm/Icon.js +50 -31
- package/dist/esm/Menu.js +2 -1
- package/dist/esm/NumberField.js +3 -2
- package/dist/esm/Pagination.js +3 -2
- package/dist/esm/PasswordField.js +2 -1
- package/dist/esm/Popover.js +2 -1
- package/dist/esm/RadioGroup.js +2 -1
- package/dist/esm/Select.js +4 -3
- package/dist/esm/SideNav.js +3 -2
- package/dist/esm/Stat.js +8 -1
- package/dist/esm/Stepper.js +2 -1
- package/dist/esm/Tabs.js +79 -9
- package/dist/esm/Tag.js +3 -2
- package/dist/esm/TimePicker.js +5 -4
- package/dist/esm/Toaster.js +3 -2
- package/dist/esm/classes.js +14 -1
- package/dist/esm/colorScheme.js +8 -1
- package/dist/esm/display.js +22 -12
- package/dist/esm/iconSvg.js +53 -0
- package/dist/esm/icons.js +531 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/internal.js +1 -14
- package/dist/esm/locale.js +7 -0
- package/dist/esm/strings.js +4 -245
- package/dist/esm/strings.sv.js +127 -0
- package/dist/esm/strings.th.js +127 -0
- package/dist/icons/index.cjs +613 -0
- package/dist/icons/index.d.cts +106 -0
- package/dist/icons/index.d.ts +106 -0
- package/dist/index.d.cts +44 -2
- package/dist/index.d.ts +44 -2
- package/dist/locales/sv.cjs +149 -0
- package/dist/locales/sv.d.cts +96 -0
- package/dist/locales/sv.d.ts +96 -0
- package/dist/locales/th.cjs +149 -0
- package/dist/locales/th.d.cts +96 -0
- package/dist/locales/th.d.ts +96 -0
- package/dist/server/index.cjs +445 -314
- package/dist/server/index.d.cts +24 -0
- package/dist/server/index.d.ts +24 -0
- package/dist/server/index.js +445 -314
- package/dist/styles.css +1 -0
- package/dist/styles.layer.css +1 -0
- package/package.json +35 -3
- package/scripts/aura-icons-codemod.mjs +345 -0
package/dist/styles.css
CHANGED
|
@@ -623,6 +623,7 @@ a.aura-tab { text-decoration: none; }
|
|
|
623
623
|
font-size: 11px; line-height: 18px; text-align: center; font-variant-numeric: tabular-nums;
|
|
624
624
|
}
|
|
625
625
|
.aura-tabs__panel { padding-top: var(--aura-space-6); outline: none; }
|
|
626
|
+
.aura-tabs__panel[hidden] { display: none; }
|
|
626
627
|
.aura-tabs__panel:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 2px; border-radius: var(--aura-radius-sm); }
|
|
627
628
|
|
|
628
629
|
/* ---------- SideNav ---------- */
|
package/dist/styles.layer.css
CHANGED
|
@@ -625,6 +625,7 @@ a.aura-tab { text-decoration: none; }
|
|
|
625
625
|
font-size: 11px; line-height: 18px; text-align: center; font-variant-numeric: tabular-nums;
|
|
626
626
|
}
|
|
627
627
|
.aura-tabs__panel { padding-top: var(--aura-space-6); outline: none; }
|
|
628
|
+
.aura-tabs__panel[hidden] { display: none; }
|
|
628
629
|
.aura-tabs__panel:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 2px; border-radius: var(--aura-radius-sm); }
|
|
629
630
|
|
|
630
631
|
/* ---------- SideNav ---------- */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jirawatpyk/aura-react",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
4
4
|
"description": "AURA Design System — React components (Enterprise Standard, Human Creative). Thai-first: Buddhist-era dates, Thai collation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,6 +28,36 @@
|
|
|
28
28
|
"default": "./dist/server/index.cjs"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
|
+
"./icons": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/icons/index.d.ts",
|
|
34
|
+
"default": "./dist/esm/icons.js"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/icons/index.d.cts",
|
|
38
|
+
"default": "./dist/icons/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"./locales/th": {
|
|
42
|
+
"import": {
|
|
43
|
+
"types": "./dist/locales/th.d.ts",
|
|
44
|
+
"default": "./dist/esm/strings.th.js"
|
|
45
|
+
},
|
|
46
|
+
"require": {
|
|
47
|
+
"types": "./dist/locales/th.d.cts",
|
|
48
|
+
"default": "./dist/locales/th.cjs"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"./locales/sv": {
|
|
52
|
+
"import": {
|
|
53
|
+
"types": "./dist/locales/sv.d.ts",
|
|
54
|
+
"default": "./dist/esm/strings.sv.js"
|
|
55
|
+
},
|
|
56
|
+
"require": {
|
|
57
|
+
"types": "./dist/locales/sv.d.cts",
|
|
58
|
+
"default": "./dist/locales/sv.cjs"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
31
61
|
"./styles.css": "./dist/styles.css",
|
|
32
62
|
"./styles.layer.css": "./dist/styles.layer.css",
|
|
33
63
|
"./aura.bundle.js": "./dist/aura.bundle.js",
|
|
@@ -36,6 +66,7 @@
|
|
|
36
66
|
"files": [
|
|
37
67
|
"dist",
|
|
38
68
|
"scripts/aura-theme.mjs",
|
|
69
|
+
"scripts/aura-icons-codemod.mjs",
|
|
39
70
|
"README.md",
|
|
40
71
|
"LICENSE"
|
|
41
72
|
],
|
|
@@ -45,7 +76,7 @@
|
|
|
45
76
|
],
|
|
46
77
|
"scripts": {
|
|
47
78
|
"build": "node scripts/build.ts && node scripts/sync-bundle.ts",
|
|
48
|
-
"test:ssr": "node scripts/ssr-test.ts && node --conditions=react-server scripts/server-entry-test.ts",
|
|
79
|
+
"test:ssr": "node scripts/ssr-test.ts && node scripts/prep60-test.ts && node --conditions=react-server scripts/server-entry-test.ts",
|
|
49
80
|
"typecheck": "tsc -p tsconfig.src.json && tsc -p tsconfig.json && tsc -p tsconfig.exact.json && tsc -p examples/tsconfig.json",
|
|
50
81
|
"examples": "node examples/build.ts",
|
|
51
82
|
"themes": "node scripts/test-themes.ts",
|
|
@@ -76,7 +107,8 @@
|
|
|
76
107
|
"@playwright/test": "^1.45.0"
|
|
77
108
|
},
|
|
78
109
|
"bin": {
|
|
79
|
-
"aura-theme": "scripts/aura-theme.mjs"
|
|
110
|
+
"aura-theme": "scripts/aura-theme.mjs",
|
|
111
|
+
"aura-icons-codemod": "scripts/aura-icons-codemod.mjs"
|
|
80
112
|
},
|
|
81
113
|
"repository": {
|
|
82
114
|
"type": "git",
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
/* aura-icons-codemod (5.9) — moves icon names as strings to the per-icon components, ready for 6.0.
|
|
4
|
+
* npx aura-icons-codemod src [more paths…] [--dry]
|
|
5
|
+
* In .tsx and .jsx files that import from @jirawatpyk/aura-react (or /server), for names in AURA's set, it rewrites
|
|
6
|
+
* <Icon name="users" size={20} /> → <IconUsers size={20} />
|
|
7
|
+
* <Button icon="plus"> (icon / iconRight on an AURA component) → <Button icon={<IconPlus />}>
|
|
8
|
+
* { id: 'home', label: 'Home', icon: 'house' } → { id: 'home', label: 'Home', icon: <IconHouse /> }
|
|
9
|
+
* (an object literal with a `label` or `title`: nav, menu, tab and option items)
|
|
10
|
+
* and adds `import { IconHouse, IconPlus, IconUsers } from '@jirawatpyk/aura-react/icons'`. It reads the code with
|
|
11
|
+
* the TypeScript parser (your project's `typescript`), so types, strings and comments are never touched. Everything
|
|
12
|
+
* it leaves — names chosen at run time, items in .ts files, objects it isn't sure are AURA's, a name already taken by
|
|
13
|
+
* another import — is listed at the end: convert those by hand, or keep names with registerIcons(allIcons).
|
|
14
|
+
* --dry prints what would change and writes nothing. */
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const iconsSrc = fs.readFileSync(path.join(here, '../dist/esm/icons.js'), 'utf8');
|
|
22
|
+
const NAMES = new Set(Array.from(iconsSrc.matchAll(/defineIcon\d*\("([a-z0-9-]+)"/g), (m) => m[1]));
|
|
23
|
+
/** @param {string} n */
|
|
24
|
+
export const pascal = (n) =>
|
|
25
|
+
'Icon' +
|
|
26
|
+
n
|
|
27
|
+
.split('-')
|
|
28
|
+
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
29
|
+
.join('');
|
|
30
|
+
|
|
31
|
+
/** TypeScript from the project being migrated, else from next to this package. */
|
|
32
|
+
function loadTs() {
|
|
33
|
+
for (const from of [path.join(process.cwd(), 'package.json'), fileURLToPath(import.meta.url)]) {
|
|
34
|
+
try {
|
|
35
|
+
return createRequire(from)('typescript');
|
|
36
|
+
} catch (e) {
|
|
37
|
+
/* try the next */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
/** @type {any} */
|
|
43
|
+
let ts = null;
|
|
44
|
+
|
|
45
|
+
const ROOT = '@jirawatpyk/aura-react';
|
|
46
|
+
const ICONS = '@jirawatpyk/aura-react/icons';
|
|
47
|
+
const ICON_PROPS = ['icon', 'iconRight'];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Rewrites one file. Returns the new text, the icon names it imported, and notes on what it left.
|
|
51
|
+
* @param {string} src
|
|
52
|
+
* @param {string} [file]
|
|
53
|
+
*/
|
|
54
|
+
export function transform(src, file = 'file.tsx') {
|
|
55
|
+
if (!ts) ts = loadTs();
|
|
56
|
+
if (!ts) throw new Error('aura-icons-codemod needs TypeScript to read your code: npm i -D typescript');
|
|
57
|
+
/** @type {Set<string>} */
|
|
58
|
+
const used = new Set();
|
|
59
|
+
/** @type {string[]} */
|
|
60
|
+
const left = [];
|
|
61
|
+
const none = { code: src, used, left };
|
|
62
|
+
if (!/@jirawatpyk\/aura-react/.test(src)) return none;
|
|
63
|
+
const ext = path.extname(file).toLowerCase();
|
|
64
|
+
/** @type {Record<string, number>} */
|
|
65
|
+
const kinds = { '.tsx': ts.ScriptKind.TSX, '.ts': ts.ScriptKind.TS };
|
|
66
|
+
/* .js / .jsx / .mjs are read as JSX (plain JavaScript parses the same). */
|
|
67
|
+
const kind = kinds[ext] || ts.ScriptKind.JSX;
|
|
68
|
+
const sf = ts.createSourceFile(file, src, ts.ScriptTarget.Latest, true, kind);
|
|
69
|
+
/* An element can go where the file already allows JSX: .tsx/.jsx, or a .js file that has some. */
|
|
70
|
+
let jsx = ext === '.tsx' || ext === '.jsx';
|
|
71
|
+
if (!jsx && kind === ts.ScriptKind.JSX) {
|
|
72
|
+
const find = (/** @type {any} */ n) => {
|
|
73
|
+
if (jsx) return;
|
|
74
|
+
if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) jsx = true;
|
|
75
|
+
else ts.forEachChild(n, find);
|
|
76
|
+
};
|
|
77
|
+
find(sf);
|
|
78
|
+
}
|
|
79
|
+
const nl = src.indexOf('\r\n') >= 0 ? '\r\n' : '\n';
|
|
80
|
+
const line = (/** @type {any} */ node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
81
|
+
|
|
82
|
+
/* AURA's components by local name, the namespace (`import * as Aura`), Icon's local name, the icons import. */
|
|
83
|
+
/** @type {Set<string>} */
|
|
84
|
+
const aura = new Set();
|
|
85
|
+
/** @type {string[]} */
|
|
86
|
+
const nsNames = [];
|
|
87
|
+
let iconLocal = '';
|
|
88
|
+
/** @type {any} */
|
|
89
|
+
let iconsImport = null;
|
|
90
|
+
/** @type {any} */
|
|
91
|
+
let lastImport = null;
|
|
92
|
+
/** Names bound by imports from anywhere else and top-level declarations — a new IconX must not collide. */
|
|
93
|
+
/** @type {Set<string>} */
|
|
94
|
+
const taken = new Set();
|
|
95
|
+
for (const st of sf.statements) {
|
|
96
|
+
if (!ts.isImportDeclaration(st)) continue;
|
|
97
|
+
lastImport = st;
|
|
98
|
+
const from = st.moduleSpecifier.text;
|
|
99
|
+
const cl = st.importClause;
|
|
100
|
+
if (from === ICONS) {
|
|
101
|
+
/* Merge into a value import only: names added to `import type { … }` couldn't be used as JSX. */
|
|
102
|
+
if (cl && !cl.isTypeOnly) iconsImport = st;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const mine = from === ROOT || from === ROOT + '/server';
|
|
106
|
+
if (!cl) continue;
|
|
107
|
+
if (cl.name) (mine ? aura : taken).add(cl.name.text);
|
|
108
|
+
const nb = cl.namedBindings;
|
|
109
|
+
if (nb && ts.isNamespaceImport(nb)) {
|
|
110
|
+
if (mine) nsNames.push(nb.name.text);
|
|
111
|
+
else taken.add(nb.name.text);
|
|
112
|
+
}
|
|
113
|
+
if (nb && ts.isNamedImports(nb))
|
|
114
|
+
for (const el of nb.elements) {
|
|
115
|
+
if (!mine) {
|
|
116
|
+
taken.add(el.name.text);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
aura.add(el.name.text);
|
|
120
|
+
if ((el.propertyName || el.name).text === 'Icon') iconLocal = el.name.text;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (!aura.size && !nsNames.length) return none;
|
|
124
|
+
/* Every name declared anywhere in the file (a local `const IconUsers` inside a component too): a new IconX that
|
|
125
|
+
* matches one would silently render the local thing. */
|
|
126
|
+
const declared = (/** @type {any} */ n) => {
|
|
127
|
+
if (
|
|
128
|
+
(ts.isVariableDeclaration(n) ||
|
|
129
|
+
ts.isParameter(n) ||
|
|
130
|
+
ts.isFunctionDeclaration(n) ||
|
|
131
|
+
ts.isClassDeclaration(n) ||
|
|
132
|
+
ts.isBindingElement(n)) &&
|
|
133
|
+
n.name &&
|
|
134
|
+
ts.isIdentifier(n.name)
|
|
135
|
+
)
|
|
136
|
+
taken.add(n.name.text);
|
|
137
|
+
ts.forEachChild(n, declared);
|
|
138
|
+
};
|
|
139
|
+
declared(sf);
|
|
140
|
+
|
|
141
|
+
const isAura = (/** @type {string} */ t) => aura.has(t) || nsNames.some((n) => t.startsWith(n + '.'));
|
|
142
|
+
const isIconTag = (/** @type {string} */ t) =>
|
|
143
|
+
(!!iconLocal && t === iconLocal) || nsNames.some((n) => t === n + '.Icon');
|
|
144
|
+
/** @type {Array<{ start: number, end: number, text: string }>} */
|
|
145
|
+
const edits = [];
|
|
146
|
+
/** A literal icon name in an attribute initializer or a property value, else null.
|
|
147
|
+
* @param {any} init @returns {string | null} */
|
|
148
|
+
const literal = (init) => {
|
|
149
|
+
if (!init) return null;
|
|
150
|
+
if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) return init.text;
|
|
151
|
+
if (ts.isJsxExpression(init) && init.expression) return literal(init.expression);
|
|
152
|
+
if (ts.isParenthesizedExpression(init)) return literal(init.expression);
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
/** Can this name be imported as its component here? */
|
|
156
|
+
const usable = (/** @type {string} */ n, /** @type {any} */ node) => {
|
|
157
|
+
if (!NAMES.has(n)) return false;
|
|
158
|
+
if (taken.has(pascal(n))) {
|
|
159
|
+
left.push(line(node) + ': ' + pascal(n) + ' is already a name in this file — "' + n + '" left as a string');
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
};
|
|
164
|
+
const isElement = (/** @type {any} */ e) => !!e && (ts.isJsxElement(e) || ts.isJsxSelfClosingElement(e));
|
|
165
|
+
|
|
166
|
+
/** @param {any} node */
|
|
167
|
+
function visit(node) {
|
|
168
|
+
if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) {
|
|
169
|
+
const tag = node.tagName.getText(sf);
|
|
170
|
+
if (isAura(tag)) {
|
|
171
|
+
for (const attr of node.attributes.properties) {
|
|
172
|
+
if (!ts.isJsxAttribute(attr)) continue;
|
|
173
|
+
const prop = attr.name.getText(sf);
|
|
174
|
+
const isName = prop === 'name' && isIconTag(tag);
|
|
175
|
+
if (!isName && ICON_PROPS.indexOf(prop) < 0) continue;
|
|
176
|
+
const init = attr.initializer;
|
|
177
|
+
const n = literal(init);
|
|
178
|
+
if (n == null) {
|
|
179
|
+
if (init && !(ts.isJsxExpression(init) && isElement(init.expression)))
|
|
180
|
+
left.push(line(attr) + ': ' + attr.getText(sf) + ' (chosen at run time)');
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!usable(n, attr)) continue;
|
|
184
|
+
used.add(n);
|
|
185
|
+
if (isName) {
|
|
186
|
+
/* <Icon name="users" …> → <IconUsers …>: rename the tag (and a closing tag), drop `name`. */
|
|
187
|
+
edits.push({ start: node.tagName.getStart(sf), end: node.tagName.end, text: pascal(n) });
|
|
188
|
+
edits.push({ start: attr.getFullStart(), end: attr.end, text: '' });
|
|
189
|
+
if (ts.isJsxOpeningElement(node)) {
|
|
190
|
+
const close = node.parent.closingElement;
|
|
191
|
+
edits.push({ start: close.tagName.getStart(sf), end: close.tagName.end, text: pascal(n) });
|
|
192
|
+
}
|
|
193
|
+
} else edits.push({ start: init.getStart(sf), end: init.end, text: '{<' + pascal(n) + ' />}' });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (ts.isPropertyAssignment(node) && ts.isObjectLiteralExpression(node.parent)) {
|
|
198
|
+
const key = ts.isIdentifier(node.name) || ts.isStringLiteral(node.name) ? node.name.text : '';
|
|
199
|
+
if (ICON_PROPS.indexOf(key) >= 0) {
|
|
200
|
+
const n = literal(node.initializer);
|
|
201
|
+
/* An item has a label or title that is text (a string, template or JSX), not a config object. */
|
|
202
|
+
const item = node.parent.properties.some(
|
|
203
|
+
(/** @type {any} */ p) =>
|
|
204
|
+
ts.isPropertyAssignment(p) &&
|
|
205
|
+
(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
|
|
206
|
+
(p.name.text === 'label' || p.name.text === 'title') &&
|
|
207
|
+
(ts.isStringLiteral(p.initializer) ||
|
|
208
|
+
ts.isNoSubstitutionTemplateLiteral(p.initializer) ||
|
|
209
|
+
ts.isTemplateExpression(p.initializer) ||
|
|
210
|
+
ts.isJsxElement(p.initializer) ||
|
|
211
|
+
ts.isJsxSelfClosingElement(p.initializer) ||
|
|
212
|
+
ts.isJsxFragment(p.initializer) ||
|
|
213
|
+
ts.isIdentifier(p.initializer) ||
|
|
214
|
+
ts.isPropertyAccessExpression(p.initializer) ||
|
|
215
|
+
ts.isCallExpression(p.initializer)),
|
|
216
|
+
);
|
|
217
|
+
if (n != null && NAMES.has(n)) {
|
|
218
|
+
if (!item)
|
|
219
|
+
left.push(
|
|
220
|
+
line(node) + ': ' + node.getText(sf) + " (an object with no label or title — AURA's? convert by hand)",
|
|
221
|
+
);
|
|
222
|
+
else if (!jsx)
|
|
223
|
+
left.push(line(node) + ': ' + node.getText(sf) + ' (in a ' + ext + ' file: an element needs JSX)');
|
|
224
|
+
else if (usable(n, node)) {
|
|
225
|
+
used.add(n);
|
|
226
|
+
edits.push({
|
|
227
|
+
start: node.initializer.getStart(sf),
|
|
228
|
+
end: node.initializer.end,
|
|
229
|
+
text: '<' + pascal(n) + ' />',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
} else if (
|
|
233
|
+
n == null &&
|
|
234
|
+
item &&
|
|
235
|
+
(ts.isIdentifier(node.initializer) || ts.isPropertyAccessExpression(node.initializer))
|
|
236
|
+
)
|
|
237
|
+
left.push(line(node) + ': ' + node.getText(sf) + ' (chosen at run time)');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
ts.forEachChild(node, visit);
|
|
241
|
+
}
|
|
242
|
+
visit(sf);
|
|
243
|
+
if (!edits.length) return { code: src, used, left };
|
|
244
|
+
|
|
245
|
+
/* The import: merged into an existing icons import, else after the last import (or after the directives). */
|
|
246
|
+
const names = Array.from(used, pascal);
|
|
247
|
+
const nbIcons =
|
|
248
|
+
iconsImport &&
|
|
249
|
+
iconsImport.importClause &&
|
|
250
|
+
!iconsImport.importClause.isTypeOnly &&
|
|
251
|
+
iconsImport.importClause.namedBindings;
|
|
252
|
+
if (nbIcons && ts.isNamedImports(nbIcons)) {
|
|
253
|
+
const have = nbIcons.elements.map((/** @type {any} */ e) => e.getText(sf));
|
|
254
|
+
const add = names.filter((x) => have.indexOf(x) < 0);
|
|
255
|
+
if (add.length)
|
|
256
|
+
edits.push({
|
|
257
|
+
start: nbIcons.getStart(sf),
|
|
258
|
+
end: nbIcons.end,
|
|
259
|
+
text: '{ ' + have.concat(add).sort().join(', ') + ' }',
|
|
260
|
+
});
|
|
261
|
+
} else {
|
|
262
|
+
let at = 0;
|
|
263
|
+
if (lastImport) {
|
|
264
|
+
/* After the import's line, so a trailing comment stays with it. */
|
|
265
|
+
const eol = src.indexOf('\n', lastImport.end);
|
|
266
|
+
at = eol < 0 ? src.length : src[eol - 1] === '\r' ? eol - 1 : eol;
|
|
267
|
+
} else
|
|
268
|
+
for (const st of sf.statements) {
|
|
269
|
+
if (ts.isExpressionStatement(st) && ts.isStringLiteral(st.expression)) at = st.end;
|
|
270
|
+
else break;
|
|
271
|
+
}
|
|
272
|
+
const text = 'import { ' + names.sort().join(', ') + " } from '" + ICONS + "';";
|
|
273
|
+
edits.push({ start: at, end: at, text: at ? nl + text : text + nl });
|
|
274
|
+
}
|
|
275
|
+
edits.sort((a, b) => b.start - a.start || b.end - a.end);
|
|
276
|
+
let code = src;
|
|
277
|
+
for (const e of edits) code = code.slice(0, e.start) + e.text + code.slice(e.end);
|
|
278
|
+
|
|
279
|
+
/* Drop AURA's Icon from its import when nothing uses it any more (not when it is the only name there). */
|
|
280
|
+
if (iconLocal) {
|
|
281
|
+
const again = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true, kind);
|
|
282
|
+
let refs = 0;
|
|
283
|
+
/** @type {any} */
|
|
284
|
+
let spec = null;
|
|
285
|
+
const count = (/** @type {any} */ n) => {
|
|
286
|
+
if (ts.isImportSpecifier(n) && n.name.text === iconLocal) spec = n;
|
|
287
|
+
else if (ts.isIdentifier(n) && n.text === iconLocal && !ts.isImportSpecifier(n.parent)) refs++;
|
|
288
|
+
ts.forEachChild(n, count);
|
|
289
|
+
};
|
|
290
|
+
count(again);
|
|
291
|
+
if (!refs && spec && spec.parent.elements.length > 1) {
|
|
292
|
+
const list = spec.parent.elements
|
|
293
|
+
.filter((/** @type {any} */ e) => e !== spec)
|
|
294
|
+
.map((/** @type {any} */ e) => e.getText(again));
|
|
295
|
+
code = code.slice(0, spec.parent.getStart(again)) + '{ ' + list.join(', ') + ' }' + code.slice(spec.parent.end);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { code, used, left };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** @param {string} p @param {string[]} out */
|
|
302
|
+
function walk(p, out) {
|
|
303
|
+
const st = fs.statSync(p);
|
|
304
|
+
if (st.isDirectory()) {
|
|
305
|
+
for (const f of fs.readdirSync(p))
|
|
306
|
+
if (!/^(node_modules|dist|build|\.next|\.git)$/.test(f)) walk(path.join(p, f), out);
|
|
307
|
+
} else if (/\.(tsx|jsx|ts|js|mjs)$/.test(p) && !/\.d\.ts$/.test(p)) out.push(p);
|
|
308
|
+
return out;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === fs.realpathSync(process.argv[1])) {
|
|
312
|
+
const argv = process.argv.slice(2);
|
|
313
|
+
const dry = argv.includes('--dry');
|
|
314
|
+
const roots = argv.filter((a) => !a.startsWith('--'));
|
|
315
|
+
if (!roots.length || argv.includes('--help')) {
|
|
316
|
+
console.log('Usage: aura-icons-codemod <files or folders…> [--dry]');
|
|
317
|
+
process.exit(roots.length ? 0 : 2);
|
|
318
|
+
}
|
|
319
|
+
if (!NAMES.size) {
|
|
320
|
+
console.error('aura-icons-codemod: no icons found in dist/esm/icons.js — is the package built?');
|
|
321
|
+
process.exit(2);
|
|
322
|
+
}
|
|
323
|
+
ts = loadTs();
|
|
324
|
+
if (!ts) {
|
|
325
|
+
console.error('aura-icons-codemod: needs TypeScript to read your code (it only parses): npm i -D typescript');
|
|
326
|
+
process.exit(2);
|
|
327
|
+
}
|
|
328
|
+
let changed = 0;
|
|
329
|
+
/** @type {string[]} */
|
|
330
|
+
const todo = [];
|
|
331
|
+
for (const f of roots.flatMap((r) => walk(r, []))) {
|
|
332
|
+
const src = fs.readFileSync(f, 'utf8');
|
|
333
|
+
const r = transform(src, f);
|
|
334
|
+
for (const l of r.left) todo.push(f + ':' + l);
|
|
335
|
+
if (r.code === src) continue;
|
|
336
|
+
changed++;
|
|
337
|
+
console.log((dry ? 'would change ' : 'changed ') + f + ' (' + Array.from(r.used, pascal).join(', ') + ')');
|
|
338
|
+
if (!dry) fs.writeFileSync(f, r.code);
|
|
339
|
+
}
|
|
340
|
+
console.log('\n' + changed + (changed === 1 ? ' file' : ' files') + (dry ? ' would change.' : ' changed.'));
|
|
341
|
+
if (todo.length) {
|
|
342
|
+
console.log('\nLeft as names — convert by hand, or keep them with registerIcons(allIcons):');
|
|
343
|
+
for (const t of todo) console.log(' ' + t);
|
|
344
|
+
}
|
|
345
|
+
}
|