├── .github └── workflows │ └── update.yml ├── .gitignore ├── .prettierrc.yaml ├── README.md ├── assets ├── README.md └── package.json ├── bin └── generate-kubernetes-types ├── package-lock.json ├── package.json ├── src ├── generate │ ├── index.ts │ └── util.ts └── openapi │ ├── generate.ts │ └── index.ts └── tsconfig.json /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: "Check for updates" 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: >- 7 | 15 15 * * * 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | packages: read 15 | steps: 16 | - name: Check for updates 17 | id: check 18 | uses: silverlyra/script-action@v0.2 19 | with: 20 | script: | 21 | const npmResponse = await fetch('https://registry.npmjs.org/kubernetes-types/'); 22 | const npmTags = (await npmResponse.json())['dist-tags']; 23 | const [major, minor] = npmTags.latest.split('.', 2).map(Number); 24 | 25 | const next = `${major}.${minor + 1}`; 26 | console.log(`Latest package: ${npmTags.latest}; checking for Kubernetes ${next}`); 27 | 28 | const { data: refs } = await github.rest.git.listMatchingRefs({ 29 | owner: 'kubernetes', 30 | repo: 'kubernetes', 31 | ref: `tags/v${next}.0`, 32 | }); 33 | 34 | // exclude pre-releases 35 | const ref = refs.find(({ ref }) => ref === `refs/tags/v${next}.0`); 36 | const found = ref != null; 37 | 38 | if (found) { 39 | console.log(`Found new release: ${next}.0`); 40 | 41 | const { data: {download_url: url} } = await github.rest.repos.getContent({ 42 | owner: 'kubernetes', 43 | repo: 'kubernetes', 44 | ref: `v${next}.0`, 45 | path: 'api/openapi-spec/swagger.json', 46 | }); 47 | 48 | const response = await fetch(url); 49 | const spec = await response.text(); 50 | 51 | await fs.writeFile(path.join(env.RUNNER_TEMP, 'swagger.json'), spec, 'utf-8'); 52 | console.log(`Saved swagger.json to ${env.RUNNER_TEMP}`); 53 | } 54 | 55 | return { next, found, ref }; 56 | - name: Checkout repository 57 | if: fromJson(steps.check.outputs.result).found 58 | uses: actions/checkout@v4 59 | - name: Setup node.js 60 | if: fromJson(steps.check.outputs.result).found 61 | uses: actions/setup-node@v4 62 | with: 63 | node-version: '20.x' 64 | registry-url: 'https://registry.npmjs.org' 65 | cache: npm 66 | - name: Update types 67 | if: fromJson(steps.check.outputs.result).found 68 | id: update 69 | env: 70 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 71 | VERSION: v${{ fromJson(steps.check.outputs.result).next }} 72 | run: | 73 | npm ci 74 | npm run build 75 | 76 | echo "Generating type package for ${VERSION}" 77 | mkdir -p types 78 | 79 | ./bin/generate-kubernetes-types --api "$VERSION" --file "${RUNNER_TEMP}/swagger.json" 80 | 81 | cd "./types/${VERSION}.0" 82 | npm publish -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /types 3 | node_modules/ 4 | 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | bracketSpacing: false 2 | printWidth: 100 3 | semi: false 4 | singleQuote: true 5 | trailingComma: es5 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kubernetes-types 2 | 3 | This package provides TypeScript definitions for Kubernetes API types, generated from the Kubernetes OpenAPI definitions. 4 | 5 | ## Example 6 | 7 | ```typescript 8 | import {Pod} from 'kubernetes-types/core/v1' 9 | import {ObjectMeta} from 'kubernetes-types/meta/v1' 10 | 11 | let metadata: ObjectMeta = {name: 'example', labels: {app: 'example'}} 12 | let pod: Pod = { 13 | apiVersion: 'v1', 14 | kind: 'Pod', // 'v1' and 'Pod' are the only accepted values for a Pod 15 | 16 | metadata, 17 | 18 | spec: { 19 | containers: [ 20 | /* ... */ 21 | ], 22 | }, 23 | } 24 | ``` 25 | 26 | ## Versioning 27 | 28 | As an NPM package, kubernetes-types follows semver. The major and minor version of the package will track the Kubernetes API version, while the patch version will follow updates to the generated types. 29 | 30 | You should install the version of the types matching the Kubernetes API version you want to be compatible with. Consult [NPM][versions] for the list of available versions of this package. 31 | 32 | [versions]: https://www.npmjs.com/package/kubernetes-types?activeTab=versions 33 | 34 | ## This repository 35 | 36 | This repository contains the code used to generate the TypeScript types, not the types themselves. 37 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # kubernetes-types 2 | 3 | This package provides TypeScript definitions for Kubernetes API types, generated from the Kubernetes OpenAPI definitions. 4 | 5 | ## Example 6 | 7 | ```typescript 8 | import {Pod} from 'kubernetes-types/core/v1' 9 | import {ObjectMeta} from 'kubernetes-types/meta/v1' 10 | 11 | let metadata: ObjectMeta = {name: 'example', labels: {app: 'example'}} 12 | let pod: Pod = { 13 | apiVersion: 'v1', 14 | kind: 'Pod', // 'v1' and 'Pod' are the only accepted values for a Pod 15 | 16 | metadata, 17 | 18 | spec: { 19 | containers: [ 20 | /* ... */ 21 | ], 22 | }, 23 | } 24 | ``` 25 | 26 | ## Versioning 27 | 28 | As an NPM package, kubernetes-types follows semver. The major and minor version of the package will track the Kubernetes API version, while the patch version will follow updates to the generated types. 29 | 30 | You should install the version of the types matching the Kubernetes API version you want to be compatible with. Consult [NPM][versions] for the list of available versions of this package. 31 | 32 | [versions]: https://www.npmjs.com/package/kubernetes-types?activeTab=versions 33 | -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubernetes-types", 3 | "version": "", 4 | "description": "TypeScript definitions of Kubernetes resource types", 5 | "repository": "https://github.com/silverlyra/kubernetes-types", 6 | "author": "Lyra Naeseth ", 7 | "license": "Apache-2.0", 8 | "devDependencies": { 9 | "typescript": ">=3.2.0" 10 | } 11 | } -------------------------------------------------------------------------------- /bin/generate-kubernetes-types: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as path from 'path'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 7 | 8 | await import(path.join(__dirname, '..', 'lib', 'generate', 'index.js')); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubernetes-type-generator", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "kubernetes-type-generator", 9 | "version": "0.1.0", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "argparse": "^1.0.10", 13 | "mkdirp": "^3.0.1", 14 | "node-fetch": "^3.3.2", 15 | "ts-morph": "^22.0.0" 16 | }, 17 | "devDependencies": { 18 | "@types/argparse": "^1.0.35", 19 | "@types/node": "^20.12.12", 20 | "prettier": "^1.15.3", 21 | "typescript": "^5.0.0" 22 | } 23 | }, 24 | "node_modules/@nodelib/fs.scandir": { 25 | "version": "2.1.5", 26 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 27 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 28 | "dependencies": { 29 | "@nodelib/fs.stat": "2.0.5", 30 | "run-parallel": "^1.1.9" 31 | }, 32 | "engines": { 33 | "node": ">= 8" 34 | } 35 | }, 36 | "node_modules/@nodelib/fs.scandir/node_modules/@nodelib/fs.stat": { 37 | "version": "2.0.5", 38 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 39 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 40 | "engines": { 41 | "node": ">= 8" 42 | } 43 | }, 44 | "node_modules/@nodelib/fs.walk": { 45 | "version": "1.2.8", 46 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 47 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 48 | "dependencies": { 49 | "@nodelib/fs.scandir": "2.1.5", 50 | "fastq": "^1.6.0" 51 | }, 52 | "engines": { 53 | "node": ">= 8" 54 | } 55 | }, 56 | "node_modules/@ts-morph/common": { 57 | "version": "0.23.0", 58 | "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.23.0.tgz", 59 | "integrity": "sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==", 60 | "dependencies": { 61 | "fast-glob": "^3.3.2", 62 | "minimatch": "^9.0.3", 63 | "mkdirp": "^3.0.1", 64 | "path-browserify": "^1.0.1" 65 | } 66 | }, 67 | "node_modules/@ts-morph/common/node_modules/@nodelib/fs.stat": { 68 | "version": "2.0.5", 69 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 70 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 71 | "engines": { 72 | "node": ">= 8" 73 | } 74 | }, 75 | "node_modules/@ts-morph/common/node_modules/brace-expansion": { 76 | "version": "2.0.1", 77 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 78 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 79 | "dependencies": { 80 | "balanced-match": "^1.0.0" 81 | } 82 | }, 83 | "node_modules/@ts-morph/common/node_modules/braces": { 84 | "version": "3.0.2", 85 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 86 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 87 | "dependencies": { 88 | "fill-range": "^7.0.1" 89 | }, 90 | "engines": { 91 | "node": ">=8" 92 | } 93 | }, 94 | "node_modules/@ts-morph/common/node_modules/fast-glob": { 95 | "version": "3.3.2", 96 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", 97 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 98 | "dependencies": { 99 | "@nodelib/fs.stat": "^2.0.2", 100 | "@nodelib/fs.walk": "^1.2.3", 101 | "glob-parent": "^5.1.2", 102 | "merge2": "^1.3.0", 103 | "micromatch": "^4.0.4" 104 | }, 105 | "engines": { 106 | "node": ">=8.6.0" 107 | } 108 | }, 109 | "node_modules/@ts-morph/common/node_modules/fill-range": { 110 | "version": "7.0.1", 111 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 112 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 113 | "dependencies": { 114 | "to-regex-range": "^5.0.1" 115 | }, 116 | "engines": { 117 | "node": ">=8" 118 | } 119 | }, 120 | "node_modules/@ts-morph/common/node_modules/glob-parent": { 121 | "version": "5.1.2", 122 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 123 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 124 | "dependencies": { 125 | "is-glob": "^4.0.1" 126 | }, 127 | "engines": { 128 | "node": ">= 6" 129 | } 130 | }, 131 | "node_modules/@ts-morph/common/node_modules/is-glob": { 132 | "version": "4.0.3", 133 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 134 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 135 | "dependencies": { 136 | "is-extglob": "^2.1.1" 137 | }, 138 | "engines": { 139 | "node": ">=0.10.0" 140 | } 141 | }, 142 | "node_modules/@ts-morph/common/node_modules/is-number": { 143 | "version": "7.0.0", 144 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 145 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 146 | "engines": { 147 | "node": ">=0.12.0" 148 | } 149 | }, 150 | "node_modules/@ts-morph/common/node_modules/micromatch": { 151 | "version": "4.0.5", 152 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 153 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 154 | "dependencies": { 155 | "braces": "^3.0.2", 156 | "picomatch": "^2.3.1" 157 | }, 158 | "engines": { 159 | "node": ">=8.6" 160 | } 161 | }, 162 | "node_modules/@ts-morph/common/node_modules/minimatch": { 163 | "version": "9.0.4", 164 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", 165 | "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", 166 | "dependencies": { 167 | "brace-expansion": "^2.0.1" 168 | }, 169 | "engines": { 170 | "node": ">=16 || 14 >=14.17" 171 | }, 172 | "funding": { 173 | "url": "https://github.com/sponsors/isaacs" 174 | } 175 | }, 176 | "node_modules/@ts-morph/common/node_modules/to-regex-range": { 177 | "version": "5.0.1", 178 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 179 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 180 | "dependencies": { 181 | "is-number": "^7.0.0" 182 | }, 183 | "engines": { 184 | "node": ">=8.0" 185 | } 186 | }, 187 | "node_modules/@types/argparse": { 188 | "version": "1.0.38", 189 | "dev": true, 190 | "license": "MIT" 191 | }, 192 | "node_modules/@types/node": { 193 | "version": "20.12.12", 194 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", 195 | "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", 196 | "dev": true, 197 | "dependencies": { 198 | "undici-types": "~5.26.4" 199 | } 200 | }, 201 | "node_modules/argparse": { 202 | "version": "1.0.10", 203 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 204 | "license": "MIT", 205 | "dependencies": { 206 | "sprintf-js": "~1.0.2" 207 | } 208 | }, 209 | "node_modules/balanced-match": { 210 | "version": "1.0.2", 211 | "license": "MIT" 212 | }, 213 | "node_modules/data-uri-to-buffer": { 214 | "version": "4.0.1", 215 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 216 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 217 | "engines": { 218 | "node": ">= 12" 219 | } 220 | }, 221 | "node_modules/fastq": { 222 | "version": "1.17.1", 223 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", 224 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 225 | "dependencies": { 226 | "reusify": "^1.0.4" 227 | } 228 | }, 229 | "node_modules/fetch-blob": { 230 | "version": "3.2.0", 231 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 232 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 233 | "funding": [ 234 | { 235 | "type": "github", 236 | "url": "https://github.com/sponsors/jimmywarting" 237 | }, 238 | { 239 | "type": "paypal", 240 | "url": "https://paypal.me/jimmywarting" 241 | } 242 | ], 243 | "dependencies": { 244 | "node-domexception": "^1.0.0", 245 | "web-streams-polyfill": "^3.0.3" 246 | }, 247 | "engines": { 248 | "node": "^12.20 || >= 14.13" 249 | } 250 | }, 251 | "node_modules/formdata-polyfill": { 252 | "version": "4.0.10", 253 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 254 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 255 | "dependencies": { 256 | "fetch-blob": "^3.1.2" 257 | }, 258 | "engines": { 259 | "node": ">=12.20.0" 260 | } 261 | }, 262 | "node_modules/is-extglob": { 263 | "version": "2.1.1", 264 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 265 | "license": "MIT", 266 | "engines": { 267 | "node": ">=0.10.0" 268 | } 269 | }, 270 | "node_modules/merge2": { 271 | "version": "1.4.1", 272 | "license": "MIT", 273 | "engines": { 274 | "node": ">= 8" 275 | } 276 | }, 277 | "node_modules/mkdirp": { 278 | "version": "3.0.1", 279 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", 280 | "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", 281 | "bin": { 282 | "mkdirp": "dist/cjs/src/bin.js" 283 | }, 284 | "engines": { 285 | "node": ">=10" 286 | }, 287 | "funding": { 288 | "url": "https://github.com/sponsors/isaacs" 289 | } 290 | }, 291 | "node_modules/node-domexception": { 292 | "version": "1.0.0", 293 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 294 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 295 | "funding": [ 296 | { 297 | "type": "github", 298 | "url": "https://github.com/sponsors/jimmywarting" 299 | }, 300 | { 301 | "type": "github", 302 | "url": "https://paypal.me/jimmywarting" 303 | } 304 | ], 305 | "engines": { 306 | "node": ">=10.5.0" 307 | } 308 | }, 309 | "node_modules/node-fetch": { 310 | "version": "3.3.2", 311 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 312 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 313 | "dependencies": { 314 | "data-uri-to-buffer": "^4.0.0", 315 | "fetch-blob": "^3.1.4", 316 | "formdata-polyfill": "^4.0.10" 317 | }, 318 | "engines": { 319 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 320 | }, 321 | "funding": { 322 | "type": "opencollective", 323 | "url": "https://opencollective.com/node-fetch" 324 | } 325 | }, 326 | "node_modules/path-browserify": { 327 | "version": "1.0.1", 328 | "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", 329 | "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" 330 | }, 331 | "node_modules/picomatch": { 332 | "version": "2.3.1", 333 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 334 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 335 | "engines": { 336 | "node": ">=8.6" 337 | }, 338 | "funding": { 339 | "url": "https://github.com/sponsors/jonschlinkert" 340 | } 341 | }, 342 | "node_modules/prettier": { 343 | "version": "1.19.1", 344 | "dev": true, 345 | "license": "MIT", 346 | "bin": { 347 | "prettier": "bin-prettier.js" 348 | }, 349 | "engines": { 350 | "node": ">=4" 351 | } 352 | }, 353 | "node_modules/queue-microtask": { 354 | "version": "1.2.3", 355 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 356 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 357 | "funding": [ 358 | { 359 | "type": "github", 360 | "url": "https://github.com/sponsors/feross" 361 | }, 362 | { 363 | "type": "patreon", 364 | "url": "https://www.patreon.com/feross" 365 | }, 366 | { 367 | "type": "consulting", 368 | "url": "https://feross.org/support" 369 | } 370 | ] 371 | }, 372 | "node_modules/reusify": { 373 | "version": "1.0.4", 374 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 375 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 376 | "engines": { 377 | "iojs": ">=1.0.0", 378 | "node": ">=0.10.0" 379 | } 380 | }, 381 | "node_modules/run-parallel": { 382 | "version": "1.2.0", 383 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 384 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 385 | "funding": [ 386 | { 387 | "type": "github", 388 | "url": "https://github.com/sponsors/feross" 389 | }, 390 | { 391 | "type": "patreon", 392 | "url": "https://www.patreon.com/feross" 393 | }, 394 | { 395 | "type": "consulting", 396 | "url": "https://feross.org/support" 397 | } 398 | ], 399 | "dependencies": { 400 | "queue-microtask": "^1.2.2" 401 | } 402 | }, 403 | "node_modules/sprintf-js": { 404 | "version": "1.0.3", 405 | "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 406 | "license": "BSD-3-Clause" 407 | }, 408 | "node_modules/ts-morph": { 409 | "version": "22.0.0", 410 | "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-22.0.0.tgz", 411 | "integrity": "sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==", 412 | "dependencies": { 413 | "@ts-morph/common": "~0.23.0", 414 | "code-block-writer": "^13.0.1" 415 | } 416 | }, 417 | "node_modules/ts-morph/node_modules/code-block-writer": { 418 | "version": "13.0.1", 419 | "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.1.tgz", 420 | "integrity": "sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==" 421 | }, 422 | "node_modules/typescript": { 423 | "version": "5.4.5", 424 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", 425 | "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", 426 | "dev": true, 427 | "bin": { 428 | "tsc": "bin/tsc", 429 | "tsserver": "bin/tsserver" 430 | }, 431 | "engines": { 432 | "node": ">=14.17" 433 | } 434 | }, 435 | "node_modules/undici-types": { 436 | "version": "5.26.5", 437 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 438 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 439 | "dev": true 440 | }, 441 | "node_modules/web-streams-polyfill": { 442 | "version": "3.3.3", 443 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 444 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 445 | "engines": { 446 | "node": ">= 8" 447 | } 448 | } 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubernetes-type-generator", 3 | "version": "0.1.0", 4 | "description": "Generate TypeScript definitions of Kubernetes resource types", 5 | "type": "module", 6 | "module": "lib/generate/index.mjs", 7 | "repository": "https://github.com/silverlyra/kubernetes-types", 8 | "author": "Lyra Naeseth ", 9 | "license": "Apache-2.0", 10 | "scripts": { 11 | "build": "tsc -p tsconfig.json" 12 | }, 13 | "dependencies": { 14 | "argparse": "^1.0.10", 15 | "mkdirp": "^3.0.1", 16 | "node-fetch": "^3.3.2", 17 | "ts-morph": "^22.0.0" 18 | }, 19 | "devDependencies": { 20 | "@types/argparse": "^1.0.35", 21 | "@types/node": "^20.12.12", 22 | "prettier": "^1.15.3", 23 | "typescript": "^5.0.0" 24 | } 25 | } -------------------------------------------------------------------------------- /src/generate/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * kubernetes-types-generator 3 | * 4 | * Generates TypeScript types for Kubernetes API resources. 5 | */ 6 | 7 | import {ArgumentParser} from 'argparse' 8 | import {readFileSync, writeFileSync} from 'fs' 9 | import {sync as mkdirpSync} from 'mkdirp' 10 | import fetch from 'node-fetch' 11 | import * as path from 'path' 12 | import {Project, ScriptTarget} from 'ts-morph' 13 | import {fileURLToPath} from 'url' 14 | 15 | import {API} from '../openapi/index.js' 16 | import generate from '../openapi/generate.js' 17 | 18 | const __dirname = path.dirname(fileURLToPath(import.meta.url)) 19 | const assetsPath = path.normalize(path.join(__dirname, '..', '..', 'assets')) 20 | 21 | interface Arguments { 22 | api: string 23 | file: string | undefined 24 | patch: number 25 | beta: number | undefined 26 | } 27 | 28 | async function main({api: apiVersion, file, patch, beta}: Arguments) { 29 | apiVersion = normalizeVersion(apiVersion) 30 | 31 | let api: API = file ? JSON.parse(readFileSync(file, 'utf8')) : await fetchAPI(apiVersion) 32 | 33 | let proj = new Project({ 34 | compilerOptions: {target: ScriptTarget.ES2016, declaration: true}, 35 | useInMemoryFileSystem: true, 36 | }) 37 | 38 | generate(proj, api) 39 | let result = proj.emitToMemory({emitOnlyDtsFiles: true}) 40 | let files = result.getFiles() 41 | 42 | const version = releaseVersion(apiVersion, {patch, beta}) 43 | const destPath = path.normalize(path.join(__dirname, '..', '..', 'types', `v${version}`)) 44 | for (let {filePath, text} of files) { 45 | let destFilePath = path.join(destPath, filePath.replace(/^\//, '')) 46 | mkdirpSync(path.dirname(destFilePath)) 47 | writeFileSync(destFilePath, text, 'utf8') 48 | console.log(`v${version}${filePath}`) 49 | } 50 | 51 | let generatedPackage = JSON.parse(readFileSync(path.join(assetsPath, 'package.json'), 'utf8')) 52 | generatedPackage.version = version 53 | writeFileSync( 54 | path.join(destPath, 'package.json'), 55 | JSON.stringify(generatedPackage, null, 2), 56 | 'utf8' 57 | ) 58 | 59 | writeFileSync( 60 | path.join(destPath, 'README.md'), 61 | readFileSync(path.join(assetsPath, 'README.md'), 'utf8'), 62 | 'utf8' 63 | ) 64 | } 65 | 66 | function normalizeVersion(version: string): string { 67 | if (/^\d/.test(version)) { 68 | version = `v${version}` 69 | } 70 | if (/^v\d+\.\d+$/.test(version)) { 71 | version = `${version}.0` 72 | } 73 | 74 | return version 75 | } 76 | 77 | async function fetchAPI(version: string): Promise { 78 | let response = await fetch( 79 | `https://raw.githubusercontent.com/kubernetes/kubernetes/${version}/api/openapi-spec/swagger.json` 80 | ) 81 | 82 | let api = await response.json() 83 | return api as API 84 | } 85 | 86 | function releaseVersion( 87 | apiVersion: string, 88 | {patch, beta}: Pick 89 | ): string { 90 | let [major, minor] = apiVersion.replace(/^v/, '').split('.') 91 | let version = `${major}.${minor}.${patch}` 92 | if (beta) { 93 | version += `-beta.${beta}` 94 | } 95 | return version 96 | } 97 | 98 | const parser = new ArgumentParser({ 99 | description: 'Generate TypeScript types for the Kubernetes API', 100 | }) 101 | parser.addArgument(['-a', '--api'], {help: 'Kubernetes API version', defaultValue: 'master'}) 102 | parser.addArgument(['-f', '--file'], {help: 'Path to local swagger.json file'}) 103 | parser.addArgument(['-p', '--patch'], { 104 | help: 'Patch version of generated types', 105 | type: Number, 106 | defaultValue: 0, 107 | }) 108 | parser.addArgument('--beta', {help: 'Create a beta release', type: Number}) 109 | 110 | main(parser.parseArgs()).catch(err => { 111 | console.error(err.stack) 112 | process.exit(1) 113 | }) 114 | -------------------------------------------------------------------------------- /src/generate/util.ts: -------------------------------------------------------------------------------- 1 | import {Project, SourceFile} from 'ts-morph' 2 | 3 | export class Imports { 4 | private imports: Map> = new Map() 5 | 6 | constructor(private file: SourceFile) {} 7 | 8 | public add(from: SourceFile, name: string): this { 9 | if (from === this.file) { 10 | return this 11 | } 12 | 13 | let fileImports = this.imports.get(from) 14 | if (fileImports == null) { 15 | fileImports = new Set() 16 | this.imports.set(from, fileImports) 17 | } 18 | 19 | fileImports.add(name) 20 | return this 21 | } 22 | 23 | public apply() { 24 | for (let [from, names] of this.imports) { 25 | let relativePath = this.file.getDirectory().getRelativePathAsModuleSpecifierTo(from) 26 | this.file.addImportDeclaration({ 27 | moduleSpecifier: relativePath, 28 | namedImports: [...names].sort(), 29 | }) 30 | } 31 | } 32 | } 33 | 34 | export const ensureFile = (proj: Project, path: string): SourceFile => { 35 | let sourceFile = proj.getSourceFile(path) 36 | if (sourceFile == null) { 37 | sourceFile = proj.createSourceFile(path) 38 | } 39 | return sourceFile 40 | } 41 | 42 | export const filePath = (importPath: string): string => `${importPath}.ts` 43 | -------------------------------------------------------------------------------- /src/openapi/generate.ts: -------------------------------------------------------------------------------- 1 | import {Project, PropertySignatureStructure, StructureKind} from 'ts-morph' 2 | 3 | import {ensureFile, filePath, Imports} from '../generate/util.js' 4 | import {API, Definition, GroupVersionKind, resolve, Value} from './index.js' 5 | 6 | export default function generate(proj: Project, api: API) { 7 | let imports: Map = new Map() 8 | 9 | for (let {name, path, def} of definitions(api)) { 10 | if (name in elidedTypes) { 11 | continue 12 | } 13 | 14 | let file = ensureFile(proj, filePath(path)) 15 | let fileImports = imports.get(file.getFilePath()) 16 | if (fileImports == null) { 17 | fileImports = new Imports(file) 18 | imports.set(file.getFilePath(), fileImports) 19 | } 20 | 21 | if (name in scalarTypes) { 22 | file.addTypeAlias({ 23 | name, 24 | isExported: true, 25 | type: scalarTypes[name], 26 | docs: def.description ? [{description: def.description}] : [], 27 | }) 28 | } else { 29 | file.addInterface({ 30 | name, 31 | isExported: true, 32 | properties: properties(proj, api, def, fileImports), 33 | docs: def.description ? [{description: def.description}] : [], 34 | }) 35 | } 36 | } 37 | 38 | for (let fileImports of imports.values()) { 39 | fileImports.apply() 40 | } 41 | } 42 | 43 | interface ResolvedDefinition { 44 | name: string 45 | path: string 46 | def: Definition 47 | } 48 | 49 | export function definitions(api: API): ResolvedDefinition[] { 50 | let defs = [] 51 | 52 | for (let name of Object.keys(api.definitions)) { 53 | let parsed = parseDefName(name) 54 | if (parsed == null) { 55 | continue 56 | } 57 | 58 | defs.push({...parsed, def: api.definitions[name]}) 59 | } 60 | 61 | return defs 62 | } 63 | 64 | export function properties( 65 | proj: Project, 66 | api: API, 67 | {required, properties: props, 'x-kubernetes-group-version-kind': gvk}: Definition, 68 | imports: Imports 69 | ): PropertySignatureStructure[] { 70 | if (!props) { 71 | return [] 72 | } 73 | 74 | return Object.keys(props).map(name => { 75 | let prop = props[name] 76 | return { 77 | name, 78 | kind: StructureKind.PropertySignature, 79 | type: kindType(gvk, name) || type(proj, api, imports, prop), 80 | docs: prop.description ? [prop.description] : [], 81 | hasQuestionToken: !(required || []).includes(name), 82 | isReadonly: prop.description ? prop.description.includes('Read-only.') : false, 83 | } 84 | }) 85 | } 86 | 87 | export function kindType( 88 | gvkList: GroupVersionKind[] | undefined, 89 | propName: string 90 | ): string | undefined { 91 | if (gvkList != null && gvkList.length === 1) { 92 | const gvk = gvkList[0] 93 | if (propName === 'apiVersion') { 94 | return JSON.stringify([gvk.group, gvk.version].filter(Boolean).join('/')) 95 | } else if (propName === 'kind') { 96 | return JSON.stringify(gvk.kind) 97 | } 98 | } 99 | 100 | return undefined 101 | } 102 | 103 | export function type(proj: Project, api: API, imports: Imports, value: Value): string { 104 | let t = '' 105 | 106 | if ('$ref' in value) { 107 | let ref = parseDefName(resolve(api, value).name) 108 | if (ref == null) { 109 | throw new Error(`Value references excluded type: ${JSON.stringify(value)}`) 110 | } 111 | 112 | if (ref.name in elidedTypes) { 113 | t = elidedTypes[ref.name] 114 | } else { 115 | imports.add(ensureFile(proj, filePath(ref.path)), ref.name) 116 | t = ref.name 117 | } 118 | } else if ('type' in value) { 119 | switch (value.type) { 120 | case 'string': 121 | case 'number': 122 | case 'boolean': 123 | t = value.type 124 | break 125 | case 'integer': 126 | t = 'number' 127 | break 128 | case 'object': 129 | t = `{[name: string]: ${type(proj, api, imports, value.additionalProperties)}}` 130 | break 131 | case 'array': 132 | t = `Array<${type(proj, api, imports, value.items)}>` 133 | break 134 | default: 135 | assertNever(value) 136 | } 137 | } else { 138 | assertNever(value) 139 | } 140 | 141 | return t 142 | } 143 | 144 | const simplifyDefName = (name: string): string | undefined => { 145 | const simplifications = { 146 | 'io.k8s.api.': '', 147 | 'io.k8s.apimachinery.pkg.apis.': '', 148 | 'io.k8s.apimachinery.pkg.': '', 149 | 'io.k8s.apiextensions-apiserver.pkg.apis.': '', 150 | } 151 | for (let [prefix, replacement] of Object.entries(simplifications)) { 152 | if (name.startsWith(prefix)) { 153 | return `${replacement}${name.slice(prefix.length)}` 154 | } 155 | } 156 | 157 | return undefined 158 | } 159 | 160 | export function parseDefName(name: string): {name: string; path: string} | undefined { 161 | let simplifiedName = simplifyDefName(name) 162 | if (simplifiedName == null) { 163 | return undefined 164 | } 165 | name = simplifiedName 166 | 167 | let parts = name.split('.') 168 | name = parts[parts.length - 1] 169 | let path = parts.slice(0, -1).join('/') 170 | 171 | return {name, path} 172 | } 173 | 174 | const elidedTypes: {[name: string]: string} = { 175 | IntOrString: 'number | string', 176 | } 177 | const scalarTypes: {[name: string]: string} = { 178 | Quantity: 'string', 179 | Time: 'string', 180 | MicroTime: 'string', 181 | JSONSchemaPropsOrArray: 'JSONSchemaProps | JSONSchemaProps[]', 182 | JSONSchemaPropsOrBool: 'JSONSchemaProps | boolean', 183 | JSONSchemaPropsOrStringArray: 'JSONSchemaProps | string[]', 184 | } 185 | 186 | const assertNever = (_: never) => { 187 | throw new Error('"unreachable" code was reached') 188 | } 189 | -------------------------------------------------------------------------------- /src/openapi/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An OpenAPI API. Only enough of it is implemented as we need. 3 | */ 4 | export interface API { 5 | info: APIInfo 6 | definitions: {[name: string]: Definition} 7 | } 8 | 9 | export interface APIInfo { 10 | title: string 11 | version: string 12 | } 13 | 14 | export interface Definition { 15 | description: string 16 | required?: string[] 17 | properties?: {[name: string]: Property} 18 | 'x-kubernetes-group-version-kind'?: GroupVersionKind[] 19 | } 20 | 21 | export interface GroupVersionKind { 22 | group: string 23 | version: string 24 | kind: string 25 | } 26 | 27 | export interface PropertyMeta { 28 | description: string 29 | } 30 | 31 | export type Property = PropertyMeta & Value 32 | 33 | export type Value = ScalarValue | ArrayValue | ObjectValue | Reference 34 | 35 | export interface Reference { 36 | $ref: string 37 | } 38 | 39 | export interface ScalarValue { 40 | type: 'string' | 'integer' | 'number' | 'boolean' 41 | } 42 | 43 | export interface ArrayValue { 44 | type: 'array' 45 | items: Value 46 | } 47 | 48 | export interface ObjectValue { 49 | type: 'object' 50 | additionalProperties: Value 51 | } 52 | 53 | export const resolve = (api: API, {$ref: ref}: Reference): {name: string; def: Definition} => { 54 | const prefix = '#/definitions/' 55 | if (!ref.startsWith(prefix)) { 56 | throw new Error(`Invalid or unsupported $ref: ${JSON.stringify(ref)}`) 57 | } 58 | 59 | let name = ref.slice(prefix.length) 60 | let def = api.definitions[name] 61 | if (def == null) { 62 | throw new Error(`Failed to resolve ${name} in ${api.info.title}/${api.info.version}.`) 63 | } 64 | 65 | return {name, def} 66 | } 67 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "lib": ["esnext"], 5 | "outDir": "lib", 6 | "rootDir": "src", 7 | "module": "nodenext", 8 | "resolveJsonModule": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "esModuleInterop": true, 12 | 13 | "strict": true, 14 | "alwaysStrict": true, 15 | "noImplicitAny": true, 16 | "noImplicitThis": true, 17 | "strictFunctionTypes": true, 18 | "strictNullChecks": true, 19 | "strictPropertyInitialization": true, 20 | 21 | "noImplicitReturns": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | 25 | "pretty": true 26 | }, 27 | "include": ["src/**/*.ts"], 28 | "exclude": ["node_modules/**", "bin/**"] 29 | } 30 | --------------------------------------------------------------------------------