kubetsx 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  Kubetsx transforms Kubernetes configuration from fragile YAML files into type-safe, composable TypeScript components. Get full IntelliSense, compile-time validation, and the power of loops and conditionals.
9
9
 
10
- **If you can write React, you can configure Kubernetes.**
10
+ **Type-safe. Composable. Finally, Kubernetes configuration that makes sense.**
11
11
 
12
12
  ```tsx
13
13
  import { render, Manifest, Deployment, Container, Port, Service } from 'kubetsx';
@@ -73,6 +73,14 @@ Or pipe directly to kubectl:
73
73
  npx kubetsx your-config.tsx | kubectl apply -f -
74
74
  ```
75
75
 
76
+ Save to file:
77
+
78
+ ```bash
79
+ npx kubetsx your-config.tsx > k8s.yaml
80
+ ```
81
+
82
+ > **šŸ’” Tip:** Use `console.error()` for debug output. The CLI validates that stdout contains only valid YAML and will error if `console.log()` corrupts the output.
83
+
76
84
  ---
77
85
 
78
86
  ## šŸŽÆ Quick Start
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ Examples:
29
29
  process.exit(0);
30
30
  }
31
31
  if (args.includes('--version') || args.includes('-v')) {
32
- console.log('kubetsx v0.1.1');
32
+ console.log('kubetsx v0.1.3');
33
33
  process.exit(0);
34
34
  }
35
35
  // Find tsx CLI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubetsx",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "The Declarative Kubernetes Framework. Write K8s configs with JSX. Yes, really.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,6 +18,11 @@
18
18
  "import": "./dist/jsx-runtime.js"
19
19
  }
