@tpitre/story-ui 3.7.0 → 3.9.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/dist/cli/index.js +22 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +28 -2
- package/dist/cli/update.d.ts +29 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +398 -0
- package/dist/mcp-server/index.js +81 -0
- package/dist/mcp-server/routes/generateStory.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStory.js +36 -0
- package/dist/story-generator/runtimeValidator.d.ts +64 -0
- package/dist/story-generator/runtimeValidator.d.ts.map +1 -0
- package/dist/story-generator/runtimeValidator.js +356 -0
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.js +183 -11
- package/package.json +1 -1
- package/templates/StoryUI/StoryUIPanel.tsx +239 -13
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import path from 'path';
|
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { setupCommand, cleanupDefaultStorybookComponents } from './setup.js';
|
|
8
8
|
import { deployCommand } from './deploy.js';
|
|
9
|
+
import { updateCommand, statusCommand } from './update.js';
|
|
9
10
|
import net from 'net';
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -279,4 +280,25 @@ program
|
|
|
279
280
|
mcpServer.kill('SIGINT');
|
|
280
281
|
});
|
|
281
282
|
});
|
|
283
|
+
program
|
|
284
|
+
.command('update')
|
|
285
|
+
.description('Update Story UI managed files to the latest version')
|
|
286
|
+
.option('-f, --force', 'Skip confirmation prompts')
|
|
287
|
+
.option('--no-backup', 'Skip creating backups of existing files')
|
|
288
|
+
.option('-n, --dry-run', 'Show what would be updated without making changes')
|
|
289
|
+
.option('-v, --verbose', 'Show detailed output')
|
|
290
|
+
.action(async (options) => {
|
|
291
|
+
await updateCommand({
|
|
292
|
+
force: options.force,
|
|
293
|
+
backup: options.backup,
|
|
294
|
+
dryRun: options.dryRun,
|
|
295
|
+
verbose: options.verbose
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
program
|
|
299
|
+
.command('status')
|
|
300
|
+
.description('Show Story UI installation status and version info')
|
|
301
|
+
.action(() => {
|
|
302
|
+
statusCommand();
|
|
303
|
+
});
|
|
282
304
|
program.parse(process.argv);
|
package/dist/cli/setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../cli/setup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../cli/setup.ts"],"names":[],"mappings":"AAmDA;;GAEG;AACH,wBAAgB,iCAAiC,SA8ChD;AAiWD,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC7C,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,iBA2sB5D"}
|
package/dist/cli/setup.js
CHANGED
|
@@ -8,6 +8,23 @@ import net from 'net';
|
|
|
8
8
|
import { execSync } from 'child_process';
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = path.dirname(__filename);
|
|
11
|
+
/**
|
|
12
|
+
* Get the Story UI package version for version tracking
|
|
13
|
+
*/
|
|
14
|
+
function getStoryUIVersion() {
|
|
15
|
+
try {
|
|
16
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
17
|
+
const packageJsonPath = path.join(pkgRoot, 'package.json');
|
|
18
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
19
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
20
|
+
return packageJson.version || 'unknown';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// Fallback
|
|
25
|
+
}
|
|
26
|
+
return 'unknown';
|
|
27
|
+
}
|
|
11
28
|
// FIRST_EDIT: helper functions to check for free ports
|
|
12
29
|
async function isPortAvailable(port) {
|
|
13
30
|
return new Promise((resolve) => {
|
|
@@ -244,10 +261,16 @@ const DESIGN_SYSTEM_CONFIGS = {
|
|
|
244
261
|
framework: 'vue'
|
|
245
262
|
},
|
|
246
263
|
vuetify: {
|
|
247
|
-
packages: ['vuetify'],
|
|
264
|
+
packages: ['vuetify', '@mdi/font', '@fontsource/roboto'],
|
|
248
265
|
name: 'Vuetify',
|
|
249
266
|
importPath: 'vuetify',
|
|
250
|
-
additionalSetup:
|
|
267
|
+
additionalSetup: `import "vuetify/styles";
|
|
268
|
+
import "@mdi/font/css/materialdesignicons.css";
|
|
269
|
+
// Roboto font required for proper Vuetify typography
|
|
270
|
+
import "@fontsource/roboto/300.css";
|
|
271
|
+
import "@fontsource/roboto/400.css";
|
|
272
|
+
import "@fontsource/roboto/500.css";
|
|
273
|
+
import "@fontsource/roboto/700.css";`,
|
|
251
274
|
framework: 'vue'
|
|
252
275
|
},
|
|
253
276
|
'element-plus': {
|
|
@@ -835,6 +858,9 @@ Material UI (MUI) is a React component library implementing Material Design.
|
|
|
835
858
|
config.componentFramework = componentFramework; // react, angular, vue, svelte, or web-components
|
|
836
859
|
config.storybookFramework = storybookFramework; // e.g., @storybook/react-vite, @storybook/angular
|
|
837
860
|
config.llmProvider = answers.llmProvider || 'claude'; // claude, openai, or gemini
|
|
861
|
+
// Add version tracking for update command
|
|
862
|
+
config._storyUIVersion = getStoryUIVersion();
|
|
863
|
+
config._lastUpdated = new Date().toISOString();
|
|
838
864
|
// Create configuration file
|
|
839
865
|
const configContent = `module.exports = ${JSON.stringify(config, null, 2)};`;
|
|
840
866
|
const configPath = path.join(process.cwd(), 'story-ui.config.js');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Story UI Update Command
|
|
3
|
+
*
|
|
4
|
+
* Refreshes managed Story UI files (StoryUIPanel.tsx, StoryUIPanel.mdx, index.tsx)
|
|
5
|
+
* while preserving user configuration files (story-ui.config.js, .env, etc.)
|
|
6
|
+
*/
|
|
7
|
+
export interface UpdateOptions {
|
|
8
|
+
force?: boolean;
|
|
9
|
+
backup?: boolean;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface UpdateResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
filesUpdated: string[];
|
|
16
|
+
filesBackedUp: string[];
|
|
17
|
+
errors: string[];
|
|
18
|
+
currentVersion: string;
|
|
19
|
+
newVersion: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Main update command
|
|
23
|
+
*/
|
|
24
|
+
export declare function updateCommand(options?: UpdateOptions): Promise<UpdateResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Show current Story UI installation status
|
|
27
|
+
*/
|
|
28
|
+
export declare function statusCommand(): void;
|
|
29
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../cli/update.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AA2SD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkItF;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CA+BpC"}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
// Files managed by Story UI that can be safely overwritten
|
|
9
|
+
const MANAGED_FILES = [
|
|
10
|
+
{
|
|
11
|
+
source: 'templates/StoryUI/StoryUIPanel.tsx',
|
|
12
|
+
target: 'src/stories/StoryUI/StoryUIPanel.tsx',
|
|
13
|
+
description: 'Main chat panel component'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
source: 'templates/StoryUI/StoryUIPanel.mdx',
|
|
17
|
+
target: 'src/stories/StoryUI/StoryUIPanel.mdx',
|
|
18
|
+
description: 'Cross-framework MDX wrapper'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
source: 'templates/StoryUI/index.tsx',
|
|
22
|
+
target: 'src/stories/StoryUI/index.tsx',
|
|
23
|
+
description: 'Panel registration'
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
// Files that should NEVER be modified by update
|
|
27
|
+
const USER_CONFIG_FILES = [
|
|
28
|
+
'story-ui.config.js',
|
|
29
|
+
'story-ui.config.mjs',
|
|
30
|
+
'story-ui.config.cjs',
|
|
31
|
+
'.env',
|
|
32
|
+
'story-ui-considerations.md',
|
|
33
|
+
'story-ui-docs/'
|
|
34
|
+
];
|
|
35
|
+
// Directories that should NEVER be touched
|
|
36
|
+
const PROTECTED_DIRECTORIES = [
|
|
37
|
+
'src/stories/generated/'
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Get the Story UI package version
|
|
41
|
+
*/
|
|
42
|
+
function getPackageVersion() {
|
|
43
|
+
try {
|
|
44
|
+
// Try multiple paths to find package.json
|
|
45
|
+
// When running from dist/cli/index.js, we need to go up 2 levels
|
|
46
|
+
const possiblePaths = [
|
|
47
|
+
path.resolve(__dirname, '..', 'package.json'), // From dist/cli
|
|
48
|
+
path.resolve(__dirname, '..', '..', 'package.json'), // From src/cli
|
|
49
|
+
];
|
|
50
|
+
for (const packageJsonPath of possiblePaths) {
|
|
51
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
52
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
53
|
+
if (packageJson.name === '@tpitre/story-ui' && packageJson.version) {
|
|
54
|
+
return packageJson.version;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// Fallback
|
|
61
|
+
}
|
|
62
|
+
return 'unknown';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Detect if Story UI is initialized in the current directory
|
|
66
|
+
*/
|
|
67
|
+
function detectStoryUIInstallation() {
|
|
68
|
+
const cwd = process.cwd();
|
|
69
|
+
// Check for Story UI directory
|
|
70
|
+
const possibleStoryUIDirs = [
|
|
71
|
+
path.join(cwd, 'src', 'stories', 'StoryUI'),
|
|
72
|
+
path.join(cwd, 'stories', 'StoryUI')
|
|
73
|
+
];
|
|
74
|
+
let storyUIDir;
|
|
75
|
+
for (const dir of possibleStoryUIDirs) {
|
|
76
|
+
if (fs.existsSync(dir)) {
|
|
77
|
+
storyUIDir = dir;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Check for config file
|
|
82
|
+
const configFiles = [
|
|
83
|
+
'story-ui.config.js',
|
|
84
|
+
'story-ui.config.mjs',
|
|
85
|
+
'story-ui.config.cjs'
|
|
86
|
+
];
|
|
87
|
+
let configPath;
|
|
88
|
+
for (const configFile of configFiles) {
|
|
89
|
+
const fullPath = path.join(cwd, configFile);
|
|
90
|
+
if (fs.existsSync(fullPath)) {
|
|
91
|
+
configPath = fullPath;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Try to read installed version from config
|
|
96
|
+
let installedVersion;
|
|
97
|
+
if (configPath) {
|
|
98
|
+
try {
|
|
99
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
100
|
+
const versionMatch = configContent.match(/_storyUIVersion:\s*['"]([^'"]+)['"]/);
|
|
101
|
+
if (versionMatch) {
|
|
102
|
+
installedVersion = versionMatch[1];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
// Ignore read errors
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
isInstalled: !!(storyUIDir || configPath),
|
|
111
|
+
storyUIDir,
|
|
112
|
+
configPath,
|
|
113
|
+
installedVersion
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create a backup of a file
|
|
118
|
+
*/
|
|
119
|
+
function createBackup(filePath) {
|
|
120
|
+
if (!fs.existsSync(filePath)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
124
|
+
const backupPath = `${filePath}.backup-${timestamp}`;
|
|
125
|
+
try {
|
|
126
|
+
fs.copyFileSync(filePath, backupPath);
|
|
127
|
+
return backupPath;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get the source path for a template file
|
|
135
|
+
*/
|
|
136
|
+
function getSourcePath(relativePath) {
|
|
137
|
+
// First try the dist directory (when installed as package)
|
|
138
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
139
|
+
const distPath = path.join(pkgRoot, relativePath);
|
|
140
|
+
if (fs.existsSync(distPath)) {
|
|
141
|
+
return distPath;
|
|
142
|
+
}
|
|
143
|
+
// Fall back to project root (when running in development)
|
|
144
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
145
|
+
const projectPath = path.join(projectRoot, relativePath);
|
|
146
|
+
if (fs.existsSync(projectPath)) {
|
|
147
|
+
return projectPath;
|
|
148
|
+
}
|
|
149
|
+
throw new Error(`Template file not found: ${relativePath}`);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Compare file contents to check if update is needed
|
|
153
|
+
*/
|
|
154
|
+
function filesAreDifferent(sourcePath, targetPath) {
|
|
155
|
+
if (!fs.existsSync(targetPath)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
|
|
160
|
+
const targetContent = fs.readFileSync(targetPath, 'utf-8');
|
|
161
|
+
return sourceContent !== targetContent;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Update a single managed file
|
|
169
|
+
*/
|
|
170
|
+
function updateManagedFile(sourceRelative, targetRelative, options) {
|
|
171
|
+
const cwd = process.cwd();
|
|
172
|
+
const targetPath = path.join(cwd, targetRelative);
|
|
173
|
+
try {
|
|
174
|
+
const sourcePath = getSourcePath(sourceRelative);
|
|
175
|
+
// Check if update is needed
|
|
176
|
+
if (!filesAreDifferent(sourcePath, targetPath)) {
|
|
177
|
+
if (options.verbose) {
|
|
178
|
+
console.log(chalk.gray(` ⏭️ ${targetRelative} (already up to date)`));
|
|
179
|
+
}
|
|
180
|
+
return { updated: false };
|
|
181
|
+
}
|
|
182
|
+
if (options.dryRun) {
|
|
183
|
+
console.log(chalk.cyan(` 📋 Would update: ${targetRelative}`));
|
|
184
|
+
return { updated: true };
|
|
185
|
+
}
|
|
186
|
+
// Create backup if enabled and file exists
|
|
187
|
+
let backupPath;
|
|
188
|
+
if (options.backup !== false && fs.existsSync(targetPath)) {
|
|
189
|
+
const backup = createBackup(targetPath);
|
|
190
|
+
if (backup) {
|
|
191
|
+
backupPath = backup;
|
|
192
|
+
if (options.verbose) {
|
|
193
|
+
console.log(chalk.gray(` 💾 Backed up: ${path.basename(backup)}`));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Ensure target directory exists
|
|
198
|
+
const targetDir = path.dirname(targetPath);
|
|
199
|
+
if (!fs.existsSync(targetDir)) {
|
|
200
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
201
|
+
}
|
|
202
|
+
// Copy the new file
|
|
203
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
204
|
+
console.log(chalk.green(` ✅ Updated: ${targetRelative}`));
|
|
205
|
+
return { updated: true, backupPath };
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
return { updated: false, error: error.message };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Update the config file with version tracking
|
|
213
|
+
*/
|
|
214
|
+
function updateConfigVersion(configPath, version) {
|
|
215
|
+
try {
|
|
216
|
+
let content = fs.readFileSync(configPath, 'utf-8');
|
|
217
|
+
// Check if version tracking already exists
|
|
218
|
+
const hasVersion = /_storyUIVersion/.test(content);
|
|
219
|
+
const hasLastUpdated = /_lastUpdated/.test(content);
|
|
220
|
+
const timestamp = new Date().toISOString();
|
|
221
|
+
if (hasVersion) {
|
|
222
|
+
// Update existing version
|
|
223
|
+
content = content.replace(/_storyUIVersion:\s*['"][^'"]*['"]/, `_storyUIVersion: '${version}'`);
|
|
224
|
+
}
|
|
225
|
+
if (hasLastUpdated) {
|
|
226
|
+
// Update existing timestamp
|
|
227
|
+
content = content.replace(/_lastUpdated:\s*['"][^'"]*['"]/, `_lastUpdated: '${timestamp}'`);
|
|
228
|
+
}
|
|
229
|
+
// If neither exists, add them before the closing brace
|
|
230
|
+
if (!hasVersion && !hasLastUpdated) {
|
|
231
|
+
// Find the last property and add version tracking
|
|
232
|
+
const insertPosition = content.lastIndexOf('}');
|
|
233
|
+
if (insertPosition !== -1) {
|
|
234
|
+
const versionFields = `
|
|
235
|
+
// Story UI version tracking (auto-generated)
|
|
236
|
+
_storyUIVersion: '${version}',
|
|
237
|
+
_lastUpdated: '${timestamp}',
|
|
238
|
+
`;
|
|
239
|
+
// Check if there's a trailing comma needed
|
|
240
|
+
const beforeInsert = content.substring(0, insertPosition).trim();
|
|
241
|
+
const needsComma = beforeInsert.endsWith(',') || beforeInsert.endsWith('{') ? '' : ',';
|
|
242
|
+
content = content.substring(0, insertPosition - 1) +
|
|
243
|
+
needsComma +
|
|
244
|
+
versionFields +
|
|
245
|
+
'};' +
|
|
246
|
+
content.substring(insertPosition + 1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
fs.writeFileSync(configPath, content);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Main update command
|
|
258
|
+
*/
|
|
259
|
+
export async function updateCommand(options = {}) {
|
|
260
|
+
const result = {
|
|
261
|
+
success: false,
|
|
262
|
+
filesUpdated: [],
|
|
263
|
+
filesBackedUp: [],
|
|
264
|
+
errors: [],
|
|
265
|
+
currentVersion: 'unknown',
|
|
266
|
+
newVersion: getPackageVersion()
|
|
267
|
+
};
|
|
268
|
+
console.log(chalk.bold('\n🔄 Story UI Update\n'));
|
|
269
|
+
// Step 1: Detect installation
|
|
270
|
+
const installation = detectStoryUIInstallation();
|
|
271
|
+
if (!installation.isInstalled) {
|
|
272
|
+
console.log(chalk.red('❌ Story UI is not initialized in this directory.'));
|
|
273
|
+
console.log(chalk.yellow(' Run "npx story-ui init" first to set up Story UI.'));
|
|
274
|
+
result.errors.push('Story UI not initialized');
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
result.currentVersion = installation.installedVersion || 'unknown';
|
|
278
|
+
console.log(chalk.gray(` Current version: ${result.currentVersion}`));
|
|
279
|
+
console.log(chalk.gray(` New version: ${result.newVersion}`));
|
|
280
|
+
// Step 2: Show what will be updated
|
|
281
|
+
console.log(chalk.bold('\n📦 Managed files to update:'));
|
|
282
|
+
const filesToUpdate = [];
|
|
283
|
+
for (const file of MANAGED_FILES) {
|
|
284
|
+
try {
|
|
285
|
+
const sourcePath = getSourcePath(file.source);
|
|
286
|
+
const targetPath = path.join(process.cwd(), file.target);
|
|
287
|
+
const needsUpdate = filesAreDifferent(sourcePath, targetPath);
|
|
288
|
+
if (needsUpdate) {
|
|
289
|
+
filesToUpdate.push(file);
|
|
290
|
+
console.log(chalk.cyan(` • ${file.target}`));
|
|
291
|
+
console.log(chalk.gray(` ${file.description}`));
|
|
292
|
+
}
|
|
293
|
+
else if (options.verbose) {
|
|
294
|
+
console.log(chalk.gray(` ⏭️ ${file.target} (up to date)`));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.log(chalk.red(` ❌ ${file.target}: ${error.message}`));
|
|
299
|
+
result.errors.push(`${file.target}: ${error.message}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (filesToUpdate.length === 0) {
|
|
303
|
+
console.log(chalk.green('\n✅ All files are already up to date!'));
|
|
304
|
+
result.success = true;
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
// Step 3: Confirm update (unless --force or --dry-run)
|
|
308
|
+
if (!options.force && !options.dryRun) {
|
|
309
|
+
console.log(chalk.yellow('\n⚠️ The following will NOT be modified:'));
|
|
310
|
+
console.log(chalk.gray(' • story-ui.config.js (your configuration)'));
|
|
311
|
+
console.log(chalk.gray(' • .env (your API keys)'));
|
|
312
|
+
console.log(chalk.gray(' • story-ui-docs/ (your documentation)'));
|
|
313
|
+
console.log(chalk.gray(' • src/stories/generated/ (your generated stories)'));
|
|
314
|
+
const { confirm } = await inquirer.prompt([{
|
|
315
|
+
type: 'confirm',
|
|
316
|
+
name: 'confirm',
|
|
317
|
+
message: `Update ${filesToUpdate.length} file(s)?`,
|
|
318
|
+
default: true
|
|
319
|
+
}]);
|
|
320
|
+
if (!confirm) {
|
|
321
|
+
console.log(chalk.yellow('\n⏹️ Update cancelled.'));
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Step 4: Perform updates
|
|
326
|
+
if (options.dryRun) {
|
|
327
|
+
console.log(chalk.bold('\n📋 Dry run - no changes made:'));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
console.log(chalk.bold('\n🔧 Updating files...'));
|
|
331
|
+
}
|
|
332
|
+
for (const file of filesToUpdate) {
|
|
333
|
+
const updateResult = updateManagedFile(file.source, file.target, options);
|
|
334
|
+
if (updateResult.updated) {
|
|
335
|
+
result.filesUpdated.push(file.target);
|
|
336
|
+
}
|
|
337
|
+
if (updateResult.backupPath) {
|
|
338
|
+
result.filesBackedUp.push(updateResult.backupPath);
|
|
339
|
+
}
|
|
340
|
+
if (updateResult.error) {
|
|
341
|
+
result.errors.push(`${file.target}: ${updateResult.error}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Step 5: Update config version tracking
|
|
345
|
+
if (!options.dryRun && installation.configPath) {
|
|
346
|
+
if (updateConfigVersion(installation.configPath, result.newVersion)) {
|
|
347
|
+
console.log(chalk.gray(`\n Updated version tracking in ${path.basename(installation.configPath)}`));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Step 6: Summary
|
|
351
|
+
console.log(chalk.bold('\n📊 Update Summary:'));
|
|
352
|
+
console.log(chalk.green(` ✅ Files updated: ${result.filesUpdated.length}`));
|
|
353
|
+
if (result.filesBackedUp.length > 0) {
|
|
354
|
+
console.log(chalk.gray(` 💾 Backups created: ${result.filesBackedUp.length}`));
|
|
355
|
+
}
|
|
356
|
+
if (result.errors.length > 0) {
|
|
357
|
+
console.log(chalk.red(` ❌ Errors: ${result.errors.length}`));
|
|
358
|
+
for (const error of result.errors) {
|
|
359
|
+
console.log(chalk.red(` • ${error}`));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
result.success = result.errors.length === 0;
|
|
363
|
+
if (result.success && !options.dryRun) {
|
|
364
|
+
console.log(chalk.green('\n✅ Story UI updated successfully!'));
|
|
365
|
+
console.log(chalk.gray(' Restart Storybook to see the changes.'));
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Show current Story UI installation status
|
|
371
|
+
*/
|
|
372
|
+
export function statusCommand() {
|
|
373
|
+
console.log(chalk.bold('\n📊 Story UI Status\n'));
|
|
374
|
+
const installation = detectStoryUIInstallation();
|
|
375
|
+
const packageVersion = getPackageVersion();
|
|
376
|
+
if (!installation.isInstalled) {
|
|
377
|
+
console.log(chalk.red('❌ Story UI is not initialized in this directory.'));
|
|
378
|
+
console.log(chalk.gray(' Run "npx story-ui init" to set up Story UI.'));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
console.log(chalk.green('✅ Story UI is installed'));
|
|
382
|
+
console.log(chalk.gray(` Package version: ${packageVersion}`));
|
|
383
|
+
console.log(chalk.gray(` Installed version: ${installation.installedVersion || 'unknown'}`));
|
|
384
|
+
if (installation.configPath) {
|
|
385
|
+
console.log(chalk.gray(` Config: ${path.basename(installation.configPath)}`));
|
|
386
|
+
}
|
|
387
|
+
if (installation.storyUIDir) {
|
|
388
|
+
console.log(chalk.gray(` Panel directory: ${installation.storyUIDir}`));
|
|
389
|
+
}
|
|
390
|
+
// Check for updates
|
|
391
|
+
if (installation.installedVersion && installation.installedVersion !== packageVersion) {
|
|
392
|
+
console.log(chalk.yellow(`\n⚡ Update available: ${installation.installedVersion} → ${packageVersion}`));
|
|
393
|
+
console.log(chalk.gray(' Run "npx story-ui update" to update.'));
|
|
394
|
+
}
|
|
395
|
+
else if (installation.installedVersion === packageVersion) {
|
|
396
|
+
console.log(chalk.green('\n✅ Up to date!'));
|
|
397
|
+
}
|
|
398
|
+
}
|
package/dist/mcp-server/index.js
CHANGED
|
@@ -263,6 +263,87 @@ app.post('/story-ui/delete', async (req, res) => {
|
|
|
263
263
|
res.status(500).json({ error: 'Failed to delete story' });
|
|
264
264
|
}
|
|
265
265
|
});
|
|
266
|
+
// Bulk delete stories
|
|
267
|
+
app.post('/story-ui/stories/delete-bulk', async (req, res) => {
|
|
268
|
+
try {
|
|
269
|
+
const { ids } = req.body;
|
|
270
|
+
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
|
271
|
+
return res.status(400).json({ error: 'ids array is required' });
|
|
272
|
+
}
|
|
273
|
+
console.log(`🗑️ Bulk deleting ${ids.length} stories`);
|
|
274
|
+
const storiesPath = config.generatedStoriesPath;
|
|
275
|
+
const deleted = [];
|
|
276
|
+
const notFound = [];
|
|
277
|
+
const errors = [];
|
|
278
|
+
for (const id of ids) {
|
|
279
|
+
try {
|
|
280
|
+
const fileName = id.endsWith('.stories.tsx') ? id : `${id}.stories.tsx`;
|
|
281
|
+
const filePath = path.join(storiesPath, fileName);
|
|
282
|
+
if (fs.existsSync(filePath)) {
|
|
283
|
+
fs.unlinkSync(filePath);
|
|
284
|
+
deleted.push(id);
|
|
285
|
+
console.log(`✅ Deleted: ${fileName}`);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
notFound.push(id);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
errors.push(id);
|
|
293
|
+
console.error(`❌ Error deleting ${id}:`, err);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
console.log(`📊 Bulk delete complete: ${deleted.length} deleted, ${notFound.length} not found, ${errors.length} errors`);
|
|
297
|
+
return res.json({
|
|
298
|
+
success: true,
|
|
299
|
+
deleted,
|
|
300
|
+
notFound,
|
|
301
|
+
errors,
|
|
302
|
+
summary: {
|
|
303
|
+
requested: ids.length,
|
|
304
|
+
deleted: deleted.length,
|
|
305
|
+
notFound: notFound.length,
|
|
306
|
+
errors: errors.length
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
console.error('Error in bulk delete:', error);
|
|
312
|
+
return res.status(500).json({ error: 'Failed to bulk delete stories' });
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
// Clear all generated stories
|
|
316
|
+
app.delete('/story-ui/stories', async (req, res) => {
|
|
317
|
+
try {
|
|
318
|
+
const storiesPath = config.generatedStoriesPath;
|
|
319
|
+
console.log(`🗑️ Clearing all stories from: ${storiesPath}`);
|
|
320
|
+
if (!fs.existsSync(storiesPath)) {
|
|
321
|
+
return res.json({ success: true, deleted: 0, message: 'No stories directory found' });
|
|
322
|
+
}
|
|
323
|
+
const files = fs.readdirSync(storiesPath);
|
|
324
|
+
const storyFiles = files.filter(file => file.endsWith('.stories.tsx'));
|
|
325
|
+
let deleted = 0;
|
|
326
|
+
for (const file of storyFiles) {
|
|
327
|
+
try {
|
|
328
|
+
fs.unlinkSync(path.join(storiesPath, file));
|
|
329
|
+
deleted++;
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
console.error(`Error deleting ${file}:`, err);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
console.log(`✅ Cleared ${deleted} stories`);
|
|
336
|
+
return res.json({
|
|
337
|
+
success: true,
|
|
338
|
+
deleted,
|
|
339
|
+
message: `Cleared ${deleted} stories`
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
console.error('Error clearing stories:', error);
|
|
344
|
+
return res.status(500).json({ error: 'Failed to clear stories' });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
266
347
|
// MCP Remote HTTP transport routes (for Claude Desktop remote connections)
|
|
267
348
|
// Provides Streamable HTTP and legacy SSE endpoints for remote MCP access
|
|
268
349
|
app.use('/mcp-remote', mcpRemoteRouter);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateStory.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"generateStory.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAgc5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DAkgBxE"}
|
|
@@ -18,6 +18,7 @@ import { chatCompletion, generateTitle as llmGenerateTitle, isProviderConfigured
|
|
|
18
18
|
import { processImageInputs } from '../../story-generator/imageProcessor.js';
|
|
19
19
|
import { buildVisionAwarePrompt } from '../../story-generator/visionPrompts.js';
|
|
20
20
|
import { aggregateValidationErrors, shouldContinueRetrying, buildSelfHealingPrompt, hasNoErrors, getTotalErrorCount, createEmptyErrors, formatErrorsForLog, selectBestAttempt, } from '../../story-generator/selfHealingLoop.js';
|
|
21
|
+
import { validateStoryRuntime, formatRuntimeErrorForHealing, isRuntimeValidationEnabled, } from '../../story-generator/runtimeValidator.js';
|
|
21
22
|
// Build suggestion using the user's actual discovered components (design-system agnostic)
|
|
22
23
|
function buildComponentSuggestion(components) {
|
|
23
24
|
if (!components?.length) {
|
|
@@ -730,6 +731,33 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
730
731
|
// Save to history
|
|
731
732
|
historyManager.addVersion(finalFileName, prompt, fixedFileContents, parentVersionId);
|
|
732
733
|
logger.log(`Story ${isActualUpdate ? 'updated' : 'written'} to:`, outPath);
|
|
734
|
+
// --- RUNTIME VALIDATION: Check if story loads in Storybook ---
|
|
735
|
+
// This catches errors that static validation cannot detect, like CSF module loader errors
|
|
736
|
+
let runtimeValidationResult = { success: true, storyExists: true };
|
|
737
|
+
let hasRuntimeError = false;
|
|
738
|
+
if (isRuntimeValidationEnabled()) {
|
|
739
|
+
logger.info('🔍 Running runtime validation...');
|
|
740
|
+
try {
|
|
741
|
+
runtimeValidationResult = await validateStoryRuntime(fixedFileContents, aiTitle, config.storyPrefix);
|
|
742
|
+
if (!runtimeValidationResult.success) {
|
|
743
|
+
hasRuntimeError = true;
|
|
744
|
+
hasValidationWarnings = true;
|
|
745
|
+
logger.error(`❌ Runtime validation failed: ${runtimeValidationResult.renderError}`);
|
|
746
|
+
logger.error(` Error type: ${runtimeValidationResult.errorType}`);
|
|
747
|
+
// Format the error for potential future self-healing
|
|
748
|
+
const runtimeErrorMessage = formatRuntimeErrorForHealing(runtimeValidationResult);
|
|
749
|
+
logger.debug('Runtime error details:', runtimeErrorMessage);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
logger.info('✅ Runtime validation passed - story loads correctly in Storybook');
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
catch (runtimeErr) {
|
|
756
|
+
// Don't fail the request if runtime validation itself fails (e.g., Storybook not running)
|
|
757
|
+
logger.warn(`⚠️ Runtime validation could not complete: ${runtimeErr.message}`);
|
|
758
|
+
logger.warn(' Story was saved but runtime status is unknown');
|
|
759
|
+
}
|
|
760
|
+
}
|
|
733
761
|
// Track URL redirect if this is an update and the title changed
|
|
734
762
|
if (isActualUpdate && oldTitle && oldStoryUrl) {
|
|
735
763
|
const newTitleMatch = fixedFileContents.match(/title:\s*["']([^"']+)['"]/);
|
|
@@ -759,6 +787,14 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
759
787
|
warnings: [],
|
|
760
788
|
selfHealingUsed,
|
|
761
789
|
attempts
|
|
790
|
+
},
|
|
791
|
+
runtimeValidation: {
|
|
792
|
+
enabled: isRuntimeValidationEnabled(),
|
|
793
|
+
success: runtimeValidationResult.success,
|
|
794
|
+
storyExists: runtimeValidationResult.storyExists,
|
|
795
|
+
error: runtimeValidationResult.renderError,
|
|
796
|
+
errorType: runtimeValidationResult.errorType,
|
|
797
|
+
details: runtimeValidationResult.details
|
|
762
798
|
}
|
|
763
799
|
});
|
|
764
800
|
}
|