|
| 1 | +import * as path from "path"; |
| 2 | +import { App } from "./App"; |
| 3 | +import { IFileSystem, FS_TOKEN } from "../types/FileSystem"; |
| 4 | +import { ProjectConfig } from "./ProjectConfig"; |
| 5 | +import { Util } from "./Util"; |
| 6 | +import { NPM_ANGULAR, NPM_REACT, resolvePackage, UPGRADEABLE_PACKAGES } from "../update/package-resolve"; |
| 7 | + |
| 8 | +const CLAUDE_SKILLS_DIR = ".claude/skills"; |
| 9 | + |
| 10 | +/** |
| 11 | + * Returns the list of 'skills/' directory paths found in installed |
| 12 | + * Ignite UI packages that are relevant to the project's detected framework. |
| 13 | + */ |
| 14 | +function resolveSkillsRoots(): string[] { |
| 15 | + const fs = App.container.get<IFileSystem>(FS_TOKEN); |
| 16 | + const roots: string[] = []; |
| 17 | + |
| 18 | + let framework: string | null = null; |
| 19 | + try { |
| 20 | + if (ProjectConfig.hasLocalConfig()) { |
| 21 | + framework = ProjectConfig.getConfig().project?.framework?.toLowerCase() ?? null; |
| 22 | + } |
| 23 | + } catch { /* config not readable – fall through to scan all */ } |
| 24 | + |
| 25 | + const allPkgKeys = Object.keys(UPGRADEABLE_PACKAGES); |
| 26 | + let candidates: string[]; |
| 27 | + if (framework === "angular") { |
| 28 | + candidates = [NPM_ANGULAR]; |
| 29 | + } else if (framework === "react") { |
| 30 | + candidates = [NPM_REACT]; |
| 31 | + } else if (framework === "webcomponents") { |
| 32 | + candidates = allPkgKeys.filter(k => k.startsWith("igniteui-webcomponents")); |
| 33 | + } else { |
| 34 | + candidates = allPkgKeys; |
| 35 | + } |
| 36 | + |
| 37 | + for (const pkg of candidates) { |
| 38 | + const resolved = resolvePackage(pkg as keyof typeof UPGRADEABLE_PACKAGES); |
| 39 | + const skillsRoot = `node_modules/${resolved}/skills`; |
| 40 | + if (fs.directoryExists(skillsRoot) && !roots.includes(skillsRoot)) { |
| 41 | + roots.push(skillsRoot); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + return roots; |
| 46 | +} |
| 47 | + |
| 48 | +/** |
| 49 | + * Copies skill files from the installed Ignite UI package(s) into .claude/skills/. |
| 50 | + * Works with both real FS (CLI) and virtual Tree FS (schematics) through IFileSystem. |
| 51 | + */ |
| 52 | +export function copyAISkillsToProject(): "copied" | "no-source" { |
| 53 | + const fs = App.container.get<IFileSystem>(FS_TOKEN); |
| 54 | + const skillsRoots = resolveSkillsRoots(); |
| 55 | + |
| 56 | + if (!skillsRoots.length) { |
| 57 | + return "no-source"; |
| 58 | + } |
| 59 | + |
| 60 | + const multiRoot = skillsRoots.length > 1; |
| 61 | + let copied = false; |
| 62 | + |
| 63 | + for (const skillsRoot of skillsRoots) { |
| 64 | + const rawPaths = fs.glob(skillsRoot, "**/*"); |
| 65 | + const pkgDirName = multiRoot ? path.basename(path.dirname(skillsRoot)) : ""; |
| 66 | + |
| 67 | + for (const p of rawPaths) { |
| 68 | + // Normalize to posix and strip leading '/' so path.posix.relative works |
| 69 | + // across both FsFileSystem (relative paths) and NgTreeFileSystem (tree-rooted paths) |
| 70 | + const normP = p.replace(/\\/g, "/").replace(/^\//, ""); |
| 71 | + const normRoot = skillsRoot.replace(/\\/g, "/").replace(/^\//, ""); |
| 72 | + const rel = path.posix.relative(normRoot, normP); |
| 73 | + const dest = multiRoot |
| 74 | + ? `${CLAUDE_SKILLS_DIR}/${pkgDirName}/${rel}` |
| 75 | + : `${CLAUDE_SKILLS_DIR}/${rel}`; |
| 76 | + |
| 77 | + fs.writeFile(dest, fs.readFile(p)); |
| 78 | + Util.log(`${Util.greenCheck()} Created ${dest}`); |
| 79 | + copied = true; |
| 80 | + } |
| 81 | + } |
| 82 | + return copied ? "copied" : "no-source"; |
| 83 | +} |
0 commit comments