@tpitre/story-ui 1.0.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/.env.sample +17 -0
- package/LICENSE +21 -0
- package/README.md +531 -0
- package/dist/cli/index.js +250 -0
- package/dist/cli/setup.js +289 -0
- package/dist/index.js +12 -0
- package/dist/mcp-server/index.js +64 -0
- package/dist/mcp-server/routes/claude.js +30 -0
- package/dist/mcp-server/routes/components.js +26 -0
- package/dist/mcp-server/routes/generateStory.js +289 -0
- package/dist/mcp-server/routes/memoryStories.js +141 -0
- package/dist/mcp-server/routes/storySync.js +147 -0
- package/dist/story-generator/componentDiscovery.js +222 -0
- package/dist/story-generator/configLoader.js +482 -0
- package/dist/story-generator/generateStory.js +19 -0
- package/dist/story-generator/gitignoreManager.js +182 -0
- package/dist/story-generator/inMemoryStoryService.js +128 -0
- package/dist/story-generator/productionGitignoreManager.js +333 -0
- package/dist/story-generator/promptGenerator.js +201 -0
- package/dist/story-generator/storySync.js +201 -0
- package/dist/story-ui.config.js +114 -0
- package/dist/story-ui.config.loader.js +205 -0
- package/package.json +80 -0
- package/templates/README.md +32 -0
- package/templates/StoryUI/StoryUIPanel.stories.tsx +28 -0
- package/templates/StoryUI/StoryUIPanel.tsx +870 -0
- package/templates/StoryUI/index.tsx +2 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getInMemoryStoryService } from './inMemoryStoryService.js';
|
|
4
|
+
import { setupProductionGitignore } from './productionGitignoreManager.js';
|
|
5
|
+
/**
|
|
6
|
+
* Story synchronization service that keeps chat interface, file system, and memory in sync
|
|
7
|
+
*/
|
|
8
|
+
export class StorySyncService {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
const gitignoreManager = setupProductionGitignore(config);
|
|
12
|
+
this.isProduction = gitignoreManager.isProductionMode();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Gets all available stories from both file system and memory
|
|
16
|
+
*/
|
|
17
|
+
async getAllStories() {
|
|
18
|
+
const stories = [];
|
|
19
|
+
if (this.isProduction) {
|
|
20
|
+
// Production: Get from memory
|
|
21
|
+
const memoryService = getInMemoryStoryService(this.config);
|
|
22
|
+
const memoryStories = memoryService.getAllStories();
|
|
23
|
+
stories.push(...memoryStories.map(story => ({
|
|
24
|
+
id: story.id,
|
|
25
|
+
title: story.title,
|
|
26
|
+
fileName: `${story.id}.stories.tsx`,
|
|
27
|
+
description: story.description,
|
|
28
|
+
createdAt: story.createdAt,
|
|
29
|
+
lastAccessed: story.lastAccessed,
|
|
30
|
+
source: 'memory',
|
|
31
|
+
content: story.content,
|
|
32
|
+
prompt: story.prompt
|
|
33
|
+
})));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Development: Get from file system
|
|
37
|
+
const fileSystemStories = await this.getFileSystemStories();
|
|
38
|
+
stories.push(...fileSystemStories);
|
|
39
|
+
// Also include any memory stories (for hybrid scenarios)
|
|
40
|
+
const memoryService = getInMemoryStoryService(this.config);
|
|
41
|
+
const memoryStories = memoryService.getAllStories();
|
|
42
|
+
stories.push(...memoryStories.map(story => ({
|
|
43
|
+
id: story.id,
|
|
44
|
+
title: story.title,
|
|
45
|
+
fileName: `${story.id}.stories.tsx`,
|
|
46
|
+
description: story.description,
|
|
47
|
+
createdAt: story.createdAt,
|
|
48
|
+
lastAccessed: story.lastAccessed,
|
|
49
|
+
source: 'memory',
|
|
50
|
+
content: story.content,
|
|
51
|
+
prompt: story.prompt
|
|
52
|
+
})));
|
|
53
|
+
}
|
|
54
|
+
// Sort by creation date (newest first)
|
|
55
|
+
return stories.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Gets stories from the file system
|
|
59
|
+
*/
|
|
60
|
+
async getFileSystemStories() {
|
|
61
|
+
const stories = [];
|
|
62
|
+
if (!fs.existsSync(this.config.generatedStoriesPath)) {
|
|
63
|
+
return stories;
|
|
64
|
+
}
|
|
65
|
+
const files = fs.readdirSync(this.config.generatedStoriesPath);
|
|
66
|
+
const storyFiles = files.filter(file => file.endsWith('.stories.tsx'));
|
|
67
|
+
for (const file of storyFiles) {
|
|
68
|
+
try {
|
|
69
|
+
const filePath = path.join(this.config.generatedStoriesPath, file);
|
|
70
|
+
const stats = fs.statSync(filePath);
|
|
71
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
72
|
+
// Extract title from file content - handle escaped quotes more robustly
|
|
73
|
+
let title = file.replace('.stories.tsx', ''); // fallback
|
|
74
|
+
// Find the title line - look for the pattern and extract until the closing quote + comma
|
|
75
|
+
const titleStart = content.indexOf("title: '");
|
|
76
|
+
if (titleStart !== -1) {
|
|
77
|
+
const startPos = titleStart + "title: '".length;
|
|
78
|
+
const endPos = content.indexOf("',", startPos);
|
|
79
|
+
if (endPos !== -1) {
|
|
80
|
+
title = content.substring(startPos, endPos);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Remove the story prefix and unescape characters
|
|
84
|
+
title = title
|
|
85
|
+
.replace(this.config.storyPrefix, '')
|
|
86
|
+
.replace(/\\"/g, '"')
|
|
87
|
+
.replace(/\\'/g, "'")
|
|
88
|
+
.replace(/\\\\/g, '\\');
|
|
89
|
+
// Generate ID from filename
|
|
90
|
+
const id = file.replace('.stories.tsx', '');
|
|
91
|
+
stories.push({
|
|
92
|
+
id,
|
|
93
|
+
title,
|
|
94
|
+
fileName: file,
|
|
95
|
+
description: `Generated story: ${title}`,
|
|
96
|
+
createdAt: stats.birthtime,
|
|
97
|
+
lastAccessed: stats.atime,
|
|
98
|
+
source: 'filesystem',
|
|
99
|
+
content,
|
|
100
|
+
prompt: undefined
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.warn(`Failed to read story file ${file}:`, error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return stories;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Deletes a story from both file system and memory
|
|
111
|
+
*/
|
|
112
|
+
async deleteStory(storyId) {
|
|
113
|
+
let deleted = false;
|
|
114
|
+
// Delete from memory
|
|
115
|
+
const memoryService = getInMemoryStoryService(this.config);
|
|
116
|
+
if (memoryService.deleteStory(storyId)) {
|
|
117
|
+
deleted = true;
|
|
118
|
+
}
|
|
119
|
+
// Delete from file system (if not production)
|
|
120
|
+
if (!this.isProduction) {
|
|
121
|
+
const files = fs.readdirSync(this.config.generatedStoriesPath);
|
|
122
|
+
const matchingFiles = files.filter(file => file.includes(storyId) || file.startsWith(storyId));
|
|
123
|
+
for (const file of matchingFiles) {
|
|
124
|
+
try {
|
|
125
|
+
const filePath = path.join(this.config.generatedStoriesPath, file);
|
|
126
|
+
fs.unlinkSync(filePath);
|
|
127
|
+
deleted = true;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.warn(`Failed to delete story file ${file}:`, error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return deleted;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Gets a specific story by ID
|
|
138
|
+
*/
|
|
139
|
+
async getStory(storyId) {
|
|
140
|
+
const allStories = await this.getAllStories();
|
|
141
|
+
return allStories.find(story => story.id === storyId) || null;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Clears all stories
|
|
145
|
+
*/
|
|
146
|
+
async clearAllStories() {
|
|
147
|
+
// Clear memory
|
|
148
|
+
const memoryService = getInMemoryStoryService(this.config);
|
|
149
|
+
memoryService.clearAllStories();
|
|
150
|
+
// Clear file system (if not production)
|
|
151
|
+
if (!this.isProduction && fs.existsSync(this.config.generatedStoriesPath)) {
|
|
152
|
+
const files = fs.readdirSync(this.config.generatedStoriesPath);
|
|
153
|
+
const storyFiles = files.filter(file => file.endsWith('.stories.tsx'));
|
|
154
|
+
for (const file of storyFiles) {
|
|
155
|
+
try {
|
|
156
|
+
const filePath = path.join(this.config.generatedStoriesPath, file);
|
|
157
|
+
fs.unlinkSync(filePath);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.warn(`Failed to delete story file ${file}:`, error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Syncs localStorage chat history with actual stories
|
|
167
|
+
*/
|
|
168
|
+
async syncChatHistory() {
|
|
169
|
+
const actualStories = await this.getAllStories();
|
|
170
|
+
// This would be called from the frontend to sync localStorage
|
|
171
|
+
return {
|
|
172
|
+
actualStories: actualStories.map(story => ({
|
|
173
|
+
id: story.id,
|
|
174
|
+
title: story.title,
|
|
175
|
+
fileName: story.fileName,
|
|
176
|
+
lastUpdated: story.createdAt.getTime()
|
|
177
|
+
})),
|
|
178
|
+
shouldClearOrphanedChats: true
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Validates that a chat session corresponds to an actual story
|
|
183
|
+
*/
|
|
184
|
+
async validateChatSession(chatId) {
|
|
185
|
+
const story = await this.getStory(chatId);
|
|
186
|
+
return story !== null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Global story sync service instance
|
|
191
|
+
*/
|
|
192
|
+
let globalStorySyncService = null;
|
|
193
|
+
/**
|
|
194
|
+
* Gets or creates the global story sync service
|
|
195
|
+
*/
|
|
196
|
+
export function getStorySyncService(config) {
|
|
197
|
+
if (!globalStorySyncService) {
|
|
198
|
+
globalStorySyncService = new StorySyncService(config);
|
|
199
|
+
}
|
|
200
|
+
return globalStorySyncService;
|
|
201
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = path.dirname(__filename);
|
|
5
|
+
// Default generic configuration
|
|
6
|
+
export const DEFAULT_CONFIG = {
|
|
7
|
+
generatedStoriesPath: path.resolve(process.cwd(), './src/stories/generated/'),
|
|
8
|
+
componentsPath: path.resolve(process.cwd(), './src/components'),
|
|
9
|
+
componentsMetadataPath: undefined,
|
|
10
|
+
storyPrefix: 'Generated/',
|
|
11
|
+
defaultAuthor: 'Story UI AI',
|
|
12
|
+
importPath: 'your-component-library',
|
|
13
|
+
componentPrefix: '',
|
|
14
|
+
components: [], // Will be populated dynamically
|
|
15
|
+
layoutRules: {
|
|
16
|
+
multiColumnWrapper: 'div',
|
|
17
|
+
columnComponent: 'div',
|
|
18
|
+
containerComponent: 'div',
|
|
19
|
+
layoutExamples: {
|
|
20
|
+
twoColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem'}}>
|
|
21
|
+
<div>
|
|
22
|
+
<Card>
|
|
23
|
+
<h3>Left Card</h3>
|
|
24
|
+
<p>Left content</p>
|
|
25
|
+
</Card>
|
|
26
|
+
</div>
|
|
27
|
+
<div>
|
|
28
|
+
<Card>
|
|
29
|
+
<h3>Right Card</h3>
|
|
30
|
+
<p>Right content</p>
|
|
31
|
+
</Card>
|
|
32
|
+
</div>
|
|
33
|
+
</div>`,
|
|
34
|
+
threeColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem'}}>
|
|
35
|
+
<div>
|
|
36
|
+
<Card>
|
|
37
|
+
<h3>Column 1</h3>
|
|
38
|
+
<p>First column content</p>
|
|
39
|
+
</Card>
|
|
40
|
+
</div>
|
|
41
|
+
<div>
|
|
42
|
+
<Card>
|
|
43
|
+
<h3>Column 2</h3>
|
|
44
|
+
<p>Second column content</p>
|
|
45
|
+
</Card>
|
|
46
|
+
</div>
|
|
47
|
+
<div>
|
|
48
|
+
<Card>
|
|
49
|
+
<h3>Column 3</h3>
|
|
50
|
+
<p>Third column content</p>
|
|
51
|
+
</Card>
|
|
52
|
+
</div>
|
|
53
|
+
</div>`
|
|
54
|
+
},
|
|
55
|
+
prohibitedElements: []
|
|
56
|
+
},
|
|
57
|
+
sampleStory: `import type { StoryObj } from '@storybook/react-webpack5';
|
|
58
|
+
import { Card } from 'your-component-library';
|
|
59
|
+
|
|
60
|
+
export default {
|
|
61
|
+
title: 'Layouts/Sample Layout',
|
|
62
|
+
component: Card,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const Default: StoryObj<typeof Card> = {
|
|
66
|
+
args: {
|
|
67
|
+
children: (
|
|
68
|
+
<Card>
|
|
69
|
+
<h3>Sample Card</h3>
|
|
70
|
+
<p>Sample content</p>
|
|
71
|
+
</Card>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
};`
|
|
75
|
+
};
|
|
76
|
+
// Generic configuration template for other design systems
|
|
77
|
+
export const GENERIC_CONFIG_TEMPLATE = {
|
|
78
|
+
storyPrefix: 'Generated/',
|
|
79
|
+
defaultAuthor: 'Story UI AI',
|
|
80
|
+
componentPrefix: '',
|
|
81
|
+
layoutRules: {
|
|
82
|
+
multiColumnWrapper: 'div',
|
|
83
|
+
columnComponent: 'div',
|
|
84
|
+
layoutExamples: {
|
|
85
|
+
twoColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem'}}>
|
|
86
|
+
<div>Column 1 content</div>
|
|
87
|
+
<div>Column 2 content</div>
|
|
88
|
+
</div>`,
|
|
89
|
+
threeColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem'}}>
|
|
90
|
+
<div>Column 1 content</div>
|
|
91
|
+
<div>Column 2 content</div>
|
|
92
|
+
<div>Column 3 content</div>
|
|
93
|
+
</div>`
|
|
94
|
+
},
|
|
95
|
+
prohibitedElements: []
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
// Default configuration - should be overridden by user's story-ui.config.js
|
|
99
|
+
export const STORY_UI_CONFIG = DEFAULT_CONFIG;
|
|
100
|
+
// Function to merge user config with defaults
|
|
101
|
+
export function createStoryUIConfig(userConfig) {
|
|
102
|
+
return {
|
|
103
|
+
...DEFAULT_CONFIG,
|
|
104
|
+
...userConfig,
|
|
105
|
+
layoutRules: {
|
|
106
|
+
...DEFAULT_CONFIG.layoutRules,
|
|
107
|
+
...userConfig.layoutRules,
|
|
108
|
+
layoutExamples: {
|
|
109
|
+
...DEFAULT_CONFIG.layoutRules.layoutExamples,
|
|
110
|
+
...userConfig.layoutRules?.layoutExamples
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createStoryUIConfig, GENERIC_CONFIG_TEMPLATE } from './story-ui.config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Loads Story UI configuration from various sources
|
|
6
|
+
*/
|
|
7
|
+
export class StoryUIConfigLoader {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.config = null;
|
|
10
|
+
}
|
|
11
|
+
static getInstance() {
|
|
12
|
+
if (!StoryUIConfigLoader.instance) {
|
|
13
|
+
StoryUIConfigLoader.instance = new StoryUIConfigLoader();
|
|
14
|
+
}
|
|
15
|
+
return StoryUIConfigLoader.instance;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Load configuration from a file
|
|
19
|
+
*/
|
|
20
|
+
async loadFromFile(configPath) {
|
|
21
|
+
if (!fs.existsSync(configPath)) {
|
|
22
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
// Support both .js and .json config files
|
|
26
|
+
let userConfig;
|
|
27
|
+
if (configPath.endsWith('.json')) {
|
|
28
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
29
|
+
userConfig = JSON.parse(configContent);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Dynamic import for .js/.ts files
|
|
33
|
+
const configModule = await import(configPath);
|
|
34
|
+
userConfig = configModule.default || configModule.config || configModule;
|
|
35
|
+
}
|
|
36
|
+
this.config = createStoryUIConfig(userConfig);
|
|
37
|
+
return this.config;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new Error(`Failed to load configuration from ${configPath}: ${error}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Load configuration from package.json
|
|
45
|
+
*/
|
|
46
|
+
loadFromPackageJson(packagePath = process.cwd()) {
|
|
47
|
+
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
48
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
49
|
+
throw new Error(`package.json not found at ${packageJsonPath}`);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
53
|
+
const storyUIConfig = packageJson.storyUI || {};
|
|
54
|
+
// Auto-detect common paths if not specified
|
|
55
|
+
if (!storyUIConfig.generatedStoriesPath) {
|
|
56
|
+
const possiblePaths = [
|
|
57
|
+
path.join(packagePath, 'src/stories/generated'),
|
|
58
|
+
path.join(packagePath, 'stories/generated'),
|
|
59
|
+
path.join(packagePath, '.storybook/generated'),
|
|
60
|
+
path.join(packagePath, 'src/components/generated')
|
|
61
|
+
];
|
|
62
|
+
for (const possiblePath of possiblePaths) {
|
|
63
|
+
if (fs.existsSync(path.dirname(possiblePath))) {
|
|
64
|
+
storyUIConfig.generatedStoriesPath = possiblePath;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Auto-detect components path
|
|
70
|
+
if (!storyUIConfig.componentsPath) {
|
|
71
|
+
const possiblePaths = [
|
|
72
|
+
path.join(packagePath, 'src/components'),
|
|
73
|
+
path.join(packagePath, 'lib/components'),
|
|
74
|
+
path.join(packagePath, 'components'),
|
|
75
|
+
path.join(packagePath, 'src')
|
|
76
|
+
];
|
|
77
|
+
for (const possiblePath of possiblePaths) {
|
|
78
|
+
if (fs.existsSync(possiblePath)) {
|
|
79
|
+
storyUIConfig.componentsPath = possiblePath;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Auto-detect import path from package name
|
|
85
|
+
if (!storyUIConfig.importPath && packageJson.name) {
|
|
86
|
+
storyUIConfig.importPath = packageJson.name;
|
|
87
|
+
}
|
|
88
|
+
this.config = createStoryUIConfig(storyUIConfig);
|
|
89
|
+
return this.config;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw new Error(`Failed to load configuration from package.json: ${error}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Auto-detect configuration from project structure
|
|
97
|
+
*/
|
|
98
|
+
autoDetectConfig(projectPath = process.cwd()) {
|
|
99
|
+
const config = { ...GENERIC_CONFIG_TEMPLATE };
|
|
100
|
+
// Try to detect from package.json first
|
|
101
|
+
try {
|
|
102
|
+
return this.loadFromPackageJson(projectPath);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.warn('Could not load from package.json, using auto-detection');
|
|
106
|
+
}
|
|
107
|
+
// Auto-detect paths
|
|
108
|
+
const possibleComponentPaths = [
|
|
109
|
+
path.join(projectPath, 'src/components'),
|
|
110
|
+
path.join(projectPath, 'lib/components'),
|
|
111
|
+
path.join(projectPath, 'components'),
|
|
112
|
+
path.join(projectPath, 'src')
|
|
113
|
+
];
|
|
114
|
+
for (const possiblePath of possibleComponentPaths) {
|
|
115
|
+
if (fs.existsSync(possiblePath)) {
|
|
116
|
+
config.componentsPath = possiblePath;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Set generated stories path
|
|
121
|
+
config.generatedStoriesPath = path.join(projectPath, 'src/stories/generated');
|
|
122
|
+
// Try to detect component prefix from existing components
|
|
123
|
+
if (config.componentsPath) {
|
|
124
|
+
const componentDirs = fs.readdirSync(config.componentsPath, { withFileTypes: true })
|
|
125
|
+
.filter(d => d.isDirectory())
|
|
126
|
+
.map(d => d.name)
|
|
127
|
+
.slice(0, 5); // Check first 5 components
|
|
128
|
+
// Look for common prefixes
|
|
129
|
+
const prefixes = ['UI', 'App', 'My', 'Custom'];
|
|
130
|
+
for (const prefix of prefixes) {
|
|
131
|
+
if (componentDirs.some(name => name.startsWith(prefix))) {
|
|
132
|
+
config.componentPrefix = prefix;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.config = createStoryUIConfig(config);
|
|
138
|
+
return this.config;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get current configuration
|
|
142
|
+
*/
|
|
143
|
+
getConfig() {
|
|
144
|
+
return this.config;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Set configuration directly
|
|
148
|
+
*/
|
|
149
|
+
setConfig(config) {
|
|
150
|
+
this.config = config;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Generate a sample configuration file
|
|
154
|
+
*/
|
|
155
|
+
generateSampleConfig(outputPath, type = 'json') {
|
|
156
|
+
const sampleConfig = {
|
|
157
|
+
generatedStoriesPath: './src/stories/generated',
|
|
158
|
+
componentsPath: './src/components',
|
|
159
|
+
storyPrefix: 'Generated/',
|
|
160
|
+
defaultAuthor: 'Story UI AI',
|
|
161
|
+
importPath: 'your-component-library',
|
|
162
|
+
componentPrefix: 'UI',
|
|
163
|
+
layoutRules: {
|
|
164
|
+
multiColumnWrapper: 'div',
|
|
165
|
+
columnComponent: 'div',
|
|
166
|
+
layoutExamples: {
|
|
167
|
+
twoColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem'}}>
|
|
168
|
+
<div>Column 1 content</div>
|
|
169
|
+
<div>Column 2 content</div>
|
|
170
|
+
</div>`,
|
|
171
|
+
threeColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem'}}>
|
|
172
|
+
<div>Column 1 content</div>
|
|
173
|
+
<div>Column 2 content</div>
|
|
174
|
+
<div>Column 3 content</div>
|
|
175
|
+
</div>`
|
|
176
|
+
},
|
|
177
|
+
prohibitedElements: []
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
if (type === 'json') {
|
|
181
|
+
fs.writeFileSync(outputPath, JSON.stringify(sampleConfig, null, 2));
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const jsContent = `export default ${JSON.stringify(sampleConfig, null, 2)};`;
|
|
185
|
+
fs.writeFileSync(outputPath, jsContent);
|
|
186
|
+
}
|
|
187
|
+
console.log(`Sample configuration written to: ${outputPath}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Convenience functions
|
|
191
|
+
export const configLoader = StoryUIConfigLoader.getInstance();
|
|
192
|
+
export function loadStoryUIConfig(configPath) {
|
|
193
|
+
if (configPath) {
|
|
194
|
+
return configLoader.loadFromFile(configPath);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// Try auto-detection
|
|
198
|
+
try {
|
|
199
|
+
return Promise.resolve(configLoader.loadFromPackageJson());
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return Promise.resolve(configLoader.autoDetectConfig());
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tpitre/story-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered Storybook story generator for any React component library",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"story-ui": "dist/cli/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"templates",
|
|
14
|
+
".env.sample",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"start": "yarn build && node dist/mcp-server/index.js",
|
|
21
|
+
"dev": "tsc --watch",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"test": "echo \"Tests coming soon\" && exit 0"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"storybook",
|
|
27
|
+
"ai",
|
|
28
|
+
"react",
|
|
29
|
+
"components",
|
|
30
|
+
"ui",
|
|
31
|
+
"design-system",
|
|
32
|
+
"claude",
|
|
33
|
+
"mcp",
|
|
34
|
+
"story-generation"
|
|
35
|
+
],
|
|
36
|
+
"author": "Story UI Contributors",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/southleft/story-ui.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/southleft/story-ui/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/southleft/story-ui#readme",
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=16.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"express": "^4.18.2",
|
|
51
|
+
"cors": "^2.8.5",
|
|
52
|
+
"dotenv": "^16.3.1",
|
|
53
|
+
"node-fetch": "^2.6.7",
|
|
54
|
+
"commander": "^11.0.0",
|
|
55
|
+
"chalk": "^5.3.0",
|
|
56
|
+
"inquirer": "^9.2.0",
|
|
57
|
+
"concurrently": "^8.2.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/express": "^4.17.21",
|
|
61
|
+
"@types/node": "^20.4.2",
|
|
62
|
+
"@types/inquirer": "^9.0.0",
|
|
63
|
+
"@types/cors": "^2.8.17",
|
|
64
|
+
"@types/node-fetch": "^2.6.12",
|
|
65
|
+
"ts-node": "^10.9.2",
|
|
66
|
+
"typescript": "^5.2.2"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"react": ">=16.8.0",
|
|
70
|
+
"@storybook/react": ">=6.0.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"react": {
|
|
74
|
+
"optional": false
|
|
75
|
+
},
|
|
76
|
+
"@storybook/react": {
|
|
77
|
+
"optional": false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Story UI Templates
|
|
2
|
+
|
|
3
|
+
This directory contains template files that are copied to your project during the Story UI setup process.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
### StoryUI/
|
|
8
|
+
- `StoryUIPanel.tsx` - The main Story UI interface component
|
|
9
|
+
- `StoryUIPanel.stories.tsx` - Storybook story configuration
|
|
10
|
+
- `index.tsx` - Export file for cleaner imports
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
These files are automatically copied to your project when you run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx story-ui init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
They will be installed to your configured stories directory (default: `./src/stories/generated/StoryUI/`).
|
|
21
|
+
|
|
22
|
+
## Customization
|
|
23
|
+
|
|
24
|
+
After installation, you can customize these components to match your design system:
|
|
25
|
+
|
|
26
|
+
1. Update the styles in `StoryUIPanel.tsx` to match your theme
|
|
27
|
+
2. Modify the story configuration in `StoryUIPanel.stories.tsx`
|
|
28
|
+
3. Add additional features or integrations as needed
|
|
29
|
+
|
|
30
|
+
## Note
|
|
31
|
+
|
|
32
|
+
These files are automatically added to your `.gitignore` during setup since they're part of the Story UI installation and can be regenerated.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StoryFn, Meta } from '@storybook/react';
|
|
3
|
+
import StoryUIPanel from './StoryUIPanel';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Story UI/Story Generator',
|
|
7
|
+
component: StoryUIPanel,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'fullscreen',
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: 'AI-powered story generator for creating complex UI layouts using your component library.'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
} as Meta<typeof StoryUIPanel>;
|
|
17
|
+
|
|
18
|
+
const Template: StoryFn<typeof StoryUIPanel> = (args) => <StoryUIPanel {...args} />;
|
|
19
|
+
|
|
20
|
+
export const Default = Template.bind({});
|
|
21
|
+
Default.args = {};
|
|
22
|
+
Default.parameters = {
|
|
23
|
+
docs: {
|
|
24
|
+
description: {
|
|
25
|
+
story: 'Use natural language prompts to generate Storybook stories with your components.'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|