From dd0c211ac4ae4ca284e9429275a64af717837a84 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 25 Jul 2024 13:09:18 +0200 Subject: [PATCH 01/16] Clan-app: add dependencies; floating-ui; eslint-query --- pkgs/webview-ui/app/eslint.config.mjs | 2 + pkgs/webview-ui/app/package-lock.json | 312 ++++++++++++++------------ pkgs/webview-ui/app/package.json | 2 + pkgs/webview-ui/flake-module.nix | 2 +- 4 files changed, 170 insertions(+), 148 deletions(-) diff --git a/pkgs/webview-ui/app/eslint.config.mjs b/pkgs/webview-ui/app/eslint.config.mjs index 8a408bee5..619db917b 100644 --- a/pkgs/webview-ui/app/eslint.config.mjs +++ b/pkgs/webview-ui/app/eslint.config.mjs @@ -1,9 +1,11 @@ import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; import tailwind from "eslint-plugin-tailwindcss"; +import pluginQuery from "@tanstack/eslint-plugin-query"; export default tseslint.config( eslint.configs.recommended, + ...pluginQuery.configs["flat/recommended"], ...tseslint.configs.strict, ...tseslint.configs.stylistic, ...tailwind.configs["flat/recommended"], diff --git a/pkgs/webview-ui/app/package-lock.json b/pkgs/webview-ui/app/package-lock.json index 595f4b1cb..5f435d99c 100644 --- a/pkgs/webview-ui/app/package-lock.json +++ b/pkgs/webview-ui/app/package-lock.json @@ -9,8 +9,10 @@ "version": "0.0.1", "license": "MIT", "dependencies": { + "@floating-ui/dom": "^1.6.8", "@modular-forms/solid": "^0.21.0", "@solid-primitives/storage": "^3.7.1", + "@tanstack/eslint-plugin-query": "^5.51.12", "@tanstack/solid-query": "^5.51.2", "material-icons": "^1.13.12", "nanoid": "^5.0.7", @@ -787,7 +789,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -802,7 +803,6 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -811,7 +811,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -834,7 +833,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -844,7 +842,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -859,7 +856,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -876,11 +872,32 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz", + "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==", + "dependencies": { + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz", + "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", + "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -894,7 +911,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -904,7 +920,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -916,7 +931,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "engines": { "node": ">=12.22" }, @@ -928,8 +942,7 @@ "node_modules/@humanwhocodes/object-schema": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -1023,7 +1036,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1036,7 +1048,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -1045,7 +1056,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1569,6 +1579,124 @@ "node": ">=4" } }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.51.12", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.51.12.tgz", + "integrity": "sha512-vzUXXIVzDP2c6wVSJ+1imPGaKQ2ILuWnta64FJc/JnQ5WunfO17bQJSk6uKDbzTQG/YKgPYBMG3C9qFA4b7Ayg==", + "dependencies": { + "@typescript-eslint/utils": "8.0.0-alpha.30" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8 || ^9" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/scope-manager": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.30.tgz", + "integrity": "sha512-FGW/iPWGyPFamAVZ60oCAthMqQrqafUGebF8UKuq/ha+e9SVG6YhJoRzurlQXOVf8dHfOhJ0ADMXyFnMc53clg==", + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.30", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.30" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/types": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.30.tgz", + "integrity": "sha512-4WzLlw27SO9pK9UFj/Hu7WGo8WveT0SEiIpFVsV2WwtQmLps6kouwtVCB8GJPZKJyurhZhcqCoQVQFmpv441Vg==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.30.tgz", + "integrity": "sha512-WSXbc9ZcXI+7yC+6q95u77i8FXz6HOLsw3ST+vMUlFy1lFbXyFL/3e6HDKQCm2Clt0krnoCPiTGvIn+GkYPn4Q==", + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.30", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.30", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/utils": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.30.tgz", + "integrity": "sha512-rfhqfLqFyXhHNDwMnHiVGxl/Z2q/3guQ1jLlGQ0hi9Rb7inmwz42crM+NnLPR+2vEnwyw1P/g7fnQgQ3qvFx4g==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.0.0-alpha.30", + "@typescript-eslint/types": "8.0.0-alpha.30", + "@typescript-eslint/typescript-estree": "8.0.0-alpha.30" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.30.tgz", + "integrity": "sha512-XZuNurZxBqmr6ZIRIwWFq7j5RZd6ZlkId/HZEWyfciK+CWoyOxSF9Pv2VXH9Rlu2ZG2PfbhLz2Veszl4Pfn7yA==", + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.30", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tanstack/query-core": { "version": "5.51.1", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.1.tgz", @@ -1849,8 +1977,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@vitest/expect": { "version": "1.6.0", @@ -1952,7 +2079,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -1964,7 +2090,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1994,7 +2119,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2058,14 +2182,12 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, "engines": { "node": ">=8" } @@ -2165,8 +2287,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -2184,7 +2305,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -2193,7 +2313,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -2252,7 +2371,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -2411,8 +2529,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/confbox": { "version": "0.1.7", @@ -2452,7 +2569,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2558,7 +2674,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2592,8 +2707,7 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -2623,7 +2737,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "dependencies": { "path-type": "^4.0.0" }, @@ -2641,7 +2754,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -2753,7 +2865,6 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2824,7 +2935,6 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -2840,7 +2950,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2852,7 +2961,6 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2861,7 +2969,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2870,7 +2977,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2885,7 +2991,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2895,7 +3000,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2911,7 +3015,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2922,14 +3025,12 @@ "node_modules/eslint/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -2941,7 +3042,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2956,7 +3056,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2965,7 +3064,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2977,7 +3075,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2989,7 +3086,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3001,7 +3097,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -3031,7 +3126,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -3043,7 +3137,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -3055,7 +3148,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -3073,7 +3165,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3104,14 +3195,12 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -3127,7 +3216,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3138,14 +3226,12 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fastparse": { "version": "1.1.2", @@ -3157,7 +3243,6 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -3166,7 +3251,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -3178,7 +3262,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3190,7 +3273,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3206,7 +3288,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -3219,8 +3300,7 @@ "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, "node_modules/foreground-child": { "version": "3.1.1", @@ -3274,8 +3354,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -3356,7 +3435,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -3377,7 +3455,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -3404,8 +3481,7 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/has-flag": { "version": "3.0.0", @@ -3497,7 +3573,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, "engines": { "node": ">= 4" } @@ -3528,7 +3603,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3544,7 +3618,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -3554,7 +3627,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3563,8 +3635,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -3594,7 +3665,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3612,7 +3682,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -3624,7 +3693,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -3633,7 +3701,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3671,8 +3738,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/jackspeak": { "version": "2.3.6", @@ -3711,7 +3777,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -3774,8 +3839,7 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-schema-faker": { "version": "0.5.6", @@ -3840,14 +3904,12 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "node_modules/json5": { "version": "2.2.3", @@ -3874,7 +3936,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "dependencies": { "json-buffer": "3.0.1" } @@ -3911,7 +3972,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -3955,7 +4015,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -3981,8 +4040,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/loupe": { "version": "2.3.7", @@ -4078,7 +4136,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -4087,7 +4144,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -4147,7 +4203,6 @@ "version": "9.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4182,8 +4237,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mz": { "version": "2.7.0", @@ -4216,8 +4270,7 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/needle": { "version": "3.3.1", @@ -4316,7 +4369,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -4349,7 +4401,6 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -4366,7 +4417,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -4381,7 +4431,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -4396,7 +4445,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -4431,7 +4479,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -4440,7 +4487,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4449,7 +4495,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -4489,7 +4534,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -4519,7 +4563,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -4794,7 +4837,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "engines": { "node": ">= 0.8.0" } @@ -4858,7 +4900,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } @@ -4873,7 +4914,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -4949,7 +4989,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -4958,7 +4997,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -4969,7 +5007,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -4984,7 +5021,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4995,7 +5031,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5015,7 +5050,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5068,7 +5102,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -5164,7 +5197,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5176,7 +5208,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -5203,7 +5234,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, "engines": { "node": ">=8" } @@ -5416,7 +5446,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -5618,8 +5647,7 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, "node_modules/thenify": { "version": "3.3.1", @@ -5679,7 +5707,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -5724,7 +5751,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, "engines": { "node": ">=16" }, @@ -5750,7 +5776,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -5771,7 +5796,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -5783,7 +5807,6 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5873,7 +5896,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -6144,7 +6166,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -6175,7 +6196,6 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6304,8 +6324,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.17.0", @@ -6374,7 +6393,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "engines": { "node": ">=10" }, diff --git a/pkgs/webview-ui/app/package.json b/pkgs/webview-ui/app/package.json index 2facfba64..356215a3e 100644 --- a/pkgs/webview-ui/app/package.json +++ b/pkgs/webview-ui/app/package.json @@ -38,8 +38,10 @@ "vitest": "^1.6.0" }, "dependencies": { + "@floating-ui/dom": "^1.6.8", "@modular-forms/solid": "^0.21.0", "@solid-primitives/storage": "^3.7.1", + "@tanstack/eslint-plugin-query": "^5.51.12", "@tanstack/solid-query": "^5.51.2", "material-icons": "^1.13.12", "nanoid": "^5.0.7", diff --git a/pkgs/webview-ui/flake-module.nix b/pkgs/webview-ui/flake-module.nix index be51c7f51..0fddc56e1 100644 --- a/pkgs/webview-ui/flake-module.nix +++ b/pkgs/webview-ui/flake-module.nix @@ -16,7 +16,7 @@ npmDeps = pkgs.fetchNpmDeps { src = ./app; - hash = "sha256-/PFSBAIodZjInElYoNsDQUV4isxmcvL3YM1hzAmdDWA="; + hash = "sha256-n9IXcfCpydykoYD+P/YNtNIwrvgJTZND0kg7oXBfmJ0="; }; # The prepack script runs the build script, which we'd rather do in the build phase. npmPackFlags = [ "--ignore-scripts" ]; From b48135ca51df8dea0256d664957ef6e6d0cc197a Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 25 Jul 2024 13:10:06 +0200 Subject: [PATCH 02/16] Clan-app: add popover - remove clan confirm --- pkgs/webview-ui/app/src/floating/index.tsx | 128 ++++++++++++++++++ .../app/src/routes/settings/index.tsx | 82 +++++++++-- 2 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 pkgs/webview-ui/app/src/floating/index.tsx diff --git a/pkgs/webview-ui/app/src/floating/index.tsx b/pkgs/webview-ui/app/src/floating/index.tsx new file mode 100644 index 000000000..96267c82f --- /dev/null +++ b/pkgs/webview-ui/app/src/floating/index.tsx @@ -0,0 +1,128 @@ +import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"; +import type { + ComputePositionConfig, + ComputePositionReturn, + ReferenceElement, +} from "@floating-ui/dom"; +import { computePosition } from "@floating-ui/dom"; + +export interface UseFloatingOptions< + R extends ReferenceElement, + F extends HTMLElement, +> extends Partial { + whileElementsMounted?: ( + reference: R, + floating: F, + update: () => void + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type + ) => void | (() => void); +} + +interface UseFloatingState extends Omit { + x?: number | null; + y?: number | null; +} + +export interface UseFloatingResult extends UseFloatingState { + update(): void; +} + +export function useFloating( + reference: () => R | undefined | null, + floating: () => F | undefined | null, + options?: UseFloatingOptions +): UseFloatingResult { + const placement = () => options?.placement ?? "bottom"; + const strategy = () => options?.strategy ?? "absolute"; + + const [data, setData] = createSignal({ + x: null, + y: null, + placement: placement(), + strategy: strategy(), + middlewareData: {}, + }); + + const [error, setError] = createSignal<{ value: unknown } | undefined>(); + + createEffect(() => { + const currentError = error(); + if (currentError) { + throw currentError.value; + } + }); + + const version = createMemo(() => { + reference(); + floating(); + return {}; + }); + + function update() { + const currentReference = reference(); + const currentFloating = floating(); + + if (currentReference && currentFloating) { + const capturedVersion = version(); + computePosition(currentReference, currentFloating, { + middleware: options?.middleware, + placement: placement(), + strategy: strategy(), + }).then( + (currentData) => { + // Check if it's still valid + if (capturedVersion === version()) { + setData(currentData); + } + }, + (err) => { + setError(err); + } + ); + } + } + + createEffect(() => { + const currentReference = reference(); + const currentFloating = floating(); + + options?.middleware; + placement(); + strategy(); + + if (currentReference && currentFloating) { + if (options?.whileElementsMounted) { + const cleanup = options.whileElementsMounted( + currentReference, + currentFloating, + update + ); + + if (cleanup) { + onCleanup(cleanup); + } + } else { + update(); + } + } + }); + + return { + get x() { + return data().x; + }, + get y() { + return data().y; + }, + get placement() { + return data().placement; + }, + get strategy() { + return data().strategy; + }, + get middlewareData() { + return data().middlewareData; + }, + update, + }; +} diff --git a/pkgs/webview-ui/app/src/routes/settings/index.tsx b/pkgs/webview-ui/app/src/routes/settings/index.tsx index 29eb2846b..1c83c41ab 100644 --- a/pkgs/webview-ui/app/src/routes/settings/index.tsx +++ b/pkgs/webview-ui/app/src/routes/settings/index.tsx @@ -12,8 +12,18 @@ import { setRoute, clanList, } from "@/src/App"; -import { For, Show } from "solid-js"; +import { createEffect, createSignal, For, Show } from "solid-js"; import { createQuery } from "@tanstack/solid-query"; +import { useFloating } from "@/src/floating"; +import { + arrow, + autoUpdate, + flip, + hide, + offset, + shift, + size, +} from "@floating-ui/dom"; export const registerClan = async () => { try { @@ -54,6 +64,30 @@ const ClanDetails = (props: ClanDetailsProps) => { }, })); + const [reference, setReference] = createSignal(); + const [floating, setFloating] = createSignal(); + const [arrowEl, setArrowEl] = createSignal(); + + // `position` is a reactive object. + const position = useFloating(reference, floating, { + placement: "top", + + // pass options. Ensure the cleanup function is returned. + whileElementsMounted: (reference, floating, update) => + autoUpdate(reference, floating, update, { + animationFrame: true, + }), + middleware: [ + offset(5), + shift(), + flip(), + + hide({ + strategy: "referenceHidden", + }), + ], + }); + return (
@@ -72,23 +106,43 @@ const ClanDetails = (props: ClanDetailsProps) => { {activeURI() === clan_dir ? "active" : "select"} +
+ +
Clan URI
From d7b6fc16a4f004c406f67bcafd773804efe39bd0 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 26 Jul 2024 14:22:07 +0200 Subject: [PATCH 03/16] Serde: add unit tests for all serialization and deserialization logic --- .../clan_app/components/serializer.py | 92 ---------- pkgs/clan-cli/clan_cli/api/__init__.py | 129 +------------- pkgs/clan-cli/clan_cli/api/serde.py | 101 +++++++++++ pkgs/clan-cli/default.nix | 3 + pkgs/clan-cli/tests/test_deserializers.py | 157 ++++++++++++++++++ pkgs/clan-cli/tests/test_serializers.py | 106 ++++++++++++ 6 files changed, 372 insertions(+), 216 deletions(-) delete mode 100644 pkgs/clan-app/clan_app/components/serializer.py create mode 100644 pkgs/clan-cli/clan_cli/api/serde.py create mode 100644 pkgs/clan-cli/tests/test_deserializers.py create mode 100644 pkgs/clan-cli/tests/test_serializers.py diff --git a/pkgs/clan-app/clan_app/components/serializer.py b/pkgs/clan-app/clan_app/components/serializer.py deleted file mode 100644 index d3b0563e3..000000000 --- a/pkgs/clan-app/clan_app/components/serializer.py +++ /dev/null @@ -1,92 +0,0 @@ -import dataclasses -import logging -from dataclasses import fields, is_dataclass -from pathlib import Path -from types import UnionType -from typing import Any, get_args - -import gi - -gi.require_version("WebKit", "6.0") - -log = logging.getLogger(__name__) - - -def sanitize_string(s: str) -> str: - return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") - - -def dataclass_to_dict(obj: Any) -> Any: - """ - Utility function to convert dataclasses to dictionaries - It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries - - It does NOT convert member functions. - """ - if dataclasses.is_dataclass(obj): - return { - sanitize_string(k): dataclass_to_dict(v) - for k, v in dataclasses.asdict(obj).items() - } - elif isinstance(obj, list | tuple): - return [dataclass_to_dict(item) for item in obj] - elif isinstance(obj, dict): - return {sanitize_string(k): dataclass_to_dict(v) for k, v in obj.items()} - elif isinstance(obj, Path): - return str(obj) - elif isinstance(obj, str): - return sanitize_string(obj) - else: - return obj - - -def is_union_type(type_hint: type) -> bool: - return type(type_hint) is UnionType - - -def get_inner_type(type_hint: type) -> type: - if is_union_type(type_hint): - # Return the first non-None type - return next(t for t in get_args(type_hint) if t is not type(None)) - return type_hint - - -def from_dict(t: type, data: dict[str, Any] | None) -> Any: - """ - Dynamically instantiate a data class from a dictionary, handling nested data classes. - """ - if not data: - return None - - try: - # Attempt to create an instance of the data_class - field_values = {} - for field in fields(t): - field_value = data.get(field.name) - field_type = get_inner_type(field.type) - if field_value is not None: - # If the field is another dataclass, recursively instantiate it - if is_dataclass(field_type): - field_value = from_dict(field_type, field_value) - elif isinstance(field_type, Path | str) and isinstance( - field_value, str - ): - field_value = ( - Path(field_value) if field_type == Path else field_value - ) - - if ( - field.default is not dataclasses.MISSING - or field.default_factory is not dataclasses.MISSING - ): - # Field has a default value. We cannot set the value to None - if field_value is not None: - field_values[field.name] = field_value - else: - field_values[field.name] = field_value - - return t(**field_values) - - except (TypeError, ValueError) as e: - print(f"Failed to instantiate {t.__name__}: {e}") - return None diff --git a/pkgs/clan-cli/clan_cli/api/__init__.py b/pkgs/clan-cli/clan_cli/api/__init__.py index 5237bc2a5..192bd4524 100644 --- a/pkgs/clan-cli/clan_cli/api/__init__.py +++ b/pkgs/clan-cli/clan_cli/api/__init__.py @@ -1,141 +1,22 @@ -import dataclasses -import json from collections.abc import Callable -from dataclasses import dataclass, fields, is_dataclass +from dataclasses import dataclass from functools import wraps from inspect import Parameter, Signature, signature -from pathlib import Path -from types import UnionType from typing import ( Annotated, Any, Generic, Literal, TypeVar, - get_args, - get_origin, get_type_hints, ) +from .serde import dataclass_to_dict, from_dict, sanitize_string + +__all__ = ["from_dict", "dataclass_to_dict", "sanitize_string"] + from clan_cli.errors import ClanError - -def sanitize_string(s: str) -> str: - # Using the native string sanitizer to handle all edge cases - # Remove the outer quotes '"string"' - return json.dumps(s)[1:-1] - - -def dataclass_to_dict(obj: Any) -> Any: - """ - Utility function to convert dataclasses to dictionaries - It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries - - It does NOT convert member functions. - """ - if is_dataclass(obj): - return { - # Use either the original name or name - sanitize_string( - field.metadata.get("original_name", field.name) - ): dataclass_to_dict(getattr(obj, field.name)) - for field in fields(obj) # type: ignore - } - elif isinstance(obj, list | tuple): - return [dataclass_to_dict(item) for item in obj] - elif isinstance(obj, dict): - return {sanitize_string(k): dataclass_to_dict(v) for k, v in obj.items()} - elif isinstance(obj, Path): - return sanitize_string(str(obj)) - elif isinstance(obj, str): - return sanitize_string(obj) - else: - return obj - - -def is_union_type(type_hint: type) -> bool: - return type(type_hint) is UnionType - - -def get_inner_type(type_hint: type) -> type: - if is_union_type(type_hint): - # Return the first non-None type - return next(t for t in get_args(type_hint) if t is not type(None)) - return type_hint - - -def get_second_type(type_hint: type[dict]) -> type: - """ - Get the value type of a dictionary type hint - """ - args = get_args(type_hint) - if len(args) == 2: - # Return the second argument, which should be the value type (Machine) - return args[1] - - raise ValueError(f"Invalid type hint for dict: {type_hint}") - - -def from_dict(t: type, data: dict[str, Any] | None) -> Any: - """ - Dynamically instantiate a data class from a dictionary, handling nested data classes. - """ - if data is None: - return None - - try: - # Attempt to create an instance of the data_class - field_values = {} - for field in fields(t): - original_name = field.metadata.get("original_name", field.name) - - field_value = data.get(original_name) - - field_type = get_inner_type(field.type) # type: ignore - - if original_name in data: - # If the field is another dataclass, recursively instantiate it - if is_dataclass(field_type): - field_value = from_dict(field_type, field_value) - elif isinstance(field_type, Path | str) and isinstance( - field_value, str - ): - field_value = ( - Path(field_value) if field_type == Path else field_value - ) - elif get_origin(field_type) is dict and isinstance(field_value, dict): - # The field is a dictionary with a specific type - inner_type = get_second_type(field_type) - field_value = { - k: from_dict(inner_type, v) for k, v in field_value.items() - } - elif get_origin is list and isinstance(field_value, list): - # The field is a list with a specific type - inner_type = get_args(field_type)[0] - field_value = [from_dict(inner_type, v) for v in field_value] - - # Set the value - if ( - field.default is not dataclasses.MISSING - or field.default_factory is not dataclasses.MISSING - ): - # Fields with default value - # a: Int = 1 - # b: list = Field(default_factory=list) - if original_name in data or field_value is not None: - field_values[field.name] = field_value - else: - # Fields without default value - # a: Int - field_values[field.name] = field_value - - return t(**field_values) - - except (TypeError, ValueError) as e: - print(f"Failed to instantiate {t.__name__}: {e} {data}") - return None - - T = TypeVar("T") ResponseDataType = TypeVar("ResponseDataType") diff --git a/pkgs/clan-cli/clan_cli/api/serde.py b/pkgs/clan-cli/clan_cli/api/serde.py new file mode 100644 index 000000000..25fa33498 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/api/serde.py @@ -0,0 +1,101 @@ +""" +This module provides utility functions for serialization and deserialization of data classes. + +Functions: +- sanitize_string(s: str) -> str: Ensures a string is properly escaped for json serializing. +- dataclass_to_dict(obj: Any) -> Any: Converts a data class and its nested data classes, lists, tuples, and dictionaries to dictionaries. +- from_dict(t: type[T], data: Any) -> T: Dynamically instantiates a data class from a dictionary, constructing nested data classes, validates all required fields exist and have the expected type. + +Classes: +- TypeAdapter: A Pydantic type adapter for data classes. + +Exceptions: +- ValidationError: Raised when there is a validation error during deserialization. +- ClanError: Raised when there is an error during serialization or deserialization. + +Dependencies: +- dataclasses: Provides the @dataclass decorator and related functions for creating data classes. +- json: Provides functions for working with JSON data. +- collections.abc: Provides abstract base classes for collections. +- functools: Provides functions for working with higher-order functions and decorators. +- inspect: Provides functions for inspecting live objects. +- operator: Provides functions for working with operators. +- pathlib: Provides classes for working with filesystem paths. +- types: Provides functions for working with types. +- typing: Provides support for type hints. +- pydantic: A library for data validation and settings management. +- pydantic_core: Core functionality for Pydantic. + +Note: This module assumes the presence of other modules and classes such as `ClanError` and `ErrorDetails` from the `clan_cli.errors` module. +""" + +import json +from dataclasses import dataclass, fields, is_dataclass +from pathlib import Path +from typing import ( + Any, + TypeVar, +) + +from pydantic import TypeAdapter, ValidationError +from pydantic_core import ErrorDetails + +from clan_cli.errors import ClanError + + +def sanitize_string(s: str) -> str: + # Using the native string sanitizer to handle all edge cases + # Remove the outer quotes '"string"' + return json.dumps(s)[1:-1] + + +def dataclass_to_dict(obj: Any) -> Any: + """ + Utility function to convert dataclasses to dictionaries + It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries + + It does NOT convert member functions. + """ + if is_dataclass(obj): + return { + # Use either the original name or name + sanitize_string( + field.metadata.get("original_name", field.name) + ): dataclass_to_dict(getattr(obj, field.name)) + for field in fields(obj) + if not field.name.startswith("_") # type: ignore + } + elif isinstance(obj, list | tuple): + return [dataclass_to_dict(item) for item in obj] + elif isinstance(obj, dict): + return {sanitize_string(k): dataclass_to_dict(v) for k, v in obj.items()} + elif isinstance(obj, Path): + return sanitize_string(str(obj)) + elif isinstance(obj, str): + return sanitize_string(obj) + else: + return obj + + +T = TypeVar("T", bound=dataclass) # type: ignore + + +def from_dict(t: type[T], data: Any) -> T: + """ + Dynamically instantiate a data class from a dictionary, handling nested data classes. + We use dataclasses. But the deserialization logic of pydantic takes a lot of complexity. + """ + adapter = TypeAdapter(t) + try: + return adapter.validate_python(data) + except ValidationError as e: + fst_error: ErrorDetails = e.errors()[0] + if not fst_error: + raise ClanError(msg=str(e)) + + msg = fst_error.get("msg") + loc = fst_error.get("loc") + field_path = "Unknown" + if loc: + field_path = str(loc) + raise ClanError(msg=msg, location=f"{t!s}: {field_path}", description=str(e)) diff --git a/pkgs/clan-cli/default.nix b/pkgs/clan-cli/default.nix index a1968f850..bdde4d6c3 100644 --- a/pkgs/clan-cli/default.nix +++ b/pkgs/clan-cli/default.nix @@ -17,6 +17,8 @@ setuptools, stdenv, + pydantic, + # custom args clan-core-path, nixpkgs, @@ -28,6 +30,7 @@ let pythonDependencies = [ argcomplete # Enables shell completions + pydantic ]; # load nixpkgs runtime dependencies from a json file diff --git a/pkgs/clan-cli/tests/test_deserializers.py b/pkgs/clan-cli/tests/test_deserializers.py new file mode 100644 index 000000000..778d3728f --- /dev/null +++ b/pkgs/clan-cli/tests/test_deserializers.py @@ -0,0 +1,157 @@ +from dataclasses import dataclass, field +from pathlib import Path + +import pytest + +# Functions to test +from clan_cli.api import ( + dataclass_to_dict, + from_dict, +) +from clan_cli.errors import ClanError +from clan_cli.inventory import ( + Inventory, + Machine, + MachineDeploy, + Meta, + Service, + ServiceBorgbackup, + ServiceBorgbackupRole, + ServiceBorgbackupRoleClient, + ServiceBorgbackupRoleServer, + ServiceMeta, +) + + +def test_simple() -> None: + @dataclass + class Person: + name: str + + person_dict = { + "name": "John", + } + + expected_person = Person( + name="John", + ) + + assert from_dict(Person, person_dict) == expected_person + + +def test_nested() -> None: + @dataclass + class Age: + value: str + + @dataclass + class Person: + name: str + # deeply nested dataclasses + age: Age + age_list: list[Age] + age_dict: dict[str, Age] + # Optional field + home: Path | None + + person_dict = { + "name": "John", + "age": { + "value": "99", + }, + "age_list": [{"value": "66"}, {"value": "77"}], + "age_dict": {"now": {"value": "55"}, "max": {"value": "100"}}, + "home": "/home", + } + + expected_person = Person( + name="John", + age=Age("99"), + age_list=[Age("66"), Age("77")], + age_dict={"now": Age("55"), "max": Age("100")}, + home=Path("/home"), + ) + + assert from_dict(Person, person_dict) == expected_person + + +def test_simple_field_missing() -> None: + @dataclass + class Person: + name: str + + person_dict = {} + + with pytest.raises(ClanError): + from_dict(Person, person_dict) + + +def test_deserialize_extensive_inventory() -> None: + data = { + "meta": {"name": "superclan", "description": "nice clan"}, + "services": { + "borgbackup": { + "instance1": { + "meta": { + "name": "borg1", + }, + "roles": { + "client": {}, + "server": {}, + }, + } + }, + }, + "machines": {"foo": {"name": "foo", "deploy": {}}}, + } + expected = Inventory( + meta=Meta(name="superclan", description="nice clan"), + services=Service( + borgbackup={ + "instance1": ServiceBorgbackup( + meta=ServiceMeta(name="borg1"), + roles=ServiceBorgbackupRole( + client=ServiceBorgbackupRoleClient(), + server=ServiceBorgbackupRoleServer(), + ), + ) + } + ), + machines={"foo": Machine(deploy=MachineDeploy(), name="foo")}, + ) + assert from_dict(Inventory, data) == expected + + +def test_alias_field() -> None: + @dataclass + class Person: + name: str = field(metadata={"alias": "--user-name--"}) + + data = {"--user-name--": "John"} + expected = Person(name="John") + + assert from_dict(Person, data) == expected + + +def test_path_field() -> None: + @dataclass + class Person: + name: Path + + data = {"name": "John"} + expected = Person(name=Path("John")) + + assert from_dict(Person, data) == expected + + +def test_private_public_fields() -> None: + @dataclass + class Person: + name: Path + _name: str | None = None + + data = {"name": "John"} + expected = Person(name=Path("John")) + assert from_dict(Person, data) == expected + + assert dataclass_to_dict(expected) == data diff --git a/pkgs/clan-cli/tests/test_serializers.py b/pkgs/clan-cli/tests/test_serializers.py new file mode 100644 index 000000000..fa6557d90 --- /dev/null +++ b/pkgs/clan-cli/tests/test_serializers.py @@ -0,0 +1,106 @@ +from dataclasses import dataclass, field + +# Functions to test +from clan_cli.api import ( + dataclass_to_dict, + sanitize_string, +) + + +# +def test_sanitize_string() -> None: + # Simple strings + assert sanitize_string("Hello World") == "Hello World" + assert sanitize_string("Hello\nWorld") == "Hello\\nWorld" + assert sanitize_string("Hello\tWorld") == "Hello\\tWorld" + assert sanitize_string("Hello\rWorld") == "Hello\\rWorld" + assert sanitize_string("Hello\fWorld") == "Hello\\fWorld" + assert sanitize_string("Hello\vWorld") == "Hello\\u000bWorld" + assert sanitize_string("Hello\bWorld") == "Hello\\bWorld" + assert sanitize_string("Hello\\World") == "Hello\\\\World" + assert sanitize_string('Hello"World') == 'Hello\\"World' + assert sanitize_string("Hello'World") == "Hello'World" + assert sanitize_string("Hello\0World") == "Hello\\u0000World" + # Console escape characters + + assert sanitize_string("\033[1mBold\033[0m") == "\\u001b[1mBold\\u001b[0m" # Red + assert sanitize_string("\033[31mRed\033[0m") == "\\u001b[31mRed\\u001b[0m" # Blue + assert ( + sanitize_string("\033[42mGreen\033[0m") == "\\u001b[42mGreen\\u001b[0m" + ) # Green + assert sanitize_string("\033[4mUnderline\033[0m") == "\\u001b[4mUnderline\\u001b[0m" + assert ( + sanitize_string("\033[91m\033[1mBold Red\033[0m") + == "\\u001b[91m\\u001b[1mBold Red\\u001b[0m" + ) + + +def test_dataclass_to_dict() -> None: + @dataclass + class Person: + name: str + age: int + + person = Person(name="John", age=25) + expected_dict = {"name": "John", "age": 25} + assert dataclass_to_dict(person) == expected_dict + + +def test_dataclass_to_dict_nested() -> None: + @dataclass + class Address: + city: str = "afghanistan" + zip: str = "01234" + + @dataclass + class Person: + name: str + age: int + address: Address = field(default_factory=Address) + + person1 = Person(name="John", age=25) + expected_dict1 = { + "name": "John", + "age": 25, + "address": {"city": "afghanistan", "zip": "01234"}, + } + # address must be constructed with default values if not passed + assert dataclass_to_dict(person1) == expected_dict1 + + person2 = Person(name="John", age=25, address=Address(zip="0", city="Anywhere")) + expected_dict2 = { + "name": "John", + "age": 25, + "address": {"zip": "0", "city": "Anywhere"}, + } + assert dataclass_to_dict(person2) == expected_dict2 + + +def test_dataclass_to_dict_defaults() -> None: + @dataclass + class Foo: + home: dict[str, str] = field(default_factory=dict) + work: list[str] = field(default_factory=list) + + @dataclass + class Person: + name: str = field(default="jon") + age: int = field(default=1) + foo: Foo = field(default_factory=Foo) + + default_person = Person() + expected_default = { + "name": "jon", + "age": 1, + "foo": {"home": {}, "work": []}, + } + # address must be constructed with default values if not passed + assert dataclass_to_dict(default_person) == expected_default + + real_person = Person(name="John", age=25, foo=Foo(home={"a": "b"}, work=["a", "b"])) + expected = { + "name": "John", + "age": 25, + "foo": {"home": {"a": "b"}, "work": ["a", "b"]}, + } + assert dataclass_to_dict(real_person) == expected From 7a3fad01e060de4b3548b22b76d42c4df8a66d6e Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 26 Jul 2024 15:23:25 +0200 Subject: [PATCH 04/16] PropagatedBuild inputs workaround --- docs/nix/flake-module.nix | 4 +++- pkgs/clan-app/default.nix | 9 ++++++++- pkgs/clan-cli/clan_cli/inventory/classes.py | 2 +- pkgs/clan-cli/default.nix | 3 ++- pkgs/clan-cli/flake-module.nix | 14 ++++++++++++-- pkgs/clan-vm-manager/default.nix | 9 ++++++++- pkgs/classgen/main.py | 2 +- 7 files changed, 35 insertions(+), 8 deletions(-) diff --git a/docs/nix/flake-module.nix b/docs/nix/flake-module.nix index 89052d53f..d2d82d6f4 100644 --- a/docs/nix/flake-module.nix +++ b/docs/nix/flake-module.nix @@ -54,9 +54,11 @@ module-docs = pkgs.runCommand "rendered" { - nativeBuildInputs = [ + buildInputs = [ pkgs.python3 self'.packages.clan-cli + # TODO: see postFixup clan-cli/default.nix:L188 + self'.packages.clan-cli.propagatedBuildInputs ]; } '' diff --git a/pkgs/clan-app/default.nix b/pkgs/clan-app/default.nix index a8e6bcf97..e54cc5966 100644 --- a/pkgs/clan-app/default.nix +++ b/pkgs/clan-app/default.nix @@ -94,7 +94,14 @@ python3.pkgs.buildPythonApplication rec { # that all necessary dependencies are consistently available both # at build time and runtime, buildInputs = allPythonDeps ++ runtimeDependencies; - propagatedBuildInputs = allPythonDeps ++ runtimeDependencies; + propagatedBuildInputs = + allPythonDeps + ++ runtimeDependencies + ++ [ + + # TODO: see postFixup clan-cli/default.nix:L188 + clan-cli.propagatedBuildInputs + ]; # also re-expose dependencies so we test them in CI passthru = { diff --git a/pkgs/clan-cli/clan_cli/inventory/classes.py b/pkgs/clan-cli/clan_cli/inventory/classes.py index 41d116831..60c9107b9 100644 --- a/pkgs/clan-cli/clan_cli/inventory/classes.py +++ b/pkgs/clan-cli/clan_cli/inventory/classes.py @@ -153,7 +153,7 @@ class ServiceSingleDisk: class Service: borgbackup: dict[str, ServiceBorgbackup] = field(default_factory = dict) packages: dict[str, ServicePackage] = field(default_factory = dict) - single_disk: dict[str, ServiceSingleDisk] = field(default_factory = dict, metadata = {"original_name": "single-disk"}) + single_disk: dict[str, ServiceSingleDisk] = field(default_factory = dict, metadata = {"alias": "single-disk"}) @dataclass diff --git a/pkgs/clan-cli/default.nix b/pkgs/clan-cli/default.nix index bdde4d6c3..12d8db646 100644 --- a/pkgs/clan-cli/default.nix +++ b/pkgs/clan-cli/default.nix @@ -30,7 +30,7 @@ let pythonDependencies = [ argcomplete # Enables shell completions - pydantic + pydantic # Dataclass deserialisation / validation / schemas ]; # load nixpkgs runtime dependencies from a json file @@ -183,6 +183,7 @@ python3.pkgs.buildPythonApplication { ''; # Clean up after the package to avoid leaking python packages into a devshell + # TODO: factor seperate cli / API packages postFixup = '' rm $out/nix-support/propagated-build-inputs ''; diff --git a/pkgs/clan-cli/flake-module.nix b/pkgs/clan-cli/flake-module.nix index c783a494f..deee72d8c 100644 --- a/pkgs/clan-cli/flake-module.nix +++ b/pkgs/clan-cli/flake-module.nix @@ -62,7 +62,12 @@ name = "clan-cli-docs"; src = ./.; - buildInputs = [ pkgs.python3 ]; + buildInputs = [ + + # TODO: see postFixup clan-cli/default.nix:L188 + pkgs.python3 + self'.packages.clan-cli.propagatedBuildInputs + ]; installPhase = '' ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json ./clan_cli/inventory/classes.py @@ -77,7 +82,12 @@ name = "clan-ts-api"; src = ./.; - buildInputs = [ pkgs.python3 ]; + buildInputs = [ + pkgs.python3 + + # TODO: see postFixup clan-cli/default.nix:L188 + self'.packages.clan-cli.propagatedBuildInputs + ]; installPhase = '' ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json ./clan_cli/inventory/classes.py diff --git a/pkgs/clan-vm-manager/default.nix b/pkgs/clan-vm-manager/default.nix index 172fa0666..4c15ee8ee 100644 --- a/pkgs/clan-vm-manager/default.nix +++ b/pkgs/clan-vm-manager/default.nix @@ -93,7 +93,14 @@ python3.pkgs.buildPythonApplication rec { # that all necessary dependencies are consistently available both # at build time and runtime, buildInputs = allPythonDeps ++ runtimeDependencies; - propagatedBuildInputs = allPythonDeps ++ runtimeDependencies; + propagatedBuildInputs = + allPythonDeps + ++ runtimeDependencies + ++ [ + + # TODO: see postFixup clan-cli/default.nix:L188 + clan-cli.propagatedBuildInputs + ]; # also re-expose dependencies so we test them in CI passthru = { diff --git a/pkgs/classgen/main.py b/pkgs/classgen/main.py index a4537c276..8cafe0b6c 100644 --- a/pkgs/classgen/main.py +++ b/pkgs/classgen/main.py @@ -245,7 +245,7 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) -> field_meta = None if field_name != prop: - field_meta = f"""{{"original_name": "{prop}"}}""" + field_meta = f"""{{"alias": "{prop}"}}""" finalize_field = partial(get_field_def, field_name, field_meta) From 395a7fc70e5fbfce50a0a3dff2e9608aab4c978c Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 29 Jul 2024 09:00:24 +0200 Subject: [PATCH 05/16] Serializer: use alias, make it configurable for different use cases --- pkgs/clan-cli/clan_cli/api/serde.py | 53 ++++++++++++----------- pkgs/clan-cli/tests/test_deserializers.py | 17 +++++--- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/api/serde.py b/pkgs/clan-cli/clan_cli/api/serde.py index 25fa33498..0f9c5d1fc 100644 --- a/pkgs/clan-cli/clan_cli/api/serde.py +++ b/pkgs/clan-cli/clan_cli/api/serde.py @@ -49,32 +49,35 @@ def sanitize_string(s: str) -> str: return json.dumps(s)[1:-1] -def dataclass_to_dict(obj: Any) -> Any: - """ - Utility function to convert dataclasses to dictionaries - It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries +def dataclass_to_dict(obj: Any, *, use_alias: bool = True) -> Any: + def _to_dict(obj: Any) -> Any: + """ + Utility function to convert dataclasses to dictionaries + It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries - It does NOT convert member functions. - """ - if is_dataclass(obj): - return { - # Use either the original name or name - sanitize_string( - field.metadata.get("original_name", field.name) - ): dataclass_to_dict(getattr(obj, field.name)) - for field in fields(obj) - if not field.name.startswith("_") # type: ignore - } - elif isinstance(obj, list | tuple): - return [dataclass_to_dict(item) for item in obj] - elif isinstance(obj, dict): - return {sanitize_string(k): dataclass_to_dict(v) for k, v in obj.items()} - elif isinstance(obj, Path): - return sanitize_string(str(obj)) - elif isinstance(obj, str): - return sanitize_string(obj) - else: - return obj + It does NOT convert member functions. + """ + if is_dataclass(obj): + return { + # Use either the original name or name + sanitize_string( + field.metadata.get("alias", field.name) if use_alias else field.name + ): _to_dict(getattr(obj, field.name)) + for field in fields(obj) + if not field.name.startswith("_") # type: ignore + } + elif isinstance(obj, list | tuple): + return [_to_dict(item) for item in obj] + elif isinstance(obj, dict): + return {sanitize_string(k): _to_dict(v) for k, v in obj.items()} + elif isinstance(obj, Path): + return sanitize_string(str(obj)) + elif isinstance(obj, str): + return sanitize_string(obj) + else: + return obj + + return _to_dict(obj) T = TypeVar("T", bound=dataclass) # type: ignore diff --git a/pkgs/clan-cli/tests/test_deserializers.py b/pkgs/clan-cli/tests/test_deserializers.py index 778d3728f..c93fa041f 100644 --- a/pkgs/clan-cli/tests/test_deserializers.py +++ b/pkgs/clan-cli/tests/test_deserializers.py @@ -4,10 +4,7 @@ from pathlib import Path import pytest # Functions to test -from clan_cli.api import ( - dataclass_to_dict, - from_dict, -) +from clan_cli.api import dataclass_to_dict, from_dict from clan_cli.errors import ClanError from clan_cli.inventory import ( Inventory, @@ -87,6 +84,7 @@ def test_simple_field_missing() -> None: def test_deserialize_extensive_inventory() -> None: + # TODO: Make this an abstract test, so it doesn't break the test if the inventory changes data = { "meta": {"name": "superclan", "description": "nice clan"}, "services": { @@ -130,7 +128,16 @@ def test_alias_field() -> None: data = {"--user-name--": "John"} expected = Person(name="John") - assert from_dict(Person, data) == expected + person = from_dict(Person, data) + + # Deserialize + assert person == expected + + # Serialize with alias + assert dataclass_to_dict(person) == data + + # Serialize without alias + assert dataclass_to_dict(person, use_alias=False) == {"name": "John"} def test_path_field() -> None: From 4be5ed28dbc31e7e8a530ab62c30d732f03df045 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 29 Jul 2024 11:48:39 +0200 Subject: [PATCH 06/16] Clan-app: example disk view --- pkgs/clan-app/shell.nix | 2 +- pkgs/clan-cli/clan_cli/api/serde.py | 4 ++- pkgs/clan-cli/clan_cli/api/util.py | 4 ++- pkgs/clan-cli/tests/test_deserializers.py | 15 +++++++++ pkgs/webview-ui/app/src/Routes.tsx | 6 ++++ pkgs/webview-ui/app/src/routes/disk/view.tsx | 33 ++++++++++++++++++++ 6 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 pkgs/webview-ui/app/src/routes/disk/view.tsx diff --git a/pkgs/clan-app/shell.nix b/pkgs/clan-app/shell.nix index 7d123a9b4..6f66c1953 100644 --- a/pkgs/clan-app/shell.nix +++ b/pkgs/clan-app/shell.nix @@ -29,7 +29,7 @@ let ]); in mkShell { - inherit (clan-app) nativeBuildInputs; + inherit (clan-app) nativeBuildInputs propagatedBuildInputs; inputsFrom = [ self'.devShells.default ]; diff --git a/pkgs/clan-cli/clan_cli/api/serde.py b/pkgs/clan-cli/clan_cli/api/serde.py index 0f9c5d1fc..57345c3db 100644 --- a/pkgs/clan-cli/clan_cli/api/serde.py +++ b/pkgs/clan-cli/clan_cli/api/serde.py @@ -90,7 +90,9 @@ def from_dict(t: type[T], data: Any) -> T: """ adapter = TypeAdapter(t) try: - return adapter.validate_python(data) + return adapter.validate_python( + data, + ) except ValidationError as e: fst_error: ErrorDetails = e.errors()[0] if not fst_error: diff --git a/pkgs/clan-cli/clan_cli/api/util.py b/pkgs/clan-cli/clan_cli/api/util.py index 505fab545..2dd4e03c1 100644 --- a/pkgs/clan-cli/clan_cli/api/util.py +++ b/pkgs/clan-cli/clan_cli/api/util.py @@ -74,7 +74,9 @@ def type_to_dict(t: Any, scope: str = "", type_map: dict[TypeVar, type] = {}) -> if dataclasses.is_dataclass(t): fields = dataclasses.fields(t) properties = { - f.name: type_to_dict(f.type, f"{scope} {t.__name__}.{f.name}", type_map) + f.metadata.get("alias", f.name): type_to_dict( + f.type, f"{scope} {t.__name__}.{f.name}", type_map + ) for f in fields if not f.name.startswith("_") } diff --git a/pkgs/clan-cli/tests/test_deserializers.py b/pkgs/clan-cli/tests/test_deserializers.py index c93fa041f..f87b65adc 100644 --- a/pkgs/clan-cli/tests/test_deserializers.py +++ b/pkgs/clan-cli/tests/test_deserializers.py @@ -140,6 +140,21 @@ def test_alias_field() -> None: assert dataclass_to_dict(person, use_alias=False) == {"name": "John"} +def test_alias_field_from_orig_name() -> None: + """ + Field declares an alias. But the data is provided with the field name. + """ + + @dataclass + class Person: + name: str = field(metadata={"alias": "--user-name--"}) + + data = {"user": "John"} + + with pytest.raises(ClanError): + from_dict(Person, data) + + def test_path_field() -> None: @dataclass class Person: diff --git a/pkgs/webview-ui/app/src/Routes.tsx b/pkgs/webview-ui/app/src/Routes.tsx index aceef3271..a9a618fff 100644 --- a/pkgs/webview-ui/app/src/Routes.tsx +++ b/pkgs/webview-ui/app/src/Routes.tsx @@ -9,6 +9,7 @@ import { Settings } from "./routes/settings"; import { Welcome } from "./routes/welcome"; import { Deploy } from "./routes/deploy"; import { CreateMachine } from "./routes/machines/create"; +import { DiskView } from "./routes/disk/view"; export type Route = keyof typeof routes; @@ -63,6 +64,11 @@ export const routes = { label: "deploy", icon: "content_copy", }, + diskConfig: { + child: DiskView, + label: "diskConfig", + icon: "disk", + }, }; interface RouterProps { diff --git a/pkgs/webview-ui/app/src/routes/disk/view.tsx b/pkgs/webview-ui/app/src/routes/disk/view.tsx new file mode 100644 index 000000000..c9798c119 --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/disk/view.tsx @@ -0,0 +1,33 @@ +import { callApi } from "@/src/api"; +import { activeURI } from "@/src/App"; +import { createQuery } from "@tanstack/solid-query"; +import { createEffect } from "solid-js"; + +export function DiskView() { + const query = createQuery(() => ({ + queryKey: ["disk", activeURI], + queryFn: async () => { + const currUri = activeURI(); + if (currUri) { + // Example of calling an API + const result = await callApi("get_inventory", { base_path: currUri }); + if (result.status === "error") throw new Error("Failed to fetch data"); + + return result.data; + } + }, + })); + createEffect(() => { + // Example debugging the data + console.log(query.data); + }); + return ( +
+

Configure Disk

+

+ Select machine then configure the disk. Required before installing for + the first time. +

+
+ ); +} From 395c5cbace91b8940ef7c5028dba1a8b3048c83c Mon Sep 17 00:00:00 2001 From: DavHau Date: Thu, 25 Jul 2024 14:08:11 +0700 Subject: [PATCH 07/16] vars/sops: fix loading of vars from directory structure --- nixosModules/clanCore/vars/default.nix | 2 +- nixosModules/clanCore/vars/interface.nix | 56 +++++++++-------- nixosModules/clanCore/vars/public/in_repo.nix | 5 +- .../clanCore/vars/secret/password-store.nix | 2 +- nixosModules/clanCore/vars/secret/sops.nix | 61 ------------------- .../clanCore/vars/secret/sops/default.nix | 50 +++++++++++++++ .../vars/secret/sops/eval-tests/default.nix | 43 +++++++++++++ .../vars/my_machine/my_generator/my_secret | 0 .../clanCore/vars/secret/sops/funcs.nix | 28 +++++++++ nixosModules/clanCore/vars/settings-opts.nix | 1 + pkgs/clan-cli/clan_cli/machines/update.py | 2 + 11 files changed, 158 insertions(+), 92 deletions(-) delete mode 100644 nixosModules/clanCore/vars/secret/sops.nix create mode 100644 nixosModules/clanCore/vars/secret/sops/default.nix create mode 100644 nixosModules/clanCore/vars/secret/sops/eval-tests/default.nix create mode 100644 nixosModules/clanCore/vars/secret/sops/eval-tests/populated/vars/my_machine/my_generator/my_secret create mode 100644 nixosModules/clanCore/vars/secret/sops/funcs.nix diff --git a/nixosModules/clanCore/vars/default.nix b/nixosModules/clanCore/vars/default.nix index db5d55c9b..396f4e531 100644 --- a/nixosModules/clanCore/vars/default.nix +++ b/nixosModules/clanCore/vars/default.nix @@ -18,7 +18,7 @@ in ./public/in_repo.nix # ./public/vm.nix ./secret/password-store.nix - ./secret/sops.nix + ./secret/sops # ./secret/vm.nix ]; options.clan.core.vars = lib.mkOption { diff --git a/nixosModules/clanCore/vars/interface.nix b/nixosModules/clanCore/vars/interface.nix index 4b42766f3..c86f6925d 100644 --- a/nixosModules/clanCore/vars/interface.nix +++ b/nixosModules/clanCore/vars/interface.nix @@ -72,7 +72,7 @@ in name of the generator ''; readOnly = true; - default = generator.name; + default = generator.config._module.args.name; }; secret = { description = '' @@ -87,7 +87,6 @@ in This will be set automatically ''; type = str; - readOnly = true; }; value = { description = '' @@ -109,32 +108,35 @@ in For example, a prompt named 'prompt1' will be available via $prompts/prompt1 ''; default = { }; - type = attrsOf (submodule { - options = options { - description = { - description = '' - The description of the prompted value - ''; - type = str; - example = "SSH private key"; + type = attrsOf ( + submodule (prompt: { + options = options { + description = { + description = '' + The description of the prompted value + ''; + type = str; + example = "SSH private key"; + default = prompt.config._module.args.name; + }; + type = { + description = '' + The input type of the prompt. + The following types are available: + - hidden: A hidden text (e.g. password) + - line: A single line of text + - multiline: A multiline text + ''; + type = enum [ + "hidden" + "line" + "multiline" + ]; + default = "line"; + }; }; - type = { - description = '' - The input type of the prompt. - The following types are available: - - hidden: A hidden text (e.g. password) - - line: A single line of text - - multiline: A multiline text - ''; - type = enum [ - "hidden" - "line" - "multiline" - ]; - default = "line"; - }; - }; - }); + }) + ); }; runtimeInputs = { description = '' diff --git a/nixosModules/clanCore/vars/public/in_repo.nix b/nixosModules/clanCore/vars/public/in_repo.nix index ba7a6aa41..3533681a2 100644 --- a/nixosModules/clanCore/vars/public/in_repo.nix +++ b/nixosModules/clanCore/vars/public/in_repo.nix @@ -5,8 +5,9 @@ { publicModule = "clan_cli.vars.public_modules.in_repo"; fileModule = file: { - path = - config.clan.core.clanDir + "/machines/${config.clan.core.machineName}/vars/${file.config.name}"; + path = lib.mkIf (file.config.secret == false) ( + config.clan.core.clanDir + "/machines/${config.clan.core.machineName}/vars/${file.config.name}" + ); }; }; } diff --git a/nixosModules/clanCore/vars/secret/password-store.nix b/nixosModules/clanCore/vars/secret/password-store.nix index 900737c76..ceab97029 100644 --- a/nixosModules/clanCore/vars/secret/password-store.nix +++ b/nixosModules/clanCore/vars/secret/password-store.nix @@ -4,7 +4,7 @@ lib.mkIf (config.clan.core.vars.settings.secretStore == "password-store") { fileModule = file: { - path = lib.mkIf file.secret "${config.clan.core.password-store.targetDirectory}/${config.clan.core.machineName}-${file.config.generatorName}-${file.config.name}"; + path = lib.mkIf file.config.secret "${config.clan.core.password-store.targetDirectory}/${config.clan.core.machineName}-${file.config.generatorName}-${file.config.name}"; }; secretUploadDirectory = lib.mkDefault "/etc/secrets"; secretModule = "clan_cli.vars.secret_modules.password_store"; diff --git a/nixosModules/clanCore/vars/secret/sops.nix b/nixosModules/clanCore/vars/secret/sops.nix deleted file mode 100644 index 3e22e8628..000000000 --- a/nixosModules/clanCore/vars/secret/sops.nix +++ /dev/null @@ -1,61 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: -let - secretsDir = config.clan.core.clanDir + "/sops/secrets"; - groupsDir = config.clan.core.clanDir + "/sops/groups"; - - # My symlink is in the nixos module detected as a directory also it works in the repl. Is this because of pure evaluation? - containsSymlink = - path: - builtins.pathExists path - && (builtins.readFileType path == "directory" || builtins.readFileType path == "symlink"); - - containsMachine = - parent: name: type: - type == "directory" && containsSymlink "${parent}/${name}/machines/${config.clan.core.machineName}"; - - containsMachineOrGroups = - name: type: - (containsMachine secretsDir name type) - || lib.any ( - group: type == "directory" && containsSymlink "${secretsDir}/${name}/groups/${group}" - ) groups; - - filterDir = - filter: dir: - lib.optionalAttrs (builtins.pathExists dir) (lib.filterAttrs filter (builtins.readDir dir)); - - groups = builtins.attrNames (filterDir (containsMachine groupsDir) groupsDir); - secrets = filterDir containsMachineOrGroups secretsDir; -in -{ - config.clan.core.vars.settings = lib.mkIf (config.clan.core.vars.settings.secretStore == "sops") { - # Before we generate a secret we cannot know the path yet, so we need to set it to an empty string - fileModule = file: { - path = - lib.mkIf file.secret - config.sops.secrets.${"vars-${config.clan.core.machineName}-${file.config.generatorName}-${file.config.name}"}.path - or "/no-such-path"; - }; - secretModule = "clan_cli.vars.secret_modules.sops"; - secretUploadDirectory = lib.mkDefault "/var/lib/sops-nix"; - }; - - config.sops = lib.mkIf (config.clan.core.vars.settings.secretStore == "sops") { - secrets = builtins.mapAttrs (name: _: { - sopsFile = config.clan.core.clanDir + "/sops/secrets/${name}/secret"; - format = "binary"; - }) secrets; - # To get proper error messages about missing secrets we need a dummy secret file that is always present - defaultSopsFile = lib.mkIf config.sops.validateSopsFiles ( - lib.mkDefault (builtins.toString (pkgs.writeText "dummy.yaml" "")) - ); - age.keyFile = lib.mkIf (builtins.pathExists ( - config.clan.core.clanDir + "/sops/secrets/${config.clan.core.machineName}-age.key/secret" - )) (lib.mkDefault "/var/lib/sops-nix/key.txt"); - }; -} diff --git a/nixosModules/clanCore/vars/secret/sops/default.nix b/nixosModules/clanCore/vars/secret/sops/default.nix new file mode 100644 index 000000000..9a6234535 --- /dev/null +++ b/nixosModules/clanCore/vars/secret/sops/default.nix @@ -0,0 +1,50 @@ +{ + config, + lib, + pkgs, + ... +}: +let + + inherit (lib) flip; + + inherit (import ./funcs.nix { inherit lib; }) listVars; + + varsDir = config.clan.core.clanDir + "/sops/vars"; + + vars = listVars varsDir; + +in +{ + config.clan.core.vars.settings = lib.mkIf (config.clan.core.vars.settings.secretStore == "sops") { + # Before we generate a secret we cannot know the path yet, so we need to set it to an empty string + fileModule = file: { + path = lib.mkIf file.config.secret ( + config.sops.secrets.${"vars-${config.clan.core.machineName}-${file.config.generatorName}-${file.config.name}"}.path + or "/no-such-path" + ); + }; + secretModule = "clan_cli.vars.secret_modules.sops"; + secretUploadDirectory = lib.mkDefault "/var/lib/sops-nix"; + }; + + config.sops = lib.mkIf (config.clan.core.vars.settings.secretStore == "sops") { + secrets = lib.listToAttrs ( + flip map vars (secret: { + name = secret.name; + value = { + sopsFile = + config.clan.core.clanDir + "/sops/vars/${secret.machine}/${secret.generator}/${secret.name}/secret"; + format = "binary"; + }; + }) + ); + # To get proper error messages about missing secrets we need a dummy secret file that is always present + defaultSopsFile = lib.mkIf config.sops.validateSopsFiles ( + lib.mkDefault (builtins.toString (pkgs.writeText "dummy.yaml" "")) + ); + age.keyFile = lib.mkIf (builtins.pathExists ( + config.clan.core.clanDir + "/sops/secrets/${config.clan.core.machineName}-age.key/secret" + )) (lib.mkDefault "/var/lib/sops-nix/key.txt"); + }; +} diff --git a/nixosModules/clanCore/vars/secret/sops/eval-tests/default.nix b/nixosModules/clanCore/vars/secret/sops/eval-tests/default.nix new file mode 100644 index 000000000..d775350a0 --- /dev/null +++ b/nixosModules/clanCore/vars/secret/sops/eval-tests/default.nix @@ -0,0 +1,43 @@ +{ + lib ? import , + pkgs ? import { }, +}: +let + inherit (import ../funcs.nix { inherit lib; }) readDirNames listVars; + + noVars = pkgs.runCommand "empty-dir" { } '' + mkdir $out + ''; + + emtpyVars = pkgs.runCommand "empty-dir" { } '' + mkdir -p $out/vars + ''; + +in +{ + test_readDirNames = { + expr = readDirNames ./populated/vars; + expected = [ "my_machine" ]; + }; + + test_listSecrets = { + expr = listVars ./populated/vars; + expected = [ + { + machine = "my_machine"; + generator = "my_generator"; + name = "my_secret"; + } + ]; + }; + + test_listSecrets_no_vars = { + expr = listVars noVars; + expected = [ ]; + }; + + test_listSecrets_empty_vars = { + expr = listVars emtpyVars; + expected = [ ]; + }; +} diff --git a/nixosModules/clanCore/vars/secret/sops/eval-tests/populated/vars/my_machine/my_generator/my_secret b/nixosModules/clanCore/vars/secret/sops/eval-tests/populated/vars/my_machine/my_generator/my_secret new file mode 100644 index 000000000..e69de29bb diff --git a/nixosModules/clanCore/vars/secret/sops/funcs.nix b/nixosModules/clanCore/vars/secret/sops/funcs.nix new file mode 100644 index 000000000..980e05773 --- /dev/null +++ b/nixosModules/clanCore/vars/secret/sops/funcs.nix @@ -0,0 +1,28 @@ +{ + lib ? import , + ... +}: +let + inherit (builtins) readDir; + + inherit (lib) concatMap flip; +in +rec { + readDirNames = + dir: + if !(builtins.pathExists dir) then [ ] else lib.mapAttrsToList (name: _type: name) (readDir dir); + + listVars = + varsDir: + flip concatMap (readDirNames varsDir) ( + machine_name: + flip concatMap (readDirNames (varsDir + "/${machine_name}")) ( + generator_name: + flip map (readDirNames (varsDir + "/${machine_name}/${generator_name}")) (secret_name: { + machine = machine_name; + generator = generator_name; + name = secret_name; + }) + ) + ); +} diff --git a/nixosModules/clanCore/vars/settings-opts.nix b/nixosModules/clanCore/vars/settings-opts.nix index cd31adf98..fce8bc693 100644 --- a/nixosModules/clanCore/vars/settings-opts.nix +++ b/nixosModules/clanCore/vars/settings-opts.nix @@ -30,6 +30,7 @@ ''; }; + # TODO: see if this is the right approach. Maybe revert to secretPathFunction fileModule = lib.mkOption { type = lib.types.deferredModule; internal = true; diff --git a/pkgs/clan-cli/clan_cli/machines/update.py b/pkgs/clan-cli/clan_cli/machines/update.py index cd6910cd1..c49377070 100644 --- a/pkgs/clan-cli/clan_cli/machines/update.py +++ b/pkgs/clan-cli/clan_cli/machines/update.py @@ -13,6 +13,7 @@ from ..facts.upload import upload_secrets from ..machines.machines import Machine from ..nix import nix_command, nix_metadata from ..ssh import HostKeyCheck +from ..vars.generate import generate_vars from .inventory import get_all_machines, get_selected_machines from .machine_group import MachineGroup @@ -93,6 +94,7 @@ def deploy_machine(machines: MachineGroup) -> None: env["NIX_SSHOPTS"] = ssh_arg generate_facts([machine], None, False) + generate_vars([machine], None, False) upload_secrets(machine) path = upload_sources(".", target) From ef29976ab8637108f5510c70d2f6051ce7afcd34 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Mon, 29 Jul 2024 13:52:11 +0200 Subject: [PATCH 08/16] shell.nix: remove dependency in shell.nix on webui --- pkgs/clan-app/flake-module.nix | 2 +- pkgs/clan-app/shell.nix | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pkgs/clan-app/flake-module.nix b/pkgs/clan-app/flake-module.nix index d7e97edb1..3cceb34a6 100644 --- a/pkgs/clan-app/flake-module.nix +++ b/pkgs/clan-app/flake-module.nix @@ -14,7 +14,7 @@ else { devShells.clan-app = pkgs.callPackage ./shell.nix { - inherit (config.packages) clan-app webview-ui; + inherit (config.packages) clan-app; inherit self'; }; packages.clan-app = pkgs.python3.pkgs.callPackage ./default.nix { diff --git a/pkgs/clan-app/shell.nix b/pkgs/clan-app/shell.nix index 6f66c1953..36d0da3bc 100644 --- a/pkgs/clan-app/shell.nix +++ b/pkgs/clan-app/shell.nix @@ -12,7 +12,6 @@ python3, gtk4, libadwaita, - webview-ui, self', }: @@ -67,8 +66,5 @@ mkShell { export XDG_DATA_DIRS=${gtk4}/share/gsettings-schemas/gtk4-4.14.4:$XDG_DATA_DIRS export XDG_DATA_DIRS=${gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas-46.0:$XDG_DATA_DIRS - # Add the webview-ui to the .webui directory - ln -nsf ${webview-ui}/lib/node_modules/@clan/webview-ui/dist/ ./clan_app/.webui - ''; } From 71df351217ba46d5a09d9032371d8e31778d053a Mon Sep 17 00:00:00 2001 From: DavHau Date: Mon, 29 Jul 2024 18:17:43 +0700 Subject: [PATCH 09/16] vars: fix bug when computing taarget path for sops --- nixosModules/clanCore/vars/secret/sops/default.nix | 7 +++---- nixosModules/clanCore/vars/secret/sops/funcs.nix | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nixosModules/clanCore/vars/secret/sops/default.nix b/nixosModules/clanCore/vars/secret/sops/default.nix index 9a6234535..b1614f06e 100644 --- a/nixosModules/clanCore/vars/secret/sops/default.nix +++ b/nixosModules/clanCore/vars/secret/sops/default.nix @@ -20,7 +20,7 @@ in # Before we generate a secret we cannot know the path yet, so we need to set it to an empty string fileModule = file: { path = lib.mkIf file.config.secret ( - config.sops.secrets.${"vars-${config.clan.core.machineName}-${file.config.generatorName}-${file.config.name}"}.path + config.sops.secrets.${"${config.clan.core.machineName}/${file.config.generatorName}/${file.config.name}"}.path or "/no-such-path" ); }; @@ -31,10 +31,9 @@ in config.sops = lib.mkIf (config.clan.core.vars.settings.secretStore == "sops") { secrets = lib.listToAttrs ( flip map vars (secret: { - name = secret.name; + name = secret.id; value = { - sopsFile = - config.clan.core.clanDir + "/sops/vars/${secret.machine}/${secret.generator}/${secret.name}/secret"; + sopsFile = config.clan.core.clanDir + "/sops/vars/${secret.id}/secret"; format = "binary"; }; }) diff --git a/nixosModules/clanCore/vars/secret/sops/funcs.nix b/nixosModules/clanCore/vars/secret/sops/funcs.nix index 980e05773..b5700794f 100644 --- a/nixosModules/clanCore/vars/secret/sops/funcs.nix +++ b/nixosModules/clanCore/vars/secret/sops/funcs.nix @@ -22,6 +22,7 @@ rec { machine = machine_name; generator = generator_name; name = secret_name; + id = "${machine_name}/${generator_name}/${secret_name}"; }) ) ); From d06ee01793096e56d109ebb38d10598b13a72938 Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Fri, 19 Jul 2024 15:58:18 +0100 Subject: [PATCH 10/16] blog: introducing nixos-facter --- docs/site/blog/.authors.yml | 5 ++ docs/site/blog/posts/nixos-facter.md | 90 ++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 docs/site/blog/posts/nixos-facter.md diff --git a/docs/site/blog/.authors.yml b/docs/site/blog/.authors.yml index b1134a8b5..65368df44 100644 --- a/docs/site/blog/.authors.yml +++ b/docs/site/blog/.authors.yml @@ -24,3 +24,8 @@ authors: description: "Core Developer" avatar: "https://clan.lol/static/profiles/qubasa.png" url: "https://github.com/Qubasa" + BrianMcGee: + name: "Brian McGee" + description: "Contributor" + avatar: "https://avatars.githubusercontent.com/u/1173648?v=4" + url: "https://bmcgee.ie" diff --git a/docs/site/blog/posts/nixos-facter.md b/docs/site/blog/posts/nixos-facter.md new file mode 100644 index 000000000..9bd718c2d --- /dev/null +++ b/docs/site/blog/posts/nixos-facter.md @@ -0,0 +1,90 @@ +--- +title: "Introducing Nixos Facter" +description: "Declarative Hardware Configuration in NixOS" +authors: + - BrianMcGee +date: 2024-07-19 +slug: nixos-facter +--- + +If you've ever installed [NixOS], you'll be familiar with a little Perl script called [nixos-generate-config]. Unsurprisingly, it generates a couple of NixOS modules based on available hardware, mounted filesystems, configured swap, etc. + +It's a critical component of the install process, aiming to ensure you have a good starting point for your NixOS system, with necessary or recommended kernel modules, file system mounts, networking config and much more. + +As solutions go, it's a solid one. It has helped many users take their first steps into this rabbit hole we call NixOS. However, it does suffer from one fundamental limitation. + +## Static Generation + +When a user generates a `hardware-configuration.nix` with `nixos-generate-config`, it makes choices based on the current state of the world as it sees it. By its very nature, then, it cannot account for changes in NixOS over time. + +A recommended configuration option today might be different two NixOS releases from now. + +To account for this, you could always run `nixos-generate-config` again. But that requires a working system, which may have broken due to the historical choices made last time, or worst-case, requiring you to fire up the installer again. + +## A Layer of Indirection + +What if, instead of generating some Nix code, we first describe the current hardware in an intermediate format? This hardware report would be _'pure'_, devoid of any reference to NixOS, and intended as a stable, longer-term representation of the system. + +From here, we can create a series of NixOS modules designed to examine the report's contents and make the same kinds of decisions that `nixos-generate-config` does. The critical difference is that as NixOS evolves, so can these modules, and with a full hardware report available we can make more interesting config choices about things such as GPUs and other devices. + +In a perfect world, we should not need to regenerate the underlying report as long as there are no hardware changes. We can take this one step further. + +Provided that certain sensitive information, such as serial numbers and MAC addresses, is filtered out, there is no reason why these hardware reports could not be shared after they are generated for things like EC2 instance types, specific laptop models, and so on, much like [NixOS Hardware] currently shares Nix configs. + +## Introducing NixOS Facter + +Still in its early stages, [Nixos Facter] is intended to do what I've described above. + +A user can generate a JSON-based hardware report using a (eventually static) Go program: `nixos-facter -o facter.json`. From there, they can include this report in their NixOS config and make use of our [NixOS modules](https://github.com/numtide/nixos-facter-modules) as follows: + +```nix +# flake.nix +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + nixos-facter-modules.url = "github:numtide/nixos-facter-modules"; + }; + + outputs = inputs @ { + nixpkgs, + ... + }: { + nixosConfigurations.basic = nixpkgs.lib.nixosSystem { + modules = [ + inputs.nixos-facter-modules.nixosModules.facter + { config.facter.reportPath = ./facter.json; } + # ... + ]; + }; + }; +} + +# configuration.nix +{ + imports = [ + "${(builtins.fetchTarball { + url = "https://github.com/numtide/nixos-facter-modules/"; + })}/modules/nixos/facter.nix" + ]; + + config.facter.reportPath = ./facter.json; +} +``` + +That's it. + +## Early Days + +Please be aware that [NixOS Facter] is _super alpha_ and is still subject to significant changes as we flesh things out. Our initial goal is to reach feature parity with [nixos-generate-config]. + +From there, we want to continue building our NixOS modules, opening things up to the community, and beginning to capture shared hardware configurations for providers such as Hetzner, etc. + +Over the coming weeks, we will also build up documentation and examples to make it easier to play with. For now, please be patient. + +> Side note: if you are wondering why the repo is in the [Numtide] org, we started partnering with Clan! Both companies are looking to make self-hosting easier and we're excited to be working together on this. Expect more tools and features to come! + +[NixOS]: https://nixos.org "Declarative builds and deployments" +[nixos-generate-config]: https://github.com/NixOS/nixpkgs/blob/dac9cdf8c930c0af98a63cbfe8005546ba0125fb/nixos/modules/installer/tools/nixos-generate-config.pl +[NixOS Hardware]: https://github.com/NixOS/nixos-hardware +[NixOS Facter]: https://github.com/numtide/nixos-facter +[Numtide]: https://numtide.com \ No newline at end of file From 8a41908aea774a53448331b6afaafc5ae97f1d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 29 Jul 2024 15:43:07 +0200 Subject: [PATCH 11/16] disko fixup --- docs/site/blog/posts/nixos-facter.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/site/blog/posts/nixos-facter.md b/docs/site/blog/posts/nixos-facter.md index 9bd718c2d..40bd07cdf 100644 --- a/docs/site/blog/posts/nixos-facter.md +++ b/docs/site/blog/posts/nixos-facter.md @@ -25,7 +25,7 @@ To account for this, you could always run `nixos-generate-config` again. But tha What if, instead of generating some Nix code, we first describe the current hardware in an intermediate format? This hardware report would be _'pure'_, devoid of any reference to NixOS, and intended as a stable, longer-term representation of the system. -From here, we can create a series of NixOS modules designed to examine the report's contents and make the same kinds of decisions that `nixos-generate-config` does. The critical difference is that as NixOS evolves, so can these modules, and with a full hardware report available we can make more interesting config choices about things such as GPUs and other devices. +From here, we can create a series of NixOS modules designed to examine the report's contents and make the same kinds of decisions that `nixos-generate-config` does. The critical difference is that as NixOS evolves, so can these modules, and with a full hardware report available we can make more interesting config choices about things such as GPUs and other devices. In a perfect world, we should not need to regenerate the underlying report as long as there are no hardware changes. We can take this one step further. @@ -71,6 +71,9 @@ A user can generate a JSON-based hardware report using a (eventually static) Go } ``` +We are currently assuming that a the system uses [disko], so we have not implemented fileSystems configuration. +If you don't use disko, you have to currently specify that part of the configuration yourself or take it from nixos-generate-config. + That's it. ## Early Days @@ -83,8 +86,9 @@ Over the coming weeks, we will also build up documentation and examples to make > Side note: if you are wondering why the repo is in the [Numtide] org, we started partnering with Clan! Both companies are looking to make self-hosting easier and we're excited to be working together on this. Expect more tools and features to come! -[NixOS]: https://nixos.org "Declarative builds and deployments" -[nixos-generate-config]: https://github.com/NixOS/nixpkgs/blob/dac9cdf8c930c0af98a63cbfe8005546ba0125fb/nixos/modules/installer/tools/nixos-generate-config.pl -[NixOS Hardware]: https://github.com/NixOS/nixos-hardware [NixOS Facter]: https://github.com/numtide/nixos-facter -[Numtide]: https://numtide.com \ No newline at end of file +[NixOS Hardware]: https://github.com/NixOS/nixos-hardware +[NixOS]: https://nixos.org "Declarative builds and deployments" +[Numtide]: https://numtide.com +[disko]: https://github.com/nix-community/disko +[nixos-generate-config]: https://github.com/NixOS/nixpkgs/blob/dac9cdf8c930c0af98a63cbfe8005546ba0125fb/nixos/modules/installer/tools/nixos-generate-config.pl From 0d3800ea99af309b19767b1b227ca6aabdaf1ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 29 Jul 2024 15:53:50 +0200 Subject: [PATCH 12/16] nixos-facter: smaller fixes --- docs/site/blog/posts/nixos-facter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/site/blog/posts/nixos-facter.md b/docs/site/blog/posts/nixos-facter.md index 40bd07cdf..10e21f394 100644 --- a/docs/site/blog/posts/nixos-facter.md +++ b/docs/site/blog/posts/nixos-facter.md @@ -1,5 +1,5 @@ --- -title: "Introducing Nixos Facter" +title: "Introducing NixOS Facter" description: "Declarative Hardware Configuration in NixOS" authors: - BrianMcGee @@ -33,7 +33,7 @@ Provided that certain sensitive information, such as serial numbers and MAC addr ## Introducing NixOS Facter -Still in its early stages, [Nixos Facter] is intended to do what I've described above. +Still in its early stages, [NixOS Facter] is intended to do what I've described above. A user can generate a JSON-based hardware report using a (eventually static) Go program: `nixos-facter -o facter.json`. From there, they can include this report in their NixOS config and make use of our [NixOS modules](https://github.com/numtide/nixos-facter-modules) as follows: @@ -78,7 +78,7 @@ That's it. ## Early Days -Please be aware that [NixOS Facter] is _super alpha_ and is still subject to significant changes as we flesh things out. Our initial goal is to reach feature parity with [nixos-generate-config]. +Please be aware that [NixOS Facter] is still in early development and is still subject to significant changes especially the output json format as we flesh things out. Our initial goal is to reach feature parity with [nixos-generate-config]. From there, we want to continue building our NixOS modules, opening things up to the community, and beginning to capture shared hardware configurations for providers such as Hetzner, etc. From fa0f393cbb351812927aa30a3944e7c4e85cc653 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 29 Jul 2024 17:05:06 +0200 Subject: [PATCH 13/16] Clan-app: edit clan, memoize active clan --- pkgs/clan-cli/clan_cli/__init__.py | 4 +- pkgs/webview-ui/app/src/App.tsx | 17 +- pkgs/webview-ui/app/src/layout/header.tsx | 46 +++-- pkgs/webview-ui/app/src/layout/layout.tsx | 4 +- .../app/src/routes/clan/editClan.tsx | 175 ++++++++++++++++++ pkgs/webview-ui/app/src/routes/disk/view.tsx | 6 +- .../app/src/routes/settings/index.tsx | 112 ++++++----- 7 files changed, 286 insertions(+), 78 deletions(-) create mode 100644 pkgs/webview-ui/app/src/routes/clan/editClan.tsx diff --git a/pkgs/clan-cli/clan_cli/__init__.py b/pkgs/clan-cli/clan_cli/__init__.py index 29acb1e59..e155a04d9 100644 --- a/pkgs/clan-cli/clan_cli/__init__.py +++ b/pkgs/clan-cli/clan_cli/__init__.py @@ -7,10 +7,10 @@ from types import ModuleType # These imports are unused, but necessary for @API.register to run once. from clan_cli.api import directory, mdns_discovery, modules from clan_cli.arg_actions import AppendOptionAction -from clan_cli.clan import show +from clan_cli.clan import show, update # API endpoints that are not used in the cli. -__all__ = ["directory", "mdns_discovery", "modules"] +__all__ = ["directory", "mdns_discovery", "modules", "update"] from . import ( backups, diff --git a/pkgs/webview-ui/app/src/App.tsx b/pkgs/webview-ui/app/src/App.tsx index 9c056ae8c..016cff09f 100644 --- a/pkgs/webview-ui/app/src/App.tsx +++ b/pkgs/webview-ui/app/src/App.tsx @@ -1,4 +1,4 @@ -import { createSignal, type Component } from "solid-js"; +import { createEffect, createSignal, type Component } from "solid-js"; import { Layout } from "./layout/layout"; import { Route, Router } from "./Routes"; import { Toaster } from "solid-toast"; @@ -7,9 +7,20 @@ import { makePersisted } from "@solid-primitives/storage"; // Some global state const [route, setRoute] = createSignal("machines"); +createEffect(() => { + console.log(route()); +}); + export { route, setRoute }; -const [activeURI, setActiveURI] = createSignal(null); +const [activeURI, setActiveURI] = makePersisted( + createSignal(null), + { + name: "activeURI", + storage: localStorage, + } +); + export { activeURI, setActiveURI }; const [clanList, setClanList] = makePersisted(createSignal([]), { @@ -17,8 +28,6 @@ const [clanList, setClanList] = makePersisted(createSignal([]), { storage: localStorage, }); -clanList() && setActiveURI(clanList()[0]); - export { clanList, setClanList }; const App: Component = () => { diff --git a/pkgs/webview-ui/app/src/layout/header.tsx b/pkgs/webview-ui/app/src/layout/header.tsx index bc9234d75..1bae01663 100644 --- a/pkgs/webview-ui/app/src/layout/header.tsx +++ b/pkgs/webview-ui/app/src/layout/header.tsx @@ -1,15 +1,20 @@ import { createQuery } from "@tanstack/solid-query"; import { activeURI, setRoute } from "../App"; import { callApi } from "../api"; -import { Show } from "solid-js"; +import { Accessor, createEffect, Show } from "solid-js"; -export const Header = () => { - const { isLoading, data } = createQuery(() => ({ - queryKey: [`${activeURI()}:meta`], +interface HeaderProps { + clan_dir: Accessor; +} +export const Header = (props: HeaderProps) => { + const { clan_dir } = props; + + const query = createQuery(() => ({ + queryKey: [clan_dir(), "meta"], queryFn: async () => { - const currUri = activeURI(); - if (currUri) { - const result = await callApi("show_clan_meta", { uri: currUri }); + const curr = clan_dir(); + if (curr) { + const result = await callApi("show_clan_meta", { uri: curr }); if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; } @@ -29,16 +34,25 @@ export const Header = () => {
-
-
-
- C - - {(name) => {name()}} - + + {(meta) => ( +
+
+
+ {meta().name.slice(0, 1)} +
+
-
-
+ )} + + + + {(meta) => [ + {meta().name}, + {meta()?.description}, + ]} + +
diff --git a/pkgs/webview-ui/app/src/layout/layout.tsx b/pkgs/webview-ui/app/src/layout/layout.tsx index 84412fc9f..616faa57d 100644 --- a/pkgs/webview-ui/app/src/layout/layout.tsx +++ b/pkgs/webview-ui/app/src/layout/layout.tsx @@ -1,7 +1,7 @@ import { Component, JSXElement, Show } from "solid-js"; import { Header } from "./header"; import { Sidebar } from "../Sidebar"; -import { clanList, route, setRoute } from "../App"; +import { activeURI, clanList, route, setRoute } from "../App"; interface LayoutProps { children: JSXElement; @@ -18,7 +18,7 @@ export const Layout: Component = (props) => { />
-
+
{props.children}
diff --git a/pkgs/webview-ui/app/src/routes/clan/editClan.tsx b/pkgs/webview-ui/app/src/routes/clan/editClan.tsx new file mode 100644 index 000000000..6494d1e7d --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/clan/editClan.tsx @@ -0,0 +1,175 @@ +import { OperationResponse, callApi, pyApi } from "@/src/api"; +import { Accessor, Show, Switch, Match } from "solid-js"; +import { + SubmitHandler, + createForm, + required, + reset, +} from "@modular-forms/solid"; +import toast from "solid-toast"; +import { createQuery } from "@tanstack/solid-query"; + +type CreateForm = Meta; + +interface EditClanFormProps { + directory: Accessor; + done: () => void; +} +export const EditClanForm = (props: EditClanFormProps) => { + const { directory } = props; + const details = createQuery(() => ({ + queryKey: [directory(), "meta"], + queryFn: async () => { + const result = await callApi("show_clan_meta", { uri: directory() }); + if (result.status === "error") throw new Error("Failed to fetch data"); + return result.data; + }, + })); + + return ( + + + {(data) => ( + + )} + + + ); +}; + +interface FinalEditClanFormProps { + initial: CreateForm; + directory: string; + done: () => void; +} +export const FinalEditClanForm = (props: FinalEditClanFormProps) => { + const [formStore, { Form, Field }] = createForm({ + initialValues: props.initial, + }); + + const handleSubmit: SubmitHandler = async (values, event) => { + await toast.promise( + (async () => { + await callApi("update_clan_meta", { + options: { + directory: props.directory, + meta: values, + }, + }); + })(), + { + loading: "Updating clan...", + success: "Clan Successfully updated", + error: "Failed to update clan", + } + ); + props.done(); + }; + + return ( +
+
+ + {(field, props) => ( + <> +
+ + group + + } + > + {(icon) => ( + Clan Logo + )} + +
+ + )} +
+
+ + {(field, props) => ( + + )} + + + {(field, props) => ( + + )} + + { +
+ +
+ } +
+
+
+ ); +}; + +type Meta = Extract< + OperationResponse<"show_clan_meta">, + { status: "success" } +>["data"]; diff --git a/pkgs/webview-ui/app/src/routes/disk/view.tsx b/pkgs/webview-ui/app/src/routes/disk/view.tsx index c9798c119..43e2f4aff 100644 --- a/pkgs/webview-ui/app/src/routes/disk/view.tsx +++ b/pkgs/webview-ui/app/src/routes/disk/view.tsx @@ -2,24 +2,24 @@ import { callApi } from "@/src/api"; import { activeURI } from "@/src/App"; import { createQuery } from "@tanstack/solid-query"; import { createEffect } from "solid-js"; +import toast from "solid-toast"; export function DiskView() { const query = createQuery(() => ({ - queryKey: ["disk", activeURI], + queryKey: ["disk", activeURI()], queryFn: async () => { const currUri = activeURI(); if (currUri) { // Example of calling an API const result = await callApi("get_inventory", { base_path: currUri }); if (result.status === "error") throw new Error("Failed to fetch data"); - return result.data; } }, })); createEffect(() => { // Example debugging the data - console.log(query.data); + console.log(query); }); return (
diff --git a/pkgs/webview-ui/app/src/routes/settings/index.tsx b/pkgs/webview-ui/app/src/routes/settings/index.tsx index 1c83c41ab..8fa7869f7 100644 --- a/pkgs/webview-ui/app/src/routes/settings/index.tsx +++ b/pkgs/webview-ui/app/src/routes/settings/index.tsx @@ -12,18 +12,19 @@ import { setRoute, clanList, } from "@/src/App"; -import { createEffect, createSignal, For, Show } from "solid-js"; +import { + createEffect, + createSignal, + For, + Match, + Setter, + Show, + Switch, +} from "solid-js"; import { createQuery } from "@tanstack/solid-query"; import { useFloating } from "@/src/floating"; -import { - arrow, - autoUpdate, - flip, - hide, - offset, - shift, - size, -} from "@floating-ui/dom"; +import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom"; +import { EditClanForm } from "../clan/editClan"; export const registerClan = async () => { try { @@ -51,9 +52,10 @@ export const registerClan = async () => { interface ClanDetailsProps { clan_dir: string; + setEditURI: Setter; } const ClanDetails = (props: ClanDetailsProps) => { - const { clan_dir } = props; + const { clan_dir, setEditURI } = props; const details = createQuery(() => ({ queryKey: [clan_dir, "meta"], @@ -66,7 +68,6 @@ const ClanDetails = (props: ClanDetailsProps) => { const [reference, setReference] = createSignal(); const [floating, setFloating] = createSignal(); - const [arrowEl, setArrowEl] = createSignal(); // `position` is a reactive object. const position = useFloating(reference, floating, { @@ -92,6 +93,14 @@ const ClanDetails = (props: ClanDetailsProps) => {
+
-
Clan URI
+
{clan_dir}
-
- {details.data?.name} -
+
{details.data?.name}
- {clan_dir}
} - > -
- {details.data?.description} -
+ +
{details.data?.description}
); }; export const Settings = () => { + const [editURI, setEditURI] = createSignal(null); + return (
-
-
-
Registered Clans
- -
-
- - {(value) => } - -
-
+ + + {(uri) => ( + { + setEditURI(null); + }} + /> + )} + + +
+
+
Registered Clans
+ +
+
+ + {(value) => ( + + )} + +
+
+
+
); }; From ce1dec774eb1650ca21adf7b621558fad9bb18cc Mon Sep 17 00:00:00 2001 From: Qubasa Date: Mon, 29 Jul 2024 17:33:42 +0200 Subject: [PATCH 14/16] clan-vm-manager: Fix regression --- .envrc | 1 + formatter.nix | 4 ++-- pkgs/clan-cli/clan_cli/clan/inspect.py | 4 ++-- pkgs/clan-cli/clan_cli/history/add.py | 2 +- pkgs/clan-cli/clan_cli/history/update.py | 4 ++-- pkgs/clan-cli/tests/test_history_cli.py | 2 +- .../clan_vm_manager/singletons/use_vms.py | 8 ++++---- pkgs/clan-vm-manager/default.nix | 12 ++---------- 8 files changed, 15 insertions(+), 22 deletions(-) diff --git a/.envrc b/.envrc index fda7fa8fb..37152103b 100644 --- a/.envrc +++ b/.envrc @@ -4,6 +4,7 @@ if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then fi watch_file .direnv/selected-shell +watch_file formatter.nix if [ -e .direnv/selected-shell ]; then use flake ".#$(cat .direnv/selected-shell)" diff --git a/formatter.nix b/formatter.nix index f5a64f8a6..8f6cbb285 100644 --- a/formatter.nix +++ b/formatter.nix @@ -30,8 +30,8 @@ { "pkgs/clan-vm-manager" = { extraPythonPackages = - # clan-app currently only exists on linux - self'.packages.clan-vm-manager.testDependencies ++ self'.packages.clan-cli.testDependencies; + # # clan-app currently only exists on linux + self'.packages.clan-vm-manager.testDependencies; modules = [ "clan_vm_manager" ]; }; } diff --git a/pkgs/clan-cli/clan_cli/clan/inspect.py b/pkgs/clan-cli/clan_cli/clan/inspect.py index c64009460..0c5b28636 100644 --- a/pkgs/clan-cli/clan_cli/clan/inspect.py +++ b/pkgs/clan-cli/clan_cli/clan/inspect.py @@ -14,7 +14,7 @@ from ..vms.inspect import VmConfig, inspect_vm @dataclass class FlakeConfig: - flake_url: str | Path + flake_url: FlakeId flake_attr: str clan_name: str @@ -89,7 +89,7 @@ def inspect_flake(flake_url: str | Path, machine_name: str) -> FlakeConfig: meta = nix_metadata(flake_url) return FlakeConfig( vm=vm, - flake_url=flake_url, + flake_url=FlakeId(flake_url), clan_name=clan_name, flake_attr=machine_name, nar_hash=meta["locked"]["narHash"], diff --git a/pkgs/clan-cli/clan_cli/history/add.py b/pkgs/clan-cli/clan_cli/history/add.py index b8c6dc011..e13938e86 100644 --- a/pkgs/clan-cli/clan_cli/history/add.py +++ b/pkgs/clan-cli/clan_cli/history/add.py @@ -62,7 +62,7 @@ def list_history() -> list[HistoryEntry]: def new_history_entry(url: str, machine: str) -> HistoryEntry: flake = inspect_flake(url, machine) - flake.flake_url = str(flake.flake_url) + flake.flake_url = flake.flake_url return HistoryEntry( flake=flake, last_used=datetime.datetime.now().isoformat(), diff --git a/pkgs/clan-cli/clan_cli/history/update.py b/pkgs/clan-cli/clan_cli/history/update.py index 6c263f80f..ed9c87abc 100644 --- a/pkgs/clan-cli/clan_cli/history/update.py +++ b/pkgs/clan-cli/clan_cli/history/update.py @@ -16,7 +16,7 @@ def update_history() -> list[HistoryEntry]: for entry in logs: try: - meta = nix_metadata(entry.flake.flake_url) + meta = nix_metadata(str(entry.flake.flake_url)) except ClanCmdError as e: print(f"Failed to update {entry.flake.flake_url}: {e}") continue @@ -31,7 +31,7 @@ def update_history() -> list[HistoryEntry]: machine_name=entry.flake.flake_attr, ) flake = inspect_flake(uri.get_url(), uri.machine_name) - flake.flake_url = str(flake.flake_url) + flake.flake_url = flake.flake_url entry = HistoryEntry( flake=flake, last_used=datetime.datetime.now().isoformat() ) diff --git a/pkgs/clan-cli/tests/test_history_cli.py b/pkgs/clan-cli/tests/test_history_cli.py index d08b1bb62..e65c7b2af 100644 --- a/pkgs/clan-cli/tests/test_history_cli.py +++ b/pkgs/clan-cli/tests/test_history_cli.py @@ -27,7 +27,7 @@ def test_history_add( history_file = user_history_file() assert history_file.exists() history = [HistoryEntry(**entry) for entry in json.loads(open(history_file).read())] - assert history[0].flake.flake_url == str(test_flake_with_core.path) + assert str(history[0].flake.flake_url["loc"]) == str(test_flake_with_core.path) @pytest.mark.impure diff --git a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py index d88ae7c27..746361462 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py @@ -163,12 +163,12 @@ class ClanStore: del self.clan_store[str(vm.data.flake.flake_url)][vm.data.flake.flake_attr] def get_vm(self, uri: ClanURI) -> None | VMObject: - flake_id = Machine(uri.machine_name, uri.flake).get_id() - vm_store = self.clan_store.get(flake_id) + machine = Machine(uri.machine_name, uri.flake) + vm_store = self.clan_store.get(str(machine.flake)) if vm_store is None: return None - machine = vm_store.get(uri.machine_name, None) - return machine + vm = vm_store.get(str(machine.name), None) + return vm def get_running_vms(self) -> list[VMObject]: return [ diff --git a/pkgs/clan-vm-manager/default.nix b/pkgs/clan-vm-manager/default.nix index 4c15ee8ee..4ff1b90d0 100644 --- a/pkgs/clan-vm-manager/default.nix +++ b/pkgs/clan-vm-manager/default.nix @@ -39,7 +39,7 @@ let libadwaita webkitgtk_6_0 adwaita-icon-theme - ]; + ] ++ clan-cli.propagatedBuildInputs; # Deps including python packages from the local project allPythonDeps = [ (python3.pkgs.toPythonModule clan-cli) ] ++ externalPythonDeps; @@ -84,7 +84,6 @@ python3.pkgs.buildPythonApplication rec { setuptools copyDesktopItems wrapGAppsHook - gobject-introspection ]; @@ -93,14 +92,7 @@ python3.pkgs.buildPythonApplication rec { # that all necessary dependencies are consistently available both # at build time and runtime, buildInputs = allPythonDeps ++ runtimeDependencies; - propagatedBuildInputs = - allPythonDeps - ++ runtimeDependencies - ++ [ - - # TODO: see postFixup clan-cli/default.nix:L188 - clan-cli.propagatedBuildInputs - ]; + propagatedBuildInputs = allPythonDeps ++ runtimeDependencies ++ [ ]; # also re-expose dependencies so we test them in CI passthru = { From 927b584fd965f3c46e4516a57aa741f5ad00362d Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Mon, 29 Jul 2024 16:51:00 +0100 Subject: [PATCH 15/16] blog: grammar fixes for introducing nixos-facter --- docs/site/blog/posts/nixos-facter.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/site/blog/posts/nixos-facter.md b/docs/site/blog/posts/nixos-facter.md index 10e21f394..b0cdefed9 100644 --- a/docs/site/blog/posts/nixos-facter.md +++ b/docs/site/blog/posts/nixos-facter.md @@ -71,11 +71,12 @@ A user can generate a JSON-based hardware report using a (eventually static) Go } ``` -We are currently assuming that a the system uses [disko], so we have not implemented fileSystems configuration. -If you don't use disko, you have to currently specify that part of the configuration yourself or take it from nixos-generate-config. - That's it. +> We assume that users will rely on [disko], so we have not implemented file system configuration yet (it's on the roadmap). +> In the meantime, if you don't use disko you have to specify that part of the configuration yourself or take it from `nixos-generate-config`. + + ## Early Days Please be aware that [NixOS Facter] is still in early development and is still subject to significant changes especially the output json format as we flesh things out. Our initial goal is to reach feature parity with [nixos-generate-config]. From 2e05b28fc2338d361a4edfbdf32aea442d5f1ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 29 Jul 2024 16:17:38 +0200 Subject: [PATCH 16/16] better visual seperation between flake/non-flake version --- docs/site/blog/posts/nixos-facter.md | 63 +++++++++++++++------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/docs/site/blog/posts/nixos-facter.md b/docs/site/blog/posts/nixos-facter.md index b0cdefed9..c0b25b51a 100644 --- a/docs/site/blog/posts/nixos-facter.md +++ b/docs/site/blog/posts/nixos-facter.md @@ -37,39 +37,44 @@ Still in its early stages, [NixOS Facter] is intended to do what I've described A user can generate a JSON-based hardware report using a (eventually static) Go program: `nixos-facter -o facter.json`. From there, they can include this report in their NixOS config and make use of our [NixOS modules](https://github.com/numtide/nixos-facter-modules) as follows: -```nix -# flake.nix -{ - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - nixos-facter-modules.url = "github:numtide/nixos-facter-modules"; - }; +=== "**flake.nix**" - outputs = inputs @ { - nixpkgs, - ... - }: { - nixosConfigurations.basic = nixpkgs.lib.nixosSystem { - modules = [ - inputs.nixos-facter-modules.nixosModules.facter - { config.facter.reportPath = ./facter.json; } - # ... - ]; + ```nix + { + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + nixos-facter-modules.url = "github:numtide/nixos-facter-modules"; }; - }; -} -# configuration.nix -{ - imports = [ - "${(builtins.fetchTarball { - url = "https://github.com/numtide/nixos-facter-modules/"; - })}/modules/nixos/facter.nix" - ]; + outputs = inputs @ { + nixpkgs, + ... + }: { + nixosConfigurations.basic = nixpkgs.lib.nixosSystem { + modules = [ + inputs.nixos-facter-modules.nixosModules.facter + { config.facter.reportPath = ./facter.json; } + # ... + ]; + }; + }; + } + ``` - config.facter.reportPath = ./facter.json; -} -``` +=== "**without flakes**" + + ```nix + # configuration.nix + { + imports = [ + "${(builtins.fetchTarball { + url = "https://github.com/numtide/nixos-facter-modules/"; + })}/modules/nixos/facter.nix" + ]; + + config.facter.reportPath = ./facter.json; + } + ``` That's it.