@solar-icons/codemod 2.0.0-beta.3

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/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Hakim Saoudi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ The Software may include icons that are licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0). Commercial use is allowed, but attribution is required for the use of these icons. See LICENSE-THIRD-PARTY for more details.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,14 @@
1
+ THIRD-PARTY LICENSES
2
+
3
+ This project includes icons that are licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0).
4
+
5
+ Attribution Requirement:
6
+ You are allowed to use these icons for commercial and non-commercial purposes, but you must give appropriate credit as required by the CC BY 4.0 license.
7
+
8
+ For more details, please visit: https://creativecommons.org/licenses/by/4.0/
9
+
10
+ List of Third-Party Elements:
11
+ - **Solar Icons Set** By [480 Design](https://www.figma.com/community/file/1166831539721848736)
12
+
13
+ Attribution Instructions:
14
+ When using the icons, please attribute the original authors in a visible way according to the terms of the CC BY 4.0 license.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @solar-icons/codemod
2
+
3
+ Conservative, opt-in migrations for Solar Icons v2.
4
+
5
+ ## Run a migration
6
+
7
+ Use the interactive assistant when running the migration by hand. It detects supported frameworks, asks for the React and Vue strategy when relevant, and requires confirmation before it writes files.
8
+
9
+ ```bash
10
+ npx @solar-icons/codemod@beta --interactive
11
+ ```
12
+
13
+ Use arguments for scripts, CI, or repeatable migrations. The command previews changes unless `--write` is passed.
14
+
15
+ ```bash
16
+ npx @solar-icons/codemod@beta --react-v1-mode static
17
+ npx @solar-icons/codemod@beta ./apps/web --react-v1-mode static --write
18
+ npx @solar-icons/codemod@beta --json > solar-icons-migration.json
19
+ ```
20
+
21
+ Run `npx @solar-icons/codemod@beta --help` for every option. `--cwd <path>` remains available for existing scripts, but a directory argument is shorter.
22
+
23
+ ## React v1 strategy
24
+
25
+ `--react-v1-mode static` is the default and recommended mode. It converts a known `weight` to a per-style import, removing the prop for the smallest bundle.
26
+
27
+ `--react-v1-mode dynamic` preserves the old all-styles component model through `@solar-icons/react/dynamic`.
28
+
29
+ If a `weight` expression cannot be resolved while using `static`, the codemod safely uses `dynamic` for that icon and prints a file, line, column, code, and explanation.
30
+
31
+ ## Vue v1 strategy
32
+
33
+ `--vue-v1-mode static` follows the same per-style default for Vue single-file components. It rewrites supported `<script>` or `<script setup>` imports and their template tags. `--vue-v1-mode dynamic` preserves `weight` bindings through `@solar-icons/vue/dynamic`.
34
+
35
+ ## Nuxt v1 configuration
36
+
37
+ The Nuxt adapter renames `solarIcons.prefix` to `solarIcons.namePrefix` and upgrades the Nuxt module dependency. The removed `#solar-icons/category` alias is reported with its source location because the replacement depends on the individual icons and styles used by the application.
38
+
39
+ ## Svelte v1 imports
40
+
41
+ The Svelte adapter migrates per-style imports and deterministic direct category component imports. Category barrels and removed `mirrored` props are reported with source locations for manual follow-up.
42
+
43
+ ## Angular v1 imports
44
+
45
+ The Angular adapter renames static icon exports to their `Solar`-prefixed v2 names, updates TypeScript references and deterministic inline-template selectors. It reports external templates and removed `mirrored` inputs for manual review.
46
+
47
+ ## Manual follow-ups
48
+
49
+ The codemod reports, without rewriting, legacy providers and `useSolar`, category imports, default namespace imports, and removed `mirrored` props. These need an application-level decision and are linked to the v2 migration guide.
50
+
51
+ All 37 removed icon names are converted through an explicit v1-to-v2 mapping.
52
+
53
+ ## Verification
54
+
55
+ ```bash
56
+ pnpm test
57
+ pnpm test:fixtures
58
+ ```
59
+
60
+ The runnable fixtures build pinned v1 applications, migrate a temporary copy, install the v2 beta, and build again.
package/dist/cli.d.mts ADDED
@@ -0,0 +1,5 @@
1
+ import { MigrationOptions, MigrationReport } from "./types.mjs";
2
+ //#region src/cli.d.ts
3
+ declare function runMigration({ cwd, reactV1Mode, targetVersion, vueV1Mode, write }: MigrationOptions): Promise<MigrationReport>;
4
+ //#endregion
5
+ export { runMigration };
package/dist/cli.mjs ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{detectFrameworks as e}from"./detect.mjs";import{transformPackageJson as t}from"./package-json.mjs";import{collectAngularSelectorRenames as n,transformAngular as r,transformAngularTemplate as i}from"./transforms/angular.mjs";import{transformNuxt as a}from"./transforms/nuxt.mjs";import{transformReactNative as o}from"./transforms/react-native.mjs";import{transformReactPerf as s}from"./transforms/react-perf.mjs";import{transformReact as c}from"./transforms/react.mjs";import{transformSolid as l}from"./transforms/solid.mjs";import{transformSvelte as u}from"./transforms/svelte.mjs";import{transformVue as d}from"./transforms/vue.mjs";import{readFile as f,readdir as p,writeFile as m}from"node:fs/promises";import{extname as h,join as g,resolve as _}from"node:path";import{pathToFileURL as v}from"node:url";import*as y from"@clack/prompts";import{Command as b,InvalidArgumentError as x}from"commander";import S from"picocolors";const C=new Set([`.html`,`.js`,`.jsx`,`.mjs`,`.ts`,`.tsx`,`.svelte`,`.vue`]),w=new Set([`.git`,`.next`,`.nuxt`,`.svelte-kit`,`coverage`,`dist`,`node_modules`]);async function T(e){let t=await p(e,{withFileTypes:!0});return(await Promise.all(t.map(async t=>{let n=g(e,t.name);return t.isDirectory()?w.has(t.name)?[]:T(n):t.isFile()&&C.has(h(t.name))?[n]:[]}))).flat()}async function E({cwd:p,reactV1Mode:h=`static`,targetVersion:_,vueV1Mode:v=`static`,write:y=!1}){let b=await e(p),x=await T(p),S=new Map(await Promise.all(x.map(async e=>[e,await f(e,`utf8`)]))),C=new Map;for(let[e,t]of S)for(let[r,i]of n(t,e))C.set(r,i);let w=new Set(x),E=[],D=[];for(let e of x){let t=s(S.get(e),e),n=c(t.code,e,h),f=o(n.code,e),p=d(f.code,e,v),g=a(p.code,e),_=u(g.code,e),b=l(_.code,e),x=r(b.code,e,w),T=i(x.code,C);D.push(...t.diagnostics,...n.diagnostics,...f.diagnostics,...p.diagnostics,...g.diagnostics,..._.diagnostics,...b.diagnostics,...x.diagnostics,...T.diagnostics),!(!t.changed&&!n.changed&&!f.changed&&!p.changed&&!g.changed&&!_.changed&&!b.changed&&!x.changed&&!T.changed)&&(E.push(e),y&&await m(e,T.code))}let O=g(p,`package.json`),k=t(await f(O,`utf8`),_);return k.changed&&(E.push(O),y&&await m(O,k.code)),{changedFiles:E,diagnostics:D,detectedFrameworks:b}}function D(e){if(e===`static`||e===`dynamic`)return e;throw new x(`Expected either "static" or "dynamic".`)}function O(e){let t=e.file&&e.line?`${e.file}:${e.line}:${e.column??1}`:e.file,n=e.severity===`error`?S.red(`error`):S.yellow(`warning`);return`${S.dim(t??`solar-icons`)} ${n} ${S.bold(`[${e.code}]`)} ${e.message}`}function k(e,t){console.info(`${S.cyan(`Detected frameworks:`)} ${e.detectedFrameworks.join(`, `)||`none`}`),console.info(`${t?S.green(`Migrated`):S.cyan(`Would migrate`)} ${S.bold(String(e.changedFiles.length))} file(s).`);for(let t of e.diagnostics)console.warn(O(t));!t&&e.changedFiles.length>0&&console.info(S.dim(`Run again with --write to apply these changes.`))}async function A(e){let t=await y.select({message:`${e} migration strategy`,options:[{value:`static`,label:`Static (recommended)`,hint:`one style per import and the smallest bundle`},{value:`dynamic`,label:`Dynamic`,hint:`preserve runtime style switching`}]});if(!y.isCancel(t))return t}async function j(t){y.intro(S.bgCyan(S.black(` Solar Icons v2 migration `)));let n=await e(t);y.log.info(`Detected: ${n.join(`, `)||`no supported framework`}`);let r=n.includes(`react`)?await A(`React`):void 0;if(n.includes(`react`)&&!r){y.cancel(`Migration cancelled.`);return}let i=n.includes(`vue`)?await A(`Vue`):void 0;if(n.includes(`vue`)&&!i){y.cancel(`Migration cancelled.`);return}let a=await y.select({message:`Choose an action`,options:[{value:`preview`,label:`Preview changes`,hint:`does not modify files`},{value:`write`,label:`Apply changes`,hint:`writes the migration to disk`}]});if(y.isCancel(a)){y.cancel(`Migration cancelled.`);return}let o=a===`write`;if(o){let e=await y.confirm({message:`Write the migration to this project?`,initialValue:!1});if(y.isCancel(e)||!e){y.cancel(`Migration cancelled.`);return}}return{reactV1Mode:r,vueV1Mode:i,write:o}}async function M(){let e=new b().name(`solar-icons-migrate`).description(`Migrate a Solar Icons project from the pre-v2 API to v2.`).argument(`[directory]`,`project directory, defaults to the current directory`).option(`-C, --cwd <path>`,`project directory, kept for backwards compatibility`).option(`-i, --interactive`,`guide the migration with prompts`).option(`-w, --write`,`apply changes instead of previewing them`).option(`--react-v1-mode <mode>`,`React strategy: static or dynamic`,D).option(`--vue-v1-mode <mode>`,`Vue strategy: static or dynamic`,D).option(`--target-version <version>`,`Solar Icons v2 version to add to package.json`).option(`--json`,`write a machine-readable report to stdout`).showHelpAfterError().showSuggestionAfterError();e.action(async(t,n)=>{t&&n.cwd&&e.error(`Use either [directory] or --cwd, not both.`,{exitCode:2}),n.interactive&&n.json&&e.error(`--interactive cannot be combined with --json.`,{exitCode:2});let r=_(n.cwd??t??process.cwd()),i=n.interactive?await j(r):void 0;if(n.interactive&&!i)return;let a={cwd:r,reactV1Mode:i?.reactV1Mode??n.reactV1Mode,targetVersion:n.targetVersion,vueV1Mode:i?.vueV1Mode??n.vueV1Mode,write:i?.write??n.write},o=await E(a);n.json?console.info(JSON.stringify(o,null,2)):k(o,!!a.write),n.interactive&&y.outro(`Migration complete.`)}),await e.parseAsync()}process.argv[1]&&import.meta.url===v(process.argv[1]).href&&M();export{E as runMigration};
@@ -0,0 +1,5 @@
1
+ import { Framework } from "./types.mjs";
2
+ //#region src/detect.d.ts
3
+ declare function detectFrameworks(cwd: string): Promise<Framework[]>;
4
+ //#endregion
5
+ export { detectFrameworks };
@@ -0,0 +1 @@
1
+ import{readFile as e}from"node:fs/promises";import{join as t}from"node:path";const n={"@angular/core":`angular`,"@nuxt/kit":`nuxt`,nuxt:`nuxt`,react:`react`,"react-native":`react-native`,"solid-js":`solid`,svelte:`svelte`,vue:`vue`};async function r(r){let i=JSON.parse(await e(t(r,`package.json`),`utf8`)),a={...i.dependencies,...i.devDependencies,...i.peerDependencies};return Object.entries(n).filter(([e])=>e in a).map(([,e])=>e)}export{r as detectFrameworks};
@@ -0,0 +1,6 @@
1
+ //#region src/icon-renames.d.ts
2
+ /** Complete v1-to-v2 icon rename table. Old exports do not exist in v2. */
3
+ declare const iconRenames: Record<string, string>;
4
+ declare function renameIcon(name: string): string;
5
+ //#endregion
6
+ export { iconRenames, renameIcon };
@@ -0,0 +1 @@
1
+ const e={Accumulator:`CarBattery`,BellBing:`BellRing`,BoneBroken:`BoneFracture`,Clound:`Cloud`,Condicioner:`Conditioner`,Essentional:`Essential`,FileFavourite:`FileFavorite`,FolderFavouriteBookmark:`FolderFavoriteBookmark`,FolderFavouriteStar:`FolderFavoriteStar`,GalleryFavourite:`GalleryFavorite`,Globus:`Globe`,Happly:`Happy`,HeartBroken:`HeartCrack`,Horizontall:`Horizontal`,Infinity:`Infinite`,LinkBroken:`Unlink`,LinkBrokenMinimalistic:`UnlinkMinimalistic`,MagicStick:`MagicWand`,MagicStick2:`MagicWand2`,MagicStick3:`MagicWand3`,Magnifer:`Magnifier`,MapPointFavourite:`MapPointFavorite`,Minimlistic:`Minimalistic`,Pallete:`Palette`,Plain:`Plane`,Plain2:`Plane2`,Plain3:`Plane3`,Plaaylist:`Playlist`,Recive:`Receive`,Siderbar:`Sidebar`,SortByAlphabet:`SortAlphabetically`,Spedometer:`Speedometer`,Text:`TextFormat`,Trellis:`Vanity`,Tuneing:`Tuning`,WadOfMoney:`MoneyRoll`,Weigher:`Scale`},t={Clound:`Cloud`,Condicioner:`Conditioner`,Essentional:`Essential`,Happly:`Happy`,Magnifer:`Magnifier`,Minimlistic:`Minimalistic`,Pallete:`Palette`,Plaaylist:`Playlist`,Recive:`Receive`,Siderbar:`Sidebar`,Spedometer:`Speedometer`,Tuneing:`Tuning`};function n(n){return e[n]?e[n]:Object.entries(t).reduce((e,[t,n])=>e.replaceAll(t,n),n)}export{e as iconRenames,n as renameIcon};
@@ -0,0 +1 @@
1
+ import e from"typescript";function t(t,n,r){if(r.size===0)return[];let i={allowJs:!0,jsx:e.JsxEmit.ReactJSX,noResolve:!0,target:e.ScriptTarget.Latest},a=e.createSourceFile(n,t,e.ScriptTarget.Latest,!0),o=e.createCompilerHost(i),s=o.getSourceFile.bind(o);o.getSourceFile=(e,t,r,i)=>e===n?a:s(e,t,r,i);let c=e.createProgram([n],i,o),l=c.getSourceFile(n);if(!l)return[];let u=c.getTypeChecker(),d=new Map;for(let t of l.statements){if(!e.isImportDeclaration(t))continue;let n=t.importClause?.namedBindings;if(!(!n||!e.isNamedImports(n)))for(let e of n.elements){let t=e.name.text;if(!r.has(t))continue;let n=u.getSymbolAtLocation(e.name);n&&d.set(t,n)}}let f=[],p=t=>{if(e.isIdentifier(t)&&!e.isImportSpecifier(t.parent)){let e=r.get(t.text),n=d.get(t.text);e&&n&&u.getSymbolAtLocation(t)===n&&f.push({end:t.getEnd(),start:t.getStart(l),text:e})}e.forEachChild(t,p)};return e.forEachChild(l,p),f}export{t as renameImportedBindingReferences};
@@ -0,0 +1,13 @@
1
+ import { Diagnostic, Framework, MigrationOptions, MigrationReport, ReactV1Mode, TransformResult } from "./types.mjs";
2
+ import { detectFrameworks } from "./detect.mjs";
3
+ import { iconRenames, renameIcon } from "./icon-renames.mjs";
4
+ import { transformPackageJson } from "./package-json.mjs";
5
+ import { transformAngular } from "./transforms/angular.mjs";
6
+ import { transformNuxt } from "./transforms/nuxt.mjs";
7
+ import { transformReactNative } from "./transforms/react-native.mjs";
8
+ import { transformReactPerf } from "./transforms/react-perf.mjs";
9
+ import { transformReact } from "./transforms/react.mjs";
10
+ import { transformSolid } from "./transforms/solid.mjs";
11
+ import { transformSvelte } from "./transforms/svelte.mjs";
12
+ import { transformVue } from "./transforms/vue.mjs";
13
+ export { type Diagnostic, type Framework, type MigrationOptions, type MigrationReport, type ReactV1Mode, type TransformResult, detectFrameworks, iconRenames, renameIcon, transformAngular, transformNuxt, transformPackageJson, transformReact, transformReactNative, transformReactPerf, transformSolid, transformSvelte, transformVue };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{detectFrameworks as e}from"./detect.mjs";import{iconRenames as t,renameIcon as n}from"./icon-renames.mjs";import{transformPackageJson as r}from"./package-json.mjs";import{transformAngular as i}from"./transforms/angular.mjs";import{transformNuxt as a}from"./transforms/nuxt.mjs";import{transformReactNative as o}from"./transforms/react-native.mjs";import{transformReactPerf as s}from"./transforms/react-perf.mjs";import{transformReact as c}from"./transforms/react.mjs";import{transformSolid as l}from"./transforms/solid.mjs";import{transformSvelte as u}from"./transforms/svelte.mjs";import{transformVue as d}from"./transforms/vue.mjs";export{e as detectFrameworks,t as iconRenames,n as renameIcon,i as transformAngular,a as transformNuxt,r as transformPackageJson,c as transformReact,o as transformReactNative,s as transformReactPerf,l as transformSolid,u as transformSvelte,d as transformVue};
@@ -0,0 +1,6 @@
1
+ import { TransformResult } from "./types.mjs";
2
+ //#region src/package-json.d.ts
3
+ /** Updates supported Solar Icons v1 dependencies to the requested v2 version. */
4
+ declare function transformPackageJson(source: string, targetVersion?: string): TransformResult;
5
+ //#endregion
6
+ export { transformPackageJson };
@@ -0,0 +1 @@
1
+ const e=[`dependencies`,`devDependencies`,`peerDependencies`];function t(t,n=`^2.0.0`){let r=JSON.parse(t),i=!1;for(let t of e){let e=r[t];if(!e)continue;if(`@solar-icons/react-perf`in e){delete e[`@solar-icons/react-perf`],e[`@solar-icons/react`]=n,i=!0;continue}let a=e[`@solar-icons/react`];(a?.startsWith(`1.`)||a?.startsWith(`^1.`)||a?.startsWith(`~1.`))&&(e[`@solar-icons/react`]=n,i=!0);let o=e[`@solar-icons/react-native`];(o?.startsWith(`1.`)||o?.startsWith(`^1.`)||o?.startsWith(`~1.`))&&(e[`@solar-icons/react-native`]=n,i=!0);for(let t of[`@solar-icons/vue`,`@solar-icons/nuxt`,`@solar-icons/svelte`,`@solar-icons/solid`,`@solar-icons/angular`]){let r=e[t];(r?.startsWith(`1.`)||r?.startsWith(`^1.`)||r?.startsWith(`~1.`))&&(e[t]=n,i=!0)}}return{code:i?`${JSON.stringify(r,null,4)}\n`:t,changed:i,diagnostics:[]}}export{t as transformPackageJson};
@@ -0,0 +1,6 @@
1
+ import { TransformResult } from "../types.mjs";
2
+ //#region src/transforms/angular.d.ts
3
+ /** Migrates Angular v1 icon imports and deterministic inline-template selectors. */
4
+ declare function transformAngular(source: string, fileName?: string, existingFiles?: Set<string>): TransformResult;
5
+ //#endregion
6
+ export { transformAngular };
@@ -0,0 +1 @@
1
+ import{renameIcon as e}from"../icon-renames.mjs";import{resolve as t}from"node:path";import n from"typescript";const r=[`BoldDuotone`,`LineDuotone`,`Outline`,`Broken`,`Linear`,`Bold`];function i(e,t){return t.sort((e,t)=>t.start-e.start).reduce((e,t)=>e.slice(0,t.start)+t.text+e.slice(t.end),e)}function a(e,t){let n=e.getLineAndCharacterOfPosition(t.getStart(e));return{column:n.character+1,line:n.line+1}}function o(t){if(t===`SolarDynamicIcon`)return`SolarIcon`;let n=r.find(e=>t.endsWith(e));if(!(!n||t.startsWith(`Solar`)))return`Solar${e(t.slice(0,-n.length))}${n}`}function s(e){return`solar${e.slice(5)}`}function c(e,t=`source.ts`){let r=n.createSourceFile(t,e,n.ScriptTarget.Latest,!0),i=new Map;for(let e of r.statements){if(!n.isImportDeclaration(e)||!n.isStringLiteral(e.moduleSpecifier)||e.moduleSpecifier.text!==`@solar-icons/angular`)continue;let t=e.importClause?.namedBindings;if(!(!t||!n.isNamedImports(t)))for(let e of t.elements){let t=(e.propertyName??e.name).text,n=o(t);n&&i.set(s(`Solar${t}`),s(n))}}return i}function l(e,t){let n=[];for(let[r,i]of t){let t=RegExp(`\\b${r}\\b`,`g`);for(let a of e.matchAll(t))n.push({start:a.index,end:a.index+r.length,text:i})}let r=i(e,n);return{code:r,changed:r!==e,diagnostics:[]}}function u(e,r=`source.ts`,c=new Set){if(!r.endsWith(`.ts`))return{changed:!1,code:e,diagnostics:[]};let l=n.createSourceFile(r,e,n.ScriptTarget.Latest,!0),u=[],f=[];for(let t of l.statements){if(!n.isImportDeclaration(t)||!n.isStringLiteral(t.moduleSpecifier)||t.moduleSpecifier.text!==`@solar-icons/angular`)continue;let r=t.importClause?.namedBindings;if(!(!r||!n.isNamedImports(r)))for(let t of r.elements){let r=(t.propertyName??t.name).text,i=o(r);if(!i)continue;let a=t.name.text,c=t.propertyName?a:i;if(u.push({start:t.getStart(l),end:t.getEnd(),text:c===i?i:`${i} as ${c}`}),!t.propertyName&&a!==i){let e=r=>{n.isIdentifier(r)&&r.text===a&&r.getStart(l)!==t.name.getStart(l)&&u.push({start:r.getStart(l),end:r.getEnd(),text:i}),n.forEachChild(r,e)};n.forEachChild(l,e)}u.push(...d(e,s(`Solar${r}`),s(i)))}}let p=e=>{if(n.isPropertyAssignment(e)&&n.isIdentifier(e.name)){if(e.name.text===`templateUrl`){let i=n.isStringLiteral(e.initializer)?t(r,`..`,e.initializer.text):void 0;(!i||!c.has(i))&&f.push({code:`ANGULAR_EXTERNAL_TEMPLATE_REQUIRES_MANUAL_MIGRATION`,file:r,...a(l,e.name),message:`Skipped an external Angular template that could not be found. Review its icon selector renames manually.`})}e.name.text===`template`&&(n.isNoSubstitutionTemplateLiteral(e.initializer)||n.isStringLiteral(e.initializer))&&/\bmirrored\b/.test(e.initializer.text)&&f.push({code:`ANGULAR_MIRRORED_REQUIRES_MANUAL_MIGRATION`,file:r,...a(l,e.initializer),message:`An inline Angular template uses the removed mirrored input. Replace it with CSS transform: scaleX(-1).`,severity:`warning`})}n.forEachChild(e,p)};n.forEachChild(l,p);let m=i(e,u);return{code:m,changed:m!==e,diagnostics:f}}function d(e,t,n){let r=RegExp(`\\b${t}\\b`,`g`);return[...e.matchAll(r)].map(e=>({start:e.index,end:e.index+t.length,text:n}))}export{c as collectAngularSelectorRenames,u as transformAngular,l as transformAngularTemplate};
@@ -0,0 +1,6 @@
1
+ import { TransformResult } from "../types.mjs";
2
+ //#region src/transforms/nuxt.d.ts
3
+ /** Migrates deterministic Nuxt module configuration and reports removed category aliases. */
4
+ declare function transformNuxt(source: string, fileName?: string): TransformResult;
5
+ //#endregion
6
+ export { transformNuxt };
@@ -0,0 +1 @@
1
+ import e from"typescript";function t(e,t){return t.sort((e,t)=>t.start-e.start).reduce((e,t)=>e.slice(0,t.start)+t.text+e.slice(t.end),e)}function n(e,t){let n=e.getLineAndCharacterOfPosition(t.getStart(e));return{column:n.character+1,line:n.line+1}}function r(t){if(e.isPropertyAssignment(t))return e.isIdentifier(t.name)||e.isStringLiteral(t.name)?t.name.text:void 0}function i(i,a=`nuxt.config.ts`){let o=e.createSourceFile(a,i,e.ScriptTarget.Latest,!0),s=[],c=[],l=t=>{if(e.isImportDeclaration(t)&&e.isStringLiteral(t.moduleSpecifier)&&t.moduleSpecifier.text===`#solar-icons/category`&&c.push({code:`NUXT_CATEGORY_IMPORT_REQUIRES_MANUAL_MIGRATION`,file:a,...n(o,t.moduleSpecifier),message:`Skipped the removed #solar-icons/category alias. Import each required v2 icon individually.`}),e.isCallExpression(t)&&e.isIdentifier(t.expression)&&t.expression.text===`defineNuxtConfig`){let n=t.arguments.find(e.isObjectLiteralExpression)?.properties.find(e=>r(e)===`solarIcons`);if(n&&e.isPropertyAssignment(n)&&e.isObjectLiteralExpression(n.initializer))for(let t of n.initializer.properties)r(t)!==`prefix`||!e.isPropertyAssignment(t)||s.push({start:t.name.getStart(o),end:t.name.getEnd(),text:`namePrefix`})}e.forEachChild(t,l)};e.forEachChild(o,l);let u=t(i,s);return{code:u,changed:u!==i,diagnostics:c}}export{i as transformNuxt};
@@ -0,0 +1,6 @@
1
+ import { TransformResult } from "../types.mjs";
2
+ //#region src/transforms/react-native.d.ts
3
+ /** Migrates the v1 React Native root, style, and category imports. */
4
+ declare function transformReactNative(source: string, fileName?: string): TransformResult;
5
+ //#endregion
6
+ export { transformReactNative };
@@ -0,0 +1,2 @@
1
+ import{renameIcon as e}from"../icon-renames.mjs";import t from"typescript";const n={bold:`bold`,boldduotone:`bold-duotone`,broken:`broken`,linear:`linear`,lineduotone:`line-duotone`,outline:`outline`},r=[`BoldDuotone`,`LineDuotone`,`Outline`,`Broken`,`Linear`,`Bold`];function i(e){return e.endsWith(`Icon`)?e:`${e}Icon`}function a(e){let t=e.split(`/`).at(-1)?.replace(/[-_]/g,``).toLowerCase();return t?n[t]:void 0}function o(e){return e.replace(/([a-z\d])([A-Z])/g,`$1-$2`).replace(/([A-Z])([A-Z][a-z])/g,`$1-$2`).replace(/(\D)(\d+)/g,`$1-$2`).toLowerCase()}function s(t){let n=r.find(e=>t.endsWith(e));return i(n?`${e(t.slice(0,-n.length))}${n}`:e(t))}function c(e,t){return e.name.text===t?t:`${t} as ${e.name.text}`}function l(e,t){return t.sort((e,t)=>t.start-e.start).reduce((e,t)=>e.slice(0,t.start)+t.text+e.slice(t.end),e)}function u(e,t){let n=e.getLineAndCharacterOfPosition(t.getStart(e));return{column:n.character+1,line:n.line+1}}function d(n,r=`source.tsx`){let d=t.createSourceFile(r,n,t.ScriptTarget.Latest,!0),f=[],p=[];for(let n of d.statements){if(!t.isImportDeclaration(n)||!t.isStringLiteral(n.moduleSpecifier))continue;let l=n.moduleSpecifier.text;if(!l.startsWith(`@solar-icons/react-native`))continue;let m=n.importClause?.namedBindings;if(!m||!t.isNamedImports(m)){p.push({code:`UNSUPPORTED_REACT_NATIVE_IMPORT`,file:r,...u(d,n.moduleSpecifier),message:`Skipped non-named import from ${l}. Convert it to individual icon imports manually.`});continue}let h=l.slice(25);if(!h){for(let e of m.elements)f.push({start:e.getStart(d),end:e.getEnd(),text:c(e,s((e.propertyName??e.name).text))});continue}let g=h.match(/^\/category\/[^/]+\/([^/]+)$/),_=g?.[1]?a(g[1]):a(l);if(!_){p.push({code:`UNSUPPORTED_REACT_NATIVE_SUBPATH`,file:r,...u(d,n.moduleSpecifier),message:`Skipped unsupported React Native subpath: ${l}.`});continue}if(g){let t=m.elements.map(t=>{let n=e((t.propertyName??t.name).text);return`import { ${c(t,i(n))} } from '@solar-icons/react-native/${_}/${o(n)}'`});f.push({start:n.getStart(d),end:n.getEnd(),text:t.join(`
2
+ `)});continue}f.push({start:n.moduleSpecifier.getStart(d)+1,end:n.moduleSpecifier.getEnd()-1,text:`@solar-icons/react-native/${_}`});for(let t of m.elements)f.push({start:t.getStart(d),end:t.getEnd(),text:c(t,i(e((t.propertyName??t.name).text)))});let v=new Set(m.elements.map(e=>e.name.text)),y=e=>{if(t.isJsxOpeningLikeElement(e)&&t.isIdentifier(e.tagName)&&v.has(e.tagName.text))for(let n of e.attributes.properties){if(!t.isJsxAttribute(n))continue;n.name.getText()===`mirrored`&&p.push({code:`REACT_NATIVE_MIRRORED_REQUIRES_MANUAL_MIGRATION`,file:r,...u(d,n),message:`${e.tagName.text} uses the removed mirrored prop. Replace it with style={{ transform: [{ scaleX: -1 }] }}.`,severity:`warning`});let i=n.initializer;if(n.name.getText()===`size`&&i&&t.isStringLiteral(i)){let t=Number(i.text);Number.isFinite(t)?f.push({start:i.getStart(d),end:i.getEnd(),text:`{${t}}`}):p.push({code:`REACT_NATIVE_SIZE_REQUIRES_NUMBER`,file:r,...u(d,n),message:`${e.tagName.text} has a string size. React Native v2 requires a number.`,severity:`warning`})}}t.forEachChild(e,y)};t.forEachChild(d,y)}let m=l(n,f);return{code:m,changed:m!==n,diagnostics:p}}export{d as transformReactNative};
@@ -0,0 +1,6 @@
1
+ import { TransformResult } from "../types.mjs";
2
+ //#region src/transforms/react-perf.d.ts
3
+ /** Migrates deterministic `@solar-icons/react-perf` imports and modernizes default local names. */
4
+ declare function transformReactPerf(source: string, fileName?: string): TransformResult;
5
+ //#endregion
6
+ export { transformReactPerf };
@@ -0,0 +1 @@
1
+ import{renameIcon as e}from"../icon-renames.mjs";import{renameImportedBindingReferences as t}from"../import-bindings.mjs";import n from"typescript";const r={bold:`bold`,boldduotone:`bold-duotone`,broken:`broken`,linear:`linear`,lineduotone:`line-duotone`,outline:`outline`},i=new Set([`Icon`,`IconProps`]);function a(e){return e.endsWith(`Icon`)?e:`${e}Icon`}function o(e){let t=e.split(`/`).at(-1)?.replace(/[-_]/g,``).toLowerCase();return t?r[t]:void 0}function s(t,n){let i=Object.entries(r).find(([,e])=>e===n)?.[0];return a(e(i&&t.toLowerCase().endsWith(i)?t.slice(0,-i.length):t))}function c(t){let n=[`BoldDuotone`,`LineDuotone`,`Outline`,`Broken`,`Linear`,`Bold`].find(e=>t.endsWith(e));return a(n?`${e(t.slice(0,-n.length))}${n}`:e(t))}function l(e){return(e.propertyName??e.name).text}function u(e,t){return t.sort((e,t)=>t.start-e.start).reduce((e,t)=>e.slice(0,t.start)+t.text+e.slice(t.end),e)}function d(e,t){let n=e.getLineAndCharacterOfPosition(t.getStart(e));return{column:n.character+1,line:n.line+1}}function f(e,r=`source.tsx`){let a=n.createSourceFile(r,e,n.ScriptTarget.Latest,!0),f=[],p=[],m=new Map;for(let e of a.statements){if(!n.isImportDeclaration(e)||!n.isStringLiteral(e.moduleSpecifier))continue;let t=e.moduleSpecifier.text;if(!t.startsWith(`@solar-icons/react-perf`))continue;let u=t.slice(23),h=u?o(t):void 0;if(u===`/lib/types`){let o=e.importClause?.namedBindings;if(!o||!n.isNamedImports(o)){p.push({code:`UNSUPPORTED_REACT_PERF_IMPORT`,...d(a,e.moduleSpecifier),message:`Skipped non-named import from ${t}.`,file:r});continue}let s=o.elements.find(e=>!i.has(l(e)));if(s){p.push({code:`UNSUPPORTED_REACT_PERF_TYPE_EXPORT`,...d(a,s),message:`Skipped unsupported react-perf type export: ${l(s)}.`,file:r});continue}f.push({start:e.moduleSpecifier.getStart(a)+1,end:e.moduleSpecifier.getEnd()-1,text:`@solar-icons/react/lib/index`});continue}if(u&&!h){p.push({code:`UNSUPPORTED_REACT_PERF_SUBPATH`,...d(a,e.moduleSpecifier),message:`Skipped unsupported react-perf subpath: ${t}.`,file:r});continue}let g=e.importClause?.namedBindings;if(!g||!n.isNamedImports(g)){p.push({code:`UNSUPPORTED_REACT_PERF_IMPORT`,...d(a,e.moduleSpecifier),message:`Skipped non-named import from ${t}.`,file:r});continue}f.push({start:e.moduleSpecifier.getStart(a)+1,end:e.moduleSpecifier.getEnd()-1,text:h?`@solar-icons/react/${h}`:`@solar-icons/react`});for(let e of g.elements){let t=l(e),n=h?s(t,h):c(t),r=e.name.text,i=!!e.propertyName,o=i?r:n;!i&&r!==n&&m.set(r,n),f.push({start:e.getStart(a),end:e.getEnd(),text:o===n?n:`${n} as ${o}`})}let _=new Set(g.elements.map(e=>e.name.text)),v=e=>{if(n.isJsxOpeningLikeElement(e)&&n.isIdentifier(e.tagName)&&_.has(e.tagName.text))for(let t of e.attributes.properties)!n.isJsxAttribute(t)||t.name.getText()!==`mirrored`||p.push({code:`REACT_MIRRORED_REQUIRES_MANUAL_MIGRATION`,file:r,...d(a,t),message:`${e.tagName.text} uses the removed mirrored prop. Replace it with style={{ transform: 'scaleX(-1)' }}.`,severity:`warning`});n.forEachChild(e,v)};n.forEachChild(a,v)}let h=e=>{n.isStringLiteral(e)&&e.text===`@solar-icons/react-perf`&&!n.isImportDeclaration(e.parent)&&f.push({start:e.getStart(a)+1,end:e.getEnd()-1,text:`@solar-icons/react`}),n.forEachChild(e,h)};n.forEachChild(a,h),f.push(...t(e,r,m));let g=u(e,f);return{code:g,changed:g!==e,diagnostics:p}}export{f as transformReactPerf};
@@ -0,0 +1,9 @@
1
+ import { ReactV1Mode, TransformResult } from "../types.mjs";
2
+ //#region src/transforms/react.d.ts
3
+ /**
4
+ * Migrates React v1 imports when every imported icon has a determinable style.
5
+ * Dynamic or mixed weight usage moves to the v2 dynamic entry point.
6
+ */
7
+ declare function transformReact(source: string, fileName?: string, mode?: ReactV1Mode): TransformResult;
8
+ //#endregion
9
+ export { transformReact };
@@ -0,0 +1,2 @@
1
+ import{renameIcon as e}from"../icon-renames.mjs";import{renameImportedBindingReferences as t}from"../import-bindings.mjs";import n from"typescript";const r={Bold:`bold`,BoldDuotone:`bold-duotone`,Broken:`broken`,Linear:`linear`,LineDuotone:`line-duotone`,Outline:`outline`};function i(e){return e.endsWith(`Icon`)?e:`${e}Icon`}function a(e){return e.attributes.properties.find(e=>n.isJsxAttribute(e)&&e.name.getText()===`weight`)}function o(e){if(!e)return`Linear`;let t=e.initializer;if(t&&n.isStringLiteral(t))return t.text;if(t&&n.isJsxExpression(t)){let e=t.expression;if(e&&n.isStringLiteral(e))return e.text}}function s(e,t){return t.sort((e,t)=>t.start-e.start).reduce((e,t)=>e.slice(0,t.start)+t.text+e.slice(t.end),e)}function c(e,t){let n=e.getLineAndCharacterOfPosition(t.getStart(e));return{column:n.character+1,line:n.line+1}}function l(r,i){let a=[],o=new Map;for(let t of i.statements){if(!n.isImportDeclaration(t)||!n.isStringLiteral(t.moduleSpecifier)||!t.moduleSpecifier.text.startsWith(`@solar-icons/react`))continue;let r=t.importClause?.namedBindings;if(!(!r||!n.isNamedImports(r)))for(let t of r.elements){if(!t.propertyName)continue;let n=t.propertyName.text,r=t.name.text,s=n.slice(0,-4);!n.endsWith(`Icon`)||r!==s&&e(r)!==s||(a.push({end:t.getEnd(),start:t.getStart(i),text:n}),o.set(r,n))}}return[...a,...t(r,i.fileName,o)]}function u(u,d=`source.tsx`,f=`static`){let p=n.createSourceFile(d,u,n.ScriptTarget.Latest,!0),m=[],h=[],g=new Map;for(let t of p.statements){if(!n.isImportDeclaration(t)||!n.isStringLiteral(t.moduleSpecifier))continue;let s=t.moduleSpecifier.text;if(s===`@solar-icons/react/category`){m.push({code:`REACT_CATEGORY_IMPORT_REQUIRES_MANUAL_MIGRATION`,...c(p,t.moduleSpecifier),message:`Skipped a React category import. Replace each category member with an individual v2 icon import.`,file:d});continue}if(s!==`@solar-icons/react`&&s!==`@solar-icons/react/ssr`)continue;let l=t.importClause?.namedBindings,_=!!t.importClause?.name;if(_&&m.push({code:`UNSUPPORTED_REACT_IMPORT`,...c(p,t.moduleSpecifier),message:`Skipped a default React namespace import. Convert it to individual icon imports manually.`,file:d}),!l||!n.isNamedImports(l)){_||m.push({code:`UNSUPPORTED_REACT_IMPORT`,...c(p,t.moduleSpecifier),message:`Skipped a default or namespace React import. Convert it to individual icon imports manually.`,file:d});continue}if(l.elements.filter(e=>{let t=(e.propertyName??e.name).text;return t===`SolarProvider`||t===`useSolar`}).length>0){m.push({code:`REACT_PROVIDER_REQUIRES_MANUAL_MIGRATION`,...c(p,t.moduleSpecifier),message:`Skipped a legacy SolarProvider or useSolar import. Its v1 context API and weight inheritance require manual migration.`,file:d});continue}if(l.elements.every(e=>(e.propertyName??e.name).text.endsWith(`Icon`)))continue;let v=new Map;for(let e of l.elements)v.set(e.name.text,{dynamic:!1,dynamicWeightAttributes:[],mirroredAttributes:[],styles:new Set,tagNames:[],weightAttributes:[]});let y=e=>{if(n.isJsxOpeningLikeElement(e)&&n.isIdentifier(e.tagName)){let t=v.get(e.tagName.text);if(t){t.tagNames.push(e.tagName);let i=a(e),s=e.attributes.properties.find(e=>n.isJsxAttribute(e)&&e.name.getText()===`mirrored`);s&&t.mirroredAttributes.push(s);let c=o(i),l=c&&r[c];l?(t.styles.add(l),i&&t.weightAttributes.push(i)):(t.dynamic=!0,i&&t.dynamicWeightAttributes.push(i))}}n.isJsxClosingElement(e)&&n.isIdentifier(e.tagName)&&v.get(e.tagName.text)?.tagNames.push(e.tagName),n.forEachChild(e,y)};n.forEachChild(p,y);let b=[...v.entries()].filter(([,e])=>e.styles.size===0&&!e.dynamic);if(b.length>0){m.push({code:`REACT_IMPORT_REQUIRES_REVIEW`,...c(p,t.moduleSpecifier),message:`Skipped React import because ${b.map(([e])=>e).join(`, `)} is not used as JSX in this file.`,file:d});continue}let x=[];for(let t of l.elements){let n=t.name.text,r=(t.propertyName??t.name).text,a=v.get(n),o=f===`dynamic`||a.dynamic||a.styles.size!==1?`dynamic`:[...a.styles][0],s=i(e(r)),l=!!t.propertyName,_=l?n:s,y=_===s?s:`${s} as ${_}`;if(x.push(`import { ${y} } from '@solar-icons/react/${o}'`),!l&&n!==s&&g.set(n,s),f===`static`&&a.dynamicWeightAttributes.length>0){let e=a.dynamicWeightAttributes[0];if(!e)continue;m.push({code:`REACT_DYNAMIC_WEIGHT_FALLBACK`,file:d,...c(p,e),message:`${n} has a dynamic weight, so it was migrated to @solar-icons/react/dynamic instead of a static style path.`,severity:`warning`})}for(let e of a.mirroredAttributes)m.push({code:`REACT_MIRRORED_REQUIRES_MANUAL_MIGRATION`,file:d,...c(p,e),message:`${n} uses the removed mirrored prop. Replace it with style={{ transform: 'scaleX(-1)' }}.`,severity:`warning`});if(o!==`dynamic`)for(let e of a.weightAttributes){let t=e.getStart(p);h.push({start:/\s/.test(u[t-1]??``)?t-1:t,end:e.getEnd(),text:``})}}h.push({start:t.getStart(p),end:t.getEnd(),text:x.join(`
2
+ `)})}h.push(...t(u,d,g)),h.push(...l(u,p));let _=s(u,h);return{code:_,changed:_!==u,diagnostics:m}}export{u as transformReact};
@@ -0,0 +1,6 @@
1
+ import { TransformResult } from "../types.mjs";
2
+ //#region src/transforms/solid.d.ts
3
+ /** Migrates Solid v1 style and category imports. */
4
+ declare function transformSolid(source: string, fileName?: string): TransformResult;
5
+ //#endregion
6
+ export { transformSolid };
@@ -0,0 +1,4 @@
1
+ import{renameIcon as e}from"../icon-renames.mjs";import t from"typescript";const n={bold:`bold`,boldduotone:`bold-duotone`,broken:`broken`,linear:`linear`,lineduotone:`line-duotone`,outline:`outline`};function r(e){return e.endsWith(`Icon`)?e:`${e}Icon`}function i(e,t){return t.sort((e,t)=>t.start-e.start).reduce((e,t)=>e.slice(0,t.start)+t.text+e.slice(t.end),e)}function a(e,t){let n=e.getLineAndCharacterOfPosition(t.getStart(e));return{column:n.character+1,line:n.line+1}}function o(e){let t=e.split(`/`).at(-1)?.replace(/[-_]/g,``).toLowerCase();return t?n[t]:void 0}function s(e){return e.replace(/([a-z\d])([A-Z])/g,`$1-$2`).replace(/([A-Z])([A-Z][a-z])/g,`$1-$2`).replace(/(\D)(\d+)/g,`$1-$2`).toLowerCase()}function c(e,n,r){if(n===r)return[];let i=[],a=o=>{(t.isJsxOpeningLikeElement(o)||t.isJsxClosingElement(o))&&t.isIdentifier(o.tagName)&&o.tagName.text===n&&i.push({start:o.tagName.getStart(e),end:o.tagName.getEnd(),text:r}),t.forEachChild(o,a)};return t.forEachChild(e,a),i}function l(n,l=`source.tsx`){let u=t.createSourceFile(l,n,t.ScriptTarget.Latest,!0),d=[],f=[];for(let n of u.statements){if(!t.isImportDeclaration(n)||!t.isStringLiteral(n.moduleSpecifier))continue;let i=n.moduleSpecifier.text;if(!i.startsWith(`@solar-icons/solid`))continue;let p=n.importClause?.namedBindings;if(!p||!t.isNamedImports(p))continue;let m=i.match(/^@solar-icons\/solid\/category\/[^/]+\/([^/]+)$/),h=m?.[1]?o(m[1]):o(i);if(m&&h){let t=p.elements.map(t=>{let n=e((t.propertyName??t.name).text),i=r(n),a=t.name.text;return`import { ${a===i?i:`${i} as ${a}`} } from '@solar-icons/solid/${h}/${s(n)}'`});d.push({start:n.getStart(u),end:n.getEnd(),text:t.join(`
2
+ `)});continue}if(i.startsWith(`@solar-icons/solid/category/`)){let i=new Map;for(let e of p.elements){let t=(e.propertyName??e.name).text,n=o(t);n&&i.set(e.name.text,n)}let c=[],m=n=>{if((t.isJsxOpeningLikeElement(n)||t.isJsxClosingElement(n))&&t.isPropertyAccessExpression(n.tagName)){let r=n.tagName,a=r.expression;if(t.isIdentifier(a)){let t=i.get(a.text);t&&c.push({iconName:e(r.name.text),node:r,style:t})}}t.forEachChild(n,m)};if(t.forEachChild(u,m),c.length===0){f.push({code:`SOLID_CATEGORY_IMPORT_REQUIRES_MANUAL_MIGRATION`,file:l,...a(u,n.moduleSpecifier),message:`Skipped a Solid category import because no deterministic JSX members were found.`});continue}let h=new Set,g=[];for(let e of c){let t=r(e.iconName),n=`${e.style}:${e.iconName}`;h.has(n)||(h.add(n),g.push(`import { ${t} } from '@solar-icons/solid/${e.style}/${s(e.iconName)}'`)),d.push({start:e.node.getStart(u),end:e.node.getEnd(),text:t})}d.push({start:n.getStart(u),end:n.getEnd(),text:g.join(`
3
+ `)});continue}if(!h)continue;let g=[];for(let t of p.elements){let n=r(e((t.propertyName??t.name).text)),i=t.name.text,a=t.propertyName?i:n;g.push(`import { ${a===n?n:`${n} as ${a}`} } from '@solar-icons/solid/${h}'`),t.propertyName||d.push(...c(u,i,n))}d.push({start:n.getStart(u),end:n.getEnd(),text:g.join(`
4
+ `)})}let p=i(n,d);return{code:p,changed:p!==n,diagnostics:f}}export{l as transformSolid};
@@ -0,0 +1,6 @@
1
+ import { TransformResult } from "../types.mjs";
2
+ //#region src/transforms/svelte.d.ts
3
+ /** Migrates Svelte v1 style and direct icon imports. */
4
+ declare function transformSvelte(source: string, fileName?: string): TransformResult;
5
+ //#endregion
6
+ export { transformSvelte };
@@ -0,0 +1,4 @@
1
+ import{renameIcon as e}from"../icon-renames.mjs";import t from"typescript";import{parse as n}from"svelte/compiler";const r={bold:`bold`,boldduotone:`bold-duotone`,broken:`broken`,linear:`linear`,lineduotone:`line-duotone`,outline:`outline`};function i(e){return e.endsWith(`Icon`)?e:`${e}Icon`}function a(e,t){return t.sort((e,t)=>t.start-e.start).reduce((e,t)=>e.slice(0,t.start)+t.text+e.slice(t.end),e)}function o(e,t){let n=e.slice(0,t);return{column:n.length-n.lastIndexOf(`
2
+ `),line:n.split(`
3
+ `).length}}function s(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function c(e){return r[e.replace(/[-_]/g,``).toLowerCase()]}function l(e,t,n){if(t===n)return[];let r=RegExp(`<(/?)${s(t)}(?=[\\s/>])`,`g`);return[...e.matchAll(r)].map(e=>{let r=e.index+1+e[1].length;return{start:r,end:r+t.length,text:n}})}function u(e,t,n){let r=RegExp(`<${s(t)}(?=[\\s/>])[^>]*\\smirrored(?:\\s|=|/|>)`,`g`);return[...e.matchAll(r)].map(r=>({code:`SVELTE_MIRRORED_REQUIRES_MANUAL_MIGRATION`,file:n,...o(e,r.index+r[0].indexOf(`mirrored`)),message:`${t} uses the removed mirrored prop. Replace it with CSS transform: scaleX(-1).`,severity:`warning`}))}function d(e,t){return[...e.matchAll(/from\s+['"](@solar-icons\/svelte\/category\/[^'"]+)['"]/g)].map(n=>{let r=n[1];return{code:`SVELTE_CATEGORY_IMPORT_REQUIRES_MANUAL_MIGRATION`,file:t,...o(e,n.index+n[0].indexOf(r)),message:`Skipped a Svelte category import. Replace each required icon with a v2 single-icon import.`}})}function f(r,s=`source.svelte`){if(!s.endsWith(`.svelte`))return{changed:!1,code:r,diagnostics:[]};try{n(r)}catch{let e=d(r,s);return{changed:!1,code:r,diagnostics:e.length>0?e:[{code:`SVELTE_PARSE_ERROR`,file:s,message:`Skipped this Svelte component because it could not be parsed.`}]}}let f=[],m=[];for(let n of r.matchAll(/<script(?:\s[^>]*)?>([\s\S]*?)<\/script>/g)){let a=n[1],d=n.index+n[0].indexOf(a),h=t.createSourceFile(s,a,t.ScriptTarget.Latest,!0);for(let n of h.statements){if(!t.isImportDeclaration(n)||!t.isStringLiteral(n.moduleSpecifier))continue;let a=n.moduleSpecifier.text;if(!a.startsWith(`@solar-icons/svelte`))continue;if(a.startsWith(`@solar-icons/svelte/category/`)){let t=a.match(/^@solar-icons\/svelte\/category\/[^/]+\/([^/]+)\/([^/]+)\.svelte$/),g=n.importClause?.name,_=t?.[1]?c(t[1]):void 0,v=t?.[2]?e(t[2]):void 0;if(t&&g&&_&&v){let e=i(v);f.push({start:d+n.getStart(h),end:d+n.getEnd(),text:`import ${e} from '@solar-icons/svelte/${_}/${p(v)}'`}),f.push(...l(r,g.text,e)),m.push(...u(r,g.text,s))}else m.push({code:`SVELTE_CATEGORY_IMPORT_REQUIRES_MANUAL_MIGRATION`,file:s,...o(r,d+n.moduleSpecifier.getStart(h)),message:`Skipped a Svelte category import. Replace each required icon with a v2 single-icon import.`});continue}let g=a.match(/^@solar-icons\/svelte\/(Bold|BoldDuotone|Broken|Linear|LineDuotone|Outline)$/),_=n.importClause?.namedBindings;if(!g||!_||!t.isNamedImports(_))continue;let v=c(g[1]);if(!v)continue;let y=[];for(let t of _.elements){let n=i(e((t.propertyName??t.name).text)),a=t.name.text,o=t.propertyName?a:n;y.push(`import { ${o===n?n:`${n} as ${o}`} } from '@solar-icons/svelte/${v}'`),t.propertyName||f.push(...l(r,a,n)),m.push(...u(r,a,s))}f.push({start:d+n.getStart(h),end:d+n.getEnd(),text:y.join(`
4
+ `)})}}let h=a(r,f);return{code:h,changed:h!==r,diagnostics:m}}function p(e){return e.replace(/([a-z\d])([A-Z])/g,`$1-$2`).replace(/([A-Z])([A-Z][a-z])/g,`$1-$2`).replace(/(\D)(\d+)/g,`$1-$2`).toLowerCase()}export{f as transformSvelte};
@@ -0,0 +1,6 @@
1
+ import { ReactV1Mode, TransformResult } from "../types.mjs";
2
+ //#region src/transforms/vue.d.ts
3
+ /** Migrates Vue single-file component imports and template component references. */
4
+ declare function transformVue(source: string, fileName?: string, mode?: ReactV1Mode): TransformResult;
5
+ //#endregion
6
+ export { transformVue };
@@ -0,0 +1,4 @@
1
+ import{renameIcon as e}from"../icon-renames.mjs";import t from"typescript";import{parse as n}from"@vue/compiler-sfc";const r={Bold:`bold`,BoldDuotone:`bold-duotone`,Broken:`broken`,Linear:`linear`,LineDuotone:`line-duotone`,Outline:`outline`};function i(e){return e.endsWith(`Icon`)?e:`${e}Icon`}function a(e,t){return t.sort((e,t)=>t.start-e.start).reduce((e,t)=>e.slice(0,t.start)+t.text+e.slice(t.end),e)}function o(e,t){let n=e.slice(0,t),r=n.split(`
2
+ `).length;return{column:n.length-n.lastIndexOf(`
3
+ `),line:r}}function s(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function c(e,t,n){let i={dynamic:!1,mirroredOffsets:[],styles:new Set,tagEdits:[],weightEdits:[]},a=s(n),o=RegExp(`<(/?)${a}(?=[\\s/>])([^>]*)>`,`g`);for(let a of e.matchAll(o)){let e=t+a.index,o=a[1]??``,s=a[2]??``;if(i.tagEdits.push({start:e+1+o.length,end:e+1+o.length+n.length,text:``}),o)continue;let c=/\s(?:weight|:weight)\s*=\s*("[^"]*"|'[^']*')/.exec(s);if(!c)i.styles.add(`linear`);else{let t=(c[1]?.slice(1,-1))?.replace(/^['"]|['"]$/g,``),a=t&&r[t];if(a){i.styles.add(a);let t=e+1+n.length+c.index;i.weightEdits.push({start:t,end:t+c[0].length,text:``})}else i.dynamic=!0}let l=/\smirrored(?:\s*=\s*(?:"[^"]*"|'[^']*'))?/.exec(s);l&&i.mirroredOffsets.push(e+1+n.length+l.index+1)}return i}function l(s,l=`source.vue`,u=`static`){if(!l.endsWith(`.vue`))return{changed:!1,code:s,diagnostics:[]};let d=n(s,{filename:l}),f=d.descriptor.template,p=[d.descriptor.script,d.descriptor.scriptSetup].filter(e=>!!e),m=[],h=[];if(!f)return{changed:!1,code:s,diagnostics:[{code:`VUE_TEMPLATE_REQUIRES_MANUAL_MIGRATION`,file:l,message:`Skipped Vue icon imports because this component has no template to analyze.`}]};for(let n of p){let a=n.content,d=n.loc.start.offset,p=t.createSourceFile(l,a,t.ScriptTarget.Latest,!0);for(let n of p.statements){if(!t.isImportDeclaration(n)||!t.isStringLiteral(n.moduleSpecifier))continue;let a=n.moduleSpecifier.text;if(a===`@solar-icons/vue/category`){h.push({code:`VUE_CATEGORY_IMPORT_REQUIRES_MANUAL_MIGRATION`,file:l,...o(s,d+n.moduleSpecifier.getStart(p)),message:`Skipped a Vue category import. Replace each category member with an individual v2 icon import.`});continue}if(!a.startsWith(`@solar-icons/vue`))continue;let g=n.importClause?.namedBindings;if(!g||!t.isNamedImports(g)){h.push({code:`UNSUPPORTED_VUE_IMPORT`,file:l,...o(s,d+n.moduleSpecifier.getStart(p)),message:`Skipped non-named import from ${a}. Convert it to individual icon imports manually.`});continue}let _=a===`@solar-icons/vue`;if(g.elements.some(e=>[`SolarProvider`,`SolarIconsPlugin`,`useSolar`].includes((e.propertyName??e.name).text))&&_){h.push({code:`VUE_PROVIDER_REQUIRES_MANUAL_MIGRATION`,file:l,...o(s,d+n.moduleSpecifier.getStart(p)),message:`Skipped a legacy Vue provider, plugin, or useSolar import. Its v1 global configuration semantics require manual migration.`});continue}let v=a.match(/^@solar-icons\/vue\/(Bold|BoldDuotone|Broken|Linear|LineDuotone|Outline)$/);if(!_&&!v){h.push({code:`UNSUPPORTED_VUE_SUBPATH`,file:l,...o(s,d+n.moduleSpecifier.getStart(p)),message:`Skipped unsupported Vue subpath: ${a}.`});continue}let y=[];for(let t of g.elements){let a=(t.propertyName??t.name).text,g=t.name.text,_=c(f.content,f.loc.start.offset,g);if(_.styles.size===0&&!_.dynamic){h.push({code:`VUE_IMPORT_REQUIRES_REVIEW`,file:l,...o(s,d+n.moduleSpecifier.getStart(p)),message:`Skipped Vue import because ${g} is not used as a template component in this file.`});continue}let b=v?.[1],x=b?r[b]:u===`dynamic`||_.dynamic||_.styles.size!==1?`dynamic`:[..._.styles][0],S=i(e(a)),C=t.propertyName?g:S,w=C===S?S:`${S} as ${C}`;if(y.push(`import { ${w} } from '@solar-icons/vue/${x}'`),!t.propertyName&&g!==S)for(let e of _.tagEdits)m.push({...e,text:S});x!==`dynamic`&&m.push(..._.weightEdits);for(let e of _.mirroredOffsets)h.push({code:`VUE_MIRRORED_REQUIRES_MANUAL_MIGRATION`,file:l,...o(s,e),message:`${g} uses the removed mirrored prop. Replace it with CSS transform: scaleX(-1).`,severity:`warning`})}y.length>0&&m.push({start:d+n.getStart(p),end:d+n.getEnd(),text:y.join(`
4
+ `)})}}let g=a(s,m);return{code:g,changed:g!==s,diagnostics:h}}export{l as transformVue};
@@ -0,0 +1,30 @@
1
+ //#region src/types.d.ts
2
+ type Framework = 'angular' | 'nuxt' | 'react' | 'react-native' | 'solid' | 'svelte' | 'vue';
3
+ interface Diagnostic {
4
+ code: string;
5
+ column?: number;
6
+ message: string;
7
+ file?: string;
8
+ line?: number;
9
+ severity?: 'warning' | 'error';
10
+ }
11
+ interface TransformResult {
12
+ code: string;
13
+ changed: boolean;
14
+ diagnostics: Diagnostic[];
15
+ }
16
+ interface MigrationReport {
17
+ changedFiles: string[];
18
+ diagnostics: Diagnostic[];
19
+ detectedFrameworks: Framework[];
20
+ }
21
+ interface MigrationOptions {
22
+ cwd: string;
23
+ reactV1Mode?: ReactV1Mode;
24
+ vueV1Mode?: ReactV1Mode;
25
+ targetVersion?: string;
26
+ write?: boolean;
27
+ }
28
+ type ReactV1Mode = 'dynamic' | 'static';
29
+ //#endregion
30
+ export { Diagnostic, Framework, MigrationOptions, MigrationReport, ReactV1Mode, TransformResult };
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@solar-icons/codemod",
3
+ "version": "2.0.0-beta.3",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "description": "Conservative migration codemods for Solar Icons",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/saoudi-h/solar-icons.git",
12
+ "directory": "packages/codemod"
13
+ },
14
+ "type": "module",
15
+ "bin": {
16
+ "solar-icons-migrate": "./dist/cli.mjs"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE",
22
+ "LICENSE-THIRD-PARTY"
23
+ ],
24
+ "types": "./dist/index.d.mts",
25
+ "exports": {
26
+ "./package.json": "./package.json",
27
+ ".": {
28
+ "types": "./dist/index.d.mts",
29
+ "import": "./dist/index.mjs"
30
+ }
31
+ },
32
+ "dependencies": {
33
+ "@clack/prompts": "^1.7.0",
34
+ "@vue/compiler-sfc": "^3.5.39",
35
+ "commander": "^15.0.0",
36
+ "picocolors": "^1.1.1",
37
+ "svelte": "^5.56.4",
38
+ "typescript": "^6.0.3"
39
+ },
40
+ "devDependencies": {
41
+ "@tala-tools/eslint": "0.1.4",
42
+ "@tala-tools/prettier": "0.0.3",
43
+ "@tala-tools/tsconfig": "0.0.1",
44
+ "@types/node": "^26.1.1",
45
+ "prettier": "^3.9.4",
46
+ "rimraf": "^6.1.3",
47
+ "tsdown": "^0.22.4",
48
+ "tsx": "^4.23.0",
49
+ "vitest": "^4.1.10"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ },
54
+ "keywords": [
55
+ "solar-icons",
56
+ "codemod",
57
+ "migration"
58
+ ],
59
+ "author": {
60
+ "name": "Saoudi Hakim",
61
+ "url": "https://hakimsaoudi.dev",
62
+ "email": "saoudi-hakim@outlook.com"
63
+ },
64
+ "license": "MIT",
65
+ "scripts": {
66
+ "build": "pnpm copy:licenses && tsdown -l error",
67
+ "copy:licenses": "cp ../../LICENSE ./LICENSE && cp ../../LICENSE-THIRD-PARTY ./LICENSE-THIRD-PARTY",
68
+ "lint": "eslint . --max-warnings 0",
69
+ "lint:fix": "eslint . --fix",
70
+ "format": "prettier \"**/*\" --ignore-unknown --list-different",
71
+ "format:fix": "prettier \"**/*\" --ignore-unknown --write",
72
+ "clean": "rimraf *.tsbuildinfo node_modules dist",
73
+ "test": "vitest run",
74
+ "test:fixtures": "tsx scripts/run-fixtures.ts",
75
+ "typecheck": "tsgo --noEmit",
76
+ "pre-commit": "lint-staged"
77
+ }
78
+ }