feat: scaffold headlamp-argocd-plugin with standard plugin structure
Adds the full plugin scaffold matching the Headlamp plugin pattern (polaris, kube-vip, etc.): - package.json with full devDependencies (Vitest, TypeScript, ESLint, Prettier) - tsconfig.json, vitest.config.mts, vitest.setup.ts - src/index.tsx with ArgoCDErrorBoundary and stub Applications route - src/index.test.tsx smoke test to verify module importability - CLAUDE.md documentation for future development - .gitignore for node_modules/dist - pnpm-lock.yaml pinned via packageManager field ArtifactHub metadata already present (created by Hugh in PRI-186). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.pnp/
|
||||
.pnp.js
|
||||
*.log
|
||||
*.tgz
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
coverage/
|
||||
.nyc_output/
|
||||
.cache/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.vscode/
|
||||
.idea/
|
||||
@@ -0,0 +1,64 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project
|
||||
|
||||
Headlamp plugin for ArgoCD visibility. Monitors ArgoCD Applications, Rollouts, and health status via the ArgoCD REST API proxied through Kubernetes. Read-only — no cluster write operations.
|
||||
|
||||
- **Plugin name**: `argocd`
|
||||
- **Target**: Headlamp >= v0.26
|
||||
- **Data source**: ArgoCD server at `/api/v1/namespaces/argocd/services/argocd-server/proxy/api/v1/applications`
|
||||
- **RBAC**: `get`/`list` on `services/proxy` for `argocd-server` in `argocd` namespace
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
pnpm start # dev server with hot reload
|
||||
pnpm build # production build
|
||||
pnpm package # package for headlamp
|
||||
pnpm tsc # TypeScript type check (no emit)
|
||||
pnpm lint # ESLint
|
||||
pnpm lint:fix # ESLint with auto-fix
|
||||
pnpm format # Prettier write
|
||||
pnpm format:check # Prettier check
|
||||
pnpm test # vitest run
|
||||
pnpm test:watch # vitest watch mode
|
||||
```
|
||||
|
||||
All tests and `pnpm tsc` must pass before committing.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.tsx # Plugin entry: registerRoute, registerSidebarEntry; ArgoCDErrorBoundary
|
||||
├── test-utils.tsx # Shared test fixtures
|
||||
├── api/
|
||||
│ └── argocd.ts # ArgoCD API types, ApiProxy request helpers
|
||||
└── components/
|
||||
└── ArgoCDStubView.tsx # Placeholder Applications List view
|
||||
```
|
||||
|
||||
## Code conventions
|
||||
|
||||
- Functional React components only — class components only for error boundaries (ArgoCDErrorBoundary in index.tsx)
|
||||
- All imports from `@kinvolk/headlamp-plugin/lib` and `@kinvolk/headlamp-plugin/lib/CommonComponents`
|
||||
- `@mui/material` is available as a shared external via Headlamp — use `useTheme` from `@mui/material/styles` for theming. Do NOT add `@mui/material` to package.json dependencies.
|
||||
- Use `useTheme()` + `theme.palette.*` for all theme-aware colors — never use `var(--mui-palette-*)` CSS variables
|
||||
- No other UI libraries (no Ant Design, etc.)
|
||||
- TypeScript strict mode — no `any`, use `unknown` + type guards at API boundaries
|
||||
- Context provider wraps each route component in `index.tsx`
|
||||
- All registered components wrapped in `ArgoCDErrorBoundary` for graceful error handling
|
||||
- Tests: vitest + @testing-library/react, mock with `vi.mock('@kinvolk/headlamp-plugin/lib', ...)`
|
||||
- `vitest.setup.ts` provides a spec-compliant `localStorage` shim for Node 22+ compatibility
|
||||
|
||||
## Testing
|
||||
|
||||
Mock pattern for headlamp APIs:
|
||||
```typescript
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: vi.fn().mockResolvedValue({}) },
|
||||
K8s: { ResourceClasses: {} },
|
||||
}));
|
||||
```
|
||||
+51
-9
@@ -1,18 +1,60 @@
|
||||
{
|
||||
"name": "headlamp-argocd",
|
||||
"name": "@privilegedescalation/headlamp-argocd-plugin",
|
||||
"version": "0.1.0",
|
||||
"description": "Headlamp plugin for ArgoCD visibility",
|
||||
"description": "Headlamp plugin for ArgoCD visibility — monitors ArgoCD Applications, Rollouts, and health status",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/privilegedescalation/headlamp-argocd-plugin.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/privilegedescalation/headlamp-argocd-plugin/issues"
|
||||
},
|
||||
"homepage": "https://github.com/privilegedescalation/headlamp-argocd-plugin#readme",
|
||||
"author": "privilegedescalation",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": ["headlamp", "argocd", "kubernetes", "gitops"],
|
||||
"packageManager": "pnpm@10.32.1",
|
||||
"scripts": {
|
||||
"start": "headlamp-plugin start",
|
||||
"build": "headlamp-plugin build",
|
||||
"lint": "headlamp-plugin lint",
|
||||
"tsc": "headlamp-plugin tsc",
|
||||
"format:check": "headlamp-plugin format:check",
|
||||
"test": "headlamp-plugin test",
|
||||
"package": "headlamp-plugin package"
|
||||
"package": "headlamp-plugin package",
|
||||
"tsc": "tsc --noEmit",
|
||||
"lint": "eslint --ext .ts,.tsx src/",
|
||||
"lint:fix": "eslint --ext .ts,.tsx --fix src/",
|
||||
"format": "prettier --write src/",
|
||||
"format:check": "prettier --check src/",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"tar": "^7.5.11",
|
||||
"undici": "^7.24.3",
|
||||
"flatted": "^3.4.2"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kinvolk/headlamp-plugin": "^0.26.0"
|
||||
"@kinvolk/headlamp-plugin": "^0.13.0",
|
||||
"@mui/material": "^5.15.14",
|
||||
"@testing-library/jest-dom": "^6.4.8",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@headlamp-k8s/eslint-config": "^0.6.0",
|
||||
"eslint": "^8.57.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"tar": "^7.5.11",
|
||||
"typescript": "~5.6.2",
|
||||
"undici": "^7.24.3",
|
||||
"vitest": "^3.0.5"
|
||||
}
|
||||
}
|
||||
Generated
+12150
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
/**
|
||||
* Minimal test to verify the plugin index module loads without throwing.
|
||||
* Full component tests will be added in subsequent tasks.
|
||||
*/
|
||||
describe('ArgoCD Plugin Scaffold', () => {
|
||||
it('index module is importable', async () => {
|
||||
// Dynamic import to verify the module parses and exports correctly
|
||||
const mod = await import('./index');
|
||||
expect(mod).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
registerRoute,
|
||||
registerSidebarEntry,
|
||||
} from '@kinvolk/headlamp-plugin/lib';
|
||||
import { SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
|
||||
// --- Error boundary for plugin components ---
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
class ArgoCDErrorBoundary extends React.Component<
|
||||
{ children: React.ReactNode },
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
state: ErrorBoundaryState = { error: null };
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { error: error.message };
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return (
|
||||
<SectionBox title="ArgoCD Plugin Error">
|
||||
<StatusLabel status="error">{this.state.error}</StatusLabel>
|
||||
</SectionBox>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Stub Applications List View ---
|
||||
|
||||
function ArgoCDStubView() {
|
||||
return (
|
||||
<SectionBox title="ArgoCD Applications">
|
||||
<StatusLabel status="info">Plugin scaffold — features coming soon.</StatusLabel>
|
||||
</SectionBox>
|
||||
);
|
||||
}
|
||||
|
||||
// --- Sidebar entry ---
|
||||
|
||||
registerSidebarEntry({
|
||||
parent: null,
|
||||
name: 'argocd',
|
||||
label: 'ArgoCD',
|
||||
url: '/argocd',
|
||||
icon: 'mdi:git',
|
||||
});
|
||||
|
||||
registerSidebarEntry({
|
||||
parent: 'argocd',
|
||||
name: 'argocd-overview',
|
||||
label: 'Applications',
|
||||
url: '/argocd',
|
||||
icon: 'mdi:view-list',
|
||||
});
|
||||
|
||||
// --- Routes ---
|
||||
|
||||
registerRoute({
|
||||
path: '/argocd',
|
||||
sidebar: 'argocd-overview',
|
||||
name: 'argocd',
|
||||
exact: true,
|
||||
component: () => (
|
||||
<ArgoCDErrorBoundary>
|
||||
<ArgoCDStubView />
|
||||
</ArgoCDErrorBoundary>
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "@kinvolk/headlamp-plugin/config/plugins-tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vitest/globals", "@testing-library/jest-dom"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"test"',
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./vitest.setup.ts'],
|
||||
exclude: ['e2e/**', 'node_modules/**'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
include: ['src/**/*.{ts,tsx}'],
|
||||
exclude: ['src/**/*.test.{ts,tsx}', 'src/test-utils.tsx', 'src/index.tsx'],
|
||||
thresholds: {
|
||||
lines: 80,
|
||||
functions: 80,
|
||||
branches: 80,
|
||||
statements: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
// Node 22+ ships a minimal built-in `localStorage` global (property-bag only,
|
||||
// no getItem/setItem/removeItem/clear) that shadows jsdom's Web Storage
|
||||
// implementation. Provide a spec-compliant shim so code under test works.
|
||||
if (typeof localStorage !== 'undefined' && typeof localStorage.getItem !== 'function') {
|
||||
const store = new Map<string, string>();
|
||||
|
||||
const storage = {
|
||||
getItem(key: string): string | null {
|
||||
return store.get(key) ?? null;
|
||||
},
|
||||
setItem(key: string, value: string): void {
|
||||
store.set(key, String(value));
|
||||
},
|
||||
removeItem(key: string): void {
|
||||
store.delete(key);
|
||||
},
|
||||
clear(): void {
|
||||
store.clear();
|
||||
},
|
||||
get length(): number {
|
||||
return store.size;
|
||||
},
|
||||
key(index: number): string | null {
|
||||
return [...store.keys()][index] ?? null;
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
value: storage,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: storage,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user