diff --git a/tools/gframework-config-tool/.gitignore b/tools/gframework-config-tool/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/tools/gframework-config-tool/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/tools/gframework-config-tool/.vscodeignore b/tools/gframework-config-tool/.vscodeignore new file mode 100644 index 0000000..917dd95 --- /dev/null +++ b/tools/gframework-config-tool/.vscodeignore @@ -0,0 +1,7 @@ +.github/** +.vscode/** +.vscode-test/** +test/** +*.log +.gitignore +.vscodeignore diff --git a/tools/gframework-config-tool/LICENSE b/tools/gframework-config-tool/LICENSE new file mode 100644 index 0000000..946080c --- /dev/null +++ b/tools/gframework-config-tool/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets.) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 GeWuYou + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tools/gframework-config-tool/README.md b/tools/gframework-config-tool/README.md new file mode 100644 index 0000000..ffa850d --- /dev/null +++ b/tools/gframework-config-tool/README.md @@ -0,0 +1,55 @@ +# GFramework Config Tool + +VS Code extension for the GFramework AI-First config workflow. + +## Current MVP + +- Browse config files from the workspace `config/` directory +- Open raw YAML files +- Open matching schema files from `schemas/` +- Run lightweight schema validation for nested required fields, unknown nested fields, scalar types, scalar arrays, and + arrays of objects +- Open a lightweight form preview for nested object fields, object arrays, top-level scalar fields, and scalar arrays +- Batch edit one config domain across multiple files for top-level scalar and scalar-array fields +- Surface schema metadata such as `title`, `description`, `default`, `enum`, and `x-gframework-ref-table` in the + lightweight editors + +## Validation Coverage + +The extension currently validates the repository's minimal config-schema subset: + +- required properties in nested objects +- unknown properties in nested objects +- scalar compatibility for `integer`, `number`, `boolean`, and `string` +- scalar arrays with scalar item type checks +- arrays of objects whose items use the same supported subset recursively +- scalar `enum` constraints and scalar-array item `enum` constraints + +## Local Testing + +```bash +cd tools/vscode-config-extension +bun install +bun run test +``` + +## Packaging And Publishing + +```bash +cd tools/vscode-config-extension +bun install +bun run package:vsix +VSCE_PAT=your_marketplace_pat bun run publish:marketplace +``` + +## Current Constraints + +- Multi-root workspaces use the first workspace folder +- Validation only covers a minimal subset of JSON Schema +- Form preview supports object-array editing, but nested object arrays inside array items still fall back to raw YAML +- Batch editing remains limited to top-level scalar fields and top-level scalar arrays + +## Workspace Settings + +- `gframeworkConfig.configPath` +- `gframeworkConfig.schemasPath` diff --git a/tools/gframework-config-tool/bun.lock b/tools/gframework-config-tool/bun.lock new file mode 100644 index 0000000..7bc1c78 --- /dev/null +++ b/tools/gframework-config-tool/bun.lock @@ -0,0 +1,627 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "gframework-config-tool", + "devDependencies": { + "@vscode/vsce": "^3.7.1", + }, + }, + }, + "packages": { + "@azu/format-text": ["@azu/format-text@1.0.2", "", {}, "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg=="], + + "@azu/style-format": ["@azu/style-format@1.0.1", "", { "dependencies": { "@azu/format-text": "^1.0.1" } }, "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g=="], + + "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], + + "@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="], + + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.23.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ=="], + + "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], + + "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], + + "@azure/identity": ["@azure/identity@4.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", "@azure/msal-node": "^5.1.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw=="], + + "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], + + "@azure/msal-browser": ["@azure/msal-browser@5.6.2", "", { "dependencies": { "@azure/msal-common": "16.4.0" } }, "sha512-ZgcN9ToRJ80f+wNPBBKYJ+DG0jlW7ktEjYtSNkNsTrlHVMhKB8tKMdI1yIG1I9BJtykkXtqnuOjlJaEMC7J6aw=="], + + "@azure/msal-common": ["@azure/msal-common@16.4.0", "", {}, "sha512-twXt09PYtj1PffNNIAzQlrBd0DS91cdA6i1gAfzJ6BnPM4xNk5k9q/5xna7jLIjU3Jnp0slKYtucshGM8OGNAw=="], + + "@azure/msal-node": ["@azure/msal-node@5.1.1", "", { "dependencies": { "@azure/msal-common": "16.4.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-71grXU6+5hl+3CL3joOxlj/AW6rmhthuTlG0fRqsTrhPArQBpZuUFzCIlKOGdcafLUa/i1hBdV78ZxJdlvRA+g=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@secretlint/config-creator": ["@secretlint/config-creator@10.2.2", "", { "dependencies": { "@secretlint/types": "^10.2.2" } }, "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ=="], + + "@secretlint/config-loader": ["@secretlint/config-loader@10.2.2", "", { "dependencies": { "@secretlint/profiler": "^10.2.2", "@secretlint/resolver": "^10.2.2", "@secretlint/types": "^10.2.2", "ajv": "^8.17.1", "debug": "^4.4.1", "rc-config-loader": "^4.1.3" } }, "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ=="], + + "@secretlint/core": ["@secretlint/core@10.2.2", "", { "dependencies": { "@secretlint/profiler": "^10.2.2", "@secretlint/types": "^10.2.2", "debug": "^4.4.1", "structured-source": "^4.0.0" } }, "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw=="], + + "@secretlint/formatter": ["@secretlint/formatter@10.2.2", "", { "dependencies": { "@secretlint/resolver": "^10.2.2", "@secretlint/types": "^10.2.2", "@textlint/linter-formatter": "^15.2.0", "@textlint/module-interop": "^15.2.0", "@textlint/types": "^15.2.0", "chalk": "^5.4.1", "debug": "^4.4.1", "pluralize": "^8.0.0", "strip-ansi": "^7.1.0", "table": "^6.9.0", "terminal-link": "^4.0.0" } }, "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA=="], + + "@secretlint/node": ["@secretlint/node@10.2.2", "", { "dependencies": { "@secretlint/config-loader": "^10.2.2", "@secretlint/core": "^10.2.2", "@secretlint/formatter": "^10.2.2", "@secretlint/profiler": "^10.2.2", "@secretlint/source-creator": "^10.2.2", "@secretlint/types": "^10.2.2", "debug": "^4.4.1", "p-map": "^7.0.3" } }, "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ=="], + + "@secretlint/profiler": ["@secretlint/profiler@10.2.2", "", {}, "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig=="], + + "@secretlint/resolver": ["@secretlint/resolver@10.2.2", "", {}, "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w=="], + + "@secretlint/secretlint-formatter-sarif": ["@secretlint/secretlint-formatter-sarif@10.2.2", "", { "dependencies": { "node-sarif-builder": "^3.2.0" } }, "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ=="], + + "@secretlint/secretlint-rule-no-dotenv": ["@secretlint/secretlint-rule-no-dotenv@10.2.2", "", { "dependencies": { "@secretlint/types": "^10.2.2" } }, "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg=="], + + "@secretlint/secretlint-rule-preset-recommend": ["@secretlint/secretlint-rule-preset-recommend@10.2.2", "", {}, "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA=="], + + "@secretlint/source-creator": ["@secretlint/source-creator@10.2.2", "", { "dependencies": { "@secretlint/types": "^10.2.2", "istextorbinary": "^9.5.0" } }, "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw=="], + + "@secretlint/types": ["@secretlint/types@10.2.2", "", {}, "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], + + "@textlint/ast-node-types": ["@textlint/ast-node-types@15.5.2", "", {}, "sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg=="], + + "@textlint/linter-formatter": ["@textlint/linter-formatter@15.5.2", "", { "dependencies": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", "@textlint/module-interop": "15.5.2", "@textlint/resolver": "15.5.2", "@textlint/types": "15.5.2", "chalk": "^4.1.2", "debug": "^4.4.3", "js-yaml": "^4.1.1", "lodash": "^4.17.23", "pluralize": "^2.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "table": "^6.9.0", "text-table": "^0.2.0" } }, "sha512-jAw7jWM8+wU9cG6Uu31jGyD1B+PAVePCvnPKC/oov+2iBPKk3ao30zc/Itmi7FvXo4oPaL9PmzPPQhyniPVgVg=="], + + "@textlint/module-interop": ["@textlint/module-interop@15.5.2", "", {}, "sha512-mg6rMQ3+YjwiXCYoQXbyVfDucpTa1q5mhspd/9qHBxUq4uY6W8GU42rmT3GW0V1yOfQ9z/iRrgPtkp71s8JzXg=="], + + "@textlint/resolver": ["@textlint/resolver@15.5.2", "", {}, "sha512-YEITdjRiJaQrGLUWxWXl4TEg+d2C7+TNNjbGPHPH7V7CCnXm+S9GTjGAL7Q2WSGJyFEKt88Jvx6XdJffRv4HEA=="], + + "@textlint/types": ["@textlint/types@15.5.2", "", { "dependencies": { "@textlint/ast-node-types": "15.5.2" } }, "sha512-sJOrlVLLXp4/EZtiWKWq9y2fWyZlI8GP+24rnU5avtPWBIMm/1w97yzKrAqYF8czx2MqR391z5akhnfhj2f/AQ=="], + + "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], + + "@types/sarif": ["@types/sarif@2.1.7", "", {}, "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ=="], + + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.4", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ=="], + + "@vscode/vsce": ["@vscode/vsce@3.7.1", "", { "dependencies": { "@azure/identity": "^4.1.0", "@secretlint/node": "^10.1.2", "@secretlint/secretlint-formatter-sarif": "^10.1.2", "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^4.1.2", "cheerio": "^1.0.0-rc.9", "cockatiel": "^3.1.2", "commander": "^12.1.0", "form-data": "^4.0.0", "glob": "^11.0.0", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", "leven": "^3.1.0", "markdown-it": "^14.1.0", "mime": "^1.3.4", "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", "secretlint": "^10.1.2", "semver": "^7.5.2", "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", "xml2js": "^0.5.0", "yauzl": "^2.3.1", "yazl": "^2.2.2" }, "optionalDependencies": { "keytar": "^7.7.0" }, "bin": { "vsce": "vsce" } }, "sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g=="], + + "@vscode/vsce-sign": ["@vscode/vsce-sign@2.0.9", "", { "optionalDependencies": { "@vscode/vsce-sign-alpine-arm64": "2.0.6", "@vscode/vsce-sign-alpine-x64": "2.0.6", "@vscode/vsce-sign-darwin-arm64": "2.0.6", "@vscode/vsce-sign-darwin-x64": "2.0.6", "@vscode/vsce-sign-linux-arm": "2.0.6", "@vscode/vsce-sign-linux-arm64": "2.0.6", "@vscode/vsce-sign-linux-x64": "2.0.6", "@vscode/vsce-sign-win32-arm64": "2.0.6", "@vscode/vsce-sign-win32-x64": "2.0.6" } }, "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g=="], + + "@vscode/vsce-sign-alpine-arm64": ["@vscode/vsce-sign-alpine-arm64@2.0.6", "", { "os": "none", "cpu": "arm64" }, "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q=="], + + "@vscode/vsce-sign-alpine-x64": ["@vscode/vsce-sign-alpine-x64@2.0.6", "", { "os": "none", "cpu": "x64" }, "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w=="], + + "@vscode/vsce-sign-darwin-arm64": ["@vscode/vsce-sign-darwin-arm64@2.0.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ=="], + + "@vscode/vsce-sign-darwin-x64": ["@vscode/vsce-sign-darwin-x64@2.0.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw=="], + + "@vscode/vsce-sign-linux-arm": ["@vscode/vsce-sign-linux-arm@2.0.6", "", { "os": "linux", "cpu": "arm" }, "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA=="], + + "@vscode/vsce-sign-linux-arm64": ["@vscode/vsce-sign-linux-arm64@2.0.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA=="], + + "@vscode/vsce-sign-linux-x64": ["@vscode/vsce-sign-linux-x64@2.0.6", "", { "os": "linux", "cpu": "x64" }, "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA=="], + + "@vscode/vsce-sign-win32-arm64": ["@vscode/vsce-sign-win32-arm64@2.0.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg=="], + + "@vscode/vsce-sign-win32-x64": ["@vscode/vsce-sign-win32-x64@2.0.6", "", { "os": "win32", "cpu": "x64" }, "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + + "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "azure-devops-node-api": ["azure-devops-node-api@12.5.0", "", { "dependencies": { "tunnel": "0.0.6", "typed-rest-client": "^1.8.4" } }, "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "binaryextensions": ["binaryextensions@6.11.0", "", { "dependencies": { "editions": "^6.21.0" } }, "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "boundary": ["boundary@2.0.0", "", {}, "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA=="], + + "brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "cheerio": ["cheerio@1.2.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.1.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg=="], + + "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], + + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "cockatiel": ["cockatiel@3.2.1", "", {}, "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "editions": ["editions@6.22.0", "", { "dependencies": { "version-range": "^4.15.0" } }, "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "globby": ["globby@14.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", "ignore": "^7.0.3", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + + "htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "istextorbinary": ["istextorbinary@9.5.0", "", { "dependencies": { "binaryextensions": "^6.11.0", "editions": "^6.21.0", "textextensions": "^6.11.0" } }, "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw=="], + + "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "keytar": ["keytar@7.9.0", "", { "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" } }, "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ=="], + + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + + "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + + "lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="], + + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], + + "node-addon-api": ["node-addon-api@4.3.0", "", {}, "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="], + + "node-sarif-builder": ["node-sarif-builder@3.4.0", "", { "dependencies": { "@types/sarif": "^2.1.7", "fs-extra": "^11.1.1" } }, "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg=="], + + "normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], + + "parse-semver": ["parse-semver@1.1.1", "", { "dependencies": { "semver": "^5.1.0" } }, "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], + + "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "path-type": ["path-type@6.0.0", "", {}, "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + + "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "rc-config-loader": ["rc-config-loader@4.1.4", "", { "dependencies": { "debug": "^4.4.3", "js-yaml": "^4.1.1", "json5": "^2.2.3", "require-from-string": "^2.0.2" } }, "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ=="], + + "read": ["read@1.0.7", "", { "dependencies": { "mute-stream": "~0.0.4" } }, "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ=="], + + "read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + + "secretlint": ["secretlint@10.2.2", "", { "dependencies": { "@secretlint/config-creator": "^10.2.2", "@secretlint/formatter": "^10.2.2", "@secretlint/node": "^10.2.2", "@secretlint/profiler": "^10.2.2", "debug": "^4.4.1", "globby": "^14.1.0", "read-pkg": "^9.0.1" }, "bin": "./bin/secretlint.js" }, "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], + + "slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.23", "", {}, "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "structured-source": ["structured-source@4.0.0", "", { "dependencies": { "boundary": "^2.0.0" } }, "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], + + "table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "terminal-link": ["terminal-link@4.0.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "supports-hyperlinks": "^3.2.0" } }, "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "textextensions": ["textextensions@6.11.0", "", { "dependencies": { "editions": "^6.21.0" } }, "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ=="], + + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "typed-rest-client": ["typed-rest-client@1.8.11", "", { "dependencies": { "qs": "^6.9.1", "tunnel": "0.0.6", "underscore": "^1.12.1" } }, "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA=="], + + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + + "underscore": ["underscore@1.13.8", "", {}, "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ=="], + + "undici": ["undici@7.24.7", "", {}, "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "url-join": ["url-join@4.0.1", "", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + + "version-range": ["version-range@4.15.0", "", {}, "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg=="], + + "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + + "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + + "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + + "yazl": ["yazl@2.5.1", "", { "dependencies": { "buffer-crc32": "~0.2.3" } }, "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw=="], + + "@secretlint/formatter/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@textlint/linter-formatter/pluralize": ["pluralize@2.0.0", "", {}, "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw=="], + + "@textlint/linter-formatter/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "parse-semver/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "path-scurry/lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], + + "read-pkg/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "table/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@textlint/linter-formatter/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + + "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "table/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + } +} diff --git a/tools/gframework-config-tool/package.json b/tools/gframework-config-tool/package.json new file mode 100644 index 0000000..71588e3 --- /dev/null +++ b/tools/gframework-config-tool/package.json @@ -0,0 +1,139 @@ +{ + "name": "gframework-config-tool", + "displayName": "GFramework Config Tool", + "description": "VS Code tooling for browsing, validating, and editing AI-First config files in GFramework projects.", + "version": "0.0.1", + "publisher": "GeWuYou", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/GeWuYou/GFramework.git", + "directory": "tools/vscode-config-extension" + }, + "homepage": "https://github.com/GeWuYou/GFramework/tree/main/tools/gframework-config-tool", + "bugs": { + "url": "https://github.com/GeWuYou/GFramework/issues" + }, + "packageManager": "bun@1.2.15", + "keywords": [ + "gframework", + "config", + "yaml", + "json-schema", + "game-dev" + ], + "engines": { + "vscode": "^1.90.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onView:gframeworkConfigExplorer", + "onCommand:gframeworkConfig.refresh", + "onCommand:gframeworkConfig.openRaw", + "onCommand:gframeworkConfig.openSchema", + "onCommand:gframeworkConfig.openFormPreview", + "onCommand:gframeworkConfig.batchEditDomain", + "onCommand:gframeworkConfig.validateAll" + ], + "main": "./src/extension.js", + "scripts": { + "test": "node --test ./test/*.test.js", + "package:vsix": "vsce package", + "publish:marketplace": "vsce publish" + }, + "devDependencies": { + "@vscode/vsce": "^3.7.1" + }, + "vsce": { + "yarn": false + }, + "contributes": { + "views": { + "explorer": [ + { + "id": "gframeworkConfigExplorer", + "name": "GFramework Config" + } + ] + }, + "commands": [ + { + "command": "gframeworkConfig.refresh", + "title": "GFramework Config: Refresh" + }, + { + "command": "gframeworkConfig.openRaw", + "title": "GFramework Config: Open Raw File" + }, + { + "command": "gframeworkConfig.openSchema", + "title": "GFramework Config: Open Schema" + }, + { + "command": "gframeworkConfig.openFormPreview", + "title": "GFramework Config: Open Form Preview" + }, + { + "command": "gframeworkConfig.batchEditDomain", + "title": "GFramework Config: Batch Edit Domain" + }, + { + "command": "gframeworkConfig.validateAll", + "title": "GFramework Config: Validate All" + } + ], + "menus": { + "view/title": [ + { + "command": "gframeworkConfig.refresh", + "when": "view == gframeworkConfigExplorer", + "group": "navigation" + }, + { + "command": "gframeworkConfig.validateAll", + "when": "view == gframeworkConfigExplorer", + "group": "navigation" + } + ], + "view/item/context": [ + { + "command": "gframeworkConfig.openRaw", + "when": "view == gframeworkConfigExplorer && viewItem == gframeworkConfigFile", + "group": "inline" + }, + { + "command": "gframeworkConfig.openSchema", + "when": "view == gframeworkConfigExplorer && viewItem == gframeworkConfigFile", + "group": "navigation" + }, + { + "command": "gframeworkConfig.openFormPreview", + "when": "view == gframeworkConfigExplorer && viewItem == gframeworkConfigFile", + "group": "navigation" + }, + { + "command": "gframeworkConfig.batchEditDomain", + "when": "view == gframeworkConfigExplorer && viewItem == domain", + "group": "navigation" + } + ] + }, + "configuration": { + "title": "GFramework Config", + "properties": { + "gframeworkConfig.configPath": { + "type": "string", + "default": "config", + "description": "Relative path from the workspace root to the config directory." + }, + "gframeworkConfig.schemasPath": { + "type": "string", + "default": "schemas", + "description": "Relative path from the workspace root to the schema directory." + } + } + } + } +} diff --git a/tools/gframework-config-tool/src/configValidation.js b/tools/gframework-config-tool/src/configValidation.js new file mode 100644 index 0000000..301e3d6 --- /dev/null +++ b/tools/gframework-config-tool/src/configValidation.js @@ -0,0 +1,842 @@ +/** + * Parse the repository's minimal config-schema subset into a recursive tree. + * The parser intentionally mirrors the same high-level contract used by the + * runtime validator and source generator so tooling diagnostics stay aligned. + * + * @param {string} content Raw schema JSON text. + * @returns {{ + * type: "object", + * required: string[], + * properties: Record + * }} Parsed schema info. + */ +function parseSchemaContent(content) { + const parsed = JSON.parse(content); + return parseSchemaNode(parsed, ""); +} + +/** + * Collect top-level schema fields that the current batch editor can update + * safely. Batch editing intentionally remains conservative even though the form + * preview can now navigate nested object structures. + * + * @param {{type: "object", required: string[], properties: Record}} schemaInfo Parsed schema. + * @returns {Array<{ + * key: string, + * path: string, + * type: string, + * itemType?: string, + * title?: string, + * description?: string, + * defaultValue?: string, + * enumValues?: string[], + * itemEnumValues?: string[], + * refTable?: string, + * inputKind: "scalar" | "array", + * required: boolean + * }>} Editable field descriptors. + */ +function getEditableSchemaFields(schemaInfo) { + const editableFields = []; + const requiredSet = new Set(Array.isArray(schemaInfo.required) ? schemaInfo.required : []); + + for (const [key, property] of Object.entries(schemaInfo.properties || {})) { + if (isEditableScalarType(property.type)) { + editableFields.push({ + key, + path: key, + type: property.type, + title: property.title, + description: property.description, + defaultValue: property.defaultValue, + enumValues: property.enumValues, + refTable: property.refTable, + inputKind: "scalar", + required: requiredSet.has(key) + }); + continue; + } + + if (property.type === "array" && property.items && isEditableScalarType(property.items.type)) { + editableFields.push({ + key, + path: key, + type: property.type, + itemType: property.items.type, + title: property.title, + description: property.description, + defaultValue: property.defaultValue, + itemEnumValues: property.items.enumValues, + refTable: property.refTable, + inputKind: "array", + required: requiredSet.has(key) + }); + } + } + + return editableFields.sort((left, right) => left.key.localeCompare(right.key)); +} + +/** + * Parse YAML into a recursive object/array/scalar tree. + * The parser covers the config system's intended subset: root mappings, + * indentation-based nested objects, scalar arrays, and arrays of objects. + * + * @param {string} text YAML text. + * @returns {YamlNode} Parsed YAML tree. + */ +function parseTopLevelYaml(text) { + const tokens = tokenizeYaml(text); + if (tokens.length === 0) { + return createObjectNode(); + } + + const state = {index: 0}; + return parseBlock(tokens, state, tokens[0].indent); +} + +/** + * Produce extension-facing validation diagnostics from schema and parsed YAML. + * + * @param {{type: "object", required: string[], properties: Record}} schemaInfo Parsed schema. + * @param {YamlNode} parsedYaml Parsed YAML tree. + * @returns {Array<{severity: "error" | "warning", message: string}>} Validation diagnostics. + */ +function validateParsedConfig(schemaInfo, parsedYaml) { + const diagnostics = []; + validateNode(schemaInfo, parsedYaml, "", diagnostics); + return diagnostics; +} + +/** + * Determine whether the current schema type can be edited through the batch + * editor. The richer form preview handles nested objects separately. + * + * @param {string} schemaType Schema type. + * @returns {boolean} True when the type is batch-editable. + */ +function isEditableScalarType(schemaType) { + return schemaType === "string" || + schemaType === "integer" || + schemaType === "number" || + schemaType === "boolean"; +} + +/** + * Determine whether a scalar value matches a minimal schema type. + * + * @param {string} expectedType Schema type. + * @param {string} scalarValue YAML scalar value. + * @returns {boolean} True when compatible. + */ +function isScalarCompatible(expectedType, scalarValue) { + const value = unquoteScalar(String(scalarValue)); + switch (expectedType) { + case "integer": + return /^-?\d+$/u.test(value); + case "number": + return /^-?\d+(?:\.\d+)?$/u.test(value); + case "boolean": + return /^(true|false)$/iu.test(value); + case "string": + return true; + default: + return true; + } +} + +/** + * Apply form updates back into YAML. The implementation rewrites the YAML tree + * from the parsed structure so nested object edits can be saved safely. + * + * @param {string} originalYaml Original YAML content. + * @param {{scalars?: Record, arrays?: Record, objectArrays?: Record>>}} updates Updated form values. + * @returns {string} Updated YAML content. + */ +function applyFormUpdates(originalYaml, updates) { + const root = normalizeRootNode(parseTopLevelYaml(originalYaml)); + const scalarUpdates = updates.scalars || {}; + const arrayUpdates = updates.arrays || {}; + const objectArrayUpdates = updates.objectArrays || {}; + + for (const [path, value] of Object.entries(scalarUpdates)) { + setNodeAtPath(root, path.split("."), createScalarNode(String(value))); + } + + for (const [path, values] of Object.entries(arrayUpdates)) { + setNodeAtPath(root, path.split("."), createArrayNode( + (values || []).map((item) => createScalarNode(String(item))))); + } + + for (const [path, items] of Object.entries(objectArrayUpdates)) { + setNodeAtPath(root, path.split("."), createArrayNode( + (items || []).map((item) => createNodeFromFormValue(item)))); + } + + return renderYaml(root).join("\n"); +} + +/** + * Apply only scalar updates back into YAML. + * + * @param {string} originalYaml Original YAML content. + * @param {Record} updates Updated scalar values. + * @returns {string} Updated YAML content. + */ +function applyScalarUpdates(originalYaml, updates) { + return applyFormUpdates(originalYaml, {scalars: updates}); +} + +/** + * Parse the batch editor's comma-separated array input. + * + * @param {string} value Raw input value. + * @returns {string[]} Parsed array items. + */ +function parseBatchArrayValue(value) { + return String(value) + .split(",") + .map((item) => item.trim()) + .filter((item) => item.length > 0); +} + +/** + * Normalize a schema enum array into string values that can be shown in UI + * hints and compared against parsed YAML scalar content. + * + * @param {unknown} value Raw schema enum value. + * @returns {string[] | undefined} Normalized enum values. + */ +function normalizeSchemaEnumValues(value) { + if (!Array.isArray(value)) { + return undefined; + } + + const normalized = value + .filter((item) => ["string", "number", "boolean"].includes(typeof item)) + .map((item) => String(item)); + + return normalized.length > 0 ? normalized : undefined; +} + +/** + * Convert a schema default value into a compact string that can be shown in UI + * metadata hints. + * + * @param {unknown} value Raw schema default value. + * @returns {string | undefined} Display string for the default value. + */ +function formatSchemaDefaultValue(value) { + if (value === null || value === undefined) { + return undefined; + } + + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return String(value); + } + + if (Array.isArray(value)) { + const normalized = value + .filter((item) => ["string", "number", "boolean"].includes(typeof item)) + .map((item) => String(item)); + + return normalized.length > 0 ? normalized.join(", ") : undefined; + } + + if (typeof value === "object") { + return JSON.stringify(value); + } + + return undefined; +} + +/** + * Format a scalar value for YAML output. + * + * @param {string} value Scalar value. + * @returns {string} YAML-ready scalar. + */ +function formatYamlScalar(value) { + if (/^-?\d+(?:\.\d+)?$/u.test(value) || /^(true|false)$/iu.test(value)) { + return value; + } + + if (value.length === 0 || /[:#\[\]\{\},]|^\s|\s$/u.test(value)) { + return JSON.stringify(value); + } + + return value; +} + +/** + * Remove a simple YAML string quote wrapper. + * + * @param {string} value Scalar value. + * @returns {string} Unquoted value. + */ +function unquoteScalar(value) { + if ((value.startsWith("\"") && value.endsWith("\"")) || + (value.startsWith("'") && value.endsWith("'"))) { + return value.slice(1, -1); + } + + return value; +} + +/** + * Parse one schema node recursively. + * + * @param {unknown} rawNode Raw schema node. + * @param {string} displayPath Logical property path. + * @returns {SchemaNode} Parsed schema node. + */ +function parseSchemaNode(rawNode, displayPath) { + const value = rawNode && typeof rawNode === "object" ? rawNode : {}; + const type = typeof value.type === "string" ? value.type : "object"; + const metadata = { + title: typeof value.title === "string" ? value.title : undefined, + description: typeof value.description === "string" ? value.description : undefined, + defaultValue: formatSchemaDefaultValue(value.default), + refTable: typeof value["x-gframework-ref-table"] === "string" + ? value["x-gframework-ref-table"] + : undefined + }; + + if (type === "object") { + const required = Array.isArray(value.required) + ? value.required.filter((item) => typeof item === "string") + : []; + const properties = {}; + for (const [key, propertyNode] of Object.entries(value.properties || {})) { + properties[key] = parseSchemaNode(propertyNode, combinePath(displayPath, key)); + } + + return { + type: "object", + displayPath, + required, + properties, + title: metadata.title, + description: metadata.description, + defaultValue: metadata.defaultValue + }; + } + + if (type === "array") { + const itemNode = parseSchemaNode(value.items || {}, `${displayPath}[]`); + return { + type: "array", + displayPath, + title: metadata.title, + description: metadata.description, + defaultValue: metadata.defaultValue, + refTable: metadata.refTable, + items: itemNode + }; + } + + return { + type, + displayPath, + title: metadata.title, + description: metadata.description, + defaultValue: metadata.defaultValue, + enumValues: normalizeSchemaEnumValues(value.enum), + refTable: metadata.refTable + }; +} + +/** + * Validate one schema node against one YAML node. + * + * @param {SchemaNode} schemaNode Schema node. + * @param {YamlNode} yamlNode YAML node. + * @param {string} displayPath Current logical path. + * @param {Array<{severity: "error" | "warning", message: string}>} diagnostics Diagnostic sink. + */ +function validateNode(schemaNode, yamlNode, displayPath, diagnostics) { + if (schemaNode.type === "object") { + validateObjectNode(schemaNode, yamlNode, displayPath, diagnostics); + return; + } + + if (schemaNode.type === "array") { + if (!yamlNode || yamlNode.kind !== "array") { + diagnostics.push({ + severity: "error", + message: `Property '${displayPath}' is expected to be an array.` + }); + return; + } + + for (let index = 0; index < yamlNode.items.length; index += 1) { + validateNode(schemaNode.items, yamlNode.items[index], `${displayPath}[${index}]`, diagnostics); + } + return; + } + + if (!yamlNode || yamlNode.kind !== "scalar") { + diagnostics.push({ + severity: "error", + message: `Property '${displayPath}' is expected to be '${schemaNode.type}', but the current YAML shape is '${yamlNode ? yamlNode.kind : "missing"}'.` + }); + return; + } + + if (!isScalarCompatible(schemaNode.type, yamlNode.value)) { + diagnostics.push({ + severity: "error", + message: `Property '${displayPath}' is expected to be '${schemaNode.type}', but the current scalar value is incompatible.` + }); + return; + } + + if (Array.isArray(schemaNode.enumValues) && + schemaNode.enumValues.length > 0 && + !schemaNode.enumValues.includes(unquoteScalar(yamlNode.value))) { + diagnostics.push({ + severity: "error", + message: `Property '${displayPath}' must be one of: ${schemaNode.enumValues.join(", ")}.` + }); + } +} + +/** + * Validate an object node recursively. + * + * @param {Extract} schemaNode Object schema node. + * @param {YamlNode} yamlNode YAML node. + * @param {string} displayPath Current logical path. + * @param {Array<{severity: "error" | "warning", message: string}>} diagnostics Diagnostic sink. + */ +function validateObjectNode(schemaNode, yamlNode, displayPath, diagnostics) { + if (!yamlNode || yamlNode.kind !== "object") { + const subject = displayPath.length === 0 ? "Root object" : `Property '${displayPath}'`; + diagnostics.push({ + severity: "error", + message: `${subject} is expected to be an object.` + }); + return; + } + + for (const requiredProperty of schemaNode.required) { + if (!yamlNode.map.has(requiredProperty)) { + diagnostics.push({ + severity: "error", + message: `Required property '${combinePath(displayPath, requiredProperty)}' is missing.` + }); + } + } + + for (const entry of yamlNode.entries) { + if (!Object.prototype.hasOwnProperty.call(schemaNode.properties, entry.key)) { + diagnostics.push({ + severity: "error", + message: `Property '${combinePath(displayPath, entry.key)}' is not declared in the matching schema.` + }); + continue; + } + + validateNode( + schemaNode.properties[entry.key], + entry.node, + combinePath(displayPath, entry.key), + diagnostics); + } +} + +/** + * Tokenize YAML lines into indentation-aware units. + * + * @param {string} text YAML text. + * @returns {Array<{indent: number, text: string}>} Tokens. + */ +function tokenizeYaml(text) { + const tokens = []; + const lines = String(text).split(/\r?\n/u); + + for (const line of lines) { + if (!line || line.trim().length === 0 || line.trimStart().startsWith("#")) { + continue; + } + + const indentMatch = /^(\s*)/u.exec(line); + const indent = indentMatch ? indentMatch[1].length : 0; + const trimmed = line.slice(indent); + tokens.push({indent, text: trimmed}); + } + + return tokens; +} + +/** + * Parse the next YAML block from the token stream. + * + * @param {Array<{indent: number, text: string}>} tokens Token array. + * @param {{index: number}} state Mutable parser state. + * @param {number} indent Expected indentation. + * @returns {YamlNode} Parsed node. + */ +function parseBlock(tokens, state, indent) { + if (state.index >= tokens.length) { + return createObjectNode(); + } + + const token = tokens[state.index]; + if (token.text.startsWith("-")) { + return parseSequence(tokens, state, indent); + } + + return parseMapping(tokens, state, indent); +} + +/** + * Parse a mapping block. + * + * @param {Array<{indent: number, text: string}>} tokens Token array. + * @param {{index: number}} state Mutable parser state. + * @param {number} indent Expected indentation. + * @returns {YamlNode} Parsed object node. + */ +function parseMapping(tokens, state, indent) { + const entries = []; + const map = new Map(); + + while (state.index < tokens.length) { + const token = tokens[state.index]; + if (token.indent < indent || token.text.startsWith("-")) { + break; + } + + if (token.indent > indent) { + state.index += 1; + continue; + } + + const match = /^([A-Za-z0-9_]+):(.*)$/u.exec(token.text); + if (!match) { + state.index += 1; + continue; + } + + const key = match[1]; + const rawValue = match[2].trim(); + state.index += 1; + + let node; + if (rawValue.length > 0 && !rawValue.startsWith("|") && !rawValue.startsWith(">")) { + node = createScalarNode(rawValue); + } else if (state.index < tokens.length && tokens[state.index].indent > indent) { + node = parseBlock(tokens, state, tokens[state.index].indent); + } else { + node = createScalarNode(""); + } + + entries.push({key, node}); + map.set(key, node); + } + + return {kind: "object", entries, map}; +} + +/** + * Parse a sequence block. + * + * @param {Array<{indent: number, text: string}>} tokens Token array. + * @param {{index: number}} state Mutable parser state. + * @param {number} indent Expected indentation. + * @returns {YamlNode} Parsed array node. + */ +function parseSequence(tokens, state, indent) { + const items = []; + + while (state.index < tokens.length) { + const token = tokens[state.index]; + if (token.indent !== indent || !token.text.startsWith("-")) { + break; + } + + const rest = token.text.slice(1).trim(); + state.index += 1; + + if (rest.length === 0) { + if (state.index < tokens.length && tokens[state.index].indent > indent) { + items.push(parseBlock(tokens, state, tokens[state.index].indent)); + } else { + items.push(createScalarNode("")); + } + continue; + } + + if (/^[A-Za-z0-9_]+:/u.test(rest)) { + items.push(parseInlineObjectItem(tokens, state, indent, rest)); + continue; + } + + items.push(createScalarNode(rest)); + } + + return createArrayNode(items); +} + +/** + * Parse an array item written as an inline mapping head followed by nested + * child lines, for example `- wave: 1`. + * + * @param {Array<{indent: number, text: string}>} tokens Token array. + * @param {{index: number}} state Mutable parser state. + * @param {number} parentIndent Array indentation. + * @param {string} firstEntry Inline first entry text. + * @returns {YamlNode} Parsed object node. + */ +function parseInlineObjectItem(tokens, state, parentIndent, firstEntry) { + const syntheticTokens = [{indent: parentIndent + 2, text: firstEntry}]; + while (state.index < tokens.length && tokens[state.index].indent > parentIndent) { + syntheticTokens.push(tokens[state.index]); + state.index += 1; + } + + return parseBlock(syntheticTokens, {index: 0}, parentIndent + 2); +} + +/** + * Ensure the root node is an object, creating one if the YAML was empty or not + * object-shaped enough for structured edits. + * + * @param {YamlNode} node Parsed node. + * @returns {YamlObjectNode} Root object node. + */ +function normalizeRootNode(node) { + return node && node.kind === "object" ? node : createObjectNode(); +} + +/** + * Replace or create a node at a dot-separated object path. + * + * @param {YamlObjectNode} root Root object node. + * @param {string[]} segments Path segments. + * @param {YamlNode} valueNode Value node. + */ +function setNodeAtPath(root, segments, valueNode) { + let current = root; + + for (let index = 0; index < segments.length; index += 1) { + const segment = segments[index]; + if (!segment) { + continue; + } + + if (index === segments.length - 1) { + setObjectEntry(current, segment, valueNode); + return; + } + + let nextNode = current.map.get(segment); + if (!nextNode || nextNode.kind !== "object") { + nextNode = createObjectNode(); + setObjectEntry(current, segment, nextNode); + } + + current = nextNode; + } +} + +/** + * Insert or replace one mapping entry while preserving insertion order. + * + * @param {YamlObjectNode} objectNode Target object node. + * @param {string} key Mapping key. + * @param {YamlNode} valueNode Value node. + */ +function setObjectEntry(objectNode, key, valueNode) { + const existingIndex = objectNode.entries.findIndex((entry) => entry.key === key); + if (existingIndex >= 0) { + objectNode.entries[existingIndex] = {key, node: valueNode}; + } else { + objectNode.entries.push({key, node: valueNode}); + } + + objectNode.map.set(key, valueNode); +} + +/** + * Render a YAML node back to text lines. + * + * @param {YamlNode} node YAML node. + * @param {number} indent Current indentation. + * @returns {string[]} YAML lines. + */ +function renderYaml(node, indent = 0) { + if (node.kind === "object") { + return renderObjectNode(node, indent); + } + + if (node.kind === "array") { + return renderArrayNode(node, indent); + } + + return [`${" ".repeat(indent)}${formatYamlScalar(node.value)}`]; +} + +/** + * Render an object node. + * + * @param {YamlObjectNode} node Object node. + * @param {number} indent Current indentation. + * @returns {string[]} YAML lines. + */ +function renderObjectNode(node, indent) { + const lines = []; + for (const entry of node.entries) { + if (entry.node.kind === "scalar") { + lines.push(`${" ".repeat(indent)}${entry.key}: ${formatYamlScalar(entry.node.value)}`); + continue; + } + + if (entry.node.kind === "array" && entry.node.items.length === 0) { + lines.push(`${" ".repeat(indent)}${entry.key}: []`); + continue; + } + + lines.push(`${" ".repeat(indent)}${entry.key}:`); + lines.push(...renderYaml(entry.node, indent + 2)); + } + + return lines; +} + +/** + * Render an array node. + * + * @param {YamlArrayNode} node Array node. + * @param {number} indent Current indentation. + * @returns {string[]} YAML lines. + */ +function renderArrayNode(node, indent) { + const lines = []; + for (const item of node.items) { + if (item.kind === "scalar") { + lines.push(`${" ".repeat(indent)}- ${formatYamlScalar(item.value)}`); + continue; + } + + lines.push(`${" ".repeat(indent)}-`); + lines.push(...renderYaml(item, indent + 2)); + } + + return lines; +} + +/** + * Create a scalar node. + * + * @param {string} value Scalar value. + * @returns {YamlScalarNode} Scalar node. + */ +function createScalarNode(value) { + return {kind: "scalar", value}; +} + +/** + * Create an array node. + * + * @param {YamlNode[]} items Array items. + * @returns {YamlArrayNode} Array node. + */ +function createArrayNode(items) { + return {kind: "array", items}; +} + +/** + * Convert one structured form value back into a YAML node tree. + * Object-array editors submit plain JavaScript objects so the writer can + * rebuild the full array deterministically instead of patching item paths + * one by one. + * + * @param {unknown} value Structured form value. + * @returns {YamlNode} YAML node. + */ +function createNodeFromFormValue(value) { + if (Array.isArray(value)) { + return createArrayNode(value.map((item) => createNodeFromFormValue(item))); + } + + if (value && typeof value === "object") { + const objectNode = createObjectNode(); + for (const [key, childValue] of Object.entries(value)) { + setObjectEntry(objectNode, key, createNodeFromFormValue(childValue)); + } + + return objectNode; + } + + return createScalarNode(String(value ?? "")); +} + +/** + * Create an object node. + * + * @returns {YamlObjectNode} Object node. + */ +function createObjectNode() { + return {kind: "object", entries: [], map: new Map()}; +} + +/** + * Combine a parent path with one child segment. + * + * @param {string} parentPath Parent path. + * @param {string} key Child key. + * @returns {string} Combined path. + */ +function combinePath(parentPath, key) { + return parentPath && parentPath !== "" ? `${parentPath}.${key}` : key; +} + +module.exports = { + applyFormUpdates, + applyScalarUpdates, + getEditableSchemaFields, + isEditableScalarType, + isScalarCompatible, + parseBatchArrayValue, + parseSchemaContent, + parseTopLevelYaml, + unquoteScalar, + validateParsedConfig +}; + +/** + * @typedef {{ + * type: "object", + * displayPath: string, + * required: string[], + * properties: Record, + * title?: string, + * description?: string, + * defaultValue?: string + * } | { + * type: "array", + * displayPath: string, + * title?: string, + * description?: string, + * defaultValue?: string, + * refTable?: string, + * items: SchemaNode + * } | { + * type: "string" | "integer" | "number" | "boolean", + * displayPath: string, + * title?: string, + * description?: string, + * defaultValue?: string, + * enumValues?: string[], + * refTable?: string + * }} SchemaNode + */ + +/** + * @typedef {{kind: "scalar", value: string}} YamlScalarNode + * @typedef {{kind: "array", items: YamlNode[]}} YamlArrayNode + * @typedef {{kind: "object", entries: Array<{key: string, node: YamlNode}>, map: Map}} YamlObjectNode + * @typedef {YamlScalarNode | YamlArrayNode | YamlObjectNode} YamlNode + */ diff --git a/tools/gframework-config-tool/src/extension.js b/tools/gframework-config-tool/src/extension.js new file mode 100644 index 0000000..22fceb8 --- /dev/null +++ b/tools/gframework-config-tool/src/extension.js @@ -0,0 +1,1482 @@ +const fs = require("fs"); +const path = require("path"); +const vscode = require("vscode"); +const { + applyFormUpdates, + getEditableSchemaFields, + parseBatchArrayValue, + parseSchemaContent, + parseTopLevelYaml, + unquoteScalar, + validateParsedConfig +} = require("./configValidation"); + +/** + * Activate the GFramework config extension. + * The current tool focuses on workspace file navigation, lightweight + * validation, and a schema-aware form preview for common editing workflows. + * + * @param {vscode.ExtensionContext} context Extension context. + */ +function activate(context) { + const diagnostics = vscode.languages.createDiagnosticCollection("gframeworkConfig"); + const provider = new ConfigTreeDataProvider(); + + context.subscriptions.push(diagnostics); + context.subscriptions.push( + vscode.window.registerTreeDataProvider("gframeworkConfigExplorer", provider), + vscode.commands.registerCommand("gframeworkConfig.refresh", async () => { + provider.refresh(); + await validateAllConfigs(diagnostics); + }), + vscode.commands.registerCommand("gframeworkConfig.openRaw", async (item) => { + await openRawFile(item); + }), + vscode.commands.registerCommand("gframeworkConfig.openSchema", async (item) => { + await openSchemaFile(item); + }), + vscode.commands.registerCommand("gframeworkConfig.openFormPreview", async (item) => { + await openFormPreview(item, diagnostics); + }), + vscode.commands.registerCommand("gframeworkConfig.batchEditDomain", async (item) => { + await openBatchEdit(item, diagnostics, provider); + }), + vscode.commands.registerCommand("gframeworkConfig.validateAll", async () => { + await validateAllConfigs(diagnostics); + }), + vscode.workspace.onDidSaveTextDocument(async (document) => { + const workspaceRoot = getWorkspaceRoot(); + if (!workspaceRoot) { + return; + } + + if (!isConfigFile(document.uri, workspaceRoot)) { + return; + } + + await validateConfigFile(document.uri, diagnostics); + provider.refresh(); + }), + vscode.workspace.onDidChangeWorkspaceFolders(async () => { + provider.refresh(); + await validateAllConfigs(diagnostics); + }) + ); + + void validateAllConfigs(diagnostics); +} + +/** + * Deactivate the extension. + */ +function deactivate() { +} + +/** + * Tree provider for the GFramework config explorer view. + */ +class ConfigTreeDataProvider { + constructor() { + this._emitter = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._emitter.event; + } + + /** + * Refresh the tree view. + */ + refresh() { + this._emitter.fire(undefined); + } + + /** + * Resolve a tree item. + * + * @param {ConfigTreeItem} element Tree element. + * @returns {vscode.TreeItem} Tree item. + */ + getTreeItem(element) { + return element; + } + + /** + * Resolve child elements. + * + * @param {ConfigTreeItem | undefined} element Parent element. + * @returns {Thenable} Child items. + */ + async getChildren(element) { + const workspaceRoot = getWorkspaceRoot(); + if (!workspaceRoot) { + return []; + } + + if (!element) { + return this.getRootItems(workspaceRoot); + } + + if (element.kind !== "domain" || !element.resourceUri) { + return []; + } + + return this.getFileItems(workspaceRoot, element.resourceUri); + } + + /** + * Build root domain items from the config directory. + * + * @param {vscode.WorkspaceFolder} workspaceRoot Workspace root. + * @returns {Promise} Root items. + */ + async getRootItems(workspaceRoot) { + const configRoot = getConfigRoot(workspaceRoot); + if (!configRoot || !fs.existsSync(configRoot.fsPath)) { + return [ + new ConfigTreeItem( + "No config directory", + "info", + vscode.TreeItemCollapsibleState.None, + undefined, + "Set gframeworkConfig.configPath or create the directory.") + ]; + } + + const entries = fs.readdirSync(configRoot.fsPath, {withFileTypes: true}) + .filter((entry) => entry.isDirectory()) + .sort((left, right) => left.name.localeCompare(right.name)); + + return entries.map((entry) => { + const domainUri = vscode.Uri.joinPath(configRoot, entry.name); + return new ConfigTreeItem( + entry.name, + "domain", + vscode.TreeItemCollapsibleState.Collapsed, + domainUri, + undefined); + }); + } + + /** + * Build file items for a config domain directory. + * + * @param {vscode.WorkspaceFolder} workspaceRoot Workspace root. + * @param {vscode.Uri} domainUri Domain directory URI. + * @returns {Promise} File items. + */ + async getFileItems(workspaceRoot, domainUri) { + const entries = fs.readdirSync(domainUri.fsPath, {withFileTypes: true}) + .filter((entry) => entry.isFile() && isYamlPath(entry.name)) + .sort((left, right) => left.name.localeCompare(right.name)); + + return entries.map((entry) => { + const fileUri = vscode.Uri.joinPath(domainUri, entry.name); + const schemaUri = getSchemaUriForConfigFile(fileUri, workspaceRoot); + const description = schemaUri && fs.existsSync(schemaUri.fsPath) + ? "schema" + : "schema missing"; + const item = new ConfigTreeItem( + entry.name, + "file", + vscode.TreeItemCollapsibleState.None, + fileUri, + description); + + item.contextValue = "gframeworkConfigFile"; + item.command = { + command: "gframeworkConfig.openRaw", + title: "Open Raw", + arguments: [item] + }; + + return item; + }); + } +} + +/** + * Tree item used by the config explorer. + */ +class ConfigTreeItem extends vscode.TreeItem { + /** + * @param {string} label Display label. + * @param {"domain" | "file" | "info"} kind Item kind. + * @param {vscode.TreeItemCollapsibleState} collapsibleState Collapsible state. + * @param {vscode.Uri | undefined} resourceUri Resource URI. + * @param {string | undefined} description Description. + */ + constructor(label, kind, collapsibleState, resourceUri, description) { + super(label, collapsibleState); + this.kind = kind; + this.resourceUri = resourceUri; + this.description = description; + this.contextValue = kind === "file" ? "gframeworkConfigFile" : kind; + } +} + +/** + * Open the selected raw config file. + * + * @param {ConfigTreeItem | { resourceUri?: vscode.Uri }} item Tree item. + * @returns {Promise} Async task. + */ +async function openRawFile(item) { + const uri = item && item.resourceUri; + if (!uri) { + return; + } + + const document = await vscode.workspace.openTextDocument(uri); + await vscode.window.showTextDocument(document, {preview: false}); +} + +/** + * Open the matching schema file for a selected config item. + * + * @param {ConfigTreeItem | { resourceUri?: vscode.Uri }} item Tree item. + * @returns {Promise} Async task. + */ +async function openSchemaFile(item) { + const workspaceRoot = getWorkspaceRoot(); + const configUri = item && item.resourceUri; + if (!workspaceRoot || !configUri) { + return; + } + + const schemaUri = getSchemaUriForConfigFile(configUri, workspaceRoot); + if (!schemaUri || !fs.existsSync(schemaUri.fsPath)) { + void vscode.window.showWarningMessage("Matching schema file was not found."); + return; + } + + const document = await vscode.workspace.openTextDocument(schemaUri); + await vscode.window.showTextDocument(document, {preview: false}); +} + +/** + * Open a lightweight form preview for schema-bound config fields. + * The preview walks nested object structures recursively and now supports + * object-array editing for the repository's supported schema subset. + * + * @param {ConfigTreeItem | { resourceUri?: vscode.Uri }} item Tree item. + * @param {vscode.DiagnosticCollection} diagnostics Diagnostic collection. + * @returns {Promise} Async task. + */ +async function openFormPreview(item, diagnostics) { + const workspaceRoot = getWorkspaceRoot(); + const configUri = item && item.resourceUri; + if (!workspaceRoot || !configUri) { + return; + } + + const yamlText = await fs.promises.readFile(configUri.fsPath, "utf8"); + const parsedYaml = parseTopLevelYaml(yamlText); + const schemaInfo = await loadSchemaInfoForConfig(configUri, workspaceRoot); + + const panel = vscode.window.createWebviewPanel( + "gframeworkConfigFormPreview", + `Config Form: ${path.basename(configUri.fsPath)}`, + vscode.ViewColumn.Beside, + {enableScripts: true}); + + panel.webview.html = renderFormHtml( + path.basename(configUri.fsPath), + schemaInfo, + parsedYaml); + + panel.webview.onDidReceiveMessage(async (message) => { + if (message.type === "save") { + const latestYamlText = await fs.promises.readFile(configUri.fsPath, "utf8"); + const updatedYaml = applyFormUpdates(latestYamlText, { + scalars: message.scalars || {}, + arrays: parseArrayFieldPayload(message.arrays || {}), + objectArrays: message.objectArrays || {} + }); + await fs.promises.writeFile(configUri.fsPath, updatedYaml, "utf8"); + const document = await vscode.workspace.openTextDocument(configUri); + await document.save(); + await validateConfigFile(configUri, diagnostics); + void vscode.window.showInformationMessage("Config file saved from form preview."); + } + + if (message.type === "openRaw") { + await openRawFile({resourceUri: configUri}); + } + }); +} + +/** + * Validate all config files in the configured config directory. + * + * @param {vscode.DiagnosticCollection} diagnostics Diagnostic collection. + * @returns {Promise} Async task. + */ +async function validateAllConfigs(diagnostics) { + diagnostics.clear(); + + const workspaceRoot = getWorkspaceRoot(); + if (!workspaceRoot) { + return; + } + + const configRoot = getConfigRoot(workspaceRoot); + if (!configRoot || !fs.existsSync(configRoot.fsPath)) { + return; + } + + const files = enumerateYamlFiles(configRoot.fsPath); + for (const filePath of files) { + await validateConfigFile(vscode.Uri.file(filePath), diagnostics); + } +} + +/** + * Validate a single config file against its matching schema. + * + * @param {vscode.Uri} configUri Config file URI. + * @param {vscode.DiagnosticCollection} diagnostics Diagnostic collection. + * @returns {Promise} Async task. + */ +async function validateConfigFile(configUri, diagnostics) { + const workspaceRoot = getWorkspaceRoot(); + if (!workspaceRoot) { + return; + } + + if (!isConfigFile(configUri, workspaceRoot)) { + return; + } + + const yamlText = await fs.promises.readFile(configUri.fsPath, "utf8"); + const parsedYaml = parseTopLevelYaml(yamlText); + const schemaInfo = await loadSchemaInfoForConfig(configUri, workspaceRoot); + const fileDiagnostics = []; + + if (!schemaInfo.exists) { + fileDiagnostics.push(new vscode.Diagnostic( + new vscode.Range(0, 0, 0, 1), + `Matching schema file not found: ${schemaInfo.schemaPath}`, + vscode.DiagnosticSeverity.Warning)); + diagnostics.set(configUri, fileDiagnostics); + return; + } + + for (const diagnostic of validateParsedConfig(schemaInfo, parsedYaml)) { + fileDiagnostics.push(new vscode.Diagnostic( + new vscode.Range(0, 0, 0, 1), + diagnostic.message, + diagnostic.severity === "error" + ? vscode.DiagnosticSeverity.Error + : vscode.DiagnosticSeverity.Warning)); + } + + diagnostics.set(configUri, fileDiagnostics); +} + +/** + * Open a minimal batch editor for one config domain. + * The workflow intentionally focuses on one schema-bound directory at a time + * so designers can apply the same top-level scalar or scalar-array values + * across multiple files without dropping down to repetitive raw-YAML edits. + * + * @param {ConfigTreeItem | { kind?: string, resourceUri?: vscode.Uri }} item Tree item. + * @param {vscode.DiagnosticCollection} diagnostics Diagnostic collection. + * @param {ConfigTreeDataProvider} provider Tree provider. + * @returns {Promise} Async task. + */ +async function openBatchEdit(item, diagnostics, provider) { + const workspaceRoot = getWorkspaceRoot(); + const domainUri = item && item.resourceUri; + if (!workspaceRoot || !domainUri || item.kind !== "domain") { + return; + } + + const fileItems = fs.readdirSync(domainUri.fsPath, {withFileTypes: true}) + .filter((entry) => entry.isFile() && isYamlPath(entry.name)) + .sort((left, right) => left.name.localeCompare(right.name)) + .map((entry) => { + const fileUri = vscode.Uri.joinPath(domainUri, entry.name); + return { + label: entry.name, + description: path.relative(workspaceRoot.uri.fsPath, fileUri.fsPath), + fileUri, + picked: true + }; + }); + + if (fileItems.length === 0) { + void vscode.window.showWarningMessage("No YAML config files were found in the selected domain."); + return; + } + + const selectedFiles = await vscode.window.showQuickPick(fileItems, { + canPickMany: true, + title: `Batch Edit: ${path.basename(domainUri.fsPath)}`, + placeHolder: "Select the config files to update." + }); + if (!selectedFiles || selectedFiles.length === 0) { + return; + } + + const schemaInfo = await loadSchemaInfoForConfig(selectedFiles[0].fileUri, workspaceRoot); + if (!schemaInfo.exists) { + void vscode.window.showWarningMessage("Batch edit requires a matching schema file for the selected domain."); + return; + } + + const editableFields = getEditableSchemaFields(schemaInfo); + if (editableFields.length === 0) { + void vscode.window.showWarningMessage( + "No top-level scalar or scalar-array fields were found in the matching schema."); + return; + } + + const selectedFields = await vscode.window.showQuickPick( + editableFields.map((field) => ({ + label: field.title || field.key, + description: field.inputKind === "array" + ? `array<${field.itemType}>` + : field.type, + detail: [ + field.required ? "required" : "", + field.description || "", + field.refTable ? `ref: ${field.refTable}` : "" + ].filter((part) => part.length > 0).join(" · ") || undefined, + field + })), + { + canPickMany: true, + title: `Batch Edit Fields: ${path.basename(domainUri.fsPath)}`, + placeHolder: "Select the fields to apply across the chosen files." + }); + if (!selectedFields || selectedFields.length === 0) { + return; + } + + const updates = { + scalars: {}, + arrays: {} + }; + + for (const selectedField of selectedFields) { + const field = selectedField.field; + const rawValue = await promptBatchFieldValue(field); + if (rawValue === undefined) { + return; + } + + if (field.inputKind === "array") { + updates.arrays[field.key] = parseBatchArrayValue(rawValue); + continue; + } + + updates.scalars[field.key] = rawValue; + } + + const edit = new vscode.WorkspaceEdit(); + const touchedDocuments = []; + let changedFileCount = 0; + + for (const fileItem of selectedFiles) { + const document = await vscode.workspace.openTextDocument(fileItem.fileUri); + const originalYaml = document.getText(); + const updatedYaml = applyFormUpdates(originalYaml, updates); + if (updatedYaml === originalYaml) { + continue; + } + + const fullRange = new vscode.Range( + document.positionAt(0), + document.positionAt(originalYaml.length)); + edit.replace(fileItem.fileUri, fullRange, updatedYaml); + touchedDocuments.push(document); + changedFileCount += 1; + } + + if (changedFileCount === 0) { + void vscode.window.showInformationMessage("Batch edit did not change any selected config files."); + return; + } + + const applied = await vscode.workspace.applyEdit(edit); + if (!applied) { + throw new Error("VS Code rejected the batch edit workspace update."); + } + + for (const document of touchedDocuments) { + await document.save(); + await validateConfigFile(document.uri, diagnostics); + } + + provider.refresh(); + void vscode.window.showInformationMessage( + `Batch updated ${changedFileCount} config file(s) in '${path.basename(domainUri.fsPath)}'.`); +} + +/** + * Load schema info for a config file. + * + * @param {vscode.Uri} configUri Config file URI. + * @param {vscode.WorkspaceFolder} workspaceRoot Workspace root. + * @returns {Promise<{exists: boolean, schemaPath: string, required: string[], properties: Record}>} Schema info. + */ +async function loadSchemaInfoForConfig(configUri, workspaceRoot) { + const schemaUri = getSchemaUriForConfigFile(configUri, workspaceRoot); + const schemaPath = schemaUri ? schemaUri.fsPath : ""; + if (!schemaUri || !fs.existsSync(schemaUri.fsPath)) { + return { + exists: false, + schemaPath, + required: [], + properties: {} + }; + } + + const content = await fs.promises.readFile(schemaUri.fsPath, "utf8"); + try { + const parsed = parseSchemaContent(content); + + return { + exists: true, + schemaPath, + type: parsed.type, + required: parsed.required, + properties: parsed.properties + }; + } catch (error) { + return { + exists: false, + schemaPath, + required: [], + properties: {} + }; + } +} + +/** + * Render the form-preview webview HTML. + * + * @param {string} fileName File name. + * @param {{exists: boolean, schemaPath: string, required: string[], properties: Record, type?: string}} schemaInfo Schema info. + * @param {unknown} parsedYaml Parsed YAML data. + * @returns {string} HTML string. + */ +function renderFormHtml(fileName, schemaInfo, parsedYaml) { + const formModel = buildFormModel(schemaInfo, parsedYaml); + const renderedFields = formModel.fields + .map((field) => renderFormField(field)) + .join("\n"); + + const unsupportedFields = formModel.unsupported + .map((field) => ` +
+ ${escapeHtml(field.path)}: ${escapeHtml(field.message)} +
+ `) + .join("\n"); + + const schemaStatus = schemaInfo.exists + ? `Schema: ${escapeHtml(schemaInfo.schemaPath)}` + : `Schema missing: ${escapeHtml(schemaInfo.schemaPath)}`; + + const editableContent = renderedFields; + const unsupportedSection = unsupportedFields.length > 0 + ? `
${unsupportedFields}
` + : ""; + const emptyState = editableContent.length > 0 + ? `${editableContent}${unsupportedSection}` + : "

No editable schema-bound fields were detected. Use raw YAML for unsupported shapes.

"; + + return ` + + + + + + + +
+ + +
+
+
File: ${escapeHtml(fileName)}
+
${schemaStatus}
+
+
${emptyState}
+ + +`; +} + +/** + * Render one form field. + * + * @param {Record} field Form field descriptor. + * @returns {string} HTML fragment. + */ +function renderFormField(field) { + if (field.kind === "section") { + return ` +
+
${escapeHtml(field.label)} ${field.required ? "required" : ""}
+
${escapeHtml(field.displayPath || field.path)}
+ ${field.description ? `${escapeHtml(field.description)}` : ""} +
+ `; + } + + if (field.kind === "objectArray") { + const renderedItems = field.items + .map((item) => renderObjectArrayItem(item)) + .join("\n"); + const renderedTemplate = renderObjectArrayItem({ + title: "Item", + fields: field.templateFields + }); + return ` +
+
${escapeHtml(field.label)} ${field.required ? "required" : ""}
+
${escapeHtml(field.displayPath || field.path)}
+ Each item uses the object schema below. + ${renderFieldHint(field.schema, true)} +
${renderedItems}
+ + +
+ `; + } + + if (field.kind === "array") { + const itemType = field.itemType + ? `array<${escapeHtml(field.itemType)}>` + : "array"; + const dataAttribute = field.itemMode + ? `data-item-array-path="${escapeHtml(field.path)}"` + : `data-array-path="${escapeHtml(field.path)}"`; + return ` + + `; + } + + const enumValues = Array.isArray(field.schema.enumValues) ? field.schema.enumValues : []; + const dataAttribute = field.itemMode + ? `data-item-local-path="${escapeHtml(field.path)}"` + : `data-path="${escapeHtml(field.path)}"`; + const inputControl = enumValues.length > 0 + ? ` + + ` + : ``; + + return ` + + `; +} + +/** + * Render one object-array item editor block. + * + * @param {{title: string, fields: Array>}} item Item model. + * @returns {string} HTML fragment. + */ +function renderObjectArrayItem(item) { + return ` +
+
+ ${escapeHtml(item.title)} + +
+ ${item.fields.map((field) => renderFormField(field)).join("\n")} +
+ `; +} + +/** + * Build a recursive form model from schema and parsed YAML. + * + * @param {{exists: boolean, schemaPath: string, required: string[], properties: Record, type?: string}} schemaInfo Schema info. + * @param {unknown} parsedYaml Parsed YAML data. + * @returns {{fields: Array>, unsupported: Array<{path: string, message: string}>}} Form model. + */ +function buildFormModel(schemaInfo, parsedYaml) { + if (!schemaInfo || schemaInfo.type !== "object") { + return {fields: [], unsupported: []}; + } + + const fields = []; + const unsupported = []; + collectFormFields(schemaInfo, parsedYaml, "", 0, fields, unsupported); + return {fields, unsupported}; +} + +/** + * Recursively collect top-level form-editable fields. + * + * @param {{type: string, required?: string[], properties?: Record, title?: string, description?: string}} schemaNode Schema node. + * @param {unknown} yamlNode YAML node. + * @param {string} currentPath Current logical path. + * @param {number} depth Current depth. + * @param {Array>} fields Field sink. + * @param {Array<{path: string, message: string}>} unsupported Unsupported sink. + */ +function collectFormFields(schemaNode, yamlNode, currentPath, depth, fields, unsupported) { + if (!schemaNode || schemaNode.type !== "object") { + return; + } + + const yamlMap = getYamlObjectMap(yamlNode); + const requiredSet = new Set(Array.isArray(schemaNode.required) ? schemaNode.required : []); + + for (const [key, propertySchema] of Object.entries(schemaNode.properties || {})) { + const propertyPath = currentPath ? `${currentPath}.${key}` : key; + const label = propertySchema.title || key; + const propertyValue = yamlMap.get(key); + + if (propertySchema.type === "object") { + fields.push({ + kind: "section", + path: propertyPath, + label, + description: propertySchema.description, + required: requiredSet.has(key), + depth + }); + collectFormFields(propertySchema, propertyValue, propertyPath, depth + 1, fields, unsupported); + continue; + } + + if (propertySchema.type === "array" && + propertySchema.items && + ["string", "integer", "number", "boolean"].includes(propertySchema.items.type)) { + fields.push({ + kind: "array", + path: propertyPath, + displayPath: propertyPath, + label, + required: requiredSet.has(key), + depth, + itemType: propertySchema.items.type, + value: getScalarArrayValue(propertyValue), + schema: propertySchema + }); + continue; + } + + if (propertySchema.type === "array" && + propertySchema.items && + propertySchema.items.type === "object") { + const itemFieldsTemplate = []; + collectObjectArrayItemFields( + propertySchema.items, + undefined, + "", + `${propertyPath}[]`, + depth + 1, + itemFieldsTemplate, + unsupported); + fields.push({ + kind: "objectArray", + path: propertyPath, + displayPath: propertyPath, + label, + required: requiredSet.has(key), + depth, + schema: propertySchema, + items: buildObjectArrayItemModels(propertySchema.items, propertyValue, propertyPath, depth + 1, unsupported), + templateFields: itemFieldsTemplate + }); + continue; + } + + if (["string", "integer", "number", "boolean"].includes(propertySchema.type)) { + fields.push({ + kind: "scalar", + path: propertyPath, + displayPath: propertyPath, + label, + required: requiredSet.has(key), + depth, + value: getScalarFieldValue(propertyValue, propertySchema.defaultValue), + schema: propertySchema + }); + continue; + } + + unsupported.push({ + path: propertyPath, + message: propertySchema.type === "array" + ? "Unsupported array shapes are currently raw-YAML-only in the form preview." + : `${propertySchema.type} fields are currently raw-YAML-only.` + }); + } +} + +/** + * Build object-array item models from the current YAML array value. + * + * @param {{type: string, required?: string[], properties?: Record}} itemSchema Array item schema. + * @param {unknown} yamlNode YAML node. + * @param {string} propertyPath Top-level object-array path. + * @param {number} depth Current depth. + * @param {Array<{path: string, message: string}>} unsupported Unsupported sink. + * @returns {Array<{title: string, fields: Array>}>} Item models. + */ +function buildObjectArrayItemModels(itemSchema, yamlNode, propertyPath, depth, unsupported) { + if (!yamlNode || yamlNode.kind !== "array") { + return []; + } + + const items = []; + for (let index = 0; index < yamlNode.items.length; index += 1) { + const itemNode = yamlNode.items[index]; + const itemPath = `${propertyPath}[${index}]`; + if (!itemNode || itemNode.kind !== "object") { + unsupported.push({ + path: itemPath, + message: "Object-array items must be mappings. Use raw YAML if the current file mixes scalar and object items." + }); + continue; + } + + const fields = []; + collectObjectArrayItemFields( + itemSchema, + itemNode, + "", + itemPath, + depth, + fields, + unsupported); + items.push({ + title: `Item ${index + 1}`, + fields + }); + } + + return items; +} + +/** + * Recursively collect editable fields inside one object-array item. + * Nested objects remain editable, while nested object arrays still fall back + * to raw YAML until a deeper editor model is added. + * + * @param {{type: string, required?: string[], properties?: Record, title?: string, description?: string}} schemaNode Schema node. + * @param {unknown} yamlNode YAML node. + * @param {string} localPath Path inside the current array item. + * @param {string} displayPath Full logical path for UI display. + * @param {number} depth Current depth. + * @param {Array>} fields Field sink. + * @param {Array<{path: string, message: string}>} unsupported Unsupported sink. + */ +function collectObjectArrayItemFields(schemaNode, yamlNode, localPath, displayPath, depth, fields, unsupported) { + if (!schemaNode || schemaNode.type !== "object") { + return; + } + + const yamlMap = getYamlObjectMap(yamlNode); + const requiredSet = new Set(Array.isArray(schemaNode.required) ? schemaNode.required : []); + + for (const [key, propertySchema] of Object.entries(schemaNode.properties || {})) { + const itemLocalPath = localPath ? `${localPath}.${key}` : key; + const itemDisplayPath = `${displayPath}.${key}`; + const label = propertySchema.title || key; + const propertyValue = yamlMap.get(key); + + if (propertySchema.type === "object") { + fields.push({ + kind: "section", + path: itemLocalPath, + displayPath: itemDisplayPath, + label, + description: propertySchema.description, + required: requiredSet.has(key), + depth + }); + collectObjectArrayItemFields( + propertySchema, + propertyValue, + itemLocalPath, + itemDisplayPath, + depth + 1, + fields, + unsupported); + continue; + } + + if (propertySchema.type === "array" && + propertySchema.items && + ["string", "integer", "number", "boolean"].includes(propertySchema.items.type)) { + fields.push({ + kind: "array", + path: itemLocalPath, + displayPath: itemDisplayPath, + label, + required: requiredSet.has(key), + depth, + itemType: propertySchema.items.type, + value: getScalarArrayValue(propertyValue), + schema: propertySchema, + itemMode: true + }); + continue; + } + + if (["string", "integer", "number", "boolean"].includes(propertySchema.type)) { + fields.push({ + kind: "scalar", + path: itemLocalPath, + displayPath: itemDisplayPath, + label, + required: requiredSet.has(key), + depth, + value: getScalarFieldValue(propertyValue, propertySchema.defaultValue), + schema: propertySchema, + itemMode: true + }); + continue; + } + + unsupported.push({ + path: itemDisplayPath, + message: propertySchema.type === "array" + ? "Nested object-array fields are currently raw-YAML-only inside the object-array editor." + : `${propertySchema.type} fields are currently raw-YAML-only.` + }); + } +} + +/** + * Get the mapping lookup for one parsed YAML object node. + * + * @param {unknown} yamlNode YAML node. + * @returns {Map} Mapping lookup. + */ +function getYamlObjectMap(yamlNode) { + return yamlNode && yamlNode.kind === "object" && yamlNode.map instanceof Map + ? yamlNode.map + : new Map(); +} + +/** + * Extract a scalar field value from a parsed YAML node. + * + * @param {unknown} yamlNode YAML node. + * @param {string | undefined} defaultValue Default value from schema metadata. + * @returns {string} Scalar display value. + */ +function getScalarFieldValue(yamlNode, defaultValue) { + if (yamlNode && yamlNode.kind === "scalar") { + return unquoteScalar(yamlNode.value || ""); + } + + return defaultValue || ""; +} + +/** + * Extract a scalar-array value list from a parsed YAML node. + * + * @param {unknown} yamlNode YAML node. + * @returns {string[]} Scalar array value list. + */ +function getScalarArrayValue(yamlNode) { + if (!yamlNode || yamlNode.kind !== "array") { + return []; + } + + return yamlNode.items + .filter((item) => item && item.kind === "scalar") + .map((item) => unquoteScalar(item.value || "")); +} + +/** + * Render human-facing metadata hints for one schema field. + * + * @param {{description?: string, defaultValue?: string, enumValues?: string[], items?: {enumValues?: string[]}, refTable?: string}} propertySchema Property schema metadata. + * @param {boolean} isArrayField Whether the field is an array. + * @returns {string} HTML fragment. + */ +function renderFieldHint(propertySchema, isArrayField) { + const hints = []; + + if (propertySchema.description) { + hints.push(escapeHtml(propertySchema.description)); + } + + if (propertySchema.defaultValue) { + hints.push(`Default: ${escapeHtml(propertySchema.defaultValue)}`); + } + + const enumValues = isArrayField + ? propertySchema.items && Array.isArray(propertySchema.items.enumValues) + ? propertySchema.items.enumValues + : [] + : propertySchema.enumValues; + if (Array.isArray(enumValues) && enumValues.length > 0) { + hints.push(`Allowed: ${escapeHtml(enumValues.join(", "))}`); + } + + if (propertySchema.refTable) { + hints.push(`Ref table: ${escapeHtml(propertySchema.refTable)}`); + } + + if (hints.length === 0) { + return ""; + } + + return `${hints.join(" · ")}`; +} + +/** + * Prompt for one batch-edit field value. + * + * @param {{key: string, type: string, itemType?: string, title?: string, description?: string, defaultValue?: string, enumValues?: string[], itemEnumValues?: string[], refTable?: string, inputKind: "scalar" | "array", required: boolean}} field Editable field descriptor. + * @returns {Promise} User input, or undefined when cancelled. + */ +async function promptBatchFieldValue(field) { + if (field.inputKind === "array") { + const hintParts = []; + if (field.itemEnumValues && field.itemEnumValues.length > 0) { + hintParts.push(`Allowed items: ${field.itemEnumValues.join(", ")}`); + } + + if (field.defaultValue) { + hintParts.push(`Default: ${field.defaultValue}`); + } + + return vscode.window.showInputBox({ + title: `Batch Edit Array: ${field.title || field.key}`, + prompt: `Enter comma-separated items for '${field.key}' (expected array<${field.itemType}>). Leave empty to clear the array.`, + placeHolder: hintParts.join(" | "), + ignoreFocusOut: true + }); + } + + if (field.enumValues && field.enumValues.length > 0) { + const picked = await vscode.window.showQuickPick( + field.enumValues.map((value) => ({ + label: value, + description: value === field.defaultValue ? "default" : undefined + })), + { + title: `Batch Edit Field: ${field.title || field.key}`, + placeHolder: `Select a value for '${field.key}'.` + }); + return picked ? picked.label : undefined; + } + + return vscode.window.showInputBox({ + title: `Batch Edit Field: ${field.title || field.key}`, + prompt: `Enter the new value for '${field.key}' (expected ${field.type}).`, + placeHolder: [ + field.description || "", + field.defaultValue ? `Default: ${field.defaultValue}` : "", + field.refTable ? `Ref table: ${field.refTable}` : "" + ].filter((part) => part.length > 0).join(" | ") || undefined, + ignoreFocusOut: true + }); +} + +/** + * Enumerate all YAML files recursively. + * + * @param {string} rootPath Root path. + * @returns {string[]} YAML file paths. + */ +function enumerateYamlFiles(rootPath) { + const results = []; + + for (const entry of fs.readdirSync(rootPath, {withFileTypes: true})) { + const fullPath = path.join(rootPath, entry.name); + if (entry.isDirectory()) { + results.push(...enumerateYamlFiles(fullPath)); + continue; + } + + if (entry.isFile() && isYamlPath(entry.name)) { + results.push(fullPath); + } + } + + return results; +} + +/** + * Check whether a path is a YAML file. + * + * @param {string} filePath File path. + * @returns {boolean} True for YAML files. + */ +function isYamlPath(filePath) { + return filePath.endsWith(".yaml") || filePath.endsWith(".yml"); +} + +/** + * Resolve the first workspace root. + * + * @returns {vscode.WorkspaceFolder | undefined} Workspace root. + */ +function getWorkspaceRoot() { + const folders = vscode.workspace.workspaceFolders; + return folders && folders.length > 0 ? folders[0] : undefined; +} + +/** + * Resolve the configured config root. + * + * @param {vscode.WorkspaceFolder} workspaceRoot Workspace root. + * @returns {vscode.Uri | undefined} Config root URI. + */ +function getConfigRoot(workspaceRoot) { + const relativePath = vscode.workspace.getConfiguration("gframeworkConfig") + .get("configPath", "config"); + return vscode.Uri.joinPath(workspaceRoot.uri, relativePath); +} + +/** + * Resolve the configured schemas root. + * + * @param {vscode.WorkspaceFolder} workspaceRoot Workspace root. + * @returns {vscode.Uri | undefined} Schema root URI. + */ +function getSchemasRoot(workspaceRoot) { + const relativePath = vscode.workspace.getConfiguration("gframeworkConfig") + .get("schemasPath", "schemas"); + return vscode.Uri.joinPath(workspaceRoot.uri, relativePath); +} + +/** + * Resolve the matching schema URI for a config file. + * + * @param {vscode.Uri} configUri Config file URI. + * @param {vscode.WorkspaceFolder} workspaceRoot Workspace root. + * @returns {vscode.Uri | undefined} Schema URI. + */ +function getSchemaUriForConfigFile(configUri, workspaceRoot) { + const configRoot = getConfigRoot(workspaceRoot); + const schemaRoot = getSchemasRoot(workspaceRoot); + if (!configRoot || !schemaRoot) { + return undefined; + } + + const relativePath = path.relative(configRoot.fsPath, configUri.fsPath); + const segments = relativePath.split(path.sep); + if (segments.length === 0 || !segments[0]) { + return undefined; + } + + return vscode.Uri.joinPath(schemaRoot, `${segments[0]}.schema.json`); +} + +/** + * Check whether a URI is inside the configured config root. + * + * @param {vscode.Uri} uri File URI. + * @param {vscode.WorkspaceFolder} workspaceRoot Workspace root. + * @returns {boolean} True when the file belongs to the config tree. + */ +function isConfigFile(uri, workspaceRoot) { + const configRoot = getConfigRoot(workspaceRoot); + if (!configRoot) { + return false; + } + + const relativePath = path.relative(configRoot.fsPath, uri.fsPath); + return !relativePath.startsWith("..") && !path.isAbsolute(relativePath) && isYamlPath(uri.fsPath); +} + +/** + * Escape HTML text. + * + * @param {string} value Raw string. + * @returns {string} Escaped string. + */ +function escapeHtml(value) { + return String(value) + .replace(/&/gu, "&") + .replace(//gu, ">") + .replace(/"/gu, """) + .replace(/'/gu, "'"); +} + +/** + * Convert raw textarea payloads into scalar-array items. + * + * @param {Record} arrays Raw array editor payload. + * @returns {Record} Parsed array updates. + */ +function parseArrayFieldPayload(arrays) { + const parsed = {}; + + for (const [key, value] of Object.entries(arrays)) { + parsed[key] = String(value) + .split(/\r?\n/u) + .map((item) => item.trim()) + .filter((item) => item.length > 0); + } + + return parsed; +} + +module.exports = { + activate, + deactivate +}; diff --git a/tools/gframework-config-tool/test/configValidation.test.js b/tools/gframework-config-tool/test/configValidation.test.js new file mode 100644 index 0000000..ae8a272 --- /dev/null +++ b/tools/gframework-config-tool/test/configValidation.test.js @@ -0,0 +1,369 @@ +const test = require("node:test"); +const assert = require("node:assert/strict"); +const { + applyFormUpdates, + applyScalarUpdates, + getEditableSchemaFields, + parseBatchArrayValue, + parseSchemaContent, + parseTopLevelYaml, + validateParsedConfig +} = require("../src/configValidation"); + +test("parseSchemaContent should capture nested objects and object-array metadata", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "required": ["id", "reward", "phases"], + "properties": { + "id": { + "type": "integer", + "title": "Monster Id", + "description": "Primary monster key.", + "default": 1 + }, + "reward": { + "type": "object", + "required": ["gold"], + "properties": { + "gold": { + "type": "integer", + "default": 10 + }, + "currency": { + "type": "string", + "enum": ["coin", "gem"] + } + } + }, + "phases": { + "type": "array", + "description": "Encounter phases.", + "items": { + "type": "object", + "required": ["wave"], + "properties": { + "wave": { "type": "integer" }, + "monsterId": { "type": "string" } + } + } + } + } + } + `); + + assert.equal(schema.type, "object"); + assert.deepEqual(schema.required, ["id", "reward", "phases"]); + assert.equal(schema.properties.id.defaultValue, "1"); + assert.equal(schema.properties.reward.type, "object"); + assert.deepEqual(schema.properties.reward.required, ["gold"]); + assert.equal(schema.properties.reward.properties.currency.enumValues[1], "gem"); + assert.equal(schema.properties.phases.type, "array"); + assert.equal(schema.properties.phases.items.type, "object"); + assert.equal(schema.properties.phases.items.properties.wave.type, "integer"); +}); + +test("parseTopLevelYaml should parse nested mappings and object arrays", () => { + const yaml = parseTopLevelYaml(` +id: 1 +reward: + gold: 10 + currency: coin +phases: + - + wave: 1 + monsterId: slime +`); + + assert.equal(yaml.kind, "object"); + assert.equal(yaml.map.get("reward").kind, "object"); + assert.equal(yaml.map.get("reward").map.get("currency").value, "coin"); + assert.equal(yaml.map.get("phases").kind, "array"); + assert.equal(yaml.map.get("phases").items[0].kind, "object"); + assert.equal(yaml.map.get("phases").items[0].map.get("wave").value, "1"); +}); + +test("validateParsedConfig should report missing and unknown nested properties", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "required": ["reward"], + "properties": { + "reward": { + "type": "object", + "required": ["gold", "currency"], + "properties": { + "gold": { "type": "integer" }, + "currency": { "type": "string" } + } + } + } + } + `); + const yaml = parseTopLevelYaml(` +reward: + gold: 10 + rarity: epic +`); + + const diagnostics = validateParsedConfig(schema, yaml); + + assert.equal(diagnostics.length, 2); + assert.match(diagnostics[0].message, /reward\.currency/u); + assert.match(diagnostics[1].message, /reward\.rarity/u); +}); + +test("validateParsedConfig should report object-array item issues", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "properties": { + "phases": { + "type": "array", + "items": { + "type": "object", + "required": ["wave", "monsterId"], + "properties": { + "wave": { "type": "integer" }, + "monsterId": { "type": "string" } + } + } + } + } + } + `); + const yaml = parseTopLevelYaml(` +phases: + - + wave: 1 + hpScale: 1.5 +`); + + const diagnostics = validateParsedConfig(schema, yaml); + + assert.equal(diagnostics.length, 2); + assert.match(diagnostics[0].message, /phases\[0\]\.monsterId/u); + assert.match(diagnostics[1].message, /phases\[0\]\.hpScale/u); +}); + +test("validateParsedConfig should report deep enum mismatches", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "properties": { + "reward": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "enum": ["coin", "gem"] + } + } + } + } + } + `); + const yaml = parseTopLevelYaml(` +reward: + currency: ticket +`); + + const diagnostics = validateParsedConfig(schema, yaml); + + assert.equal(diagnostics.length, 1); + assert.match(diagnostics[0].message, /coin, gem/u); +}); + +test("applyFormUpdates should update nested scalar and scalar-array paths", () => { + const updated = applyFormUpdates( + [ + "id: 1", + "reward:", + " gold: 10", + "phases:", + " -", + " wave: 1" + ].join("\n"), + { + scalars: { + "reward.currency": "coin", + name: "Slime" + }, + arrays: { + dropItems: ["potion", "hi potion"] + } + }); + + assert.match(updated, /^name: Slime$/mu); + assert.match(updated, /^reward:$/mu); + assert.match(updated, /^ currency: coin$/mu); + assert.match(updated, /^dropItems:$/mu); + assert.match(updated, /^ - potion$/mu); + assert.match(updated, /^ - hi potion$/mu); + assert.match(updated, /^phases:$/mu); +}); + +test("applyFormUpdates should rewrite object-array items from structured form payloads", () => { + const updated = applyFormUpdates( + [ + "id: 1", + "name: Slime", + "phases:", + " -", + " wave: 1", + " monsterId: slime" + ].join("\n"), + { + objectArrays: { + phases: [ + { + wave: "1", + monsterId: "slime", + tags: ["starter", "melee"], + reward: { + gold: "10", + currency: "coin" + } + }, + { + wave: "2", + monsterId: "goblin" + } + ] + } + }); + + assert.match(updated, /^id: 1$/mu); + assert.match(updated, /^name: Slime$/mu); + assert.match(updated, /^phases:$/mu); + assert.match(updated, /^ -$/mu); + assert.match(updated, /^ wave: 1$/mu); + assert.match(updated, /^ monsterId: slime$/mu); + assert.match(updated, /^ tags:$/mu); + assert.match(updated, /^ - starter$/mu); + assert.match(updated, /^ - melee$/mu); + assert.match(updated, /^ reward:$/mu); + assert.match(updated, /^ gold: 10$/mu); + assert.match(updated, /^ currency: coin$/mu); + assert.match(updated, /^ monsterId: goblin$/mu); +}); + +test("applyFormUpdates should clear object arrays when the form removes all items", () => { + const updated = applyFormUpdates( + [ + "id: 1", + "phases:", + " -", + " wave: 1", + " monsterId: slime" + ].join("\n"), + { + objectArrays: { + phases: [] + } + }); + + assert.equal(updated, [ + "id: 1", + "phases: []" + ].join("\n")); +}); + +test("applyScalarUpdates should preserve the scalar-only compatibility wrapper", () => { + const updated = applyScalarUpdates( + [ + "id: 1", + "name: Slime" + ].join("\n"), + { + name: "Goblin", + hp: "25" + }); + + assert.match(updated, /^name: Goblin$/mu); + assert.match(updated, /^hp: 25$/mu); +}); + +test("getEditableSchemaFields should keep batch editing limited to top-level scalar and scalar-array properties", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "required": ["id", "dropItems"], + "properties": { + "id": { "type": "integer" }, + "name": { + "type": "string", + "title": "Monster Name", + "description": "Display name." + }, + "reward": { + "type": "object", + "properties": { + "gold": { "type": "integer" } + } + }, + "dropItems": { + "type": "array", + "description": "Drop ids.", + "items": { + "type": "string", + "enum": ["potion", "bomb"] + } + }, + "waypoints": { + "type": "array", + "items": { + "type": "object", + "properties": { + "x": { "type": "integer" } + } + } + } + } + } + `); + + assert.deepEqual(getEditableSchemaFields(schema), [ + { + key: "dropItems", + path: "dropItems", + type: "array", + itemType: "string", + title: undefined, + description: "Drop ids.", + defaultValue: undefined, + itemEnumValues: ["potion", "bomb"], + refTable: undefined, + inputKind: "array", + required: true + }, + { + key: "id", + path: "id", + type: "integer", + title: undefined, + description: undefined, + defaultValue: undefined, + enumValues: undefined, + refTable: undefined, + inputKind: "scalar", + required: true + }, + { + key: "name", + path: "name", + type: "string", + title: "Monster Name", + description: "Display name.", + defaultValue: undefined, + enumValues: undefined, + refTable: undefined, + inputKind: "scalar", + required: false + } + ]); +}); + +test("parseBatchArrayValue should keep comma-separated batch editing behavior", () => { + assert.deepEqual(parseBatchArrayValue(" potion, bomb , ,elixir "), ["potion", "bomb", "elixir"]); +});