Skip to content

Commit 249a8f4

Browse files
Merge branch 'main' into feature/chrome-next
2 parents 9db9722 + 60e2daa commit 249a8f4

5,423 files changed

Lines changed: 239921 additions & 111103 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/kbn-ui-packages/SKILL.md

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
---
2+
name: kbn-ui-package
3+
description: This skill should be used when the user asks to "create a kbn-ui package", "onboard a component to kbn-ui", "package a Kibana component for external distribution", "add a package to kbn-ui", "set up packaging for a Kibana component", or any mention of distributing a Kibana UI component to Cloud UI or external consumers. Guides the full interactive process: gathers inputs, moves source files, scaffolds the packaging layer, and updates all Kibana imports.
4+
disable-model-invocation: true
5+
---
6+
7+
# kbn-ui Package Onboarding
8+
9+
## Overview
10+
11+
The `kbn-ui` system distributes Kibana UI components as versioned standalone packages for external consumers (e.g. Cloud UI). Each package lives under `src/platform/kbn-ui/` and has two layers:
12+
13+
1. **Source layer** — the Kibana workspace package (`package.json`, `kibana.jsonc`, `src/`, `index.ts`)
14+
2. **Packaging layer** — distribution scaffold (`packaging/`) that bundles the source into a standalone `.tgz` via webpack
15+
16+
Canonical reference: `src/platform/kbn-ui/side-navigation/`
17+
18+
---
19+
20+
## Phase 1 — Gather Inputs
21+
22+
Use `AskUserQuestion` to collect **three** values. `packageName` is always auto-derived — never ask the user for it.
23+
24+
| Variable | Example | Source |
25+
|---|---|---|
26+
| `sourcePath` | `src/platform/packages/private/kbn-grid-layout` | User input |
27+
| `folderName` | `grid-layout` | User input |
28+
| `packageName` | `@kbn/ui-grid-layout` | **Auto-derived**: `@kbn/ui-{folderName}` |
29+
| `description` | `Standalone Elastic grid layout component for non-Kibana applications` | User input |
30+
31+
Questions to ask:
32+
1. "What is the repo-relative path of the existing Kibana package?" (e.g. `src/platform/packages/private/kbn-grid-layout`)
33+
2. "What should the kbn-ui folder name be?" (e.g. `grid-layout` — the package name will be `@kbn/ui-{answer}`)
34+
3. "Short description for the distribution package.json?"
35+
36+
---
37+
38+
## Phase 2 — Analyze Source
39+
40+
Read these files before creating anything:
41+
42+
- `{sourcePath}/package.json` → derive `oldName` (current workspace name, e.g. `@kbn/grid-layout`), `peerDependencies`, `dependencies`
43+
- `{sourcePath}/kibana.jsonc` → derive `owner`, `group`
44+
- `{sourcePath}/index.ts` → list all exported symbols (components, types, utilities)
45+
46+
Run to find all internal `@kbn/*` imports used by the source (search the whole package, not just a `src/` subdirectory — the source dir may have any name):
47+
```bash
48+
grep -roh "from '@kbn/[^']*'" {sourcePath} --include="*.ts" --include="*.tsx" \
49+
--exclude-dir=node_modules --exclude-dir=target --exclude-dir=packaging | sort -u
50+
```
51+
52+
Partition the results:
53+
- **Externalize** — if the package also appears in `peerDependencies` (consumer will provide it)
54+
- **Stub** — everything else (not available outside Kibana; needs a no-op implementation in `packaging/react/services/`)
55+
56+
Count how many Kibana files will need import updates:
57+
```bash
58+
grep -r "from '{oldName}'" src/ x-pack/ packages/ --include="*.ts" --include="*.tsx" -l | wc -l
59+
```
60+
61+
---
62+
63+
## Phase 3 — Confirm with User
64+
65+
Show a summary and use `AskUserQuestion` to confirm before touching any files:
66+
67+
```
68+
Moving: {sourcePath}/ → src/platform/kbn-ui/{folderName}/
69+
Renaming: {oldName} → {packageName}
70+
@kbn/* stubs to generate: [list from Phase 2]
71+
Kibana files with imports to update: [count from Phase 2]
72+
```
73+
74+
---
75+
76+
## Phase 4 — Execute
77+
78+
### 4a. Move the package and normalize the source directory
79+
80+
```bash
81+
git mv {sourcePath} src/platform/kbn-ui/{folderName}
82+
```
83+
84+
After moving, the source directory inside the package must always be named `src/`. Detect the actual source directory name — it is the non-metadata subdirectory (i.e. not `packaging`, `target`, `__tests__`, etc.):
85+
86+
```bash
87+
# List top-level subdirectories in the moved package (excluding known non-source dirs)
88+
ls -d src/platform/kbn-ui/{folderName}/*/ | grep -vE "/(packaging|target|node_modules)/$"
89+
```
90+
91+
If the source directory is **not** named `src`, rename it:
92+
93+
```bash
94+
git mv src/platform/kbn-ui/{folderName}/{actualDirName} src/platform/kbn-ui/{folderName}/src
95+
```
96+
97+
Store the result as `srcDir = "src"` — all packaging templates must reference `../../src/` from inside `packaging/react/`.
98+
99+
### 4b. Update workspace package.json
100+
101+
Overwrite `src/platform/kbn-ui/{folderName}/package.json` with:
102+
103+
```json
104+
{
105+
"name": "{packageName}",
106+
"version": "1.0.0",
107+
"private": true,
108+
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
109+
}
110+
```
111+
112+
### 4c. Update kibana.jsonc
113+
114+
Keep `owner`, `group`, `type`, `visibility` from the moved file. Update only the `id` field to `{packageName}`.
115+
116+
### 4d. Create packaging/ scaffold
117+
118+
```bash
119+
mkdir -p src/platform/kbn-ui/{folderName}/packaging/scripts
120+
mkdir -p src/platform/kbn-ui/{folderName}/packaging/react/services
121+
```
122+
123+
#### `packaging/package.json`
124+
125+
```json
126+
{
127+
"name": "{packageName}",
128+
"version": "0.1.0",
129+
"private": true,
130+
"description": "{description}",
131+
"main": "index.js",
132+
"types": "index.d.ts",
133+
"files": [
134+
"index.js",
135+
"index.js.map",
136+
"index.d.ts",
137+
"metadata.json",
138+
"package.json"
139+
],
140+
"peerDependencies": {
141+
"@elastic/eui": ">=112.0.0",
142+
"react": ">=18.0.0",
143+
"react-dom": ">=18.0.0"
144+
},
145+
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
146+
}
147+
```
148+
149+
Merge any additional peer deps found in the source `package.json` (e.g. `@emotion/react`, `@emotion/css`).
150+
151+
#### `packaging/webpack.config.js`
152+
153+
Start from the side-navigation template (`src/platform/kbn-ui/side-navigation/packaging/webpack.config.js`). Customize:
154+
- **`externals`**: one entry per peer dep — `'@pkg/name': 'commonjs @pkg/name'`
155+
- **`alias`**: one entry per stubbed `@kbn/*` package, e.g.:
156+
```js
157+
'@kbn/i18n$': path.resolve(__dirname, 'react/services/i18n.tsx'),
158+
```
159+
160+
#### `packaging/tsconfig.json`
161+
162+
Start from the side-navigation template. Update `paths` to match the alias map:
163+
```json
164+
{
165+
"paths": {
166+
"@kbn/some-dep": ["./react/services/some-dep.ts"]
167+
}
168+
}
169+
```
170+
Update `include` to cover `../src/**/*.ts(x)` relative to the new package root.
171+
172+
#### `packaging/scripts/build.sh`
173+
174+
Copy verbatim from `src/platform/kbn-ui/side-navigation/packaging/scripts/build.sh`. Update only the top comment line to reference `{packageName}`. The path resolution is fully relative and generic — no other changes needed.
175+
176+
#### `packaging/react/index.tsx`
177+
178+
Re-export the main component under a distribution-friendly name alias. Always import from `../../src/` — never from the original source directory name. Derive component name, props type, and all re-exported types from the `index.ts` analysis in Phase 2:
179+
180+
```tsx
181+
/*
182+
* [Elastic license header]
183+
*/
184+
185+
// Build-time type validation
186+
import './type_validation';
187+
188+
import React from 'react';
189+
import { {SourceComponent}, type {SourceComponentProps} } from '../../src/{path-to-component}';
190+
export type { /* all public types from ../../index.ts */ };
191+
192+
void React;
193+
194+
/** Alias for the external package. */
195+
export type {ExportedComponentName}Props = {SourceComponentProps};
196+
197+
export const {ExportedComponentName} = (props: {ExportedComponentName}Props) => {
198+
return <{SourceComponent} {...props} />;
199+
};
200+
```
201+
202+
#### `packaging/react/types.ts`
203+
204+
Write standalone inline type definitions (no `@kbn/*` or `@elastic/eui` imports):
205+
- Mirror every exported type from `index.ts`
206+
- Replace complex EUI types (e.g. `IconType`) with `string`
207+
- All types self-contained with only `import type * as React from 'react'` allowed
208+
- End with `export declare function {ExportedComponentName}(props: ...): React.ReactNode;`
209+
210+
#### `packaging/react/type_validation.ts`
211+
212+
Follow the side-navigation pattern exactly:
213+
- Import source types with `Source` prefix, packaged types with `Packaged` prefix
214+
- Structural assignment checks: `const _foo: PackagedType = {} as SourceType;`
215+
- Add `@ts-expect-error` for intentional simplifications (e.g. `IconType → string`)
216+
- Export `export const TYPE_VALIDATION_PASSED = true;`
217+
218+
### 4e. Create packaging/example/ scaffold
219+
220+
```bash
221+
mkdir -p src/platform/kbn-ui/{folderName}/packaging/example/src
222+
mkdir -p src/platform/kbn-ui/{folderName}/packaging/example/public
223+
```
224+
225+
The example is a minimal runnable app that imports from `../../target` (the built package), so consumers can see the component in action without a full Kibana setup.
226+
227+
**`packaging/example/package.json`** — replace `{folderName}`:
228+
```json
229+
{
230+
"name": "{folderName}-example",
231+
"version": "1.0.0",
232+
"private": true,
233+
"license": "SEE LICENSE IN LICENSE.txt",
234+
"description": "Example application demonstrating {ExportedComponentName} usage. Uses dependencies from Kibana root.",
235+
"scripts": {
236+
"start": "./start.sh"
237+
}
238+
}
239+
```
240+
241+
**`packaging/example/tsconfig.json`** — copy verbatim from side-navigation (it's fully generic).
242+
243+
**`packaging/example/webpack.config.js`** — copy from side-navigation, update only the alias:
244+
```js
245+
alias: {
246+
'{packageName}': path.resolve(__dirname, '../../target'),
247+
},
248+
```
249+
250+
**`packaging/example/start.sh`** — copy verbatim from side-navigation (fully relative, no substitution needed).
251+
252+
**`packaging/example/public/index.html`** — copy from side-navigation, update `<title>` to `{ExportedComponentName} Example`.
253+
254+
**`packaging/example/src/index.tsx`** — copy verbatim from side-navigation (generic React bootstrap).
255+
256+
**`packaging/example/src/app.tsx`** — generate a minimal working demo from the component's public API (derived from `packaging/react/types.ts` in step 4d):
257+
- Wrap in `EuiProvider`
258+
- Import the component and its required types from `'{packageName}'` (the webpack alias resolves to `../../target`)
259+
- Initialise required props with realistic minimal values
260+
- Wire up any callback props (e.g. `onChange`, `onItemClick`) with `useState` and display the current value
261+
- Add an `<EuiText>` block listing manual test cases relevant to the component
262+
263+
**`packaging/example/README.md`** — copy from side-navigation, substituting `{packageName}` and `{folderName}`.
264+
265+
### 4f. Generate @kbn/* service stubs
266+
267+
For each `@kbn/*` package identified for stubbing in Phase 2:
268+
269+
**Known stubs** — copy directly from side-navigation:
270+
- `@kbn/i18n` and `@kbn/i18n-react` → copy `src/platform/kbn-ui/side-navigation/packaging/react/services/i18n.tsx` verbatim
271+
272+
**Unknown stubs** — for each unfamiliar `@kbn/*` package:
273+
1. Find and read its `index.ts` (search under `src/platform/packages/`) to list named exports
274+
2. Create `packaging/react/services/{package-slug}.ts`:
275+
- Functions → `export const fnName = (..._args: unknown[]) => undefined as unknown as ReturnType;`
276+
- String constants → `export const CONST_NAME = '';`
277+
- Number constants → `export const CONST_NAME = 0;`
278+
- Boolean constants → `export const CONST_NAME = false;`
279+
- Object/array constants → `export const CONST_NAME = {};` / `[]`
280+
- Classes → minimal stub with constructor and required public methods
281+
- Types/interfaces → skip (compile-time only, no runtime representation)
282+
3. Top comment: `// Stub for @kbn/{name} — no-op implementation for standalone bundle`
283+
284+
### 4g. Update all Kibana imports
285+
286+
Find and update every file importing the old package name:
287+
288+
```bash
289+
# Collect affected files
290+
grep -rl "from '${oldName}'" src/ x-pack/ packages/ --include="*.ts" --include="*.tsx"
291+
292+
# Replace static imports
293+
find src/ x-pack/ packages/ -name "*.ts" -o -name "*.tsx" | \
294+
xargs grep -l "from '${oldName}'" | \
295+
xargs sed -i "s|from '${oldName}'|from '${packageName}'|g"
296+
297+
# Replace dynamic imports
298+
find src/ x-pack/ packages/ -name "*.ts" -o -name "*.tsx" | \
299+
xargs grep -l "import('${oldName}')" | \
300+
xargs sed -i "s|import('${oldName}')|import('${packageName}')|g"
301+
```
302+
303+
Also update `kbn_references` in `tsconfig.json` files:
304+
```bash
305+
grep -rl '"${oldName}"' src/ x-pack/ packages/ --include="tsconfig.json" | \
306+
xargs sed -i "s|\"${oldName}\"|\"${packageName}\"|g"
307+
```
308+
309+
### 4h. Verify old location is gone
310+
311+
```bash
312+
ls {sourcePath} 2>/dev/null && echo "ERROR: old path still exists" || echo "OK: old path removed"
313+
```
314+
315+
Check for any remaining tsconfig.json composite project references to the old path:
316+
```bash
317+
grep -rl '{sourcePath}' . --include="tsconfig.json" | head -5
318+
```
319+
Remove any stale references found.
320+
321+
Remind the engineer:
322+
- **Review `packaging/react/types.ts`** — EUI/complex type simplifications need manual verification
323+
- **Review generated service stubs** — confirm no-op defaults are safe for the consumer context
324+
- **Check BUILD.md and I18N.md** — copy from side-navigation as reference if the package needs them
325+
- **Test the `.tgz` in the consumer app** (e.g. Cloud UI) before merging

0 commit comments

Comments
 (0)