@fragments-sdk/cli 0.11.1 → 0.12.1
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/dist/ai-client-I6MDWNYA.js +21 -0
- package/dist/bin.js +275 -368
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
- package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
- package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
- package/dist/chunk-GVDSFQ4E.js.map +1 -0
- package/dist/chunk-JJ2VRTBU.js +626 -0
- package/dist/chunk-JJ2VRTBU.js.map +1 -0
- package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
- package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
- package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
- package/dist/chunk-OQKMEFOS.js.map +1 -0
- package/dist/chunk-SXTKFDCR.js +104 -0
- package/dist/chunk-SXTKFDCR.js.map +1 -0
- package/dist/chunk-T5OMVL7E.js +443 -0
- package/dist/chunk-T5OMVL7E.js.map +1 -0
- package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
- package/dist/chunk-TPWGL2XS.js.map +1 -0
- package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
- package/dist/chunk-WFS63PCW.js.map +1 -0
- package/dist/core/index.js +9 -1
- package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
- package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/init-ZSX3NRCZ.js +636 -0
- package/dist/init-ZSX3NRCZ.js.map +1 -0
- package/dist/mcp-bin.js +2 -2
- package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
- package/dist/scan-generate-SYU4PYZD.js +1115 -0
- package/dist/scan-generate-SYU4PYZD.js.map +1 -0
- package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
- package/dist/{snapshot-SV2JOFZH.js → snapshot-XOISO2IS.js} +2 -2
- package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
- package/dist/static-viewer-5GXH2MGE.js.map +1 -0
- package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
- package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
- package/dist/{viewer-DLLJIMCK.js → viewer-7ZEAFBVN.js} +13 -13
- package/package.json +4 -4
- package/src/ai-client.ts +156 -0
- package/src/bin.ts +44 -2
- package/src/build.ts +95 -33
- package/src/commands/__tests__/drift-sync.test.ts +252 -0
- package/src/commands/__tests__/scan-generate.test.ts +497 -45
- package/src/commands/enhance.ts +11 -35
- package/src/commands/init.ts +288 -260
- package/src/commands/scan-generate.ts +740 -139
- package/src/commands/scan.ts +37 -32
- package/src/commands/setup.ts +143 -52
- package/src/commands/sync.ts +357 -0
- package/src/commands/validate.ts +43 -1
- package/src/core/component-extractor.test.ts +282 -0
- package/src/core/component-extractor.ts +1030 -0
- package/src/core/discovery.ts +93 -7
- package/src/service/enhance/props-extractor.ts +235 -13
- package/src/validators.ts +236 -0
- package/dist/chunk-5G3VZH43.js.map +0 -1
- package/dist/chunk-OQO55NKV.js.map +0 -1
- package/dist/chunk-WXSR2II7.js.map +0 -1
- package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
- package/dist/init-UFGK5TCN.js +0 -867
- package/dist/init-UFGK5TCN.js.map +0 -1
- package/dist/scan-generate-SJAN5MVI.js +0 -691
- package/dist/scan-generate-SJAN5MVI.js.map +0 -1
- package/src/ai.ts +0 -266
- package/src/commands/init-framework.ts +0 -414
- package/src/mcp/bin.ts +0 -36
- package/src/migrate/bin.ts +0 -114
- package/src/theme/index.ts +0 -77
- package/src/viewer/bin.ts +0 -86
- package/src/viewer/cli/health.ts +0 -256
- package/src/viewer/cli/index.ts +0 -33
- package/src/viewer/cli/scan.ts +0 -124
- package/src/viewer/cli/utils.ts +0 -174
- /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
- /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
- /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
- /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
- /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
- /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
- /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
- /package/dist/{snapshot-SV2JOFZH.js.map → snapshot-XOISO2IS.js.map} +0 -0
- /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
- /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.js.map} +0 -0
- /package/dist/{viewer-DLLJIMCK.js.map → viewer-7ZEAFBVN.js.map} +0 -0
|
@@ -1,13 +1,43 @@
|
|
|
1
1
|
import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
BRAND
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WFS63PCW.js";
|
|
5
5
|
|
|
6
6
|
// src/core/discovery.ts
|
|
7
7
|
import { resolve, dirname } from "path";
|
|
8
8
|
import { readFile } from "fs/promises";
|
|
9
9
|
import { existsSync } from "fs";
|
|
10
10
|
import fg from "fast-glob";
|
|
11
|
+
function toPascalCase(name) {
|
|
12
|
+
return name.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
13
|
+
}
|
|
14
|
+
async function extractPascalCaseExports(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
const content = await readFile(filePath, "utf-8");
|
|
17
|
+
const exports = /* @__PURE__ */ new Set();
|
|
18
|
+
const exportFuncRegex = /export\s+function\s+([A-Z][a-zA-Z0-9]*)/g;
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = exportFuncRegex.exec(content)) !== null) {
|
|
21
|
+
exports.add(match[1]);
|
|
22
|
+
}
|
|
23
|
+
const exportConstRegex = /export\s+const\s+([A-Z][a-zA-Z0-9]*)/g;
|
|
24
|
+
while ((match = exportConstRegex.exec(content)) !== null) {
|
|
25
|
+
exports.add(match[1]);
|
|
26
|
+
}
|
|
27
|
+
const exportBlockRegex = /export\s*\{([^}]+)\}/g;
|
|
28
|
+
while ((match = exportBlockRegex.exec(content)) !== null) {
|
|
29
|
+
const names = match[1].split(",").map((n) => n.trim().split(/\s+as\s+/)[0].trim());
|
|
30
|
+
for (const name of names) {
|
|
31
|
+
if (/^[A-Z]/.test(name)) {
|
|
32
|
+
exports.add(name);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Array.from(exports);
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
11
41
|
async function discoverBlockFiles(configDir, exclude) {
|
|
12
42
|
const patterns = [
|
|
13
43
|
`**/*${BRAND.blockFileExtension}`,
|
|
@@ -99,10 +129,16 @@ async function discoverComponentsFromSource(configDir, patterns, exclude) {
|
|
|
99
129
|
ignore: excludePatterns,
|
|
100
130
|
absolute: false
|
|
101
131
|
});
|
|
102
|
-
const
|
|
132
|
+
const pascalCaseFiles = [];
|
|
133
|
+
const lowercaseFiles = [];
|
|
134
|
+
for (const file of files) {
|
|
103
135
|
const name = extractComponentName(file);
|
|
104
|
-
|
|
105
|
-
|
|
136
|
+
if (/^[A-Z]/.test(name)) {
|
|
137
|
+
pascalCaseFiles.push(file);
|
|
138
|
+
} else if (/^[a-z]/.test(name)) {
|
|
139
|
+
lowercaseFiles.push(file);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
106
142
|
const storyPatterns = [
|
|
107
143
|
"**/*.stories.tsx",
|
|
108
144
|
"**/*.stories.ts",
|
|
@@ -120,7 +156,7 @@ async function discoverComponentsFromSource(configDir, patterns, exclude) {
|
|
|
120
156
|
storyMap.set(name, storyFile);
|
|
121
157
|
}
|
|
122
158
|
const components = [];
|
|
123
|
-
for (const file of
|
|
159
|
+
for (const file of pascalCaseFiles) {
|
|
124
160
|
const name = extractComponentName(file);
|
|
125
161
|
const absolutePath = resolve(configDir, file);
|
|
126
162
|
const storyFile = storyMap.get(name);
|
|
@@ -131,6 +167,22 @@ async function discoverComponentsFromSource(configDir, patterns, exclude) {
|
|
|
131
167
|
storyPath: storyFile ? resolve(configDir, storyFile) : void 0
|
|
132
168
|
});
|
|
133
169
|
}
|
|
170
|
+
for (const file of lowercaseFiles) {
|
|
171
|
+
const absolutePath = resolve(configDir, file);
|
|
172
|
+
const fileName = extractComponentName(file);
|
|
173
|
+
const pascalName = toPascalCase(fileName);
|
|
174
|
+
const exports = await extractPascalCaseExports(absolutePath);
|
|
175
|
+
const primaryExport = exports.find((e) => e === pascalName) || exports[0];
|
|
176
|
+
if (primaryExport) {
|
|
177
|
+
const storyFile = storyMap.get(primaryExport) || storyMap.get(fileName);
|
|
178
|
+
components.push({
|
|
179
|
+
name: primaryExport,
|
|
180
|
+
sourcePath: absolutePath,
|
|
181
|
+
relativePath: file,
|
|
182
|
+
storyPath: storyFile ? resolve(configDir, storyFile) : void 0
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
134
186
|
components.sort((a, b) => a.name.localeCompare(b.name));
|
|
135
187
|
return components;
|
|
136
188
|
}
|
|
@@ -269,4 +321,4 @@ export {
|
|
|
269
321
|
discoverInstalledFragments,
|
|
270
322
|
discoverAllComponents
|
|
271
323
|
};
|
|
272
|
-
//# sourceMappingURL=chunk-
|
|
324
|
+
//# sourceMappingURL=chunk-OQKMEFOS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/discovery.ts"],"sourcesContent":["import { resolve, dirname, basename } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport fg from 'fast-glob';\nimport { BRAND } from '@fragments-sdk/core';\nimport type { FragmentsConfig } from '@fragments-sdk/core';\n\n/**\n * Convert a lowercase file name to PascalCase component name.\n * e.g., \"button\" → \"Button\", \"date-picker\" → \"DatePicker\"\n */\nfunction toPascalCase(name: string): string {\n return name\n .split(/[-_]/)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\n/**\n * Extract PascalCase named exports from a source file using regex.\n * Finds patterns like:\n * - `export function Button`\n * - `export const Button`\n * - `export { Button, CardHeader }` (from export blocks)\n * - `function Button` + later `export { Button }` (shadcn pattern)\n */\nasync function extractPascalCaseExports(filePath: string): Promise<string[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const exports = new Set<string>();\n\n // Pattern 1: export function ComponentName\n const exportFuncRegex = /export\\s+function\\s+([A-Z][a-zA-Z0-9]*)/g;\n let match;\n while ((match = exportFuncRegex.exec(content)) !== null) {\n exports.add(match[1]);\n }\n\n // Pattern 2: export const ComponentName\n const exportConstRegex = /export\\s+const\\s+([A-Z][a-zA-Z0-9]*)/g;\n while ((match = exportConstRegex.exec(content)) !== null) {\n exports.add(match[1]);\n }\n\n // Pattern 3: export { Name1, Name2, ... }\n const exportBlockRegex = /export\\s*\\{([^}]+)\\}/g;\n while ((match = exportBlockRegex.exec(content)) !== null) {\n const names = match[1].split(',').map((n) => n.trim().split(/\\s+as\\s+/)[0].trim());\n for (const name of names) {\n if (/^[A-Z]/.test(name)) {\n exports.add(name);\n }\n }\n }\n\n return Array.from(exports);\n } catch {\n return [];\n }\n}\n\nexport interface DiscoveredFile {\n /** Absolute path to the file */\n absolutePath: string;\n /** Path relative to config directory */\n relativePath: string;\n}\n\n/**\n * Discovered component with source file information\n */\nexport interface DiscoveredComponent {\n /** Component name (e.g., \"Button\") */\n name: string;\n /** Absolute path to the component source file */\n sourcePath: string;\n /** Path relative to config directory */\n relativePath: string;\n /** Path to storybook file if found */\n storyPath?: string;\n}\n\n/**\n * Discover block files (*.block.ts) under the config directory.\n * Also discovers legacy *.recipe.ts files for backward compatibility.\n */\nexport async function discoverBlockFiles(\n configDir: string,\n exclude?: string[]\n): Promise<DiscoveredFile[]> {\n const patterns = [\n `**/*${BRAND.blockFileExtension}`,\n `**/*${BRAND.recipeFileExtension}`,\n ];\n const files = await fg(patterns, {\n cwd: configDir,\n ignore: exclude ?? ['**/node_modules/**', '**/dist/**'],\n absolute: false,\n });\n\n return files.map((relativePath) => ({\n relativePath,\n absolutePath: resolve(configDir, relativePath),\n }));\n}\n\n/**\n * @deprecated Use discoverBlockFiles instead\n */\nexport const discoverRecipeFiles = discoverBlockFiles;\n\n/**\n * Discover fragment files matching the config patterns\n */\nexport async function discoverFragmentFiles(\n config: FragmentsConfig,\n configDir: string\n): Promise<DiscoveredFile[]> {\n const defaultExcludes = [\n '**/*.test.stories.*',\n '**/*.stories.test.*',\n '**/*.test.story.*',\n '**/*.story.test.*',\n ];\n const files = await fg(config.include, {\n cwd: configDir,\n ignore: [...defaultExcludes, ...(config.exclude ?? [])],\n absolute: false,\n });\n\n return files.map((relativePath) => ({\n relativePath,\n absolutePath: resolve(configDir, relativePath),\n }));\n}\n\n/**\n * Discover component files for coverage validation\n */\nexport async function discoverComponentFiles(\n config: FragmentsConfig,\n configDir: string\n): Promise<DiscoveredFile[]> {\n if (!config.components || config.components.length === 0) {\n return [];\n }\n\n const files = await fg(config.components, {\n cwd: configDir,\n ignore: [\n ...(config.exclude ?? []),\n // Exclude fragment files themselves\n ...config.include,\n // Exclude test files\n '**/*.test.*',\n '**/*.spec.*',\n '**/__tests__/**',\n ],\n absolute: false,\n });\n\n return files.map((relativePath) => ({\n relativePath,\n absolutePath: resolve(configDir, relativePath),\n }));\n}\n\n/**\n * Extract component name from file path\n */\nexport function extractComponentName(filePath: string): string {\n // Handle index.tsx files - use parent directory name\n const parts = filePath.replace(/\\\\/g, '/').split('/');\n const fileName = parts[parts.length - 1];\n\n if (fileName === 'index.tsx' || fileName === 'index.ts') {\n return parts[parts.length - 2] ?? 'Unknown';\n }\n\n // Remove extension\n return fileName.replace(/\\.(tsx?|jsx?)$/, '');\n}\n\n/**\n * Default patterns for component discovery\n */\nconst DEFAULT_COMPONENT_PATTERNS = [\n 'src/components/**/*.tsx',\n 'src/components/**/index.tsx',\n 'components/**/*.tsx',\n 'lib/components/**/*.tsx',\n 'packages/*/src/components/**/*.tsx',\n];\n\n/**\n * Patterns to exclude from component discovery\n */\nconst DEFAULT_EXCLUDE_PATTERNS = [\n '**/*.test.*',\n '**/*.spec.*',\n '**/*.stories.*',\n '**/*.story.*',\n '**/__tests__/**',\n '**/__mocks__/**',\n '**/node_modules/**',\n '**/dist/**',\n];\n\n/**\n * Discover components from source files\n *\n * This function finds React components by:\n * 1. Looking for TypeScript/TSX files in common component directories\n * 2. Filtering out test files, stories, and internal files\n * 3. Extracting component names from file names or directories\n */\nexport async function discoverComponentsFromSource(\n configDir: string,\n patterns?: string[],\n exclude?: string[]\n): Promise<DiscoveredComponent[]> {\n const searchPatterns = patterns && patterns.length > 0\n ? patterns\n : DEFAULT_COMPONENT_PATTERNS;\n\n const excludePatterns = [\n ...DEFAULT_EXCLUDE_PATTERNS,\n ...(exclude ?? []),\n ];\n\n const files = await fg(searchPatterns, {\n cwd: configDir,\n ignore: excludePatterns,\n absolute: false,\n });\n\n // Separate files into PascalCase (existing behavior) and lowercase (new: parse exports)\n const pascalCaseFiles: string[] = [];\n const lowercaseFiles: string[] = [];\n\n for (const file of files) {\n const name = extractComponentName(file);\n if (/^[A-Z]/.test(name)) {\n pascalCaseFiles.push(file);\n } else if (/^[a-z]/.test(name)) {\n lowercaseFiles.push(file);\n }\n }\n\n // Find associated story files\n const storyPatterns = [\n '**/*.stories.tsx',\n '**/*.stories.ts',\n '**/*.story.tsx',\n '**/*.story.ts',\n ];\n\n const storyFiles = await fg(storyPatterns, {\n cwd: configDir,\n ignore: ['**/node_modules/**', '**/dist/**'],\n absolute: false,\n });\n\n const storyMap = new Map<string, string>();\n for (const storyFile of storyFiles) {\n const name = extractComponentName(storyFile.replace(/\\.stories?\\.(tsx?|jsx?)$/, '.tsx'));\n storyMap.set(name, storyFile);\n }\n\n // Build discovered components\n const components: DiscoveredComponent[] = [];\n\n // Add PascalCase-named files directly (existing behavior)\n for (const file of pascalCaseFiles) {\n const name = extractComponentName(file);\n const absolutePath = resolve(configDir, file);\n const storyFile = storyMap.get(name);\n\n components.push({\n name,\n sourcePath: absolutePath,\n relativePath: file,\n storyPath: storyFile ? resolve(configDir, storyFile) : undefined,\n });\n }\n\n // For lowercase files (e.g., shadcn's button.tsx, card.tsx), extract the\n // primary PascalCase export as the component name. This discovers components\n // from libraries that use lowercase file names.\n for (const file of lowercaseFiles) {\n const absolutePath = resolve(configDir, file);\n const fileName = extractComponentName(file);\n const pascalName = toPascalCase(fileName);\n\n // Parse exports from the file to find PascalCase component names\n const exports = await extractPascalCaseExports(absolutePath);\n\n // Use the primary component: prefer the PascalCase version of the file name,\n // otherwise take the first PascalCase export\n const primaryExport = exports.find((e) => e === pascalName) || exports[0];\n if (primaryExport) {\n const storyFile = storyMap.get(primaryExport) || storyMap.get(fileName);\n\n components.push({\n name: primaryExport,\n sourcePath: absolutePath,\n relativePath: file,\n storyPath: storyFile ? resolve(configDir, storyFile) : undefined,\n });\n }\n }\n\n // Sort by name\n components.sort((a, b) => a.name.localeCompare(b.name));\n\n return components;\n}\n\n/**\n * Discover components from a barrel export file (index.ts)\n *\n * Parses the barrel file to find exported components.\n * This is useful for libraries that expose components through a single entry point.\n */\nexport async function discoverComponentsFromBarrel(\n barrelPath: string,\n configDir: string\n): Promise<DiscoveredComponent[]> {\n const absoluteBarrelPath = resolve(configDir, barrelPath);\n\n if (!existsSync(absoluteBarrelPath)) {\n return [];\n }\n\n const content = await readFile(absoluteBarrelPath, 'utf-8');\n const components: DiscoveredComponent[] = [];\n\n // Match export statements like:\n // export { Button } from './Button'\n // export { Card, CardHeader } from './Card'\n // export * from './Modal'\n const exportRegex = /export\\s+(?:\\*|{([^}]+)})\\s+from\\s+['\"]([^'\"]+)['\"]/g;\n\n let match;\n while ((match = exportRegex.exec(content)) !== null) {\n const exportedNames = match[1];\n const importPath = match[2];\n\n // Resolve the import path\n const barrelDir = dirname(absoluteBarrelPath);\n let resolvedPath = resolve(barrelDir, importPath);\n\n // Add extension if needed\n if (!resolvedPath.endsWith('.tsx') && !resolvedPath.endsWith('.ts')) {\n if (existsSync(`${resolvedPath}.tsx`)) {\n resolvedPath = `${resolvedPath}.tsx`;\n } else if (existsSync(`${resolvedPath}.ts`)) {\n resolvedPath = `${resolvedPath}.ts`;\n } else if (existsSync(`${resolvedPath}/index.tsx`)) {\n resolvedPath = `${resolvedPath}/index.tsx`;\n } else if (existsSync(`${resolvedPath}/index.ts`)) {\n resolvedPath = `${resolvedPath}/index.ts`;\n }\n }\n\n if (!existsSync(resolvedPath)) {\n continue;\n }\n\n if (exportedNames) {\n // Named exports: { Button, Card }\n const names = exportedNames.split(',').map((n) => n.trim().split(/\\s+as\\s+/)[0].trim());\n for (const name of names) {\n if (/^[A-Z]/.test(name)) {\n const relativePath = resolvedPath.replace(configDir + '/', '');\n components.push({\n name,\n sourcePath: resolvedPath,\n relativePath,\n });\n }\n }\n } else {\n // Star export: export * from './Component'\n const name = extractComponentName(importPath);\n if (/^[A-Z]/.test(name)) {\n const relativePath = resolvedPath.replace(configDir + '/', '');\n components.push({\n name,\n sourcePath: resolvedPath,\n relativePath,\n });\n }\n }\n }\n\n return components;\n}\n\n/**\n * Default glob patterns for discovering token files (SCSS/CSS with custom properties)\n */\nconst DEFAULT_TOKEN_PATTERNS = [\n 'src/**/tokens/**/_variables.scss',\n 'src/**/tokens/**/variables.scss',\n 'src/**/styles/**/variables.scss',\n 'src/**/styles/**/tokens.scss',\n 'src/**/styles/**/variables.css',\n 'src/**/theme/**/_variables.scss',\n 'src/**/theme/**/tokens.css',\n];\n\n/**\n * Discover token files (SCSS/CSS files containing CSS custom properties).\n * Uses config.tokens.include patterns if provided, otherwise falls back\n * to default patterns that match common project structures.\n */\nexport async function discoverTokenFiles(\n configDir: string,\n patterns?: string[],\n exclude?: string[]\n): Promise<DiscoveredFile[]> {\n const searchPatterns = patterns && patterns.length > 0\n ? patterns\n : DEFAULT_TOKEN_PATTERNS;\n\n const files = await fg(searchPatterns, {\n cwd: configDir,\n ignore: exclude ?? ['**/node_modules/**', '**/dist/**'],\n absolute: false,\n });\n\n return files.map((relativePath) => ({\n relativePath,\n absolutePath: resolve(configDir, relativePath),\n }));\n}\n\n/**\n * Discover fragment files from installed packages that declare a \"fragments\" field\n * in their package.json. This allows consumer projects to see components from\n * installed packages (e.g. @fragments-sdk/ui) in the dev viewer.\n */\nexport async function discoverInstalledFragments(\n projectRoot: string\n): Promise<DiscoveredFile[]> {\n const pkgJsonPath = resolve(projectRoot, 'package.json');\n if (!existsSync(pkgJsonPath)) return [];\n\n const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));\n const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };\n const results: DiscoveredFile[] = [];\n\n for (const depName of Object.keys(allDeps)) {\n const depDir = resolve(projectRoot, 'node_modules', depName);\n const depPkgPath = resolve(depDir, 'package.json');\n if (!existsSync(depPkgPath)) continue;\n\n const depPkg = JSON.parse(await readFile(depPkgPath, 'utf-8'));\n if (!depPkg.fragments) continue;\n\n // Package declares fragments — scan for source fragment files\n const files = await fg(\n [`src/**/*${BRAND.fileExtension}`, 'src/**/*.stories.tsx'],\n { cwd: depDir, ignore: ['**/node_modules/**'], absolute: false }\n );\n\n for (const rel of files) {\n results.push({\n relativePath: `${depName}/${rel}`,\n absolutePath: resolve(depDir, rel),\n });\n }\n }\n\n return results;\n}\n\n/**\n * Discover all components using multiple strategies\n */\nexport async function discoverAllComponents(\n configDir: string,\n options: {\n patterns?: string[];\n exclude?: string[];\n barrelFiles?: string[];\n } = {}\n): Promise<DiscoveredComponent[]> {\n const componentsMap = new Map<string, DiscoveredComponent>();\n\n // Discover from source files\n const sourceComponents = await discoverComponentsFromSource(\n configDir,\n options.patterns,\n options.exclude\n );\n\n for (const comp of sourceComponents) {\n componentsMap.set(comp.name, comp);\n }\n\n // Discover from barrel files if specified\n if (options.barrelFiles && options.barrelFiles.length > 0) {\n for (const barrelFile of options.barrelFiles) {\n const barrelComponents = await discoverComponentsFromBarrel(barrelFile, configDir);\n for (const comp of barrelComponents) {\n // Only add if not already found\n if (!componentsMap.has(comp.name)) {\n componentsMap.set(comp.name, comp);\n }\n }\n }\n }\n\n return Array.from(componentsMap.values()).sort((a, b) => a.name.localeCompare(b.name));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/ai-client.ts
|
|
4
|
+
var ENRICHMENT_MODELS = {
|
|
5
|
+
anthropic: "claude-haiku-4-5-20251001",
|
|
6
|
+
openai: "gpt-4o-mini",
|
|
7
|
+
none: ""
|
|
8
|
+
};
|
|
9
|
+
function detectProvider(opts) {
|
|
10
|
+
if (opts?.provider) return opts.provider;
|
|
11
|
+
if (opts?.apiKey) {
|
|
12
|
+
if (opts.apiKey.startsWith("sk-ant-")) return "anthropic";
|
|
13
|
+
if (opts.apiKey.startsWith("sk-")) return "openai";
|
|
14
|
+
}
|
|
15
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
16
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
17
|
+
return "none";
|
|
18
|
+
}
|
|
19
|
+
function getApiKey(provider, explicitKey) {
|
|
20
|
+
if (explicitKey) return explicitKey;
|
|
21
|
+
if (provider === "anthropic") return process.env.ANTHROPIC_API_KEY;
|
|
22
|
+
if (provider === "openai") return process.env.OPENAI_API_KEY;
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
async function createAIClient(provider, apiKey) {
|
|
26
|
+
if (provider === "anthropic") {
|
|
27
|
+
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
28
|
+
return new Anthropic({ apiKey });
|
|
29
|
+
}
|
|
30
|
+
if (provider === "openai") {
|
|
31
|
+
const OpenAI = (await import("openai")).default;
|
|
32
|
+
return new OpenAI({ apiKey });
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
35
|
+
}
|
|
36
|
+
async function generateCompletion(client, provider, model, system, user, maxTokens = 1024) {
|
|
37
|
+
if (provider === "anthropic") {
|
|
38
|
+
const anthropic = client;
|
|
39
|
+
const response = await anthropic.messages.create({
|
|
40
|
+
model,
|
|
41
|
+
max_tokens: maxTokens,
|
|
42
|
+
system,
|
|
43
|
+
messages: [{ role: "user", content: user }]
|
|
44
|
+
});
|
|
45
|
+
const content = response.content[0];
|
|
46
|
+
if (content.type !== "text") {
|
|
47
|
+
throw new Error("Unexpected response type");
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
text: content.text,
|
|
51
|
+
inputTokens: response.usage?.input_tokens || 0,
|
|
52
|
+
outputTokens: response.usage?.output_tokens || 0
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (provider === "openai") {
|
|
56
|
+
const openai = client;
|
|
57
|
+
const response = await openai.chat.completions.create({
|
|
58
|
+
model,
|
|
59
|
+
max_tokens: maxTokens,
|
|
60
|
+
messages: [
|
|
61
|
+
{ role: "system", content: system },
|
|
62
|
+
{ role: "user", content: user }
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
const content = response.choices[0]?.message?.content;
|
|
66
|
+
if (!content) {
|
|
67
|
+
throw new Error("No response from OpenAI");
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
text: content,
|
|
71
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
72
|
+
outputTokens: response.usage?.completion_tokens || 0
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
76
|
+
}
|
|
77
|
+
function parseJSONResponse(text) {
|
|
78
|
+
const jsonMatch = text.match(/```json\n?([\s\S]*?)\n?```/) || text.match(/\{[\s\S]*\}/);
|
|
79
|
+
const jsonStr = jsonMatch ? jsonMatch[1] || jsonMatch[0] : text;
|
|
80
|
+
return JSON.parse(jsonStr);
|
|
81
|
+
}
|
|
82
|
+
function calculateCost(model, inputTokens, outputTokens) {
|
|
83
|
+
const pricing = {
|
|
84
|
+
// Anthropic
|
|
85
|
+
"claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
|
|
86
|
+
"claude-sonnet-4-20250514": { input: 3, output: 15 },
|
|
87
|
+
// OpenAI
|
|
88
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
89
|
+
"gpt-4o": { input: 2.5, output: 10 }
|
|
90
|
+
};
|
|
91
|
+
const modelPricing = pricing[model] || { input: 3, output: 15 };
|
|
92
|
+
return inputTokens / 1e6 * modelPricing.input + outputTokens / 1e6 * modelPricing.output;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
ENRICHMENT_MODELS,
|
|
97
|
+
detectProvider,
|
|
98
|
+
getApiKey,
|
|
99
|
+
createAIClient,
|
|
100
|
+
generateCompletion,
|
|
101
|
+
parseJSONResponse,
|
|
102
|
+
calculateCost
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=chunk-SXTKFDCR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ai-client.ts"],"sourcesContent":["/**\n * Shared AI client infrastructure for LLM-powered CLI commands.\n *\n * Used by:\n * - `fragments enhance` (usage analysis + documentation generation)\n * - `fragments init --scan --enrich` (knowledge field enrichment)\n *\n * Supports Anthropic (Claude) and OpenAI APIs via dynamic import.\n * No new dependencies — uses existing @anthropic-ai/sdk and openai.\n */\n\nexport type AIProvider = 'anthropic' | 'openai' | 'none';\n\n/**\n * Default lightweight models for enrichment (cheap, fast).\n * Enhance uses its own heavier models locally.\n */\nexport const ENRICHMENT_MODELS: Record<AIProvider, string> = {\n anthropic: 'claude-haiku-4-5-20251001',\n openai: 'gpt-4o-mini',\n none: '',\n};\n\n/**\n * Detect which AI provider to use based on available API keys and options.\n */\nexport function detectProvider(opts?: {\n provider?: AIProvider;\n apiKey?: string;\n}): AIProvider {\n if (opts?.provider) return opts.provider;\n if (opts?.apiKey) {\n if (opts.apiKey.startsWith('sk-ant-')) return 'anthropic';\n if (opts.apiKey.startsWith('sk-')) return 'openai';\n }\n if (process.env.ANTHROPIC_API_KEY) return 'anthropic';\n if (process.env.OPENAI_API_KEY) return 'openai';\n return 'none';\n}\n\n/**\n * Resolve the API key for a given provider.\n */\nexport function getApiKey(provider: AIProvider, explicitKey?: string): string | undefined {\n if (explicitKey) return explicitKey;\n if (provider === 'anthropic') return process.env.ANTHROPIC_API_KEY;\n if (provider === 'openai') return process.env.OPENAI_API_KEY;\n return undefined;\n}\n\n/**\n * Create an AI client for the given provider via dynamic import.\n */\nexport async function createAIClient(provider: AIProvider, apiKey: string): Promise<unknown> {\n if (provider === 'anthropic') {\n const Anthropic = (await import('@anthropic-ai/sdk')).default;\n return new Anthropic({ apiKey });\n }\n if (provider === 'openai') {\n const OpenAI = (await import('openai')).default;\n return new OpenAI({ apiKey });\n }\n throw new Error(`Unknown provider: ${provider}`);\n}\n\nexport interface CompletionResult {\n text: string;\n inputTokens: number;\n outputTokens: number;\n}\n\n/**\n * Generate a completion using the appropriate provider API.\n */\nexport async function generateCompletion(\n client: unknown,\n provider: AIProvider,\n model: string,\n system: string,\n user: string,\n maxTokens: number = 1024\n): Promise<CompletionResult> {\n if (provider === 'anthropic') {\n const anthropic = client as import('@anthropic-ai/sdk').default;\n const response = await anthropic.messages.create({\n model,\n max_tokens: maxTokens,\n system,\n messages: [{ role: 'user', content: user }],\n });\n\n const content = response.content[0];\n if (content.type !== 'text') {\n throw new Error('Unexpected response type');\n }\n\n return {\n text: content.text,\n inputTokens: response.usage?.input_tokens || 0,\n outputTokens: response.usage?.output_tokens || 0,\n };\n }\n\n if (provider === 'openai') {\n const openai = client as import('openai').default;\n const response = await openai.chat.completions.create({\n model,\n max_tokens: maxTokens,\n messages: [\n { role: 'system', content: system },\n { role: 'user', content: user },\n ],\n });\n\n const content = response.choices[0]?.message?.content;\n if (!content) {\n throw new Error('No response from OpenAI');\n }\n\n return {\n text: content,\n inputTokens: response.usage?.prompt_tokens || 0,\n outputTokens: response.usage?.completion_tokens || 0,\n };\n }\n\n throw new Error(`Unknown provider: ${provider}`);\n}\n\n/**\n * Parse a JSON response from an LLM, handling ```json fences and raw JSON.\n */\nexport function parseJSONResponse<T = unknown>(text: string): T {\n const jsonMatch = text.match(/```json\\n?([\\s\\S]*?)\\n?```/) || text.match(/\\{[\\s\\S]*\\}/);\n const jsonStr = jsonMatch ? (jsonMatch[1] || jsonMatch[0]) : text;\n return JSON.parse(jsonStr);\n}\n\n/**\n * Calculate estimated cost based on model and token usage.\n */\nexport function calculateCost(model: string, inputTokens: number, outputTokens: number): number {\n // Approximate costs per 1M tokens (input/output)\n const pricing: Record<string, { input: number; output: number }> = {\n // Anthropic\n 'claude-haiku-4-5-20251001': { input: 0.80, output: 4.00 },\n 'claude-sonnet-4-20250514': { input: 3.00, output: 15.00 },\n // OpenAI\n 'gpt-4o-mini': { input: 0.15, output: 0.60 },\n 'gpt-4o': { input: 2.50, output: 10.00 },\n };\n\n const modelPricing = pricing[model] || { input: 3.00, output: 15.00 };\n return (inputTokens / 1_000_000) * modelPricing.input +\n (outputTokens / 1_000_000) * modelPricing.output;\n}\n"],"mappings":"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","names":[]}
|