Skip to content

Commit ff94bd3

Browse files
authored
Merge pull request #3902 from illume/fix-plugin-storybook
headlamp-plugin: Storybook fixes
2 parents 0b78598 + 9bf9c96 commit ff94bd3

File tree

13 files changed

+220
-10
lines changed

13 files changed

+220
-10
lines changed

frontend/.storybook/HeadlampTheme.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import { create } from 'storybook/theming/create';
55
import logoUrl from '../../docs/headlamp_light.svg';
66

7+
// Please also update plugins/headlamp-plugin/config/.storybook/HeadlampTheme.js
8+
79
export default create({
810
base: 'light',
911
brandTitle: 'Headlamp Kubernetes Web UI dashboard',

frontend/.storybook/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import type { StorybookConfig } from '@storybook/react-vite';
1818

19+
// Please also update: plugins/headlamp-plugin/config/.storybook/main.js
20+
1921
export default {
2022
framework: '@storybook/react-vite',
2123
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],

frontend/.storybook/manager.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { addons } from 'storybook/manager-api';
22
import theme from './HeadlampTheme';
33

4+
// Please also update: plugins/headlamp-plugin/config/.storybook/manager.js
5+
46
addons.setConfig({
57
theme: theme,
68
});

frontend/.storybook/preview.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { darkTheme, lightTheme } from '../src/components/App/defaultAppThemes';
2424
import { createMuiTheme } from '../src/lib/themes';
2525
import App from '../src/App';
2626

27+
// Please also update: plugins/headlamp-plugin/config/.storybook/preview.tsx
28+
2729
// https://github.com/mswjs/msw-storybook-addon
2830
initialize({
2931
onUnhandledRequest: 'warn',
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright 2025 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { spawn } from 'child_process';
18+
import * as fs from 'fs';
19+
20+
const packageJsonPath = 'package.json';
21+
22+
/**
23+
* Run Storybook check for a plugin directory.
24+
* Calls exit(reason) on failure (reason is a string).
25+
* Calls exit(null) on success.
26+
*
27+
* @param {string} pluginDir - Path to plugin directory
28+
* @param {(reason: string|null|undefined) => void} exit - Callback to call on finish/failure
29+
* @returns {Promise<void>}
30+
*/
31+
function checkStorybook(pluginDir, exit) {
32+
return new Promise((resolve) => {
33+
let exited = false;
34+
function doExit(reason) {
35+
if (exited) return;
36+
exited = true;
37+
try {
38+
exit(reason);
39+
} catch (e) {
40+
// ignore user callback errors
41+
}
42+
resolve();
43+
}
44+
45+
if (!pluginDir) {
46+
console.error('Usage: checkStorybook(pluginDir, exit)');
47+
doExit('no-plugin-dir');
48+
return;
49+
}
50+
51+
try {
52+
process.chdir(pluginDir);
53+
} catch (e) {
54+
console.error('Failed to change directory:', e.message);
55+
doExit('chdir-failed');
56+
return;
57+
}
58+
59+
if (!fs.existsSync(packageJsonPath)) {
60+
console.error('No package.json found');
61+
doExit('no-package-json');
62+
return;
63+
}
64+
65+
let packageJson;
66+
try {
67+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
68+
} catch (e) {
69+
console.error('Failed to read package.json:', e.message);
70+
doExit('invalid-package-json');
71+
return;
72+
}
73+
74+
if (!packageJson.devDependencies || !packageJson.devDependencies['@kinvolk/headlamp-plugin']) {
75+
console.error('Not inside a plugin');
76+
doExit('not-inside-plugin');
77+
return;
78+
}
79+
80+
const storybook = spawn('npm', ['run', 'storybook'], {
81+
stdio: ['ignore', 'pipe', 'pipe'],
82+
shell: true,
83+
});
84+
85+
let hasError = false;
86+
function killWithTree(child) {
87+
if (!child || !child.pid) return;
88+
if (process.platform === 'win32') {
89+
spawn('taskkill', ['/pid', String(child.pid), '/T', '/F']);
90+
} else {
91+
try {
92+
process.kill(-child.pid, 'SIGINT');
93+
} catch (e) {
94+
try {
95+
child.kill('SIGINT');
96+
} catch (e2) {}
97+
}
98+
}
99+
}
100+
101+
storybook.stderr.on('data', data => {
102+
const msg = data.toString();
103+
console.error('[storybook stderr]', msg);
104+
if (msg.toLowerCase().includes('error')) {
105+
hasError = true;
106+
}
107+
108+
if (msg.includes('[webpack.Progress] 100%')) {
109+
console.log('Storybook build completed successfully');
110+
killWithTree(storybook);
111+
doExit(null); // success
112+
}
113+
});
114+
115+
storybook.stdout.on('data', data => {
116+
console.log('[storybook stdout]', data.toString());
117+
});
118+
119+
// Wait 20 seconds then kill the process and exit (error if errors were seen)
120+
const timer = setTimeout(() => {
121+
console.log('Stopping Storybook after 20 seconds...');
122+
killWithTree(storybook);
123+
if (hasError) {
124+
doExit('error-detected');
125+
} else {
126+
doExit(null);
127+
}
128+
}, 20000);
129+
130+
storybook.on('exit', code => {
131+
console.log(`Storybook exited with code ${code}`);
132+
clearTimeout(timer);
133+
if (!exited) {
134+
if (hasError || code !== 0) {
135+
doExit('storybook-exited-with-error');
136+
} else {
137+
doExit(null);
138+
}
139+
}
140+
});
141+
142+
storybook.on('error', (err) => {
143+
console.error('Failed to spawn storybook:', err.message);
144+
clearTimeout(timer);
145+
doExit('spawn-error');
146+
});
147+
});
148+
};
149+
150+
151+
await checkStorybook(process.argv[2], (reason) => {
152+
if (reason) {
153+
console.error('Storybook check failed:', reason);
154+
process.exit(1);
155+
} else {
156+
console.log('Storybook check passed');
157+
process.exit(0);
158+
}
159+
});

plugins/headlamp-plugin/config/.storybook/HeadlampTheme.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// https://storybook.js.org/docs/react/configure/theming#create-a-theme-quickstart
22
// To workaround a bug at time of writing, where theme is not refreshed,
33
// you may need to `npm run storybook --no-manager-cache`
4-
import { create } from '@storybook/theming';
4+
import { create } from 'storybook/theming/create';
55

66
export default create({
77
base: 'light',

plugins/headlamp-plugin/config/.storybook/main.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ module.exports = {
99
addons: [
1010
'@storybook/addon-links',
1111
'@storybook/addon-webpack5-compiler-swc',
12-
'@storybook/addon-essentials',
13-
'@storybook/addon-interactions',
12+
'@storybook/addon-docs',
1413
],
1514
core: {
1615
disableTelemetry: true,
@@ -62,6 +61,27 @@ module.exports = {
6261
},
6362
});
6463

64+
// Polyfill for apidevtools feature used in docs
65+
config.resolve.fallback = {
66+
...config.resolve.fallback,
67+
path: require.resolve('path-browserify'),
68+
process: require.resolve('process/browser'),
69+
};
70+
71+
config.plugins.push(
72+
new webpack.ProvidePlugin({
73+
process: 'process/browser',
74+
})
75+
);
76+
77+
config.resolve.alias = {
78+
...config.resolve.alias,
79+
'@kinvolk/headlamp-plugin/lib/k8s': path.resolve(
80+
__dirname,
81+
'../../../../../node_modules/@kinvolk/headlamp-plugin/lib/lib/k8s'
82+
),
83+
};
84+
6585
return config;
6686
},
6787
};

plugins/headlamp-plugin/config/.storybook/manager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addons } from '@storybook/manager-api';
1+
import { addons } from 'storybook/manager-api';
22
import theme from './HeadlampTheme';
33

44
addons.setConfig({

plugins/headlamp-plugin/config/.storybook/preview.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import React from 'react';
1717
import { ThemeProvider } from '@mui/material/styles';
1818
// import { initialize, mswLoader } from 'msw-storybook-addon';
1919
// import './index.css';
20-
import { Title, Subtitle, Description, Primary, Controls } from '@storybook/blocks';
20+
import { Title, Subtitle, Description, Primary, Controls } from '@storybook/addon-docs/blocks';
21+
2122
// import { baseMocks } from './baseMocks';
2223
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2324
import {

plugins/headlamp-plugin/dependencies-sync.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const dependenciesFrontDoesNotHave = new Set([
4545
'@storybook/react-webpack5',
4646
'ts-loader',
4747
'@headlamp-k8s/pluginctl',
48+
'path-browserify',
49+
'process',
4850
]);
4951

5052
// Dependencies from frontend/package.json that aren't wanted by headlamp-plugin.

0 commit comments

Comments
 (0)