@tpitre/story-ui 1.7.1 ā 2.0.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/.env.sample +3 -1
- package/README.md +160 -606
- package/dist/cli/index.js +23 -24
- package/dist/cli/setup.js +295 -36
- package/dist/mcp-server/index.js +67 -0
- package/dist/mcp-server/routes/generateStory.js +323 -56
- package/dist/story-generator/componentBlacklist.js +181 -0
- package/dist/story-generator/componentDiscovery.js +9 -2
- package/dist/story-generator/configLoader.js +109 -39
- package/dist/story-generator/considerationsLoader.js +204 -0
- package/dist/story-generator/documentation-sources.js +36 -0
- package/dist/story-generator/documentationLoader.js +214 -0
- package/dist/story-generator/dynamicPackageDiscovery.js +527 -0
- package/dist/story-generator/enhancedComponentDiscovery.js +369 -118
- package/dist/story-generator/generateStory.js +7 -3
- package/dist/story-generator/postProcessStory.js +71 -0
- package/dist/story-generator/promptGenerator.js +286 -37
- package/dist/story-generator/storyHistory.js +118 -0
- package/dist/story-generator/storyTracker.js +33 -18
- package/dist/story-generator/storyValidator.js +39 -0
- package/dist/story-generator/universalDesignSystemAdapter.js +209 -0
- package/dist/story-generator/validateStory.js +82 -7
- package/dist/story-ui.config.js +12 -5
- package/package.json +11 -6
- package/templates/StoryUI/StoryUIPanel.stories.tsx +29 -13
- package/templates/StoryUI/StoryUIPanel.tsx +489 -359
- package/templates/react-import-rule.json +36 -0
- package/templates/story-generation-rules.json +29 -0
- package/templates/story-ui-considerations.json +156 -0
- package/templates/story-ui-considerations.md +109 -0
- package/templates/story-ui-docs-README.md +55 -0
- package/dist/scripts/test-validation.js +0 -81
- package/dist/test-storybooks/chakra-test/src/components/index.js +0 -3
- package/dist/test-storybooks/custom-design-test/src/components/index.js +0 -3
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
import { setupProductionGitignore } from '../story-generator/productionGitignoreManager.js';
|
|
8
8
|
import { createStoryUIConfig } from '../story-ui.config.js';
|
|
9
9
|
import { setupCommand } from './setup.js';
|
|
10
|
+
import net from 'net';
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
12
13
|
const program = new Command();
|
|
@@ -25,16 +26,34 @@ program
|
|
|
25
26
|
.description('Start the Story UI server')
|
|
26
27
|
.option('-p, --port <port>', 'Port to run the server on', '4001')
|
|
27
28
|
.option('-c, --config <config>', 'Path to configuration file')
|
|
28
|
-
.action((options) => {
|
|
29
|
+
.action(async (options) => {
|
|
29
30
|
console.log('š Starting Story UI server...');
|
|
30
31
|
// Use absolute path to avoid dist/dist issue when package is linked
|
|
31
32
|
const pkgRoot = path.resolve(__dirname, '..');
|
|
32
33
|
const serverPath = path.join(pkgRoot, 'mcp-server/index.js');
|
|
33
34
|
console.log(`ā
Using MCP server at: ${serverPath}`);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// FIRST_EDIT: determine an available port
|
|
36
|
+
const requestedPort = parseInt(options.port || '4001', 10);
|
|
37
|
+
const isPortFree = (port) => {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const tester = net.createServer()
|
|
40
|
+
.once('error', () => resolve(false))
|
|
41
|
+
.once('listening', () => {
|
|
42
|
+
tester.close();
|
|
43
|
+
resolve(true);
|
|
44
|
+
})
|
|
45
|
+
.listen(port);
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
let finalPort = requestedPort;
|
|
49
|
+
// eslint-disable-next-line no-await-in-loop
|
|
50
|
+
while (!(await isPortFree(finalPort))) {
|
|
51
|
+
finalPort += 1;
|
|
37
52
|
}
|
|
53
|
+
if (finalPort !== requestedPort) {
|
|
54
|
+
console.log(`ā ļø Port ${requestedPort} is in use. Using ${finalPort} instead.`);
|
|
55
|
+
}
|
|
56
|
+
const env = { ...process.env, PORT: String(finalPort) };
|
|
38
57
|
if (options.config) {
|
|
39
58
|
env.STORY_UI_CONFIG_PATH = options.config;
|
|
40
59
|
}
|
|
@@ -131,26 +150,6 @@ async function autoDetectAndCreateConfig() {
|
|
|
131
150
|
async function createTemplateConfig(template) {
|
|
132
151
|
let config;
|
|
133
152
|
switch (template) {
|
|
134
|
-
case 'material-ui':
|
|
135
|
-
config = {
|
|
136
|
-
importPath: '@mui/material',
|
|
137
|
-
componentPrefix: '',
|
|
138
|
-
layoutRules: {
|
|
139
|
-
multiColumnWrapper: 'Grid',
|
|
140
|
-
columnComponent: 'Grid',
|
|
141
|
-
layoutExamples: {
|
|
142
|
-
twoColumn: `<Grid container spacing={2}>
|
|
143
|
-
<Grid item xs={6}>
|
|
144
|
-
<Card>Left content</Card>
|
|
145
|
-
</Grid>
|
|
146
|
-
<Grid item xs={6}>
|
|
147
|
-
<Card>Right content</Card>
|
|
148
|
-
</Grid>
|
|
149
|
-
</Grid>`
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
break;
|
|
154
153
|
case 'chakra-ui':
|
|
155
154
|
config = {
|
|
156
155
|
importPath: '@chakra-ui/react',
|
package/dist/cli/setup.js
CHANGED
|
@@ -4,8 +4,128 @@ import inquirer from 'inquirer';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { autoDetectDesignSystem } from '../story-generator/configLoader.js';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
|
+
import net from 'net';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
7
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
10
|
const __dirname = path.dirname(__filename);
|
|
11
|
+
// FIRST_EDIT: helper functions to check for free ports
|
|
12
|
+
async function isPortAvailable(port) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const tester = net.createServer()
|
|
15
|
+
.once('error', () => resolve(false))
|
|
16
|
+
.once('listening', () => {
|
|
17
|
+
tester.close();
|
|
18
|
+
resolve(true);
|
|
19
|
+
})
|
|
20
|
+
.listen(port);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function findAvailablePort(startPort) {
|
|
24
|
+
let port = startPort;
|
|
25
|
+
// eslint-disable-next-line no-await-in-loop
|
|
26
|
+
while (!(await isPortAvailable(port))) {
|
|
27
|
+
port += 1;
|
|
28
|
+
}
|
|
29
|
+
return port;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Clean up default Storybook template components that could conflict with design system discovery
|
|
33
|
+
*/
|
|
34
|
+
function cleanupDefaultStorybookComponents() {
|
|
35
|
+
const storiesDir = path.join(process.cwd(), 'src', 'stories');
|
|
36
|
+
// Common default Storybook files that cause conflicts
|
|
37
|
+
const defaultFiles = [
|
|
38
|
+
'Button.stories.ts',
|
|
39
|
+
'Button.stories.tsx',
|
|
40
|
+
'Header.stories.ts',
|
|
41
|
+
'Header.stories.tsx',
|
|
42
|
+
'Page.stories.ts',
|
|
43
|
+
'Page.stories.tsx',
|
|
44
|
+
'button.css',
|
|
45
|
+
'header.css',
|
|
46
|
+
'page.css',
|
|
47
|
+
'Button.tsx',
|
|
48
|
+
'Header.tsx',
|
|
49
|
+
'Page.tsx'
|
|
50
|
+
];
|
|
51
|
+
let cleanedFiles = 0;
|
|
52
|
+
for (const fileName of defaultFiles) {
|
|
53
|
+
const filePath = path.join(storiesDir, fileName);
|
|
54
|
+
if (fs.existsSync(filePath)) {
|
|
55
|
+
try {
|
|
56
|
+
fs.unlinkSync(filePath);
|
|
57
|
+
cleanedFiles++;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.warn(`Could not remove ${fileName}: ${error}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (cleanedFiles > 0) {
|
|
65
|
+
console.log(chalk.green(`ā
Cleaned up ${cleanedFiles} default Storybook template files to prevent conflicts`));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Design system installation configurations
|
|
69
|
+
const DESIGN_SYSTEM_CONFIGS = {
|
|
70
|
+
antd: {
|
|
71
|
+
packages: ['antd'],
|
|
72
|
+
name: 'Ant Design',
|
|
73
|
+
importPath: 'antd',
|
|
74
|
+
additionalSetup: 'import "antd/dist/reset.css";'
|
|
75
|
+
},
|
|
76
|
+
mantine: {
|
|
77
|
+
packages: ['@mantine/core', '@mantine/hooks', '@mantine/notifications'],
|
|
78
|
+
name: 'Mantine',
|
|
79
|
+
importPath: '@mantine/core',
|
|
80
|
+
additionalSetup: 'import "@mantine/core/styles.css";'
|
|
81
|
+
},
|
|
82
|
+
chakra: {
|
|
83
|
+
packages: ['@chakra-ui/react', '@emotion/react', '@emotion/styled', 'framer-motion'],
|
|
84
|
+
name: 'Chakra UI',
|
|
85
|
+
importPath: '@chakra-ui/react',
|
|
86
|
+
additionalSetup: 'import { ChakraProvider } from "@chakra-ui/react";'
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
async function installDesignSystem(systemKey) {
|
|
90
|
+
const config = DESIGN_SYSTEM_CONFIGS[systemKey];
|
|
91
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
|
|
92
|
+
// Check if packages are already installed
|
|
93
|
+
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
94
|
+
const missingPackages = config.packages.filter(pkg => !dependencies[pkg]);
|
|
95
|
+
if (missingPackages.length === 0) {
|
|
96
|
+
console.log(chalk.green(`ā
${config.name} packages already installed`));
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
console.log(chalk.blue(`\nš¦ Installing ${config.name} packages...`));
|
|
100
|
+
console.log(chalk.gray(`Packages: ${missingPackages.join(', ')}`));
|
|
101
|
+
// Detect package manager
|
|
102
|
+
const npmLock = fs.existsSync(path.join(process.cwd(), 'package-lock.json'));
|
|
103
|
+
const yarnLock = fs.existsSync(path.join(process.cwd(), 'yarn.lock'));
|
|
104
|
+
const pnpmLock = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'));
|
|
105
|
+
let installCommand = `npm install ${missingPackages.join(' ')}`;
|
|
106
|
+
if (yarnLock) {
|
|
107
|
+
installCommand = `yarn add ${missingPackages.join(' ')}`;
|
|
108
|
+
}
|
|
109
|
+
else if (pnpmLock) {
|
|
110
|
+
installCommand = `pnpm add ${missingPackages.join(' ')}`;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
console.log(chalk.gray(`Running: ${installCommand}`));
|
|
114
|
+
execSync(installCommand, { stdio: 'inherit' });
|
|
115
|
+
console.log(chalk.green(`ā
${config.name} installed successfully!`));
|
|
116
|
+
if (config.additionalSetup) {
|
|
117
|
+
console.log(chalk.blue('\nš Additional setup required:'));
|
|
118
|
+
console.log(chalk.gray(`Add this import to your main CSS/index file:`));
|
|
119
|
+
console.log(chalk.cyan(`${config.additionalSetup}`));
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error(chalk.red(`ā Failed to install ${config.name}:`), error);
|
|
125
|
+
console.log(chalk.yellow(`\nš” You can install manually with: ${installCommand}`));
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
9
129
|
export async function setupCommand() {
|
|
10
130
|
console.log(chalk.blue.bold('\nšØ Story UI Setup\n'));
|
|
11
131
|
console.log('This will help you configure Story UI for your design system.\n');
|
|
@@ -60,14 +180,25 @@ export async function setupCommand() {
|
|
|
60
180
|
message: 'Which design system are you using?',
|
|
61
181
|
choices: [
|
|
62
182
|
{ name: 'š¤ Auto-detect from package.json', value: 'auto' },
|
|
63
|
-
{ name: '
|
|
64
|
-
{ name: '
|
|
65
|
-
{ name: '
|
|
66
|
-
{ name: 'šÆ Mantine (@mantine/core)', value: 'mantine' },
|
|
183
|
+
{ name: 'š Ant Design (antd) - Install & Configure', value: 'antd' },
|
|
184
|
+
{ name: 'šÆ Mantine (@mantine/core) - Install & Configure', value: 'mantine' },
|
|
185
|
+
{ name: 'ā” Chakra UI (@chakra-ui/react) - Install & Configure', value: 'chakra' },
|
|
67
186
|
{ name: 'š§ Custom/Other', value: 'custom' }
|
|
68
187
|
],
|
|
69
188
|
default: autoDetected ? 'auto' : 'custom'
|
|
70
189
|
},
|
|
190
|
+
{
|
|
191
|
+
type: 'confirm',
|
|
192
|
+
name: 'installDesignSystem',
|
|
193
|
+
message: (answers) => {
|
|
194
|
+
const systemName = answers.designSystem === 'antd' ? 'Ant Design' :
|
|
195
|
+
answers.designSystem === 'mantine' ? 'Mantine' :
|
|
196
|
+
answers.designSystem === 'chakra' ? 'Chakra UI' : 'the design system';
|
|
197
|
+
return `Would you like to install ${systemName} and its dependencies now?`;
|
|
198
|
+
},
|
|
199
|
+
when: (answers) => ['antd', 'mantine', 'chakra'].includes(answers.designSystem),
|
|
200
|
+
default: true
|
|
201
|
+
},
|
|
71
202
|
{
|
|
72
203
|
type: 'input',
|
|
73
204
|
name: 'importPath',
|
|
@@ -96,6 +227,22 @@ export async function setupCommand() {
|
|
|
96
227
|
default: './src/components',
|
|
97
228
|
when: (answers) => answers.designSystem === 'custom'
|
|
98
229
|
},
|
|
230
|
+
{
|
|
231
|
+
type: 'input',
|
|
232
|
+
name: 'mcpPort',
|
|
233
|
+
message: 'Port for the Story UI MCP server',
|
|
234
|
+
default: async () => {
|
|
235
|
+
const port = await findAvailablePort(4001);
|
|
236
|
+
return String(port);
|
|
237
|
+
},
|
|
238
|
+
validate: async (input) => {
|
|
239
|
+
const value = parseInt(input, 10);
|
|
240
|
+
if (isNaN(value) || value <= 0)
|
|
241
|
+
return 'Enter a valid port number';
|
|
242
|
+
const available = await isPortAvailable(value);
|
|
243
|
+
return available ? true : `Port ${value} is already in use`;
|
|
244
|
+
}
|
|
245
|
+
},
|
|
99
246
|
{
|
|
100
247
|
type: 'confirm',
|
|
101
248
|
name: 'hasApiKey',
|
|
@@ -110,53 +257,53 @@ export async function setupCommand() {
|
|
|
110
257
|
validate: (input) => input.trim() ? true : 'API key is required'
|
|
111
258
|
}
|
|
112
259
|
]);
|
|
260
|
+
// Install design system if requested
|
|
261
|
+
if (answers.installDesignSystem && ['antd', 'mantine', 'chakra'].includes(answers.designSystem)) {
|
|
262
|
+
const installSuccess = await installDesignSystem(answers.designSystem);
|
|
263
|
+
if (!installSuccess) {
|
|
264
|
+
console.log(chalk.yellow('ā ļø Installation failed but continuing with configuration...'));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
113
267
|
// Generate configuration
|
|
114
268
|
let config = {};
|
|
115
269
|
if (answers.designSystem === 'auto' && autoDetected) {
|
|
116
270
|
config = autoDetected;
|
|
117
271
|
}
|
|
118
|
-
else if (answers.designSystem === '
|
|
272
|
+
else if (answers.designSystem === 'chakra') {
|
|
119
273
|
config = {
|
|
120
|
-
importPath: '@
|
|
274
|
+
importPath: '@chakra-ui/react',
|
|
121
275
|
componentPrefix: '',
|
|
122
276
|
layoutRules: {
|
|
123
|
-
multiColumnWrapper: '
|
|
124
|
-
columnComponent: '
|
|
277
|
+
multiColumnWrapper: 'SimpleGrid',
|
|
278
|
+
columnComponent: 'Box',
|
|
125
279
|
containerComponent: 'Container',
|
|
126
280
|
layoutExamples: {
|
|
127
|
-
twoColumn: `<
|
|
128
|
-
<
|
|
281
|
+
twoColumn: `<SimpleGrid columns={2} spacing={6}>
|
|
282
|
+
<Box>
|
|
129
283
|
<Card>
|
|
130
|
-
<
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
284
|
+
<CardHeader>
|
|
285
|
+
<Heading size="md">Left Card</Heading>
|
|
286
|
+
</CardHeader>
|
|
287
|
+
<CardBody>
|
|
288
|
+
<Text>Left content goes here</Text>
|
|
289
|
+
</CardBody>
|
|
134
290
|
</Card>
|
|
135
|
-
</
|
|
136
|
-
<
|
|
291
|
+
</Box>
|
|
292
|
+
<Box>
|
|
137
293
|
<Card>
|
|
138
|
-
<
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
294
|
+
<CardHeader>
|
|
295
|
+
<Heading size="md">Right Card</Heading>
|
|
296
|
+
</CardHeader>
|
|
297
|
+
<CardBody>
|
|
298
|
+
<Text>Right content goes here</Text>
|
|
299
|
+
</CardBody>
|
|
142
300
|
</Card>
|
|
143
|
-
</
|
|
144
|
-
</
|
|
301
|
+
</Box>
|
|
302
|
+
</SimpleGrid>`
|
|
145
303
|
}
|
|
146
304
|
}
|
|
147
305
|
};
|
|
148
306
|
}
|
|
149
|
-
else if (answers.designSystem === 'chakra') {
|
|
150
|
-
config = {
|
|
151
|
-
importPath: '@chakra-ui/react',
|
|
152
|
-
componentPrefix: '',
|
|
153
|
-
layoutRules: {
|
|
154
|
-
multiColumnWrapper: 'SimpleGrid',
|
|
155
|
-
columnComponent: 'Box',
|
|
156
|
-
containerComponent: 'Container'
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
307
|
else if (answers.designSystem === 'antd') {
|
|
161
308
|
config = {
|
|
162
309
|
importPath: 'antd',
|
|
@@ -164,7 +311,21 @@ export async function setupCommand() {
|
|
|
164
311
|
layoutRules: {
|
|
165
312
|
multiColumnWrapper: 'Row',
|
|
166
313
|
columnComponent: 'Col',
|
|
167
|
-
containerComponent: 'div'
|
|
314
|
+
containerComponent: 'div',
|
|
315
|
+
layoutExamples: {
|
|
316
|
+
twoColumn: `<Row gutter={16}>
|
|
317
|
+
<Col span={12}>
|
|
318
|
+
<Card title="Left Card" bordered={false}>
|
|
319
|
+
<p>Left content goes here</p>
|
|
320
|
+
</Card>
|
|
321
|
+
</Col>
|
|
322
|
+
<Col span={12}>
|
|
323
|
+
<Card title="Right Card" bordered={false}>
|
|
324
|
+
<p>Right content goes here</p>
|
|
325
|
+
</Card>
|
|
326
|
+
</Col>
|
|
327
|
+
</Row>`
|
|
328
|
+
}
|
|
168
329
|
}
|
|
169
330
|
};
|
|
170
331
|
}
|
|
@@ -175,7 +336,27 @@ export async function setupCommand() {
|
|
|
175
336
|
layoutRules: {
|
|
176
337
|
multiColumnWrapper: 'SimpleGrid',
|
|
177
338
|
columnComponent: 'div',
|
|
178
|
-
containerComponent: 'Container'
|
|
339
|
+
containerComponent: 'Container',
|
|
340
|
+
layoutExamples: {
|
|
341
|
+
twoColumn: `<SimpleGrid cols={2} spacing="md">
|
|
342
|
+
<div>
|
|
343
|
+
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
|
344
|
+
<Text fw={500} size="lg" mb="xs">Left Card</Text>
|
|
345
|
+
<Text size="sm" c="dimmed">
|
|
346
|
+
Left content goes here
|
|
347
|
+
</Text>
|
|
348
|
+
</Card>
|
|
349
|
+
</div>
|
|
350
|
+
<div>
|
|
351
|
+
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
|
352
|
+
<Text fw={500} size="lg" mb="xs">Right Card</Text>
|
|
353
|
+
<Text size="sm" c="dimmed">
|
|
354
|
+
Right content goes here
|
|
355
|
+
</Text>
|
|
356
|
+
</Card>
|
|
357
|
+
</div>
|
|
358
|
+
</SimpleGrid>`
|
|
359
|
+
}
|
|
179
360
|
}
|
|
180
361
|
};
|
|
181
362
|
}
|
|
@@ -236,6 +417,38 @@ export async function setupCommand() {
|
|
|
236
417
|
console.warn(chalk.yellow(`ā ļø Template file not found: ${file}`));
|
|
237
418
|
}
|
|
238
419
|
}
|
|
420
|
+
// Create considerations file
|
|
421
|
+
const considerationsTemplatePath = path.resolve(__dirname, '../../templates/story-ui-considerations.md');
|
|
422
|
+
const considerationsPath = path.join(process.cwd(), 'story-ui-considerations.md');
|
|
423
|
+
if (!fs.existsSync(considerationsPath) && fs.existsSync(considerationsTemplatePath)) {
|
|
424
|
+
let considerationsContent = fs.readFileSync(considerationsTemplatePath, 'utf-8');
|
|
425
|
+
// Customize based on selected design system
|
|
426
|
+
if (config.importPath) {
|
|
427
|
+
considerationsContent = considerationsContent.replace('[Your Component Library]', config.importPath);
|
|
428
|
+
considerationsContent = considerationsContent.replace('[your-import-path]', config.importPath);
|
|
429
|
+
}
|
|
430
|
+
fs.writeFileSync(considerationsPath, considerationsContent);
|
|
431
|
+
console.log(chalk.green('ā
Created story-ui-considerations.md for AI customization'));
|
|
432
|
+
}
|
|
433
|
+
// Create documentation directory structure
|
|
434
|
+
const docsDir = path.join(process.cwd(), 'story-ui-docs');
|
|
435
|
+
if (!fs.existsSync(docsDir)) {
|
|
436
|
+
console.log(chalk.blue('\nš Creating documentation directory structure...'));
|
|
437
|
+
// Create main directory and subdirectories
|
|
438
|
+
const subdirs = ['guidelines', 'tokens', 'components', 'patterns'];
|
|
439
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
440
|
+
for (const subdir of subdirs) {
|
|
441
|
+
fs.mkdirSync(path.join(docsDir, subdir), { recursive: true });
|
|
442
|
+
}
|
|
443
|
+
// Copy README template
|
|
444
|
+
const docsReadmeTemplatePath = path.resolve(__dirname, '../../templates/story-ui-docs-README.md');
|
|
445
|
+
const docsReadmePath = path.join(docsDir, 'README.md');
|
|
446
|
+
if (fs.existsSync(docsReadmeTemplatePath)) {
|
|
447
|
+
fs.writeFileSync(docsReadmePath, fs.readFileSync(docsReadmeTemplatePath, 'utf-8'));
|
|
448
|
+
}
|
|
449
|
+
console.log(chalk.green('ā
Created story-ui-docs/ directory structure'));
|
|
450
|
+
console.log(chalk.gray(' Add your design system documentation to enhance AI story generation'));
|
|
451
|
+
}
|
|
239
452
|
// Create .env file from template
|
|
240
453
|
const envSamplePath = path.resolve(__dirname, '../../.env.sample');
|
|
241
454
|
const envPath = path.join(process.cwd(), '.env');
|
|
@@ -246,6 +459,10 @@ export async function setupCommand() {
|
|
|
246
459
|
if (answers.apiKey) {
|
|
247
460
|
envContent = envContent.replace('your-claude-api-key-here', answers.apiKey);
|
|
248
461
|
}
|
|
462
|
+
// Update the VITE_STORY_UI_PORT with the chosen port
|
|
463
|
+
if (answers.mcpPort) {
|
|
464
|
+
envContent = envContent.replace('VITE_STORY_UI_PORT=4001', `VITE_STORY_UI_PORT=${answers.mcpPort}`);
|
|
465
|
+
}
|
|
249
466
|
fs.writeFileSync(envPath, envContent);
|
|
250
467
|
console.log(chalk.green(`ā
Created .env file${answers.apiKey ? ' with your API key' : ''}`));
|
|
251
468
|
}
|
|
@@ -274,18 +491,60 @@ export async function setupCommand() {
|
|
|
274
491
|
console.log(chalk.green(`ā
Updated .gitignore with Story UI patterns`));
|
|
275
492
|
}
|
|
276
493
|
}
|
|
494
|
+
// Clean up default Storybook template components to prevent conflicts
|
|
495
|
+
cleanupDefaultStorybookComponents();
|
|
277
496
|
// Update package.json with convenience scripts
|
|
278
497
|
if (packageJson) {
|
|
279
498
|
const scripts = packageJson.scripts || {};
|
|
499
|
+
// FIRST_EDIT: include chosen port in script
|
|
500
|
+
const portFlag = `--port ${answers.mcpPort || '4001'}`;
|
|
280
501
|
if (!scripts['story-ui']) {
|
|
281
|
-
scripts['story-ui'] =
|
|
502
|
+
scripts['story-ui'] = `story-ui start ${portFlag}`;
|
|
503
|
+
}
|
|
504
|
+
else if (!scripts['story-ui'].includes('--port')) {
|
|
505
|
+
scripts['story-ui'] += ` ${portFlag}`;
|
|
282
506
|
}
|
|
283
507
|
if (!scripts['storybook-with-ui'] && scripts['storybook']) {
|
|
284
508
|
scripts['storybook-with-ui'] = 'concurrently "npm run storybook" "npm run story-ui"';
|
|
285
509
|
}
|
|
286
510
|
packageJson.scripts = scripts;
|
|
511
|
+
// Check and add required dependencies
|
|
512
|
+
const dependencies = packageJson.dependencies || {};
|
|
513
|
+
const devDependencies = packageJson.devDependencies || {};
|
|
514
|
+
let needsInstall = false;
|
|
515
|
+
// Check for concurrently (needed for storybook-with-ui script)
|
|
516
|
+
if (!dependencies['concurrently'] && !devDependencies['concurrently']) {
|
|
517
|
+
console.log(chalk.blue('š¦ Adding concurrently dependency...'));
|
|
518
|
+
devDependencies['concurrently'] = '^8.2.0';
|
|
519
|
+
needsInstall = true;
|
|
520
|
+
}
|
|
521
|
+
packageJson.dependencies = dependencies;
|
|
522
|
+
packageJson.devDependencies = devDependencies;
|
|
287
523
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
288
524
|
console.log(chalk.green('ā
Added convenience scripts to package.json'));
|
|
525
|
+
if (needsInstall) {
|
|
526
|
+
console.log(chalk.blue('\nš¦ Installing required dependencies...'));
|
|
527
|
+
console.log(chalk.gray('This may take a moment...\n'));
|
|
528
|
+
// Detect package manager
|
|
529
|
+
const npmLock = fs.existsSync(path.join(process.cwd(), 'package-lock.json'));
|
|
530
|
+
const yarnLock = fs.existsSync(path.join(process.cwd(), 'yarn.lock'));
|
|
531
|
+
const pnpmLock = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'));
|
|
532
|
+
let installCommand = 'npm install';
|
|
533
|
+
if (yarnLock) {
|
|
534
|
+
installCommand = 'yarn install';
|
|
535
|
+
}
|
|
536
|
+
else if (pnpmLock) {
|
|
537
|
+
installCommand = 'pnpm install';
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
execSync(installCommand, { stdio: 'inherit' });
|
|
541
|
+
console.log(chalk.green('ā
Dependencies installed successfully'));
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
console.log(chalk.yellow('ā ļø Failed to install dependencies automatically.'));
|
|
545
|
+
console.log(chalk.yellow(` Please run "${installCommand}" manually to complete the setup.`));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
289
548
|
}
|
|
290
549
|
console.log(chalk.green.bold('\nš Setup complete!\n'));
|
|
291
550
|
console.log(`š Configuration saved to: ${chalk.cyan(configPath)}`);
|
package/dist/mcp-server/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { getSyncedStories, deleteSyncedStory, clearAllSyncedStories, syncChatHis
|
|
|
17
17
|
import { setupProductionGitignore } from '../story-generator/productionGitignoreManager.js';
|
|
18
18
|
import { getInMemoryStoryService } from '../story-generator/inMemoryStoryService.js';
|
|
19
19
|
import { loadUserConfig } from '../story-generator/configLoader.js';
|
|
20
|
+
import fs from 'fs';
|
|
20
21
|
const app = express();
|
|
21
22
|
app.use(cors());
|
|
22
23
|
app.use(express.json());
|
|
@@ -40,6 +41,72 @@ app.delete('/mcp/sync/stories/:id', deleteSyncedStory);
|
|
|
40
41
|
app.delete('/mcp/sync/stories', clearAllSyncedStories);
|
|
41
42
|
app.get('/mcp/sync/chat-history', syncChatHistory);
|
|
42
43
|
app.get('/mcp/sync/validate/:id', validateChatSession);
|
|
44
|
+
// Proxy routes for frontend compatibility (maps /story-ui/ to /mcp/)
|
|
45
|
+
app.get('/story-ui/stories', getStoriesMetadata);
|
|
46
|
+
app.get('/story-ui/stories/:id', getStoryById);
|
|
47
|
+
app.get('/story-ui/stories/:id/content', getStoryContent);
|
|
48
|
+
app.delete('/story-ui/stories/:id', deleteStory);
|
|
49
|
+
app.delete('/story-ui/stories', clearAllStories);
|
|
50
|
+
app.post('/story-ui/generate', generateStoryFromPrompt);
|
|
51
|
+
app.post('/story-ui/claude', claudeProxy);
|
|
52
|
+
app.get('/story-ui/components', getComponents);
|
|
53
|
+
app.get('/story-ui/props', getProps);
|
|
54
|
+
app.get('/story-ui/memory-stats', getMemoryStats);
|
|
55
|
+
// Legacy delete route for backwards compatibility
|
|
56
|
+
app.post('/story-ui/delete', async (req, res) => {
|
|
57
|
+
try {
|
|
58
|
+
const { chatId, storyId } = req.body;
|
|
59
|
+
const id = chatId || storyId; // Support both parameter names
|
|
60
|
+
if (!id) {
|
|
61
|
+
return res.status(400).json({ error: 'chatId or storyId is required' });
|
|
62
|
+
}
|
|
63
|
+
console.log(`šļø Attempting to delete story: ${id}`);
|
|
64
|
+
// First try in-memory deletion (production mode)
|
|
65
|
+
const storyService = getInMemoryStoryService(config);
|
|
66
|
+
const inMemoryDeleted = storyService.deleteStory(id);
|
|
67
|
+
if (inMemoryDeleted) {
|
|
68
|
+
console.log(`ā
Deleted story from memory: ${id}`);
|
|
69
|
+
return res.json({
|
|
70
|
+
success: true,
|
|
71
|
+
message: 'Story deleted successfully from memory'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// If not found in memory, try file-system deletion (development mode)
|
|
75
|
+
if (gitignoreManager && !gitignoreManager.isProductionMode()) {
|
|
76
|
+
const storiesPath = config.generatedStoriesPath;
|
|
77
|
+
console.log(`š Searching for file-system story in: ${storiesPath}`);
|
|
78
|
+
if (fs.existsSync(storiesPath)) {
|
|
79
|
+
const files = fs.readdirSync(storiesPath);
|
|
80
|
+
const matchingFile = files.find(file => file.includes(id) || file.replace('.stories.tsx', '') === id);
|
|
81
|
+
if (matchingFile) {
|
|
82
|
+
const filePath = path.join(storiesPath, matchingFile);
|
|
83
|
+
fs.unlinkSync(filePath);
|
|
84
|
+
console.log(`ā
Deleted story file: ${filePath}`);
|
|
85
|
+
return res.json({
|
|
86
|
+
success: true,
|
|
87
|
+
message: 'Story deleted successfully from file system'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
console.log(`ā Story not found: ${id}`);
|
|
93
|
+
return res.status(404).json({
|
|
94
|
+
success: false,
|
|
95
|
+
error: 'Story not found'
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('Error in legacy delete route:', error);
|
|
100
|
+
res.status(500).json({ error: 'Failed to delete story' });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// Synchronized story proxy routes
|
|
104
|
+
app.get('/story-ui/sync/stories', getSyncedStories);
|
|
105
|
+
app.get('/story-ui/sync/stories/:id', getSyncedStoryById);
|
|
106
|
+
app.delete('/story-ui/sync/stories/:id', deleteSyncedStory);
|
|
107
|
+
app.delete('/story-ui/sync/stories', clearAllSyncedStories);
|
|
108
|
+
app.get('/story-ui/sync/chat-history', syncChatHistory);
|
|
109
|
+
app.get('/story-ui/sync/validate/:id', validateChatSession);
|
|
43
110
|
// Set up production-ready gitignore and directory structure on startup
|
|
44
111
|
const config = loadUserConfig();
|
|
45
112
|
const gitignoreManager = setupProductionGitignore(config);
|