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 +9 -1
- package/dist/cli.js +1 -1
- package/package.json +6 -1
- package/examples/basic.tsx +0 -71
- package/examples/full-stack.tsx +0 -339
- package/examples/tsconfig.json +0 -21
- package/src/cli.ts +0 -111
- package/src/components/index.ts +0 -241
- package/src/index.ts +0 -123
- package/src/jsx-runtime.ts +0 -71
- package/src/render.ts +0 -863
- package/src/types.ts +0 -362
- package/tsconfig.examples.json +0 -18
- package/tsconfig.json +0 -22
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
|
-
**
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kubetsx",
|
|
3
|
-
"version": "0.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",
|
package/examples/basic.tsx
DELETED
|
@@ -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
|
-
|
package/examples/full-stack.tsx
DELETED
|
@@ -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
|
-
|
package/examples/tsconfig.json
DELETED
|
@@ -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
|
-
});
|