20
20
  },
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
21
26
  "scripts": {
22
27
  "build": "tsc",
23
28
  "dev": "tsc --watch",
@@ -1,71 +0,0 @@
1
- /**
2
- * šŸŽÆ Kubetsx - Basic Example
3
- *
4
- * A simple deployment with service and ingress
5
- */
6
-
7
- import {
8
- render,
9
- Manifest,
10
- Deployment,
11
- Container,
12
- Port,
13
- Env,
14
- SecretRef,
15
- Resources,
16
- Probe,
17
- HttpProbe,
18
- Service,
19
- Ingress,
20
- Route,
21
- } from '../dist/index.js';
22
-
23
- const APP_NAME = 'api-server';
24
- const VERSION = 'v1.2.3';
25
-
26
- // Define secrets as data - use loops!
27
- const secrets = [
28
- { env: 'DATABASE_URL', secret: 'db-secrets', secretKey: 'url' },
29
- { env: 'REDIS_URL', secret: 'redis-secrets', secretKey: 'url' },
30
- { env: 'JWT_SECRET', secret: 'auth-secrets', secretKey: 'jwt' },
31
- ];
32
-
33
- // Define probes as data
34
- const probes = [
35
- { type: 'liveness' as const, path: '/health', delay: 30 },
36
- { type: 'readiness' as const, path: '/ready', delay: 5 },
37
- ];
38
-
39
- const ApiServer = () => (
40
- <Manifest>
41
- <Deployment name={APP_NAME} replicas={3}>
42
- <Container name="api" image={`mycompany/api:${VERSION}`}>
43
- <Port container={3000} />
44
- {secrets.map(({ env, secret, secretKey }) => (
45
- <Env key={env} name={env}>
46
- <SecretRef name={secret} secretKey={secretKey} />
47
- </Env>
48
- ))}
49
- <Resources
50
- requestMemory="256Mi"
51
- requestCpu="250m"
52
- limitMemory="512Mi"
53
- limitCpu="500m"
54
- />
55
- {probes.map(({ type, path, delay }) => (
56
- <Probe key={type} type={type} delay={delay}>
57
- <HttpProbe path={path} port={3000} />
58
- </Probe>
59
- ))}
60
- </Container>
61
- </Deployment>
62
- <Service name={APP_NAME} port={80} targetPort={3000} />
63
- <Ingress name="api-ingress" host="api.mycompany.com" ssl>
64
- <Route path="/" service={APP_NAME} port={80} />
65
- </Ingress>
66
- </Manifest>
67
- );
68
-
69
- // Render to YAML
70
- render(<ApiServer />);
71
-
@@ -1,339 +0,0 @@
1
- /**
2
- * šŸŽÆ Kubetsx - Full Stack Example
3
- *
4
- * Complete production-ready deployment with:
5
- * - Multiple microservices
6
- * - Namespaces
7
- * - ConfigMaps & Secrets
8
- * - Autoscaling
9
- * - RBAC
10
- *
11
- * @jsxImportSource ../dist
12
- */
13
-
14
- import {
15
- render,
16
- Manifest,
17
- Cluster,
18
- Namespace,
19
- Deployment,
20
- Container,
21
- Port,
22
- Env,
23
- SecretRef,
24
- ConfigMapRef,
25
- Resources,
26
- Probe,
27
- HttpProbe,
28
- ExecProbe,
29
- Service,
30
- Ingress,
31
- IngressHost,
32
- Route,
33
- ConfigMap,
34
- Secret,
35
- Pvc,
36
- Hpa,
37
- CronJob,
38
- ServiceAccount,
39
- Role,
40
- RoleBinding,
41
- Volume,
42
- VolumeMount,
43
- EmptyDir,
44
- PvcVolume,
45
- } from '../dist/index.js';
46
-
47
- // ============================================================================
48
- // Configuration
49
- // ============================================================================
50
-
51
- const VERSION = process.env.VERSION || 'latest';
52
- const ENV = process.env.NODE_ENV || 'production';
53
- const isProduction = ENV === 'production';
54
-
55
- // Service definitions - just add to this array to deploy new services!
56
- const services = [
57
- {
58
- name: 'api',
59
- replicas: isProduction ? 5 : 1,
60
- port: 3000,
61
- host: 'api.mycompany.com',
62
- memory: '512Mi',
63
- cpu: '500m',
64
- secrets: ['DATABASE_URL', 'REDIS_URL', 'JWT_SECRET'],
65
- autoscale: { min: 3, max: 10, cpu: 70 },
66
- },
67
- {
68
- name: 'auth',
69
- replicas: isProduction ? 3 : 1,
70
- port: 4000,
71
- host: 'auth.mycompany.com',
72
- memory: '256Mi',
73
- cpu: '250m',
74
- secrets: ['DATABASE_URL', 'OAUTH_CLIENT_SECRET', 'JWT_SECRET'],
75
- autoscale: { min: 2, max: 5, cpu: 80 },
76
- },
77
- {
78
- name: 'worker',
79
- replicas: isProduction ? 3 : 1,
80
- port: 5000,
81
- memory: '1Gi',
82
- cpu: '1',
83
- secrets: ['DATABASE_URL', 'REDIS_URL', 'AWS_ACCESS_KEY', 'AWS_SECRET_KEY'],
84
- // No autoscale for workers
85
- },
86
- ];
87
-
88
- // Cron jobs
89
- const cronJobs = [
90
- { name: 'cleanup', schedule: '0 3 * * *', command: ['node', 'scripts/cleanup.js'] },
91
- { name: 'reports', schedule: '0 8 * * 1', command: ['node', 'scripts/weekly-report.js'] },
92
- { name: 'backups', schedule: '0 2 * * *', command: ['node', 'scripts/backup.js'] },
93
- ];
94
-
95
- // ============================================================================
96
- // Reusable Components
97
- // ============================================================================
98
-
99
- interface StandardContainerProps {
100
- name: string;
101
- port: number;
102
- memory: string;
103
- cpu: string;
104
- secrets: string[];
105
- }
106
-
107
- const StandardContainer = ({ name, port, memory, cpu, secrets }: StandardContainerProps) => (
108
- <Container name={name} image={`mycompany/${name}:${VERSION}`}>
109
- <Port container={port} />
110
-
111
- {/* Common env vars */}
112
- <Env name="NODE_ENV" value={ENV} />
113
- <Env name="SERVICE_NAME" value={name} />
114
- <Env name="LOG_LEVEL">
115
- <ConfigMapRef name="app-config" key="log_level" />
116
- </Env>
117
-
118
- {/* Service-specific secrets */}
119
- {secrets.map(secret => (
120
- <Env key={secret} name={secret}>
121
- <SecretRef name="app-secrets" secretKey={secret.toLowerCase().replace(/_/g, '-')} />
122
- </Env>
123
- ))}
124
-
125
- <Resources
126
- requestMemory={memory}
127
- requestCpu={cpu}
128
- limitMemory={memory}
129
- limitCpu={cpu}
130
- />
131
-
132
- <Probe type="liveness" delay={30} period={10}>
133
- <HttpProbe path="/health" port={port} />
134
- </Probe>
135
- <Probe type="readiness" delay={5} period={5}>
136
- <HttpProbe path="/ready" port={port} />
137
- </Probe>
138
-
139
- {/* Temp storage for logs */}
140
- <VolumeMount name="tmp" mountPath="/tmp" />
141
- </Container>
142
- );
143
-
144
- // ============================================================================
145
- // Main Infrastructure
146
- // ============================================================================
147
-
148
- const ProductionStack = () => (
149
- <Cluster name="production">
150
- {/* ================================================================== */}
151
- {/* Backend Namespace */}
152
- {/* ================================================================== */}
153
- <Namespace name="backend" labels={{ team: 'platform', env: ENV }}>
154
-
155
- {/* Shared ConfigMap */}
156
- <ConfigMap
157
- name="app-config"
158
- data={{
159
- log_level: isProduction ? 'info' : 'debug',
160
- feature_flags: JSON.stringify({ newUI: true, analytics: isProduction }),
161
- }}
162
- />
163
-
164
- {/* Shared Secret (in practice, use external secrets manager) */}
165
- <Secret
166
- name="app-secrets"
167
- stringData={{
168
- 'database-url': '${DATABASE_URL}',
169
- 'redis-url': '${REDIS_URL}',
170
- 'jwt-secret': '${JWT_SECRET}',
171
- 'oauth-client-secret': '${OAUTH_CLIENT_SECRET}',
172
- 'aws-access-key': '${AWS_ACCESS_KEY}',
173
- 'aws-secret-key': '${AWS_SECRET_KEY}',
174
- }}
175
- />
176
-
177
- {/* Service Account with RBAC */}
178
- <ServiceAccount name="backend-sa" />
179
- <Role
180
- name="backend-role"
181
- rules={[
182
- { apiGroups: [''], resources: ['configmaps', 'secrets'], verbs: ['get', 'list', 'watch'] },
183
- { apiGroups: [''], resources: ['pods'], verbs: ['get', 'list'] },
184
- ]}
185
- />
186
- <RoleBinding
187
- name="backend-role-binding"
188
- roleRef={{ kind: 'Role', name: 'backend-role' }}
189
- subjects={[{ kind: 'ServiceAccount', name: 'backend-sa' }]}
190
- />
191
-
192
- {/* Deploy all services using loop! */}
193
- {services.map(svc => (
194
- <Manifest key={svc.name}>
195
- <Deployment
196
- name={svc.name}
197
- replicas={svc.replicas}
198
- labels={{ app: svc.name, version: VERSION }}
199
- >
200
- <StandardContainer
201
- name={svc.name}
202
- port={svc.port}
203
- memory={svc.memory}
204
- cpu={svc.cpu}
205
- secrets={svc.secrets}
206
- />
207
- <Volume name="tmp">
208
- <EmptyDir />
209
- </Volume>
210
- </Deployment>
211
-
212
- <Service
213
- name={svc.name}
214
- port={80}
215
- targetPort={svc.port}
216
- selector={{ app: svc.name }}
217
- />
218
-
219
- {/* HPA only for services with autoscale config */}
220
- {svc.autoscale && (
221
- <Hpa
222
- name={`${svc.name}-hpa`}
223
- target={svc.name}
224
- minReplicas={svc.autoscale.min}
225
- maxReplicas={svc.autoscale.max}
226
- targetCpuUtilization={svc.autoscale.cpu}
227
- />
228
- )}
229
- </Manifest>
230
- ))}
231
-
232
- {/* Ingress for public services */}
233
- <Ingress name="backend-ingress" className="nginx" ssl>
234
- {services
235
- .filter(svc => svc.host)
236
- .map(svc => (
237
- <IngressHost key={svc.name} host={svc.host!}>
238
- <Route path="/" service={svc.name} port={80} />
239
- </IngressHost>
240
- ))
241
- }
242
- </Ingress>
243
-
244
- {/* Cron Jobs */}
245
- {cronJobs.map(job => (
246
- <CronJob
247
- key={job.name}
248
- name={job.name}
249
- schedule={job.schedule}
250
- concurrencyPolicy="Forbid"
251
- >
252
- <Container
253
- name={job.name}
254
- image={`mycompany/jobs:${VERSION}`}
255
- command={job.command}
256
- >
257
- <Env name="DATABASE_URL">
258
- <SecretRef name="app-secrets" secretKey="database-url" />
259
- </Env>
260
- <Resources requestMemory="256Mi" requestCpu="100m" />
261
- </Container>
262
- </CronJob>
263
- ))}
264
- </Namespace>
265
-
266
- {/* ================================================================== */}
267
- {/* Database Namespace */}
268
- {/* ================================================================== */}
269
- <Namespace name="databases" labels={{ team: 'platform', env: ENV }}>
270
-
271
- {/* PostgreSQL PVC */}
272
- <Pvc
273
- name="postgres-data"
274
- storage="100Gi"
275
- accessModes={['ReadWriteOnce']}
276
- storageClassName="ssd"
277
- />
278
-
279
- {/* PostgreSQL Deployment */}
280
- <Deployment name="postgres" replicas={1}>
281
- <Container name="postgres" image="postgres:15-alpine">
282
- <Port container={5432} />
283
- <Env name="POSTGRES_PASSWORD">
284
- <SecretRef name="postgres-secrets" key="password" />
285
- </Env>
286
- <Env name="POSTGRES_DB" value="myapp" />
287
- <Resources
288
- requestMemory="1Gi"
289
- requestCpu="500m"
290
- limitMemory="2Gi"
291
- limitCpu="1"
292
- />
293
- <Probe type="readiness" delay={5} period={10}>
294
- <ExecProbe command={['pg_isready', '-U', 'postgres']} />
295
- </Probe>
296
- <VolumeMount name="data" mountPath="/var/lib/postgresql/data" />
297
- </Container>
298
- <Volume name="data">
299
- <PvcVolume claimName="postgres-data" />
300
- </Volume>
301
- </Deployment>
302
-
303
- <Service name="postgres" port={5432} targetPort={5432} type="ClusterIP" />
304
-
305
- {/* Redis Deployment */}
306
- <Deployment name="redis" replicas={1}>
307
- <Container name="redis" image="redis:7-alpine">
308
- <Port container={6379} />
309
- <Resources
310
- requestMemory="256Mi"
311
- requestCpu="100m"
312
- limitMemory="512Mi"
313
- limitCpu="200m"
314
- />
315
- <Probe type="readiness" delay={5}>
316
- <ExecProbe command={['redis-cli', 'ping']} />
317
- </Probe>
318
- </Container>
319
- </Deployment>
320
-
321
- <Service name="redis" port={6379} targetPort={6379} type="ClusterIP" />
322
-
323
- </Namespace>
324
- </Cluster>
325
- );
326
-
327
- // ============================================================================
328
- // Render Output
329
- // ============================================================================
330
-
331
- console.log('šŸŽÆ Kubetsx - Generating Kubernetes manifests...\n');
332
- console.log(`Environment: ${ENV}`);
333
- console.log(`Version: ${VERSION}`);
334
- console.log(`Services: ${services.map(s => s.name).join(', ')}`);
335
- console.log(`Cron Jobs: ${cronJobs.map(j => j.name).join(', ')}`);
336
- console.log('\n---\n');
337
-
338
- render(<ProductionStack />);
339
-
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "jsx": "react-jsx",
12
- "jsxImportSource": "../dist",
13
- "paths": {
14
- "kubetsx": ["../dist/index.js"],
15
- "kubetsx/*": ["../dist/*"]
16
- },
17
- "baseUrl": "."
18
- },
19
- "include": ["./**/*"]
20
- }
21
-
package/src/cli.ts DELETED
@@ -1,111 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * šŸŽÆ Kubetsx CLI
4
- *
5
- * Run Kubernetes config files written in TSX
6
- * Validates output is clean YAML before emitting
7
- */
8
-
9
- import { spawn } from 'child_process';
10
- import { resolve, dirname } from 'path';
11
- import { fileURLToPath } from 'url';
12
- import { existsSync } from 'fs';
13
- import { parse as parseYAML } from 'yaml';
14
-
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
16
-
17
- const args = process.argv.slice(2);
18
-
19
- if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
20
- console.log(`
21
- šŸŽÆ Kubetsx - The Declarative Kubernetes Framework
22
-
23
- Usage:
24
- kubetsx <file.tsx> [options] Run a TSX config file
25
- kubetsx --help Show this help message
26
- kubetsx --version Show version
27
-
28
- Examples:
29
- kubetsx config.tsx Generate YAML from config.tsx
30
- kubetsx config.tsx > k8s.yaml Save output to file
31
- kubetsx config.tsx | kubectl apply -f - Apply directly to cluster
32
- `);
33
- process.exit(0);
34
- }
35
-
36
- if (args.includes('--version') || args.includes('-v')) {
37
- console.log('kubetsx v0.1.1');
38
- process.exit(0);
39
- }
40
-
41
- // Find tsx CLI
42
- const tsxCliPath = resolve(__dirname, '..', 'node_modules', 'tsx', 'dist', 'cli.mjs');
43
- const tsxPath = existsSync(tsxCliPath) ? tsxCliPath : null;
44
-
45
- // Run tsx and capture output
46
- const child = spawn(
47
- tsxPath ? process.execPath : 'npx',
48
- tsxPath ? [tsxCliPath, ...args] : ['tsx', ...args],
49
- {
50
- stdio: ['inherit', 'pipe', 'pipe'],
51
- }
52
- );
53
-
54
- let stdout = '';
55
- let stderr = '';
56
-
57
- child.stdout?.on('data', (data) => {
58
- stdout += data.toString();
59
- });
60
-
61
- child.stderr?.on('data', (data) => {
62
- stderr += data.toString();
63
- });
64
-
65
- child.on('close', (code) => {
66
- // Always pass through stderr
67
- if (stderr) {
68
- process.stderr.write(stderr);
69
- }
70
-
71
- if (code !== 0) {
72
- process.exit(code ?? 1);
73
- }
74
-
75
- // Validate the output is valid YAML
76
- const trimmedOutput = stdout.trim();
77
-
78
- if (!trimmedOutput) {
79
- process.stderr.write('Error: No output generated. Make sure your config calls render().\n');
80
- process.exit(1);
81
- }
82
-
83
- // Try to parse as YAML to validate
84
- try {
85
- // Split by document separator and parse each
86
- const documents = trimmedOutput.split(/^---$/m).filter(d => d.trim());
87
-
88
- for (const doc of documents) {
89
- const parsed = parseYAML(doc);
90
-
91
- // Check it looks like a K8s resource
92
- if (parsed && typeof parsed === 'object') {
93
- if (!('apiVersion' in parsed) || !('kind' in parsed)) {
94
- throw new Error('Output does not appear to be a valid Kubernetes resource (missing apiVersion or kind)');
95
- }
96
- }
97
- }
98
-
99
- // Valid YAML - output it
100
- process.stdout.write(trimmedOutput + '\n');
101
-
102
- } catch (err) {
103
- process.stderr.write('\nāŒ Error: Output is not valid Kubernetes YAML.\n\n');
104
- process.stderr.write('This usually happens when your config file has console.log() statements.\n');
105
- process.stderr.write('Use console.error() instead for debug output.\n\n');
106
- process.stderr.write('--- Raw output ---\n');
107
- process.stderr.write(stdout);
108
- process.stderr.write('------------------\n');
109
- process.exit(1);
110
- }
111
- });