diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..cb2c84d --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm lint-staged diff --git a/.nvmrc b/.nvmrc index c9758a5..54c6511 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v23.6.0 +v24 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..c13e11e --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,53 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { includeIgnoreFile } from '@eslint/config-helpers' +import js from '@eslint/js' +import stylistic from '@stylistic/eslint-plugin' +import simpleImportSort from 'eslint-plugin-simple-import-sort' +import globals from 'globals' +import tseslint from 'typescript-eslint' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const gitignorePath = path.resolve(__dirname, '.gitignore') + +export default tseslint.config( + includeIgnoreFile(gitignorePath), + { ignores: ['dist/', 'node_modules/'] }, + + js.configs.recommended, + ...tseslint.configs.recommended, + + { + languageOptions: { + globals: globals.node, + }, + plugins: { + '@stylistic': stylistic, + 'simple-import-sort': simpleImportSort, + }, + rules: { + // Formatting + '@stylistic/semi': ['error', 'never'], + '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/comma-dangle': ['error', 'always-multiline'], + '@stylistic/indent': ['error', 2], + '@stylistic/brace-style': ['error', '1tbs'], + '@stylistic/operator-linebreak': ['error', 'before'], + + // Linting + curly: 'error', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'error', + 'no-console': 'off', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'off', + + // Relax some defaults + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + }, + }, +) diff --git a/package.json b/package.json index a5e9ae9..c97c06c 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,10 @@ "dev": "tsc --watch", "test": "vitest", "test:ui": "vitest --ui", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "prepare": "husky" }, "keywords": [ "kitops", @@ -40,15 +43,27 @@ "url": "https://github.com/kitops-ml/kitops-ts/issues" }, "devDependencies": { +"@eslint/config-helpers": "^0.6.0", + "@eslint/js": "^10.0.1", + "@stylistic/eslint-plugin": "^5.10.0", "@types/node": "^25.0.8", "@vitest/ui": "^4.0.17", + "eslint": "^10.3.0", + "eslint-plugin-simple-import-sort": "^13.0.0", + "globals": "^17.6.0", + "husky": "^9.1.7", + "lint-staged": "^17.0.3", "typescript": "^5.9.3", + "typescript-eslint": "^8.59.2", "vitest": "^4.0.17" }, "packageManager": "pnpm@10.30.3", "engines": { "node": ">=23.0.0" }, + "lint-staged": { + "src/**/*.ts": "eslint --fix" + }, "dependencies": { "yaml": "^2.8.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3523362..c45a12a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,15 +12,42 @@ importers: specifier: ^2.8.2 version: 2.8.2 devDependencies: + '@eslint/config-helpers': + specifier: ^0.6.0 + version: 0.6.0 + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.3.0) + '@stylistic/eslint-plugin': + specifier: ^5.10.0 + version: 5.10.0(eslint@10.3.0) '@types/node': specifier: ^25.0.8 version: 25.0.8 '@vitest/ui': specifier: ^4.0.17 version: 4.0.17(vitest@4.0.17) + eslint: + specifier: ^10.3.0 + version: 10.3.0 + eslint-plugin-simple-import-sort: + specifier: ^13.0.0 + version: 13.0.0(eslint@10.3.0) + globals: + specifier: ^17.6.0 + version: 17.6.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + lint-staged: + specifier: ^17.0.3 + version: 17.0.3 typescript: specifier: ^5.9.3 version: 5.9.3 + typescript-eslint: + specifier: ^8.59.2 + version: 8.59.2(eslint@10.3.0)(typescript@5.9.3) vitest: specifier: ^4.0.17 version: 4.0.17(@types/node@25.0.8)(@vitest/ui@4.0.17)(yaml@2.8.2) @@ -183,6 +210,69 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -223,66 +313,79 @@ packages: resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.55.1': resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.55.1': resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.55.1': resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.55.1': resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.55.1': resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.55.1': resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.55.1': resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.55.1': resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.55.1': resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.55.1': resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.55.1': resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.55.1': resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.55.1': resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} @@ -317,18 +420,89 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@stylistic/eslint-plugin@5.10.0': + resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@25.0.8': resolution: {integrity: sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==} + '@typescript-eslint/eslint-plugin@8.59.2': + resolution: {integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.59.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.59.2': + resolution: {integrity: sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.59.2': + resolution: {integrity: sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.59.2': + resolution: {integrity: sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.59.2': + resolution: {integrity: sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.59.2': + resolution: {integrity: sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.59.2': + resolution: {integrity: sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.59.2': + resolution: {integrity: sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.59.2': + resolution: {integrity: sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.59.2': + resolution: {integrity: sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/expect@4.0.17': resolution: {integrity: sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==} @@ -363,14 +537,78 @@ packages: '@vitest/utils@4.0.17': resolution: {integrity: sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -379,13 +617,84 @@ packages: engines: {node: '>=18'} hasBin: true + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-simple-import-sort@13.0.0: + resolution: {integrity: sha512-McAc+/Nlvcg4byY/CABGH8kqnefWBj8s3JA2okEtz8ixbECQgU46p0HkTUKa4YS7wvgGceimlc34p1nXqbWqtA==} + peerDependencies: + eslint: '>=5.0.0' + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.3.0: + resolution: {integrity: sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -398,6 +707,18 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -406,21 +727,136 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + engines: {node: '>=18'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lint-staged@17.0.3: + resolution: {integrity: sha512-wnvMRhzC3GNpjixxleiG+pAW09dHTUgBCjMS7XouAg5E7wKUc8YdfogpF7zIgvXKDbH+452O6+XpnKm6V67rPw==} + engines: {node: '>=22.22.1'} + hasBin: true + + listr2@10.2.1: + resolution: {integrity: sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==} + engines: {node: '>=22.13.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -431,22 +867,66 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rollup@4.55.1: resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -457,6 +937,22 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.1: + resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} + engines: {node: '>=20'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -464,6 +960,10 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -476,6 +976,23 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.59.2: + resolution: {integrity: sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -484,6 +1001,9 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -558,16 +1078,42 @@ packages: jsdom: optional: true + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@10.0.0: + resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + engines: {node: '>=20'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true + yaml@2.8.4: + resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + snapshots: '@esbuild/aix-ppc64@0.27.2': @@ -648,6 +1194,60 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0)': + dependencies: + eslint: 10.3.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.5': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.3.0)': + optionalDependencies: + eslint: 10.3.0 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.1': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@jridgewell/sourcemap-codec@1.5.5': {} '@polka/url@1.0.0-next.29': {} @@ -729,6 +1329,16 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@stylistic/eslint-plugin@5.10.0(eslint@10.3.0)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) + '@typescript-eslint/types': 8.59.2 + eslint: 10.3.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + estraverse: 5.3.0 + picomatch: 4.0.3 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -736,12 +1346,107 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.8': {} + '@types/json-schema@7.0.15': {} + '@types/node@25.0.8': dependencies: undici-types: 7.16.0 + '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@5.9.3))(eslint@10.3.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.59.2(eslint@10.3.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/type-utils': 8.59.2(eslint@10.3.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.2(eslint@10.3.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.2 + eslint: 10.3.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.2 + debug: 4.4.3 + eslint: 10.3.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.59.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@5.9.3) + '@typescript-eslint/types': 8.59.2 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.59.2': + dependencies: + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/visitor-keys': 8.59.2 + + '@typescript-eslint/tsconfig-utils@8.59.2(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.59.2(eslint@10.3.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.2(eslint@10.3.0)(typescript@5.9.3) + debug: 4.4.3 + eslint: 10.3.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.59.2': {} + + '@typescript-eslint/typescript-estree@8.59.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.59.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@5.9.3) + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/visitor-keys': 8.59.2 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.59.2(eslint@10.3.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) + eslint: 10.3.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.59.2': + dependencies: + '@typescript-eslint/types': 8.59.2 + eslint-visitor-keys: 5.0.1 + '@vitest/expect@4.0.17': dependencies: '@standard-schema/spec': 1.1.0 @@ -792,10 +1497,62 @@ snapshots: '@vitest/pretty-format': 4.0.17 tinyrainbow: 3.0.3 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + assertion-error@2.0.1: {} + balanced-match@4.0.4: {} + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + chai@6.2.2: {} + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@5.2.0: + dependencies: + slice-ansi: 8.0.0 + string-width: 8.2.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + emoji-regex@10.6.0: {} + + environment@1.1.0: {} + es-module-lexer@1.7.0: {} esbuild@0.27.2: @@ -827,45 +1584,265 @@ snapshots: '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 + escape-string-regexp@4.0.0: {} + + eslint-plugin-simple-import-sort@13.0.0(eslint@10.3.0): + dependencies: + eslint: 10.3.0 + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.3.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.5.5 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + expect-type@1.3.0: {} + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 fflate@0.8.2: {} + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + flatted@3.3.3: {} fsevents@2.3.3: optional: true + get-east-asian-width@1.6.0: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@17.6.0: {} + + husky@9.1.7: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.6.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lint-staged@17.0.3: + dependencies: + listr2: 10.2.1 + picomatch: 4.0.4 + string-argv: 0.3.2 + tinyexec: 1.1.2 + optionalDependencies: + yaml: 2.8.4 + + listr2@10.2.1: + dependencies: + cli-truncate: 5.2.0 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 10.0.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + mimic-function@5.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + mrmime@2.0.1: {} + ms@2.1.3: {} + nanoid@3.3.11: {} + natural-compare@1.4.0: {} + obug@2.1.1: {} + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + pathe@2.0.3: {} picocolors@1.1.1: {} picomatch@4.0.3: {} + picomatch@4.0.4: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rfdc@1.4.1: {} + rollup@4.55.1: dependencies: '@types/estree': 1.0.8 @@ -897,24 +1874,63 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.55.1 fsevents: 2.3.3 + semver@7.7.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 mrmime: 2.0.1 totalist: 3.0.1 + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + source-map-js@1.2.1: {} stackback@0.0.2: {} std-env@3.10.0: {} + string-argv@0.3.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + string-width@8.2.1: + dependencies: + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + tinybench@2.9.0: {} tinyexec@1.0.2: {} + tinyexec@1.1.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -924,10 +1940,33 @@ snapshots: totalist@3.0.1: {} + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.59.2(eslint@10.3.0)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@5.9.3))(eslint@10.3.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.2(eslint@10.3.0)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.2(eslint@10.3.0)(typescript@5.9.3) + eslint: 10.3.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} undici-types@7.16.0: {} + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + vite@7.3.1(@types/node@25.0.8)(yaml@2.8.2): dependencies: esbuild: 0.27.2 @@ -979,9 +2018,32 @@ snapshots: - tsx - yaml + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + + wrap-ansi@10.0.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 8.2.1 + strip-ansi: 7.2.0 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + yaml@2.8.2: {} + + yaml@2.8.4: + optional: true + + yocto-queue@0.1.0: {} diff --git a/src/commands/__tests__/diff.spec.ts b/src/commands/__tests__/diff.spec.ts index d85f417..984b54c 100644 --- a/src/commands/__tests__/diff.spec.ts +++ b/src/commands/__tests__/diff.spec.ts @@ -1,82 +1,83 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { diff } from '../diff'; -import { runCommand } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { runCommand } from '../../core/exec' +import { diff } from '../diff' -const mockRunCommand = vi.mocked(runCommand); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) describe('diff', () => { beforeEach(() => { - vi.clearAllMocks(); - }); + vi.clearAllMocks() + }) it('should call runCommand with both references', async () => { - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) - await diff('registry.example.com/org/model:v1', 'registry.example.com/org/model:v2'); + await diff('registry.example.com/org/model:v1', 'registry.example.com/org/model:v2') expect(mockRunCommand).toHaveBeenCalledWith('diff', [ 'registry.example.com/org/model:v1', 'registry.example.com/org/model:v2', - ]); - }); + ]) + }) it('should return base structure with no differences for empty output', async () => { - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) - const result = await diff('model:v1', 'model:v2'); + const result = await diff('model:v1', 'model:v2') - expect(result.modelKit1).toBe('model:v1'); - expect(result.modelKit2).toBe('model:v2'); - expect(result.configsDiffer).toBe(false); - expect(result.annotationsIdentical).toBe(true); - expect(result.sharedLayers).toEqual([]); - expect(result.uniqueToKit1).toEqual([]); - expect(result.uniqueToKit2).toEqual([]); - }); + expect(result.modelKit1).toBe('model:v1') + expect(result.modelKit2).toBe('model:v2') + expect(result.configsDiffer).toBe(false) + expect(result.annotationsIdentical).toBe(true) + expect(result.sharedLayers).toEqual([]) + expect(result.uniqueToKit1).toEqual([]) + expect(result.uniqueToKit2).toEqual([]) + }) it('should parse configs differ section', async () => { const output = `Configurations: Configs differ: ModelKit1 Config Digest: sha256:abc123 -ModelKit2 Config Digest: sha256:def456`; +ModelKit2 Config Digest: sha256:def456` - mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }) - const result = await diff('model:v1', 'model:v2'); + const result = await diff('model:v1', 'model:v2') - expect(result.configsDiffer).toBe(true); - expect(result.config1Digest).toBe('sha256:abc123'); - expect(result.config2Digest).toBe('sha256:def456'); - }); + expect(result.configsDiffer).toBe(true) + expect(result.config1Digest).toBe('sha256:abc123') + expect(result.config2Digest).toBe('sha256:def456') + }) it('should parse annotations differ', async () => { const output = `Annotations: -Annotations differ`; +Annotations differ` - mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }) - const result = await diff('model:v1', 'model:v2'); + const result = await diff('model:v1', 'model:v2') - expect(result.annotationsIdentical).toBe(false); - }); + expect(result.annotationsIdentical).toBe(false) + }) it('should parse shared layers', async () => { const output = `Shared Layers (2): --- Type | Digest | Size model | sha256:abc | 100MB -datasets | sha256:def | 50MB`; +datasets | sha256:def | 50MB` - mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }) - const result = await diff('model:v1', 'model:v2'); + const result = await diff('model:v1', 'model:v2') - expect(result.sharedLayers).toHaveLength(2); - expect(result.sharedLayers[0]).toEqual({ type: 'model', digest: 'sha256:abc', size: '100MB' }); - expect(result.sharedLayers[1]).toEqual({ type: 'datasets', digest: 'sha256:def', size: '50MB' }); - }); + expect(result.sharedLayers).toHaveLength(2) + expect(result.sharedLayers[0]).toEqual({ type: 'model', digest: 'sha256:abc', size: '100MB' }) + expect(result.sharedLayers[1]).toEqual({ type: 'datasets', digest: 'sha256:def', size: '50MB' }) + }) it('should parse unique layers for each kit', async () => { const output = `Unique Layers to ModelKit1 (1): @@ -87,34 +88,34 @@ code | sha256:111 | 10MB Unique Layers to ModelKit2 (1): --- Type | Digest | Size -docs | sha256:222 | 5MB`; +docs | sha256:222 | 5MB` - mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }) - const result = await diff('model:v1', 'model:v2'); + const result = await diff('model:v1', 'model:v2') - expect(result.uniqueToKit1).toHaveLength(1); - expect(result.uniqueToKit1[0]).toEqual({ type: 'code', digest: 'sha256:111', size: '10MB' }); - expect(result.uniqueToKit2).toHaveLength(1); - expect(result.uniqueToKit2[0]).toEqual({ type: 'docs', digest: 'sha256:222', size: '5MB' }); - }); + expect(result.uniqueToKit1).toHaveLength(1) + expect(result.uniqueToKit1[0]).toEqual({ type: 'code', digest: 'sha256:111', size: '10MB' }) + expect(result.uniqueToKit2).toHaveLength(1) + expect(result.uniqueToKit2[0]).toEqual({ type: 'docs', digest: 'sha256:222', size: '5MB' }) + }) it('should handle entries in layer sections', async () => { const output = `Unique Layers to ModelKit1 (0): --- -`; +` - mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: output, stderr: '', exitCode: 0 }) - const result = await diff('model:v1', 'model:v2'); + const result = await diff('model:v1', 'model:v2') - expect(result.uniqueToKit1).toEqual([]); - }); + expect(result.uniqueToKit1).toEqual([]) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')) await expect(diff('model:v1', 'model:v2')) - .rejects.toThrow('Kit command failed with exit code 1: not found'); - }); -}); + .rejects.toThrow('Kit command failed with exit code 1: not found') + }) +}) diff --git a/src/commands/__tests__/info.spec.ts b/src/commands/__tests__/info.spec.ts index 6d34af7..c83228f 100644 --- a/src/commands/__tests__/info.spec.ts +++ b/src/commands/__tests__/info.spec.ts @@ -1,79 +1,80 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { info } from '../info'; -import { runCommand, prepareArgs } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { prepareArgs,runCommand } from '../../core/exec' +import { info } from '../info' + +vi.mock('../../core/exec') vi.mock('yaml', () => ({ parse: vi.fn((str: string) => JSON.parse(str)), -})); +})) -const mockRunCommand = vi.mocked(runCommand); -const mockPrepareArgs = vi.mocked(prepareArgs); +const mockRunCommand = vi.mocked(runCommand) +const mockPrepareArgs = vi.mocked(prepareArgs) const mockKitfile = { package: { name: 'my-model', version: '1.0.0' }, model: { path: './model.bin' }, -}; +} describe('info', () => { beforeEach(() => { - vi.clearAllMocks(); - mockPrepareArgs.mockReturnValue([]); - }); + vi.clearAllMocks() + mockPrepareArgs.mockReturnValue([]) + }) it('should call runCommand with the given path', async () => { mockRunCommand.mockResolvedValue({ stdout: JSON.stringify(mockKitfile), stderr: '', exitCode: 0, - }); + }) - await info('registry.example.com/org/my-model:v1.0.0'); + await info('registry.example.com/org/my-model:v1.0.0') - expect(mockRunCommand).toHaveBeenCalledWith('info', ['registry.example.com/org/my-model:v1.0.0']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('info', ['registry.example.com/org/my-model:v1.0.0']) + }) it('should return the parsed Kitfile', async () => { mockRunCommand.mockResolvedValue({ stdout: JSON.stringify(mockKitfile), stderr: '', exitCode: 0, - }); + }) - const result = await info('my-model:v1'); + const result = await info('my-model:v1') - expect(result.package?.name).toBe('my-model'); - expect(result.model?.path).toBe('./model.bin'); - }); + expect(result.package?.name).toBe('my-model') + expect(result.model?.path).toBe('./model.bin') + }) it('should attach a non-enumerable _raw property with the original output', async () => { - const raw = JSON.stringify(mockKitfile); - mockRunCommand.mockResolvedValue({ stdout: raw, stderr: '', exitCode: 0 }); + const raw = JSON.stringify(mockKitfile) + mockRunCommand.mockResolvedValue({ stdout: raw, stderr: '', exitCode: 0 }) - const result = await info('my-model:v1') as any; + const result = await info('my-model:v1') as any - expect(result._raw).toBe(raw); - expect(Object.keys(result)).not.toContain('_raw'); - }); + expect(result._raw).toBe(raw) + expect(Object.keys(result)).not.toContain('_raw') + }) it('should forward flags to prepareArgs', async () => { mockRunCommand.mockResolvedValue({ stdout: JSON.stringify(mockKitfile), stderr: '', exitCode: 0, - }); - mockPrepareArgs.mockReturnValue(['--remote']); + }) + mockPrepareArgs.mockReturnValue(['--remote']) - await info('my-model:v1', { remote: true }); + await info('my-model:v1', { remote: true }) - expect(mockPrepareArgs).toHaveBeenCalledWith({ remote: true }); - expect(mockRunCommand).toHaveBeenCalledWith('info', ['my-model:v1', '--remote']); - }); + expect(mockPrepareArgs).toHaveBeenCalledWith({ remote: true }) + expect(mockRunCommand).toHaveBeenCalledWith('info', ['my-model:v1', '--remote']) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')) await expect(info('nonexistent:v1')) - .rejects.toThrow('Kit command failed with exit code 1: not found'); - }); -}); + .rejects.toThrow('Kit command failed with exit code 1: not found') + }) +}) diff --git a/src/commands/__tests__/init.spec.ts b/src/commands/__tests__/init.spec.ts index 82dbab3..aeb017e 100644 --- a/src/commands/__tests__/init.spec.ts +++ b/src/commands/__tests__/init.spec.ts @@ -1,75 +1,76 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { init } from '../init'; -import { runCommand, prepareArgs } from '../../core/exec'; -import { resolve } from 'path'; +import { resolve } from 'path' +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); -vi.mock('path'); +import { prepareArgs,runCommand } from '../../core/exec' +import { init } from '../init' -const mockRunCommand = vi.mocked(runCommand); -const mockPrepareArgs = vi.mocked(prepareArgs); -const mockResolve = vi.mocked(resolve); +vi.mock('../../core/exec') +vi.mock('path') + +const mockRunCommand = vi.mocked(runCommand) +const mockPrepareArgs = vi.mocked(prepareArgs) +const mockResolve = vi.mocked(resolve) describe('init', () => { beforeEach(() => { - vi.clearAllMocks(); - mockPrepareArgs.mockReturnValue([]); - mockResolve.mockImplementation((path: string) => `/resolved/${path}`); - }); + vi.clearAllMocks() + mockPrepareArgs.mockReturnValue([]) + mockResolve.mockImplementation((path: string) => `/resolved/${path}`) + }) it('should work with no arguments', async () => { - await init(); + await init() - expect(mockRunCommand).toHaveBeenCalledWith('init', ['.']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('init', ['.']) + }) it('should call runCommand with default path and prepared args', async () => { - const flags = { name: 'test', desc: 'description', author: 'author', force: false }; - mockPrepareArgs.mockReturnValue(['--name', 'test', '--desc', 'description']); + const flags = { name: 'test', desc: 'description', author: 'author', force: false } + mockPrepareArgs.mockReturnValue(['--name', 'test', '--desc', 'description']) - await init('.', flags); + await init('.', flags) - expect(mockRunCommand).toHaveBeenCalledWith('init', ['.', '--name', 'test', '--desc', 'description']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('init', ['.', '--name', 'test', '--desc', 'description']) + }) it('should call runCommand with custom path', async () => { - const flags = { name: 'test', desc: 'description', author: 'author', force: false }; - const customPath = '/custom/path'; + const flags = { name: 'test', desc: 'description', author: 'author', force: false } + const customPath = '/custom/path' - await init(customPath, flags); + await init(customPath, flags) - expect(mockRunCommand).toHaveBeenCalledWith('init', [customPath]); - }); + expect(mockRunCommand).toHaveBeenCalledWith('init', [customPath]) + }) it('should return resolved paths', async () => { - const flags = { name: 'test', desc: 'description', author: 'author', force: false }; - mockResolve.mockReturnValueOnce('/resolved/path').mockReturnValueOnce('/resolved/path/Kitfile'); + const flags = { name: 'test', desc: 'description', author: 'author', force: false } + mockResolve.mockReturnValueOnce('/resolved/path').mockReturnValueOnce('/resolved/path/Kitfile') - const result = await init('./test', flags); + const result = await init('./test', flags) expect(result).toEqual({ path: '/resolved/path', - kitfilePath: '/resolved/path/Kitfile' - }); - expect(mockResolve).toHaveBeenCalledWith('./test'); - expect(mockResolve).toHaveBeenCalledWith('/resolved/path', 'Kitfile'); - }); + kitfilePath: '/resolved/path/Kitfile', + }) + expect(mockResolve).toHaveBeenCalledWith('./test') + expect(mockResolve).toHaveBeenCalledWith('/resolved/path', 'Kitfile') + }) it('should handle force flag', async () => { - const flags = { name: 'test', desc: 'description', author: 'author', force: true }; - mockPrepareArgs.mockReturnValue(['--force']); + const flags = { name: 'test', desc: 'description', author: 'author', force: true } + mockPrepareArgs.mockReturnValue(['--force']) - await init('.', flags); + await init('.', flags) - expect(mockPrepareArgs).toHaveBeenCalledWith(flags); - expect(mockRunCommand).toHaveBeenCalledWith('init', ['.', '--force']); - }); + expect(mockPrepareArgs).toHaveBeenCalledWith(flags) + expect(mockRunCommand).toHaveBeenCalledWith('init', ['.', '--force']) + }) it('should use default path when not provided', async () => { - const flags = { name: 'test', desc: 'description', author: 'author', force: false }; + const flags = { name: 'test', desc: 'description', author: 'author', force: false } - await init(undefined as any, flags); + await init(undefined as any, flags) - expect(mockRunCommand).toHaveBeenCalledWith('init', ['.']); - }); -}); \ No newline at end of file + expect(mockRunCommand).toHaveBeenCalledWith('init', ['.']) + }) +}) \ No newline at end of file diff --git a/src/commands/__tests__/inspect.spec.ts b/src/commands/__tests__/inspect.spec.ts index efd1872..0821857 100644 --- a/src/commands/__tests__/inspect.spec.ts +++ b/src/commands/__tests__/inspect.spec.ts @@ -1,11 +1,12 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { inspect } from '../inspect'; -import { runCommand } from '../../core/exec'; -import type { Manifest } from '../../types.d'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { runCommand } from '../../core/exec' +import type { Manifest } from '../../types.d' +import { inspect } from '../inspect' -const mockRunCommand = vi.mocked(runCommand); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) const mockManifest: Manifest = { digest: 'sha256:1234567890abcdef', @@ -25,7 +26,7 @@ const mockManifest: Manifest = { path: './model/part1.bin', digest: 'sha256:part1digest', diffId: 'sha256:part1diffid', - } + }, ], digest: 'sha256:modeldigest', diffId: 'sha256:modeldiffid', @@ -35,14 +36,14 @@ const mockManifest: Manifest = { path: './code/main.py', digest: 'sha256:codedigest', diffId: 'sha256:codediffid', - } + }, ], datasets: [ { path: './data/dataset.json', digest: 'sha256:datadigest', diffId: 'sha256:datadiffid', - } + }, ], docs: [ { @@ -50,8 +51,8 @@ const mockManifest: Manifest = { description: 'Documentation file', digest: 'sha256:docsdigest', diffId: 'sha256:docsdiffid', - } - ] + }, + ], }, manifest: { schemaVersion: 2, @@ -69,50 +70,50 @@ const mockManifest: Manifest = { annotations: { 'org.opencontainers.image.title': 'model-layer', }, - } + }, ], annotations: { 'org.opencontainers.image.created': '2024-01-01T00:00:00Z', - } - } + }, + }, } describe('inspect', () => { beforeEach(() => { - vi.clearAllMocks(); - }); + vi.clearAllMocks() + }) it('should call runCommand with correct arguments when modelkit is provided', async () => { mockRunCommand.mockResolvedValue({ stdout: JSON.stringify(mockManifest), stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - const result = await inspect('my-model'); + const result = await inspect('my-model') - expect(mockRunCommand).toHaveBeenCalledWith('inspect', ['my-model']); - expect(result).toEqual(mockManifest); - }); + expect(mockRunCommand).toHaveBeenCalledWith('inspect', ['my-model']) + expect(result).toEqual(mockManifest) + }) it('should parse JSON response correctly', async () => { mockRunCommand.mockResolvedValue({ stdout: JSON.stringify(mockManifest), stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - const result = await inspect('complex-model'); - expect(result).toEqual(mockManifest); - }); + const result = await inspect('complex-model') + expect(result).toEqual(mockManifest) + }) it('should throw error when JSON parsing fails', async () => { mockRunCommand.mockResolvedValue({ stdout: 'invalid json', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await expect(inspect('invalid-model')).rejects.toThrow(); - }); -}); \ No newline at end of file + await expect(inspect('invalid-model')).rejects.toThrow() + }) +}) \ No newline at end of file diff --git a/src/commands/__tests__/kit.spec.ts b/src/commands/__tests__/kit.spec.ts index 703d480..581b68c 100644 --- a/src/commands/__tests__/kit.spec.ts +++ b/src/commands/__tests__/kit.spec.ts @@ -1,46 +1,47 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { kit } from '../kit'; -import { runCommand } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { runCommand } from '../../core/exec' +import { kit } from '../kit' -const mockRunCommand = vi.mocked(runCommand); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) describe('kit', () => { beforeEach(() => { - vi.clearAllMocks(); - }); + vi.clearAllMocks() + }) it('should forward command and args to runCommand', async () => { - const expected = { stdout: 'output', stderr: '', exitCode: 0 }; - mockRunCommand.mockResolvedValue(expected); + const expected = { stdout: 'output', stderr: '', exitCode: 0 } + mockRunCommand.mockResolvedValue(expected) - const result = await kit('pack', ['.', '--tag', 'my-model:latest'], ''); + const result = await kit('pack', ['.', '--tag', 'my-model:latest'], '') - expect(mockRunCommand).toHaveBeenCalledWith('pack', ['.', '--tag', 'my-model:latest'], '', {}); - expect(result).toEqual(expected); - }); + expect(mockRunCommand).toHaveBeenCalledWith('pack', ['.', '--tag', 'my-model:latest'], '', {}) + expect(result).toEqual(expected) + }) it('should forward options to runCommand', async () => { - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) - await kit('version', [], '', { cwd: '/some/path' }); + await kit('version', [], '', { cwd: '/some/path' }) - expect(mockRunCommand).toHaveBeenCalledWith('version', [], '', { cwd: '/some/path' }); - }); + expect(mockRunCommand).toHaveBeenCalledWith('version', [], '', { cwd: '/some/path' }) + }) it('should return the raw ExecResult', async () => { - const expected = { stdout: 'kit v1.2.3', stderr: '', exitCode: 0 }; - mockRunCommand.mockResolvedValue(expected); + const expected = { stdout: 'kit v1.2.3', stderr: '', exitCode: 0 } + mockRunCommand.mockResolvedValue(expected) - const result = await kit('version', [], ''); + const result = await kit('version', [], '') - expect(result).toEqual(expected); - }); + expect(result).toEqual(expected) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: error')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: error')) - await expect(kit('pack', [], '')).rejects.toThrow('Kit command failed with exit code 1: error'); - }); -}); + await expect(kit('pack', [], '')).rejects.toThrow('Kit command failed with exit code 1: error') + }) +}) diff --git a/src/commands/__tests__/list.spec.ts b/src/commands/__tests__/list.spec.ts index 8da0da3..e26a36e 100644 --- a/src/commands/__tests__/list.spec.ts +++ b/src/commands/__tests__/list.spec.ts @@ -1,28 +1,29 @@ -import { describe, it, beforeEach, expect, vi } from 'vitest'; -import { list } from '../list'; -import { runCommand, parseTableOutput } from '../../core/exec'; +import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { parseTableOutput,runCommand } from '../../core/exec' +import { list } from '../list' -const mockRunCommand = vi.mocked(runCommand); -const mockParseTableOutput = vi.mocked(parseTableOutput); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) +const mockParseTableOutput = vi.mocked(parseTableOutput) describe('list', () => { beforeEach(() => { - vi.clearAllMocks(); - }); + vi.clearAllMocks() + }) it('should call runCommand with correct parameters', async () => { - mockRunCommand.mockResolvedValue({ stdout: 'test output', stderr: '', exitCode: 0 }); - mockParseTableOutput.mockReturnValue([]); + mockRunCommand.mockResolvedValue({ stdout: 'test output', stderr: '', exitCode: 0 }) + mockParseTableOutput.mockReturnValue([]) - await list('test-repo'); + await list('test-repo') - expect(mockRunCommand).toHaveBeenCalledWith('list', ['--format', 'table', 'test-repo']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('list', ['--format', 'table', 'test-repo']) + }) it('should parse table output and return results', async () => { - const mockOutput = 'test table output'; + const mockOutput = 'test table output' const mockParsedData = [ { repository: 'test-repo', @@ -30,32 +31,32 @@ describe('list', () => { maintainer: 'john@example.com', name: 'test-kit', size: '100MB', - digest: 'sha256:abc123' - } - ]; + digest: 'sha256:abc123', + }, + ] - mockRunCommand.mockResolvedValue({ stdout: mockOutput, stderr: '', exitCode: 0 }); - mockParseTableOutput.mockReturnValue(mockParsedData); + mockRunCommand.mockResolvedValue({ stdout: mockOutput, stderr: '', exitCode: 0 }) + mockParseTableOutput.mockReturnValue(mockParsedData) - const result = await list('test-repo'); + const result = await list('test-repo') - expect(mockParseTableOutput).toHaveBeenCalledWith(mockOutput); - expect(result).toEqual(mockParsedData); - }); + expect(mockParseTableOutput).toHaveBeenCalledWith(mockOutput) + expect(result).toEqual(mockParsedData) + }) it('should list local kits when no repository is given', async () => { - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); - mockParseTableOutput.mockReturnValue([]); + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + mockParseTableOutput.mockReturnValue([]) - await list(); + await list() - expect(mockRunCommand).toHaveBeenCalledWith('list', ['--format', 'table']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('list', ['--format', 'table']) + }) it('should propagate errors from runCommand', async () => { - const error = new Error('Command failed'); - mockRunCommand.mockRejectedValue(error); + const error = new Error('Command failed') + mockRunCommand.mockRejectedValue(error) - await expect(list('test-repo')).rejects.toThrow('Command failed'); - }); -}); \ No newline at end of file + await expect(list('test-repo')).rejects.toThrow('Command failed') + }) +}) \ No newline at end of file diff --git a/src/commands/__tests__/login.spec.ts b/src/commands/__tests__/login.spec.ts index 11e5d7b..93ce9df 100644 --- a/src/commands/__tests__/login.spec.ts +++ b/src/commands/__tests__/login.spec.ts @@ -1,163 +1,164 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { login, loginUnsafe } from '../login'; -import { runCommand } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { runCommand } from '../../core/exec' +import { login, loginUnsafe } from '../login' -const mockRunCommand = vi.mocked(runCommand); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) describe('loginUnsafe', () => { beforeEach(() => { - vi.clearAllMocks(); - }); + vi.clearAllMocks() + }) it('should call runCommand with correct arguments', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Login successful', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await loginUnsafe('registry.example.com', 'testuser', 'testpass'); + await loginUnsafe('registry.example.com', 'testuser', 'testpass') expect(mockRunCommand).toHaveBeenCalledWith('login', [ 'registry.example.com', '--username', 'testuser', '--password', - 'testpass' - ]); - }); + 'testpass', + ]) + }) it('should handle successful login', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Login Succeeded', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await expect(loginUnsafe('hub.docker.com', 'myuser', 'mypassword')).resolves.not.toThrow(); - }); + await expect(loginUnsafe('hub.docker.com', 'myuser', 'mypassword')).resolves.not.toThrow() + }) it('should propagate errors from runCommand', async () => { - const errorMessage = 'Kit command failed with exit code 1: authentication failed'; - mockRunCommand.mockRejectedValue(new Error(errorMessage)); + const errorMessage = 'Kit command failed with exit code 1: authentication failed' + mockRunCommand.mockRejectedValue(new Error(errorMessage)) await expect(loginUnsafe('registry.example.com', 'wronguser', 'wrongpass')) - .rejects.toThrow(errorMessage); - }); + .rejects.toThrow(errorMessage) + }) it('should handle empty registry parameter', async () => { mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await loginUnsafe('', 'testuser', 'testpass'); + await loginUnsafe('', 'testuser', 'testpass') expect(mockRunCommand).toHaveBeenCalledWith('login', [ '', '--username', 'testuser', '--password', - 'testpass' - ]); - }); + 'testpass', + ]) + }) it('should handle special characters in credentials', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Login Succeeded', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - const specialUsername = 'user@domain.com'; - const specialPassword = 'pass$word!123'; + const specialUsername = 'user@domain.com' + const specialPassword = 'pass$word!123' - await loginUnsafe('registry.example.com', specialUsername, specialPassword); + await loginUnsafe('registry.example.com', specialUsername, specialPassword) expect(mockRunCommand).toHaveBeenCalledWith('login', [ 'registry.example.com', '--username', specialUsername, '--password', - specialPassword - ]); - }); + specialPassword, + ]) + }) it('should handle different registry formats', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Login Succeeded', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const registries = [ 'localhost:5000', 'https://registry.example.com', 'registry.example.com:8080', 'gcr.io', - ]; + ] for (const registry of registries) { - await loginUnsafe(registry, 'user', 'pass'); + await loginUnsafe(registry, 'user', 'pass') expect(mockRunCommand).toHaveBeenCalledWith('login', [ registry, '--username', 'user', '--password', - 'pass' - ]); + 'pass', + ]) } - expect(mockRunCommand).toHaveBeenCalledTimes(registries.length); - }); + expect(mockRunCommand).toHaveBeenCalledTimes(registries.length) + }) it('should handle authentication failure gracefully', async () => { - const authError = 'Kit command failed with exit code 1: Error response from daemon: unauthorized'; - mockRunCommand.mockRejectedValue(new Error(authError)); + const authError = 'Kit command failed with exit code 1: Error response from daemon: unauthorized' + mockRunCommand.mockRejectedValue(new Error(authError)) await expect(loginUnsafe('registry.example.com', 'baduser', 'badpass')) - .rejects.toThrow('Kit command failed with exit code 1: Error response from daemon: unauthorized'); - }); + .rejects.toThrow('Kit command failed with exit code 1: Error response from daemon: unauthorized') + }) it('should handle network errors', async () => { - const networkError = 'Failed to execute kit command: connect ECONNREFUSED'; - mockRunCommand.mockRejectedValue(new Error(networkError)); + const networkError = 'Failed to execute kit command: connect ECONNREFUSED' + mockRunCommand.mockRejectedValue(new Error(networkError)) await expect(loginUnsafe('unreachable.registry.com', 'user', 'pass')) - .rejects.toThrow('Failed to execute kit command: connect ECONNREFUSED'); - }); -}); + .rejects.toThrow('Failed to execute kit command: connect ECONNREFUSED') + }) +}) describe('login', () => { beforeEach(() => { - vi.clearAllMocks(); - }); + vi.clearAllMocks() + }) it('should pass the password via stdin', async () => { - mockRunCommand.mockResolvedValue({ stdout: 'Login Succeeded', stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: 'Login Succeeded', stderr: '', exitCode: 0 }) - await login('registry.example.com', 'testuser', 'testpass'); + await login('registry.example.com', 'testuser', 'testpass') expect(mockRunCommand).toHaveBeenCalledWith( 'login', ['registry.example.com', '--username', 'testuser', '--password-stdin'], 'testpass', - ); - }); + ) + }) it('should resolve on successful login', async () => { - mockRunCommand.mockResolvedValue({ stdout: 'Login Succeeded', stderr: '', exitCode: 0 }); + mockRunCommand.mockResolvedValue({ stdout: 'Login Succeeded', stderr: '', exitCode: 0 }) - await expect(login('registry.example.com', 'user', 'pass')).resolves.not.toThrow(); - }); + await expect(login('registry.example.com', 'user', 'pass')).resolves.not.toThrow() + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: unauthorized')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: unauthorized')) await expect(login('registry.example.com', 'user', 'wrongpass')) - .rejects.toThrow('Kit command failed with exit code 1: unauthorized'); - }); -}); \ No newline at end of file + .rejects.toThrow('Kit command failed with exit code 1: unauthorized') + }) +}) \ No newline at end of file diff --git a/src/commands/__tests__/logout.spec.ts b/src/commands/__tests__/logout.spec.ts index ca0a2ee..dee8bb4 100644 --- a/src/commands/__tests__/logout.spec.ts +++ b/src/commands/__tests__/logout.spec.ts @@ -1,64 +1,65 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { logout } from '../logout'; -import { runCommand } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { runCommand } from '../../core/exec' +import { logout } from '../logout' -const mockRunCommand = vi.mocked(runCommand); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) describe('logout', () => { beforeEach(() => { - vi.clearAllMocks(); - }); + vi.clearAllMocks() + }) it('should call runCommand with correct arguments', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Logout successful', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await logout('registry.example.com'); + await logout('registry.example.com') - expect(mockRunCommand).toHaveBeenCalledWith('logout', ['registry.example.com']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('logout', ['registry.example.com']) + }) it('should handle successful logout', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Removing login credentials for registry.example.com', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await expect(logout('registry.example.com')).resolves.not.toThrow(); - }); + await expect(logout('registry.example.com')).resolves.not.toThrow() + }) it('should propagate errors from runCommand', async () => { - const errorMessage = 'Kit command failed with exit code 1: registry not found'; - mockRunCommand.mockRejectedValue(new Error(errorMessage)); + const errorMessage = 'Kit command failed with exit code 1: registry not found' + mockRunCommand.mockRejectedValue(new Error(errorMessage)) await expect(logout('nonexistent.registry.com')) - .rejects.toThrow(errorMessage); - }); + .rejects.toThrow(errorMessage) + }) it('should handle empty registry parameter', async () => { mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await logout(''); + await logout('') - expect(mockRunCommand).toHaveBeenCalledWith('logout', ['']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('logout', ['']) + }) it('should handle different registry formats', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Logout successful', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const registries = [ 'localhost:5000', @@ -66,68 +67,68 @@ describe('logout', () => { 'registry.example.com:8080', 'gcr.io', 'hub.docker.com', - ]; + ] for (const registry of registries) { - await logout(registry); - expect(mockRunCommand).toHaveBeenCalledWith('logout', [registry]); + await logout(registry) + expect(mockRunCommand).toHaveBeenCalledWith('logout', [registry]) } - expect(mockRunCommand).toHaveBeenCalledTimes(registries.length); - }); + expect(mockRunCommand).toHaveBeenCalledTimes(registries.length) + }) it('should handle logout when not logged in', async () => { - const notLoggedInError = 'Kit command failed with exit code 1: not logged in to registry.example.com'; - mockRunCommand.mockRejectedValue(new Error(notLoggedInError)); + const notLoggedInError = 'Kit command failed with exit code 1: not logged in to registry.example.com' + mockRunCommand.mockRejectedValue(new Error(notLoggedInError)) await expect(logout('registry.example.com')) - .rejects.toThrow('Kit command failed with exit code 1: not logged in to registry.example.com'); - }); + .rejects.toThrow('Kit command failed with exit code 1: not logged in to registry.example.com') + }) it('should handle network errors during logout', async () => { - const networkError = 'Failed to execute kit command: connect ECONNREFUSED'; - mockRunCommand.mockRejectedValue(new Error(networkError)); + const networkError = 'Failed to execute kit command: connect ECONNREFUSED' + mockRunCommand.mockRejectedValue(new Error(networkError)) await expect(logout('unreachable.registry.com')) - .rejects.toThrow('Failed to execute kit command: connect ECONNREFUSED'); - }); + .rejects.toThrow('Failed to execute kit command: connect ECONNREFUSED') + }) it('should handle registry with special characters in URL', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Logout successful', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - const specialRegistry = 'my-registry.example-domain.com:8080/v2'; + const specialRegistry = 'my-registry.example-domain.com:8080/v2' - await logout(specialRegistry); + await logout(specialRegistry) - expect(mockRunCommand).toHaveBeenCalledWith('logout', [specialRegistry]); - }); + expect(mockRunCommand).toHaveBeenCalledWith('logout', [specialRegistry]) + }) it('should handle multiple sequential logouts', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Logout successful', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await logout('registry1.example.com'); - await logout('registry2.example.com'); - await logout('registry3.example.com'); + await logout('registry1.example.com') + await logout('registry2.example.com') + await logout('registry3.example.com') - expect(mockRunCommand).toHaveBeenCalledTimes(3); - expect(mockRunCommand).toHaveBeenNthCalledWith(1, 'logout', ['registry1.example.com']); - expect(mockRunCommand).toHaveBeenNthCalledWith(2, 'logout', ['registry2.example.com']); - expect(mockRunCommand).toHaveBeenNthCalledWith(3, 'logout', ['registry3.example.com']); - }); + expect(mockRunCommand).toHaveBeenCalledTimes(3) + expect(mockRunCommand).toHaveBeenNthCalledWith(1, 'logout', ['registry1.example.com']) + expect(mockRunCommand).toHaveBeenNthCalledWith(2, 'logout', ['registry2.example.com']) + expect(mockRunCommand).toHaveBeenNthCalledWith(3, 'logout', ['registry3.example.com']) + }) it('should handle logout command execution failure', async () => { - const execError = 'Failed to execute kit command: command not found'; - mockRunCommand.mockRejectedValue(new Error(execError)); + const execError = 'Failed to execute kit command: command not found' + mockRunCommand.mockRejectedValue(new Error(execError)) await expect(logout('registry.example.com')) - .rejects.toThrow('Failed to execute kit command: command not found'); - }); -}); \ No newline at end of file + .rejects.toThrow('Failed to execute kit command: command not found') + }) +}) \ No newline at end of file diff --git a/src/commands/__tests__/pack.spec.ts b/src/commands/__tests__/pack.spec.ts index 22d3a36..0538502 100644 --- a/src/commands/__tests__/pack.spec.ts +++ b/src/commands/__tests__/pack.spec.ts @@ -1,295 +1,296 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { pack } from '../pack'; -import { runCommand, prepareArgs } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { prepareArgs,runCommand } from '../../core/exec' +import { pack } from '../pack' -const mockRunCommand = vi.mocked(runCommand); -const mockPrepareArgs = vi.mocked(prepareArgs); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) +const mockPrepareArgs = vi.mocked(prepareArgs) describe('pack', () => { beforeEach(() => { - vi.clearAllMocks(); + vi.clearAllMocks() // Mock prepareArgs to return the expected array format mockPrepareArgs.mockImplementation((options) => { - const args: string[] = []; + const args: string[] = [] Object.entries(options).forEach(([key, value]) => { if (typeof value === 'boolean' && value) { - args.push(`--${key}`); + args.push(`--${key}`) } else if (value !== undefined && value !== null && typeof value !== 'boolean') { - args.push(`--${key}`, String(value)); + args.push(`--${key}`, String(value)) } - }); - return args; - }); - }); + }) + return args + }) + }) it('should call runCommand with default directory when no arguments provided', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await pack(); + await pack() - expect(mockRunCommand).toHaveBeenCalledWith('pack', ['.']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('pack', ['.']) + }) it('should call runCommand with specified directory', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - await pack('/path/to/project'); + await pack('/path/to/project') - expect(mockRunCommand).toHaveBeenCalledWith('pack', ['/path/to/project']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('pack', ['/path/to/project']) + }) it('should handle pack with all flags', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const flags = { file: 'Kitfile', tag: 'my-model:v1.0.0', compression: 'gzip', - useModelPack: true - }; + useModelPack: true, + } - await pack('./src', flags); + await pack('./src', flags) - expect(prepareArgs).toHaveBeenCalledWith(flags); + expect(prepareArgs).toHaveBeenCalledWith(flags) expect(mockRunCommand).toHaveBeenCalledWith('pack', [ './src', '--file', 'Kitfile', '--tag', 'my-model:v1.0.0', '--compression', 'gzip', - '--useModelPack' - ]); - }); + '--useModelPack', + ]) + }) it('should handle pack with partial flags', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const flags = { file: 'CustomKitfile', tag: 'test-model:latest', compression: 'none', - useModelPack: false - }; + useModelPack: false, + } - await pack('.', flags); + await pack('.', flags) - expect(prepareArgs).toHaveBeenCalledWith(flags); + expect(prepareArgs).toHaveBeenCalledWith(flags) expect(mockRunCommand).toHaveBeenCalledWith('pack', [ '.', '--file', 'CustomKitfile', '--tag', 'test-model:latest', - '--compression', 'none' - ]); - }); + '--compression', 'none', + ]) + }) it('should handle pack with only file flag', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const flags = { file: 'MyKitfile', tag: '', compression: '', - useModelPack: false - }; + useModelPack: false, + } // Mock prepareArgs to handle empty strings appropriately - mockPrepareArgs.mockReturnValue(['--file', 'MyKitfile']); + mockPrepareArgs.mockReturnValue(['--file', 'MyKitfile']) - await pack('./models', flags); + await pack('./models', flags) - expect(prepareArgs).toHaveBeenCalledWith(flags); + expect(prepareArgs).toHaveBeenCalledWith(flags) expect(mockRunCommand).toHaveBeenCalledWith('pack', [ './models', - '--file', 'MyKitfile' - ]); - }); + '--file', 'MyKitfile', + ]) + }) it('should handle pack with only useModelPack flag', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const flags = { file: '', tag: '', compression: '', - useModelPack: true - }; + useModelPack: true, + } - mockPrepareArgs.mockReturnValue(['--useModelPack']); + mockPrepareArgs.mockReturnValue(['--useModelPack']) - await pack('.', flags); + await pack('.', flags) - expect(prepareArgs).toHaveBeenCalledWith(flags); + expect(prepareArgs).toHaveBeenCalledWith(flags) expect(mockRunCommand).toHaveBeenCalledWith('pack', [ '.', - '--useModelPack' - ]); - }); + '--useModelPack', + ]) + }) it('should propagate errors from runCommand', async () => { - const errorMessage = 'Kit command failed with exit code 1: Kitfile not found'; - mockRunCommand.mockRejectedValue(new Error(errorMessage)); + const errorMessage = 'Kit command failed with exit code 1: Kitfile not found' + mockRunCommand.mockRejectedValue(new Error(errorMessage)) await expect(pack('./nonexistent')) - .rejects.toThrow(errorMessage); - }); + .rejects.toThrow(errorMessage) + }) it('should handle different compression types', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) - const compressionTypes = ['gzip', 'zstd', 'none']; + const compressionTypes = ['gzip', 'zstd', 'none'] for (const compression of compressionTypes) { const flags = { file: 'Kitfile', tag: 'model:latest', compression, - useModelPack: false - }; + useModelPack: false, + } mockPrepareArgs.mockReturnValue([ '--file', 'Kitfile', '--tag', 'model:latest', - '--compression', compression - ]); + '--compression', compression, + ]) - await pack('.', flags); + await pack('.', flags) expect(mockRunCommand).toHaveBeenCalledWith('pack', [ '.', '--file', 'Kitfile', '--tag', 'model:latest', - '--compression', compression - ]); + '--compression', compression, + ]) } - }); + }) it('should handle different tag formats', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const tagFormats = [ 'simple-model:v1.0.0', 'registry.example.com/org/model:latest', 'localhost:5000/my-model:dev', 'model', - 'my-model:v2.1.0-alpha.1' - ]; + 'my-model:v2.1.0-alpha.1', + ] for (const tag of tagFormats) { const flags = { file: 'Kitfile', tag, compression: 'gzip', - useModelPack: false - }; + useModelPack: false, + } mockPrepareArgs.mockReturnValue([ '--file', 'Kitfile', '--tag', tag, - '--compression', 'gzip' - ]); + '--compression', 'gzip', + ]) - await pack('.', flags); + await pack('.', flags) expect(mockRunCommand).toHaveBeenCalledWith('pack', [ '.', '--file', 'Kitfile', '--tag', tag, - '--compression', 'gzip' - ]); + '--compression', 'gzip', + ]) } - }); + }) it('should handle relative and absolute directory paths', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Pack completed successfully', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const directories = [ '.', './src', '../project', '/absolute/path/to/project', - '~/home/user/project' - ]; + '~/home/user/project', + ] for (const directory of directories) { - await pack(directory); - expect(mockRunCommand).toHaveBeenCalledWith('pack', [directory]); + await pack(directory) + expect(mockRunCommand).toHaveBeenCalledWith('pack', [directory]) } - }); + }) it('should handle pack command execution failure', async () => { - const execError = 'Failed to execute kit command: permission denied'; - mockRunCommand.mockRejectedValue(new Error(execError)); + const execError = 'Failed to execute kit command: permission denied' + mockRunCommand.mockRejectedValue(new Error(execError)) await expect(pack('./restricted-dir')) - .rejects.toThrow('Failed to execute kit command: permission denied'); - }); + .rejects.toThrow('Failed to execute kit command: permission denied') + }) it('should handle invalid Kitfile path', async () => { - const kitfileError = 'Kit command failed with exit code 1: invalid Kitfile path'; - mockRunCommand.mockRejectedValue(new Error(kitfileError)); + const kitfileError = 'Kit command failed with exit code 1: invalid Kitfile path' + mockRunCommand.mockRejectedValue(new Error(kitfileError)) const flags = { file: '/invalid/path/Kitfile', tag: 'model:latest', compression: 'gzip', - useModelPack: false - }; + useModelPack: false, + } await expect(pack('.', flags)) - .rejects.toThrow('Kit command failed with exit code 1: invalid Kitfile path'); - }); + .rejects.toThrow('Kit command failed with exit code 1: invalid Kitfile path') + }) it('should handle successful pack operation', async () => { mockRunCommand.mockResolvedValue({ stdout: 'Successfully packed model:v1.0.0', stderr: '', - exitCode: 0 - }); + exitCode: 0, + }) const flags = { file: 'Kitfile', tag: 'model:v1.0.0', compression: 'gzip', - useModelPack: true - }; + useModelPack: true, + } - await expect(pack('./project', flags)).resolves.not.toThrow(); - }); -}); \ No newline at end of file + await expect(pack('./project', flags)).resolves.not.toThrow() + }) +}) \ No newline at end of file diff --git a/src/commands/__tests__/pull.spec.ts b/src/commands/__tests__/pull.spec.ts index 9358675..c6b6e8d 100644 --- a/src/commands/__tests__/pull.spec.ts +++ b/src/commands/__tests__/pull.spec.ts @@ -1,46 +1,47 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { pull } from '../pull'; -import { runCommand, prepareArgs } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { prepareArgs,runCommand } from '../../core/exec' +import { pull } from '../pull' -const mockRunCommand = vi.mocked(runCommand); -const mockPrepareArgs = vi.mocked(prepareArgs); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) +const mockPrepareArgs = vi.mocked(prepareArgs) describe('pull', () => { beforeEach(() => { - vi.clearAllMocks(); - mockPrepareArgs.mockReturnValue([]); - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); - }); + vi.clearAllMocks() + mockPrepareArgs.mockReturnValue([]) + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + }) it('should call runCommand with the reference', async () => { - await pull('registry.example.com/org/my-model:v1.0.0'); + await pull('registry.example.com/org/my-model:v1.0.0') - expect(mockRunCommand).toHaveBeenCalledWith('pull', ['registry.example.com/org/my-model:v1.0.0']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('pull', ['registry.example.com/org/my-model:v1.0.0']) + }) it('should forward TLS flags', async () => { - mockPrepareArgs.mockReturnValue(['--tls-cert=/path/to/cert.pem']); + mockPrepareArgs.mockReturnValue(['--tls-cert=/path/to/cert.pem']) - await pull('registry.example.com/org/my-model:v1.0.0', { tlsCert: '/path/to/cert.pem' }); + await pull('registry.example.com/org/my-model:v1.0.0', { tlsCert: '/path/to/cert.pem' }) expect(mockRunCommand).toHaveBeenCalledWith('pull', [ 'registry.example.com/org/my-model:v1.0.0', '--tls-cert=/path/to/cert.pem', - ]); - }); + ]) + }) it('should work without flags', async () => { - await pull('my-model:latest'); + await pull('my-model:latest') - expect(mockRunCommand).toHaveBeenCalledWith('pull', ['my-model:latest']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('pull', ['my-model:latest']) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')) await expect(pull('registry.example.com/org/my-model:v1.0.0')) - .rejects.toThrow('Kit command failed with exit code 1: not found'); - }); -}); + .rejects.toThrow('Kit command failed with exit code 1: not found') + }) +}) diff --git a/src/commands/__tests__/push.spec.ts b/src/commands/__tests__/push.spec.ts index 4b2ca11..420b20d 100644 --- a/src/commands/__tests__/push.spec.ts +++ b/src/commands/__tests__/push.spec.ts @@ -1,49 +1,50 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { push } from '../push'; -import { runCommand, prepareArgs } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { prepareArgs,runCommand } from '../../core/exec' +import { push } from '../push' -const mockRunCommand = vi.mocked(runCommand); -const mockPrepareArgs = vi.mocked(prepareArgs); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) +const mockPrepareArgs = vi.mocked(prepareArgs) describe('push', () => { beforeEach(() => { - vi.clearAllMocks(); - mockPrepareArgs.mockReturnValue([]); - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); - }); + vi.clearAllMocks() + mockPrepareArgs.mockReturnValue([]) + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + }) it('should call runCommand with the source reference', async () => { - await push('registry.example.com/org/my-model:v1.0.0'); + await push('registry.example.com/org/my-model:v1.0.0') - expect(mockRunCommand).toHaveBeenCalledWith('push', ['registry.example.com/org/my-model:v1.0.0']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('push', ['registry.example.com/org/my-model:v1.0.0']) + }) it('should include destination when provided', async () => { - await push('staging.example.com/my-model:rc1', 'registry.example.com/org/my-model:v1.0.0'); + await push('staging.example.com/my-model:rc1', 'registry.example.com/org/my-model:v1.0.0') expect(mockRunCommand).toHaveBeenCalledWith('push', [ 'staging.example.com/my-model:rc1', 'registry.example.com/org/my-model:v1.0.0', - ]); - }); + ]) + }) it('should forward TLS flags', async () => { - mockPrepareArgs.mockReturnValue(['--tls-verify=false']); + mockPrepareArgs.mockReturnValue(['--tls-verify=false']) - await push('registry.example.com/org/my-model:v1.0.0', undefined, { tlsVerify: false }); + await push('registry.example.com/org/my-model:v1.0.0', undefined, { tlsVerify: false }) expect(mockRunCommand).toHaveBeenCalledWith('push', [ 'registry.example.com/org/my-model:v1.0.0', '--tls-verify=false', - ]); - }); + ]) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: unauthorized')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: unauthorized')) await expect(push('registry.example.com/org/my-model:v1.0.0')) - .rejects.toThrow('Kit command failed with exit code 1: unauthorized'); - }); -}); + .rejects.toThrow('Kit command failed with exit code 1: unauthorized') + }) +}) diff --git a/src/commands/__tests__/remove.spec.ts b/src/commands/__tests__/remove.spec.ts index 56a4503..5fd8139 100644 --- a/src/commands/__tests__/remove.spec.ts +++ b/src/commands/__tests__/remove.spec.ts @@ -1,78 +1,79 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { remove, removeAll } from '../remove'; -import { runCommand, prepareArgs } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { prepareArgs,runCommand } from '../../core/exec' +import { remove, removeAll } from '../remove' -const mockRunCommand = vi.mocked(runCommand); -const mockPrepareArgs = vi.mocked(prepareArgs); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) +const mockPrepareArgs = vi.mocked(prepareArgs) describe('remove', () => { beforeEach(() => { - vi.clearAllMocks(); - mockPrepareArgs.mockReturnValue([]); - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); - }); + vi.clearAllMocks() + mockPrepareArgs.mockReturnValue([]) + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + }) it('should call runCommand with the path when no flags are given', async () => { - await remove('registry.example.com/org/my-model:v1.0.0'); + await remove('registry.example.com/org/my-model:v1.0.0') - expect(mockRunCommand).toHaveBeenCalledWith('remove', ['registry.example.com/org/my-model:v1.0.0']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('remove', ['registry.example.com/org/my-model:v1.0.0']) + }) it('should omit the path and add --all when all flag is set', async () => { - mockPrepareArgs.mockReturnValue(['--all']); + mockPrepareArgs.mockReturnValue(['--all']) - await remove('registry.example.com/org/my-model:v1.0.0', { all: true }); + await remove('registry.example.com/org/my-model:v1.0.0', { all: true }) - expect(mockRunCommand).toHaveBeenCalledWith('remove', ['--all']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('remove', ['--all']) + }) it('should pass additional flags alongside the path', async () => { - mockPrepareArgs.mockReturnValue(['--force', '--remote']); + mockPrepareArgs.mockReturnValue(['--force', '--remote']) - await remove('registry.example.com/org/my-model:v1.0.0', { force: true, remote: true }); + await remove('registry.example.com/org/my-model:v1.0.0', { force: true, remote: true }) expect(mockRunCommand).toHaveBeenCalledWith('remove', [ 'registry.example.com/org/my-model:v1.0.0', '--force', '--remote', - ]); - }); + ]) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')) await expect(remove('registry.example.com/org/my-model:v1.0.0')) - .rejects.toThrow('Kit command failed with exit code 1: not found'); - }); -}); + .rejects.toThrow('Kit command failed with exit code 1: not found') + }) +}) describe('removeAll', () => { beforeEach(() => { - vi.clearAllMocks(); - mockPrepareArgs.mockReturnValue([]); - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); - }); + vi.clearAllMocks() + mockPrepareArgs.mockReturnValue([]) + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + }) it('should call runCommand with --all', async () => { - await removeAll(); + await removeAll() - expect(mockRunCommand).toHaveBeenCalledWith('remove', ['--all']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('remove', ['--all']) + }) it('should pass additional flags after --all', async () => { - mockPrepareArgs.mockReturnValue(['--force']); + mockPrepareArgs.mockReturnValue(['--force']) - await removeAll({ force: true }); + await removeAll({ force: true }) - expect(mockRunCommand).toHaveBeenCalledWith('remove', ['--all', '--force']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('remove', ['--all', '--force']) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: permission denied')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: permission denied')) await expect(removeAll()) - .rejects.toThrow('Kit command failed with exit code 1: permission denied'); - }); -}); + .rejects.toThrow('Kit command failed with exit code 1: permission denied') + }) +}) diff --git a/src/commands/__tests__/tag.spec.ts b/src/commands/__tests__/tag.spec.ts index 86623b7..15b3116 100644 --- a/src/commands/__tests__/tag.spec.ts +++ b/src/commands/__tests__/tag.spec.ts @@ -1,36 +1,37 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { tag } from '../tag'; -import { runCommand } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { runCommand } from '../../core/exec' +import { tag } from '../tag' -const mockRunCommand = vi.mocked(runCommand); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) describe('tag', () => { beforeEach(() => { - vi.clearAllMocks(); - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); - }); + vi.clearAllMocks() + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + }) it('should call runCommand with source and destination', async () => { - await tag('registry.example.com/org/my-model:rc1', 'registry.example.com/org/my-model:v1.0.0'); + await tag('registry.example.com/org/my-model:rc1', 'registry.example.com/org/my-model:v1.0.0') expect(mockRunCommand).toHaveBeenCalledWith('tag', [ 'registry.example.com/org/my-model:rc1', 'registry.example.com/org/my-model:v1.0.0', - ]); - }); + ]) + }) it('should support local references without a registry', async () => { - await tag('my-model:dev', 'my-model:stable'); + await tag('my-model:dev', 'my-model:stable') - expect(mockRunCommand).toHaveBeenCalledWith('tag', ['my-model:dev', 'my-model:stable']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('tag', ['my-model:dev', 'my-model:stable']) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: not found')) await expect(tag('my-model:v1', 'my-model:v2')) - .rejects.toThrow('Kit command failed with exit code 1: not found'); - }); -}); + .rejects.toThrow('Kit command failed with exit code 1: not found') + }) +}) diff --git a/src/commands/__tests__/unpack.spec.ts b/src/commands/__tests__/unpack.spec.ts index e7389cf..d60bc55 100644 --- a/src/commands/__tests__/unpack.spec.ts +++ b/src/commands/__tests__/unpack.spec.ts @@ -1,54 +1,55 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { unpack } from '../unpack'; -import { runCommand, prepareArgs } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { prepareArgs,runCommand } from '../../core/exec' +import { unpack } from '../unpack' -const mockRunCommand = vi.mocked(runCommand); -const mockPrepareArgs = vi.mocked(prepareArgs); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) +const mockPrepareArgs = vi.mocked(prepareArgs) describe('unpack', () => { beforeEach(() => { - vi.clearAllMocks(); - mockPrepareArgs.mockReturnValue([]); - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); - }); + vi.clearAllMocks() + mockPrepareArgs.mockReturnValue([]) + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + }) it('should call runCommand with the destination path', async () => { - await unpack('./output'); + await unpack('./output') - expect(mockRunCommand).toHaveBeenCalledWith('unpack', ['./output']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('unpack', ['./output']) + }) it('should forward flags to prepareArgs', async () => { - mockPrepareArgs.mockReturnValue(['--filter=model']); + mockPrepareArgs.mockReturnValue(['--filter=model']) - await unpack('./output', { filter: 'model' }); + await unpack('./output', { filter: 'model' }) - expect(mockPrepareArgs).toHaveBeenCalledWith({ filter: 'model' }); - expect(mockRunCommand).toHaveBeenCalledWith('unpack', ['./output', '--filter=model']); - }); + expect(mockPrepareArgs).toHaveBeenCalledWith({ filter: 'model' }) + expect(mockRunCommand).toHaveBeenCalledWith('unpack', ['./output', '--filter=model']) + }) it('should support overwrite flag', async () => { - mockPrepareArgs.mockReturnValue(['--overwrite']); + mockPrepareArgs.mockReturnValue(['--overwrite']) - await unpack('./output', { overwrite: true }); + await unpack('./output', { overwrite: true }) - expect(mockRunCommand).toHaveBeenCalledWith('unpack', ['./output', '--overwrite']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('unpack', ['./output', '--overwrite']) + }) it('should support ignoreExisting flag', async () => { - mockPrepareArgs.mockReturnValue(['--ignore-existing']); + mockPrepareArgs.mockReturnValue(['--ignore-existing']) - await unpack('./output', { ignoreExisting: true }); + await unpack('./output', { ignoreExisting: true }) - expect(mockRunCommand).toHaveBeenCalledWith('unpack', ['./output', '--ignore-existing']); - }); + expect(mockRunCommand).toHaveBeenCalledWith('unpack', ['./output', '--ignore-existing']) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: permission denied')); + mockRunCommand.mockRejectedValue(new Error('Kit command failed with exit code 1: permission denied')) await expect(unpack('./output')) - .rejects.toThrow('Kit command failed with exit code 1: permission denied'); - }); -}); + .rejects.toThrow('Kit command failed with exit code 1: permission denied') + }) +}) diff --git a/src/commands/__tests__/version.spec.ts b/src/commands/__tests__/version.spec.ts index 5fa69d5..ca95b49 100644 --- a/src/commands/__tests__/version.spec.ts +++ b/src/commands/__tests__/version.spec.ts @@ -1,42 +1,43 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { version } from '../version'; -import { runCommand, parseKeyValueOutput } from '../../core/exec'; +import { beforeEach,describe, expect, it, vi } from 'vitest' -vi.mock('../../core/exec'); +import { parseKeyValueOutput,runCommand } from '../../core/exec' +import { version } from '../version' -const mockRunCommand = vi.mocked(runCommand); -const mockParseKeyValueOutput = vi.mocked(parseKeyValueOutput); +vi.mock('../../core/exec') + +const mockRunCommand = vi.mocked(runCommand) +const mockParseKeyValueOutput = vi.mocked(parseKeyValueOutput) describe('version', () => { beforeEach(() => { - vi.clearAllMocks(); - }); + vi.clearAllMocks() + }) it('should call runCommand with the version subcommand', async () => { - mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }); - mockParseKeyValueOutput.mockReturnValue({} as any); + mockRunCommand.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }) + mockParseKeyValueOutput.mockReturnValue({} as any) - await version(); + await version() - expect(mockRunCommand).toHaveBeenCalledWith('version'); - }); + expect(mockRunCommand).toHaveBeenCalledWith('version') + }) it('should return parsed version info', async () => { - const raw = 'Version: 1.2.3\nCommit: abc123\nBuilt: 2024-01-01\nGo Version: go1.21.0'; - const parsed = { version: '1.2.3', commit: 'abc123', built: '2024-01-01', goVersion: 'go1.21.0' }; + const raw = 'Version: 1.2.3\nCommit: abc123\nBuilt: 2024-01-01\nGo Version: go1.21.0' + const parsed = { version: '1.2.3', commit: 'abc123', built: '2024-01-01', goVersion: 'go1.21.0' } - mockRunCommand.mockResolvedValue({ stdout: raw, stderr: '', exitCode: 0 }); - mockParseKeyValueOutput.mockReturnValue(parsed); + mockRunCommand.mockResolvedValue({ stdout: raw, stderr: '', exitCode: 0 }) + mockParseKeyValueOutput.mockReturnValue(parsed) - const result = await version(); + const result = await version() - expect(mockParseKeyValueOutput).toHaveBeenCalledWith(raw); - expect(result).toEqual(parsed); - }); + expect(mockParseKeyValueOutput).toHaveBeenCalledWith(raw) + expect(result).toEqual(parsed) + }) it('should propagate errors from runCommand', async () => { - mockRunCommand.mockRejectedValue(new Error('Failed to execute kit command: ENOENT')); + mockRunCommand.mockRejectedValue(new Error('Failed to execute kit command: ENOENT')) - await expect(version()).rejects.toThrow('Failed to execute kit command: ENOENT'); - }); -}); + await expect(version()).rejects.toThrow('Failed to execute kit command: ENOENT') + }) +}) diff --git a/src/commands/diff.ts b/src/commands/diff.ts index 134f301..0c7e955 100644 --- a/src/commands/diff.ts +++ b/src/commands/diff.ts @@ -1,5 +1,5 @@ -import { runCommand } from "../core/exec.js"; -import type { DiffLayerEntry, DiffResult } from "../types/commands.js"; +import { runCommand } from '../core/exec.js' +import type { DiffLayerEntry, DiffResult } from '../types/commands.js' /** * Compares two ModelKits and returns the differences in their layers. @@ -10,12 +10,12 @@ import type { DiffLayerEntry, DiffResult } from "../types/commands.js"; * @see https://kitops.org/docs/cli/cli-reference/#kit-diff */ export async function diff(reference1: string, reference2: string): Promise { - const result = await runCommand('diff', [reference1, reference2]); - return parseDiffOutput(result.stdout, reference1, reference2); + const result = await runCommand('diff', [reference1, reference2]) + return parseDiffOutput(result.stdout, reference1, reference2) } function parseDiffOutput(output: string, ref1: string, ref2: string): DiffResult { - const lines = output.split('\n'); + const lines = output.split('\n') const diffResult: DiffResult = { modelKit1: ref1, @@ -25,58 +25,58 @@ function parseDiffOutput(output: string, ref1: string, ref2: string): DiffResult sharedLayers: [], uniqueToKit1: [], uniqueToKit2: [], - }; + } - let section = ''; + let section = '' for (const line of lines) { - const trimmed = line.trim(); + const trimmed = line.trim() if (trimmed.startsWith('Configurations:')) { - section = 'configs'; + section = 'configs' } else if (trimmed.startsWith('Annotations:')) { - section = 'annotations'; + section = 'annotations' } else if (trimmed.startsWith('Shared Layers')) { - section = 'shared'; + section = 'shared' } else if (trimmed.startsWith('Unique Layers to ModelKit1')) { - section = 'unique1'; + section = 'unique1' } else if (trimmed.startsWith('Unique Layers to ModelKit2')) { - section = 'unique2'; + section = 'unique2' } else if (trimmed.startsWith('---') || trimmed.startsWith('Type') || !trimmed) { - continue; + continue } else if (section === 'configs') { if (trimmed === 'Configs differ:') { - diffResult.configsDiffer = true; + diffResult.configsDiffer = true } else if (trimmed.startsWith('ModelKit1 Config Digest:')) { - diffResult.config1Digest = trimmed.split(':').slice(1).join(':').trim(); + diffResult.config1Digest = trimmed.split(':').slice(1).join(':').trim() } else if (trimmed.startsWith('ModelKit2 Config Digest:')) { - diffResult.config2Digest = trimmed.split(':').slice(1).join(':').trim(); + diffResult.config2Digest = trimmed.split(':').slice(1).join(':').trim() } } else if (section === 'annotations') { if (trimmed.includes('Annotations differ')) { - diffResult.annotationsIdentical = false; + diffResult.annotationsIdentical = false } } else if (section === 'shared' || section === 'unique1' || section === 'unique2') { if (trimmed === '') { - continue; + continue } - const parts = trimmed.split('|').map(p => p.trim()); + const parts = trimmed.split('|').map(p => p.trim()) if (parts.length >= 3) { const entry: DiffLayerEntry = { type: parts[0], digest: parts[1], size: parts[2], - }; + } if (section === 'shared') { - diffResult.sharedLayers.push(entry); + diffResult.sharedLayers.push(entry) } else if (section === 'unique1') { - diffResult.uniqueToKit1.push(entry); + diffResult.uniqueToKit1.push(entry) } else { - diffResult.uniqueToKit2.push(entry); + diffResult.uniqueToKit2.push(entry) } } } } - return diffResult; + return diffResult } diff --git a/src/commands/index.ts b/src/commands/index.ts index 91e752b..dfce81e 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,6 +1,6 @@ export { diff } from './diff.js' -export { init } from './init.js' export { info } from './info.js' +export { init } from './init.js' export { inspect } from './inspect.js' export { kit } from './kit.js' export { list } from './list.js' diff --git a/src/commands/info.ts b/src/commands/info.ts index a2f93bd..f1e0477 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,7 +1,8 @@ -import { parse as parseYaml } from "yaml"; -import { runCommand, prepareArgs } from "../core/exec.js"; -import type { Kitfile } from "../types/kitfile.js" -import type { InfoFlags } from "../types/commands.js"; +import { parse as parseYaml } from 'yaml' + +import { prepareArgs,runCommand } from '../core/exec.js' +import type { InfoFlags } from '../types/commands.js' +import type { Kitfile } from '../types/kitfile.js' /** * Returns the parsed Kitfile for a ModelKit. @@ -26,14 +27,14 @@ export async function info(path: string, flags?: InfoFlags): Promise { args.push(...prepareArgs(flags)) } - const result = await runCommand('info', args); - const kitfile = parseYaml(result.stdout) as Kitfile; + const result = await runCommand('info', args) + const kitfile = parseYaml(result.stdout) as Kitfile Object.defineProperty(kitfile, '_raw', { value: result.stdout, enumerable: false, writable: false, - }); + }) - return kitfile; + return kitfile } \ No newline at end of file diff --git a/src/commands/init.ts b/src/commands/init.ts index edc43a0..f62acee 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,6 +1,7 @@ -import { runCommand, prepareArgs } from '../core/exec.js'; -import { resolve } from 'path'; -import type { InitFlags, InitResult } from '../types/commands.js'; +import { resolve } from 'path' + +import { prepareArgs,runCommand } from '../core/exec.js' +import type { InitFlags, InitResult } from '../types/commands.js' /** * Scans `path` for recognizable ML artifacts and generates a Kitfile. @@ -20,12 +21,12 @@ export async function init(directory: string = '.', flags?: InitFlags): Promise< args.push(...prepareArgs(flags)) } - await runCommand('init', args); + await runCommand('init', args) - const resolvedPath = resolve(directory); + const resolvedPath = resolve(directory) return { path: resolvedPath, - kitfilePath: resolve(resolvedPath, 'Kitfile') + kitfilePath: resolve(resolvedPath, 'Kitfile'), } } \ No newline at end of file diff --git a/src/commands/inspect.ts b/src/commands/inspect.ts index 5405234..3a1c2c0 100644 --- a/src/commands/inspect.ts +++ b/src/commands/inspect.ts @@ -1,5 +1,5 @@ -import { runCommand, prepareArgs } from "../core/exec.js"; -import type { InspectFlags, InspectResult } from "../types/commands.js"; +import { prepareArgs,runCommand } from '../core/exec.js' +import type { InspectFlags, InspectResult } from '../types/commands.js' /** * Returns the full OCI manifest and parsed Kitfile for a ModelKit. @@ -10,12 +10,12 @@ import type { InspectFlags, InspectResult } from "../types/commands.js"; * @see https://kitops.org/docs/cli/cli-reference/#kit-inspect */ export async function inspect(path: string, flags?: InspectFlags): Promise { - const args = [path]; + const args = [path] if (flags) { - args.push(...prepareArgs(flags)); + args.push(...prepareArgs(flags)) } - const result = await runCommand('inspect', args); - return JSON.parse(result.stdout) as InspectResult; + const result = await runCommand('inspect', args) + return JSON.parse(result.stdout) as InspectResult } \ No newline at end of file diff --git a/src/commands/kit.ts b/src/commands/kit.ts index daa6821..6674daf 100644 --- a/src/commands/kit.ts +++ b/src/commands/kit.ts @@ -1,5 +1,5 @@ -import { runCommand } from "../core/exec.js"; -import type { KitCommand, ExecResult } from "../types/kitops.js"; +import { runCommand } from '../core/exec.js' +import type { ExecResult,KitCommand } from '../types/kitops.js' /** * Low-level escape hatch for running any `kit` subcommand directly. @@ -9,5 +9,5 @@ import type { KitCommand, ExecResult } from "../types/kitops.js"; * set `cwd` or custom `env` variables here. */ export async function kit(command: KitCommand, args: string[], stdin?: string, options: any = {}): Promise { - return runCommand(command, args, stdin, options); + return runCommand(command, args, stdin, options) } \ No newline at end of file diff --git a/src/commands/list.ts b/src/commands/list.ts index a355394..678ff88 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -1,6 +1,6 @@ -import { runCommand, parseTableOutput } from "../core/exec.js"; -import { ListFlags } from "../types/commands.js"; -import type { ModelKit } from "../types/kitops.js"; +import { parseTableOutput,runCommand } from '../core/exec.js' +import { ListFlags } from '../types/commands.js' +import type { ModelKit } from '../types/kitops.js' /** * Lists ModelKits stored in local cache or a remote repository. @@ -9,21 +9,21 @@ import type { ModelKit } from "../types/kitops.js"; * @see https://kitops.org/docs/cli/cli-path/#kit-list */ export async function list(repository?: string, flags?: ListFlags): Promise { - const format = flags?.format || 'table'; - const args = ['--format', format]; + const format = flags?.format || 'table' + const args = ['--format', format] if (repository) { args.push(repository) } - const result = await runCommand('list', args); + const result = await runCommand('list', args) if (format === 'json') { - return JSON.parse(result.stdout) as ModelKit[]; + return JSON.parse(result.stdout) as ModelKit[] } if (format === 'table') { - return parseTableOutput(result.stdout); + return parseTableOutput(result.stdout) } - return result.stdout as string; + return result.stdout as string } \ No newline at end of file diff --git a/src/commands/login.ts b/src/commands/login.ts index e46671c..5c5512d 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -1,5 +1,5 @@ -import { prepareArgs, runCommand } from "../core/exec.js"; -import { TLSFlags } from "../types/commands.js"; +import { prepareArgs, runCommand } from '../core/exec.js' +import { TLSFlags } from '../types/commands.js' /** * Authenticates with a ModelKit registry. @@ -9,13 +9,13 @@ import { TLSFlags } from "../types/commands.js"; * @see https://kitops.org/docs/cli/cli-reference/#kit-login */ export async function login(registry: string, username: string, password: string, flags?: TLSFlags): Promise { - const args = [registry, '--username', username, '--password-stdin']; + const args = [registry, '--username', username, '--password-stdin'] if (flags) { args.push(...prepareArgs(flags)) } - await runCommand('login', args, password); + await runCommand('login', args, password) } /** @@ -28,11 +28,11 @@ export async function login(registry: string, username: string, password: string * @see https://kitops.org/docs/cli/cli-reference/#kit-login */ export async function loginUnsafe(registry: string, username: string, password: string, flags?: TLSFlags): Promise { - const args = [registry, '--username', username, '--password', password]; + const args = [registry, '--username', username, '--password', password] if (flags) { args.push(...prepareArgs(flags)) } - await runCommand('login', args); + await runCommand('login', args) } \ No newline at end of file diff --git a/src/commands/logout.ts b/src/commands/logout.ts index 72292da..32763c7 100644 --- a/src/commands/logout.ts +++ b/src/commands/logout.ts @@ -1,9 +1,9 @@ -import { runCommand } from "../core/exec.js"; +import { runCommand } from '../core/exec.js' /** * Kit logout command * @see https://kitops.org/docs/cli/cli-reference/#kit-logout */ export async function logout(registry: string): Promise { - await runCommand('logout', [registry]); + await runCommand('logout', [registry]) } \ No newline at end of file diff --git a/src/commands/pack.ts b/src/commands/pack.ts index 0dc8606..39077db 100644 --- a/src/commands/pack.ts +++ b/src/commands/pack.ts @@ -1,5 +1,5 @@ -import { runCommand, prepareArgs } from "../core/exec.js"; -import type { PackFlags } from "../types/commands.js"; +import { prepareArgs,runCommand } from '../core/exec.js' +import type { PackFlags } from '../types/commands.js' /** * Packages a ModelKit from a directory that contains a Kitfile. @@ -17,7 +17,7 @@ export async function pack(directory: string = '.', flags?: PackFlags): Promise< args.push(...prepareArgs(flags)) } - await runCommand('pack', args); + await runCommand('pack', args) // @TODO: return pack result (tag, digest) and any other useful info } \ No newline at end of file diff --git a/src/commands/pull.ts b/src/commands/pull.ts index 72b0f46..ab2d9a4 100644 --- a/src/commands/pull.ts +++ b/src/commands/pull.ts @@ -1,5 +1,5 @@ -import { prepareArgs, runCommand } from "../core/exec.js"; -import { TLSFlags } from "../types/commands.js"; +import { prepareArgs, runCommand } from '../core/exec.js' +import { TLSFlags } from '../types/commands.js' /** * Pulls a ModelKit from a registry into local storage. @@ -12,11 +12,11 @@ import { TLSFlags } from "../types/commands.js"; * @see https://kitops.org/docs/cli/cli-reference/#kit-pull */ export async function pull(path: string, flags?: TLSFlags): Promise { - const args = [path]; + const args = [path] if (flags) { args.push(...prepareArgs(flags)) } - await runCommand('pull', args); + await runCommand('pull', args) } \ No newline at end of file diff --git a/src/commands/push.ts b/src/commands/push.ts index 0ef7c0e..85157b6 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -1,5 +1,5 @@ -import { prepareArgs, runCommand } from "../core/exec.js"; -import { TLSFlags } from "../types/commands.js"; +import { prepareArgs, runCommand } from '../core/exec.js' +import { TLSFlags } from '../types/commands.js' /** * Pushes a ModelKit to a registry. @@ -13,11 +13,11 @@ import { TLSFlags } from "../types/commands.js"; * @see https://kitops.org/docs/cli/cli-reference/#kit-push */ export async function push(source: string, destination?: string, flags?: TLSFlags): Promise { - const args = destination ? [source, destination] : [source]; + const args = destination ? [source, destination] : [source] if (flags) { args.push(...prepareArgs(flags)) } - await runCommand('push', args); + await runCommand('push', args) } \ No newline at end of file diff --git a/src/commands/remove.ts b/src/commands/remove.ts index b773fc0..fd63e94 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -1,17 +1,17 @@ -import { prepareArgs, runCommand } from "../core/exec.js"; -import type { RemoveFlags } from "../types/commands.js"; +import { prepareArgs, runCommand } from '../core/exec.js' +import type { RemoveFlags } from '../types/commands.js' /** * Removes a ModelKit with the given path from local storage or a remote registry. * @see https://kitops.org/docs/cli/cli-reference/#kit-remove */ export async function remove(path: string, flags?: RemoveFlags): Promise { - const args: string[] = flags?.all ? [] : [path]; + const args: string[] = flags?.all ? [] : [path] if (flags) { - args.push(...prepareArgs(flags)); + args.push(...prepareArgs(flags)) } - await runCommand('remove', args); + await runCommand('remove', args) } /** @@ -19,9 +19,9 @@ export async function remove(path: string, flags?: RemoveFlags): Promise { * avoids passing a dummy path argument. */ export async function removeAll(flags?: Omit): Promise { - const args = ['--all']; + const args = ['--all'] if (flags) { - args.push(...prepareArgs(flags)); + args.push(...prepareArgs(flags)) } - await runCommand('remove', args); + await runCommand('remove', args) } \ No newline at end of file diff --git a/src/commands/tag.ts b/src/commands/tag.ts index 2069316..5948533 100644 --- a/src/commands/tag.ts +++ b/src/commands/tag.ts @@ -1,4 +1,4 @@ -import { runCommand } from "../core/exec.js"; +import { runCommand } from '../core/exec.js' /** * Assigns an additional tag to an existing ModelKit without re-packing it. @@ -9,6 +9,6 @@ import { runCommand } from "../core/exec.js"; * @see https://kitops.org/docs/cli/cli-reference/#kit-tag */ export async function tag(source: string, destination: string): Promise { - const args = [source, destination]; - await runCommand('tag', args); + const args = [source, destination] + await runCommand('tag', args) } \ No newline at end of file diff --git a/src/commands/unpack.ts b/src/commands/unpack.ts index ba1843d..67f2d9b 100644 --- a/src/commands/unpack.ts +++ b/src/commands/unpack.ts @@ -1,5 +1,5 @@ -import { runCommand, prepareArgs } from "../core/exec.js"; -import type { UnpackFlags } from "../types/commands.js"; +import { prepareArgs,runCommand } from '../core/exec.js' +import type { UnpackFlags } from '../types/commands.js' /** * Extracts a ModelKit with the given flags @@ -14,5 +14,5 @@ export async function unpack(path: string, flags?: UnpackFlags): Promise { args.push(...prepareArgs(flags)) } - await runCommand('unpack', args); + await runCommand('unpack', args) } \ No newline at end of file diff --git a/src/commands/version.ts b/src/commands/version.ts index 0e9dbac..c2a9bb7 100644 --- a/src/commands/version.ts +++ b/src/commands/version.ts @@ -1,11 +1,11 @@ -import { runCommand, parseKeyValueOutput } from "../core/exec.js"; -import type { VersionResult } from "../types/commands.js"; +import { parseKeyValueOutput,runCommand } from '../core/exec.js' +import type { VersionResult } from '../types/commands.js' /** * Kit version command * @see https://kitops.org/docs/cli/cli-reference/#kit-version */ export async function version(): Promise { - const result = await runCommand('version'); - return parseKeyValueOutput(result.stdout) as VersionResult; + const result = await runCommand('version') + return parseKeyValueOutput(result.stdout) as VersionResult } \ No newline at end of file diff --git a/src/core/__tests__/exec.spec.ts b/src/core/__tests__/exec.spec.ts index bd0a6e8..6f33b6f 100644 --- a/src/core/__tests__/exec.spec.ts +++ b/src/core/__tests__/exec.spec.ts @@ -1,21 +1,22 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { spawn } from 'child_process'; -import { runCommand, prepareArgs, parseTableOutput } from '../exec'; +import { spawn } from 'child_process' +import { afterEach,beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('child_process'); +import { parseTableOutput,prepareArgs, runCommand } from '../exec' -const mockSpawn = vi.mocked(spawn); +vi.mock('child_process') + +const mockSpawn = vi.mocked(spawn) describe('exec', () => { beforeEach(() => { - vi.clearAllMocks(); + vi.clearAllMocks() // Reset environment variable - delete process.env.KITOPS_CLI_PATH; - }); + delete process.env.KITOPS_CLI_PATH + }) afterEach(() => { - vi.restoreAllMocks(); - }); + vi.restoreAllMocks() + }) describe('runCommand', () => { it('should execute command successfully', async () => { @@ -23,98 +24,98 @@ describe('exec', () => { stdout: { on: vi.fn() }, stderr: { on: vi.fn() }, on: vi.fn(), - }; + } - mockSpawn.mockReturnValue(mockChild as any); + mockSpawn.mockReturnValue(mockChild as any) - const promise = runCommand('version'); + const promise = runCommand('version') // Get the callbacks that were registered - const stdoutDataCallback = mockChild.stdout.on.mock.calls.find(call => call[0] === 'data')?.[1]; - const closeCallback = mockChild.on.mock.calls.find(call => call[0] === 'close')?.[1]; + const stdoutDataCallback = mockChild.stdout.on.mock.calls.find(call => call[0] === 'data')?.[1] + const closeCallback = mockChild.on.mock.calls.find(call => call[0] === 'close')?.[1] // Simulate stdout data - stdoutDataCallback?.('v1.0.0\n'); + stdoutDataCallback?.('v1.0.0\n') // Simulate successful close - closeCallback?.(0); + closeCallback?.(0) - const result = await promise; + const result = await promise expect(result).toEqual({ stdout: 'v1.0.0', stderr: '', exitCode: 0, - }); - }); + }) + }) it('should reject on non-zero exit code', async () => { const mockChild = { stdout: { on: vi.fn() }, stderr: { on: vi.fn() }, on: vi.fn(), - }; + } - mockSpawn.mockReturnValue(mockChild as any); + mockSpawn.mockReturnValue(mockChild as any) - const promise = runCommand('version'); + const promise = runCommand('version') // Get the callbacks - const stderrDataCallback = mockChild.stderr.on.mock.calls.find(call => call[0] === 'data')?.[1]; - const closeCallback = mockChild.on.mock.calls.find(call => call[0] === 'close')?.[1]; + const stderrDataCallback = mockChild.stderr.on.mock.calls.find(call => call[0] === 'data')?.[1] + const closeCallback = mockChild.on.mock.calls.find(call => call[0] === 'close')?.[1] // Simulate stderr data - stderrDataCallback?.('command not found\n'); + stderrDataCallback?.('command not found\n') // Simulate error close - closeCallback?.(1); + closeCallback?.(1) - await expect(promise).rejects.toBe('Kit command failed with exit code 1: command not found\n'); - }); + await expect(promise).rejects.toBe('Kit command failed with exit code 1: command not found\n') + }) it('should reject on spawn error', async () => { const mockChild = { stdout: { on: vi.fn() }, stderr: { on: vi.fn() }, on: vi.fn(), - }; + } - mockSpawn.mockReturnValue(mockChild as any); + mockSpawn.mockReturnValue(mockChild as any) - const promise = runCommand('version'); + const promise = runCommand('version') // Get the error callback - const errorCallback = mockChild.on.mock.calls.find(call => call[0] === 'error')?.[1]; + const errorCallback = mockChild.on.mock.calls.find(call => call[0] === 'error')?.[1] // Simulate spawn error - errorCallback?.({ message: 'ENOENT' }); + errorCallback?.({ message: 'ENOENT' }) - await expect(promise).rejects.toBe('Failed to execute kit command: ENOENT'); - }); + await expect(promise).rejects.toBe('Failed to execute kit command: ENOENT') + }) it('should use custom options', () => { const mockChild = { stdout: { on: vi.fn() }, stderr: { on: vi.fn() }, on: vi.fn(), - }; + } - mockSpawn.mockReturnValue(mockChild as any); + mockSpawn.mockReturnValue(mockChild as any) const options = { cwd: '/custom/path', env: { CUSTOM_VAR: 'value' }, stdio: 'inherit' as const, - }; + } - runCommand('version', ['--help'], undefined, options); + runCommand('version', ['--help'], undefined, options) expect(mockSpawn).toHaveBeenCalledWith('kit', ['version', '--help'], { cwd: '/custom/path', env: { ...process.env, CUSTOM_VAR: 'value' }, stdio: 'inherit', - }); - }); + }) + }) it('should use custom CLI path from environment variable', () => { // We need to mock the module to test this since KIT_CLI is evaluated at module load time @@ -124,44 +125,44 @@ describe('exec', () => { stdout: { on: vi.fn() }, stderr: { on: vi.fn() }, on: vi.fn(), - }; + } - mockSpawn.mockReturnValue(mockChild as any); + mockSpawn.mockReturnValue(mockChild as any) - runCommand('version'); + runCommand('version') expect(mockSpawn).toHaveBeenCalledWith('kit', ['version'], { cwd: process.cwd(), env: process.env, stdio: 'pipe', - }); - }); - }); + }) + }) + }) describe('prepareArgs', () => { it('should handle boolean flags', () => { - const options = { force: true, quiet: false, verbose: true }; - const result = prepareArgs(options); - expect(result).toEqual(['--force', '--quiet=false', '--verbose']); - }); + const options = { force: true, quiet: false, verbose: true } + const result = prepareArgs(options) + expect(result).toEqual(['--force', '--quiet=false', '--verbose']) + }) it('should handle string and number values', () => { - const options = { tag: 'latest', port: 8080, name: 'test' }; - const result = prepareArgs(options); - expect(result).toEqual(['--tag=latest', '--port=8080', '--name=test']); - }); + const options = { tag: 'latest', port: 8080, name: 'test' } + const result = prepareArgs(options) + expect(result).toEqual(['--tag=latest', '--port=8080', '--name=test']) + }) it('should handle array values', () => { - const options = { exclude: ['node_modules', '.git'], include: ['src'] }; - const result = prepareArgs(options); - expect(result).toEqual(['--exclude=node_modules', '--exclude=.git', '--include=src']); - }); + const options = { exclude: ['node_modules', '.git'], include: ['src'] } + const result = prepareArgs(options) + expect(result).toEqual(['--exclude=node_modules', '--exclude=.git', '--include=src']) + }) it('should ignore undefined and null values', () => { - const options = { tag: 'latest', port: undefined, name: null }; - const result = prepareArgs(options); - expect(result).toEqual(['--tag=latest']); - }); + const options = { tag: 'latest', port: undefined, name: null } + const result = prepareArgs(options) + expect(result).toEqual(['--tag=latest']) + }) it('should handle mixed option types', () => { const options = { @@ -171,8 +172,8 @@ describe('exec', () => { exclude: ['test', 'docs'], timeout: 30, debug: undefined, - }; - const result = prepareArgs(options); + } + const result = prepareArgs(options) expect(result).toEqual([ '--force', '--quiet=false', @@ -180,63 +181,63 @@ describe('exec', () => { '--exclude=test', '--exclude=docs', '--timeout=30', - ]); - }); + ]) + }) it('should convert camelCase keys to kebab-case', () => { - const result = prepareArgs({ tlsVerify: true, ignoreExisting: false }); - expect(result).toEqual(['--tls-verify', '--ignore-existing=false']); - }); - }); + const result = prepareArgs({ tlsVerify: true, ignoreExisting: false }) + expect(result).toEqual(['--tls-verify', '--ignore-existing=false']) + }) + }) describe('parseTableOutput', () => { it('should return empty array for empty output', () => { - expect(parseTableOutput('')).toEqual([]); - expect(parseTableOutput(' ')).toEqual([]); - expect(parseTableOutput('\n\n\n')).toEqual([]); - }); + expect(parseTableOutput('')).toEqual([]) + expect(parseTableOutput(' ')).toEqual([]) + expect(parseTableOutput('\n\n\n')).toEqual([]) + }) it('should parse table output with single row', () => { - const tableOutput = 'REPOSITORY TAG SIZE\ntest-repo latest 100MB'; - const result = parseTableOutput(tableOutput); + const tableOutput = 'REPOSITORY TAG SIZE\ntest-repo latest 100MB' + const result = parseTableOutput(tableOutput) expect(result).toEqual([{ repository: 'test-repo', tag: 'latest', - size: '100MB' - }]); - }); + size: '100MB', + }]) + }) it('should parse table output with multiple rows', () => { const tableOutput = `REPOSITORY TAG SIZE my-repo v1.0.0 50MB another-repo latest 150MB -test-repo dev 25MB`; +test-repo dev 25MB` - const result = parseTableOutput(tableOutput); + const result = parseTableOutput(tableOutput) expect(result).toEqual([ { repository: 'my-repo', tag: 'v1.0.0', size: '50MB' }, { repository: 'another-repo', tag: 'latest', size: '150MB' }, - { repository: 'test-repo', tag: 'dev', size: '25MB' } - ]); - }); + { repository: 'test-repo', tag: 'dev', size: '25MB' }, + ]) + }) it('should handle tables with irregular spacing', () => { - const tableOutput = 'NAME VERSION STATUS\nkit1 1.0.0 active\nkit2 2.0.0 inactive'; - const result = parseTableOutput(tableOutput); + const tableOutput = 'NAME VERSION STATUS\nkit1 1.0.0 active\nkit2 2.0.0 inactive' + const result = parseTableOutput(tableOutput) expect(result).toEqual([ { name: 'kit1', version: '1.0.0', status: 'active' }, - { name: 'kit2', version: '2.0.0', status: 'inactive' } - ]); - }); + { name: 'kit2', version: '2.0.0', status: 'inactive' }, + ]) + }) it('should convert headers to lowercase', () => { - const tableOutput = 'REPOSITORY_NAME TAG_VERSION FILE_SIZE\ntest v1.0 100MB'; - const result = parseTableOutput(tableOutput); + const tableOutput = 'REPOSITORY_NAME TAG_VERSION FILE_SIZE\ntest v1.0 100MB' + const result = parseTableOutput(tableOutput) expect(result).toEqual([{ repository_name: 'test', tag_version: 'v1.0', - file_size: '100MB' - }]); - }); - }); -}); \ No newline at end of file + file_size: '100MB', + }]) + }) + }) +}) \ No newline at end of file diff --git a/src/core/exec.ts b/src/core/exec.ts index b5a72c1..d3d4b28 100644 --- a/src/core/exec.ts +++ b/src/core/exec.ts @@ -1,5 +1,6 @@ -import { spawn } from 'child_process'; -import type { KitCommand, ExecResult } from '../types/kitops.js'; +import { spawn } from 'child_process' + +import type { ExecResult,KitCommand } from '../types/kitops.js' type ParsedTableResult = { [key: string]: string } @@ -16,7 +17,7 @@ type ExecOptions = { * Re-reads KITOPS_CLI_PATH on each call so it can be set dynamically at runtime. */ function getKitCli() { - return process.env.KITOPS_CLI_PATH || 'kit'; + return process.env.KITOPS_CLI_PATH || 'kit' } /** @@ -33,51 +34,51 @@ function getKitCli() { */ export function runCommand(command: KitCommand, args: string[] = [], stdin?: string, options: ExecOptions = {}): Promise { return new Promise((resolve, reject) => { - const fullArgs = [command, ...args]; + const fullArgs = [command, ...args] const child = spawn(getKitCli(), fullArgs, { cwd: options.cwd || process.cwd(), env: { ...process.env, ...options.env }, stdio: options.stdio || 'pipe', - }); + }) - let stdout = ''; - let stderr = ''; + let stdout = '' + let stderr = '' if (stdin && child.stdin) { - child.stdin.write(stdin); - child.stdin.end(); + child.stdin.write(stdin) + child.stdin.end() } if (child.stdout) { child.stdout.on('data', (data) => { - stdout += data.toString(); - }); + stdout += data.toString() + }) } if (child.stderr) { child.stderr.on('data', (data) => { - stderr += data.toString(); - }); + stderr += data.toString() + }) } child.on('error', (error) => { - reject(`Failed to execute kit command: ${error.message}`); - }); + reject(`Failed to execute kit command: ${error.message}`) + }) child.on('close', (code) => { const result: ExecResult = { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: code ?? 0, - }; + } if (code !== 0) { - reject(`Kit command failed with exit code ${code}: ${stderr}`); + reject(`Kit command failed with exit code ${code}: ${stderr}`) } else { - resolve(result); + resolve(result) } - }); - }); + }) + }) } /** @@ -92,32 +93,34 @@ export function runCommand(command: KitCommand, args: string[] = [], stdin?: str * - arrays => repeated `--key=value` pairs, one per element */ export function prepareArgs(options: Record): string[] { - const args: string[] = []; + const args: string[] = [] Object.entries(options).forEach(([key, value]) => { - if (value === undefined || value === null) return; + if (value === undefined || value === null) { + return + } - const flag = `--${key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`; + const flag = `--${key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}` if (value === true) { - args.push(flag); - return; + args.push(flag) + return } if (value === false) { - args.push(`${flag}=false`); - return; + args.push(`${flag}=false`) + return } - const values = Array.isArray(value) ? value : [value]; + const values = Array.isArray(value) ? value : [value] for (const val of values) { if (val !== undefined && val !== null) { - args.push(`${flag}=${val}`); + args.push(`${flag}=${val}`) } } - }); + }) - return args; + return args } /** @@ -131,26 +134,26 @@ export function prepareArgs(options: Record): string[] { * Returns an empty array for blank output rather than throwing. */ export function parseTableOutput(output: string): T { - const lines = output.split('\n').filter(line => line.trim() !== ''); + const lines = output.split('\n').filter(line => line.trim() !== '') if (lines.length === 0) { - return [] as T; + return [] as T } - const headers = lines[0].split(/\s{2,}/).map(header => header.toLowerCase().trim()); - const data: ParsedTableResult[] = []; + const headers = lines[0].split(/\s{2,}/).map(header => header.toLowerCase().trim()) + const data: ParsedTableResult[] = [] for (let i = 1; i < lines.length; i++) { - const values = lines[i].split(/\s{2,}/).map(value => value.trim()); - const entry: ParsedTableResult = {}; + const values = lines[i].split(/\s{2,}/).map(value => value.trim()) + const entry: ParsedTableResult = {} headers.forEach((header, index) => { - entry[header] = values[index] || ''; - }); + entry[header] = values[index] || '' + }) - data.push(entry); + data.push(entry) } - return data as T; + return data as T } /** @@ -174,14 +177,14 @@ export function parseTableOutput(output: string): T { * ``` */ export function parseKeyValueOutput(output: string): Record { - const lines = output.split('\n'); - const result: any = {}; + const lines = output.split('\n') + const result: any = {} for (const line of lines) { - const [key, value] = line.split(':').map(s => s.trim()); + const [key, value] = line.split(':').map(s => s.trim()) if (key && value) { - const camelKey = key.replace(/ (\w)/g, (_, c) => c.toUpperCase()); - result[camelKey] = value; + const camelKey = key.replace(/ (\w)/g, (_, c) => c.toUpperCase()) + result[camelKey] = value } } - return result; + return result } diff --git a/src/index.ts b/src/index.ts index 56f3872..a1f1b10 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,8 +29,8 @@ export type FilterFlag = `${Layer}${(`:${string}`) | ''}` | `${Layer}${(`:${stri // Export all commands export { diff, - init, info, + init, inspect, kit, list, @@ -43,45 +43,45 @@ export { remove, tag, unpack, - version -} from './commands/index.js'; + version, +} from './commands/index.js' // Export types export type { - Manifest, - ManifestAnnotations, - ManifestDescriptor -} from './types/manifest.js' - -export type { - ModelKit, - ExecResult, - KitCommand -} from './types/kitops.js'; + DiffFlags, + DiffLayerEntry, + DiffResult, + InitFlags, + InitResult, + InspectFlags, + InspectResult, + PackFlags, + RemoveFlags, + TLSFlags, + UnpackFlags, + VersionResult, +} from './types/commands.js' export type { + Code, + Dataset, + Doc, Kitfile, - Package, LayerCommons, Model, ModelPart, - Dataset, - Code, - Doc, - Prompt -} from './types/kitfile.js'; + Package, + Prompt, +} from './types/kitfile.js' export type { - DiffFlags, - DiffLayerEntry, - DiffResult, - InspectFlags, - UnpackFlags, - RemoveFlags, - PackFlags, - InitFlags, - InitResult, - InspectResult, - TLSFlags, - VersionResult - } from './types/commands.js'; + ExecResult, + KitCommand, + ModelKit, +} from './types/kitops.js' + +export type { + Manifest, + ManifestAnnotations, + ManifestDescriptor, +} from './types/manifest.js' diff --git a/src/types/commands.ts b/src/types/commands.ts index 71e03a9..30106cb 100644 --- a/src/types/commands.ts +++ b/src/types/commands.ts @@ -1,6 +1,6 @@ -import type { Kitfile } from "./kitfile.js"; -import type { Manifest } from "./manifest.js"; -import type { FilterFlag } from "./kitops.js"; +import type { Kitfile } from './kitfile.js' +import type { FilterFlag } from './kitops.js' +import type { Manifest } from './manifest.js' /** * Types for the various flags and results used by the KitOps CLI commands. @@ -58,7 +58,7 @@ export type InspectResult = { cliVersion: string, kitfile: Kitfile, manifest: Manifest -}; +} export type ListFlags = { format: 'table' | 'json' | 'template', diff --git a/src/types/kitops.ts b/src/types/kitops.ts index b85f85a..24b496e 100644 --- a/src/types/kitops.ts +++ b/src/types/kitops.ts @@ -1,6 +1,6 @@ /** The content layers a ModelKit can contain. */ -export type Layer = - | 'model' +export type Layer + = | 'model' | 'datasets' | 'code' | 'docs' @@ -26,14 +26,14 @@ export type ModelKit = { name: string; size: string; digest: string; -}; +} /** * Union of all subcommands accepted by the `kit` binary. * @see https://kitops.org/docs/cli/cli-reference/ */ -export type KitCommand = - | 'diff' +export type KitCommand + = | 'diff' | 'info' | 'inspect' | 'init' @@ -46,11 +46,11 @@ export type KitCommand = | 'remove' | 'tag' | 'unpack' - | 'version'; + | 'version' /** Raw output from a spawned `kit` process. */ export type ExecResult = { stdout: string; stderr: string; exitCode: number; -}; \ No newline at end of file +} \ No newline at end of file