amd-gaia 0.14.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- amd_gaia-0.14.1.dist-info/METADATA +768 -0
- amd_gaia-0.14.1.dist-info/RECORD +800 -0
- amd_gaia-0.14.1.dist-info/WHEEL +5 -0
- amd_gaia-0.14.1.dist-info/entry_points.txt +5 -0
- amd_gaia-0.14.1.dist-info/licenses/LICENSE.md +21 -0
- amd_gaia-0.14.1.dist-info/top_level.txt +1 -0
- gaia/__init__.py +2 -0
- gaia/agents/__init__.py +19 -0
- gaia/agents/base/__init__.py +9 -0
- gaia/agents/base/agent.py +2072 -0
- gaia/agents/base/api_agent.py +120 -0
- gaia/agents/base/console.py +1457 -0
- gaia/agents/base/mcp_agent.py +86 -0
- gaia/agents/base/tools.py +83 -0
- gaia/agents/blender/agent.py +556 -0
- gaia/agents/blender/agent_simple.py +135 -0
- gaia/agents/blender/app.py +211 -0
- gaia/agents/blender/app_simple.py +41 -0
- gaia/agents/blender/core/__init__.py +16 -0
- gaia/agents/blender/core/materials.py +506 -0
- gaia/agents/blender/core/objects.py +316 -0
- gaia/agents/blender/core/rendering.py +225 -0
- gaia/agents/blender/core/scene.py +220 -0
- gaia/agents/blender/core/view.py +146 -0
- gaia/agents/chat/__init__.py +9 -0
- gaia/agents/chat/agent.py +975 -0
- gaia/agents/chat/app.py +1058 -0
- gaia/agents/chat/session.py +508 -0
- gaia/agents/chat/tools/__init__.py +15 -0
- gaia/agents/chat/tools/file_tools.py +96 -0
- gaia/agents/chat/tools/rag_tools.py +1729 -0
- gaia/agents/chat/tools/shell_tools.py +436 -0
- gaia/agents/code/__init__.py +7 -0
- gaia/agents/code/agent.py +547 -0
- gaia/agents/code/app.py +266 -0
- gaia/agents/code/models.py +135 -0
- gaia/agents/code/orchestration/__init__.py +24 -0
- gaia/agents/code/orchestration/checklist_executor.py +1739 -0
- gaia/agents/code/orchestration/checklist_generator.py +709 -0
- gaia/agents/code/orchestration/factories/__init__.py +9 -0
- gaia/agents/code/orchestration/factories/base.py +63 -0
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -0
- gaia/agents/code/orchestration/factories/python_factory.py +106 -0
- gaia/agents/code/orchestration/orchestrator.py +610 -0
- gaia/agents/code/orchestration/project_analyzer.py +391 -0
- gaia/agents/code/orchestration/steps/__init__.py +67 -0
- gaia/agents/code/orchestration/steps/base.py +188 -0
- gaia/agents/code/orchestration/steps/error_handler.py +314 -0
- gaia/agents/code/orchestration/steps/nextjs.py +828 -0
- gaia/agents/code/orchestration/steps/python.py +307 -0
- gaia/agents/code/orchestration/template_catalog.py +463 -0
- gaia/agents/code/orchestration/workflows/__init__.py +14 -0
- gaia/agents/code/orchestration/workflows/base.py +80 -0
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -0
- gaia/agents/code/orchestration/workflows/python.py +94 -0
- gaia/agents/code/prompts/__init__.py +11 -0
- gaia/agents/code/prompts/base_prompt.py +77 -0
- gaia/agents/code/prompts/code_patterns.py +1925 -0
- gaia/agents/code/prompts/nextjs_prompt.py +40 -0
- gaia/agents/code/prompts/python_prompt.py +109 -0
- gaia/agents/code/schema_inference.py +365 -0
- gaia/agents/code/system_prompt.py +41 -0
- gaia/agents/code/tools/__init__.py +42 -0
- gaia/agents/code/tools/cli_tools.py +1138 -0
- gaia/agents/code/tools/code_formatting.py +319 -0
- gaia/agents/code/tools/code_tools.py +769 -0
- gaia/agents/code/tools/error_fixing.py +1347 -0
- gaia/agents/code/tools/external_tools.py +180 -0
- gaia/agents/code/tools/file_io.py +845 -0
- gaia/agents/code/tools/prisma_tools.py +190 -0
- gaia/agents/code/tools/project_management.py +1016 -0
- gaia/agents/code/tools/testing.py +321 -0
- gaia/agents/code/tools/typescript_tools.py +122 -0
- gaia/agents/code/tools/validation_parsing.py +461 -0
- gaia/agents/code/tools/validation_tools.py +803 -0
- gaia/agents/code/tools/web_dev_tools.py +1744 -0
- gaia/agents/code/validators/__init__.py +16 -0
- gaia/agents/code/validators/antipattern_checker.py +241 -0
- gaia/agents/code/validators/ast_analyzer.py +197 -0
- gaia/agents/code/validators/requirements_validator.py +145 -0
- gaia/agents/code/validators/syntax_validator.py +171 -0
- gaia/agents/docker/__init__.py +7 -0
- gaia/agents/docker/agent.py +642 -0
- gaia/agents/jira/__init__.py +11 -0
- gaia/agents/jira/agent.py +894 -0
- gaia/agents/jira/jql_templates.py +299 -0
- gaia/agents/routing/__init__.py +7 -0
- gaia/agents/routing/agent.py +512 -0
- gaia/agents/routing/system_prompt.py +75 -0
- gaia/api/__init__.py +23 -0
- gaia/api/agent_registry.py +238 -0
- gaia/api/app.py +305 -0
- gaia/api/openai_server.py +575 -0
- gaia/api/schemas.py +186 -0
- gaia/api/sse_handler.py +370 -0
- gaia/apps/__init__.py +4 -0
- gaia/apps/llm/__init__.py +6 -0
- gaia/apps/llm/app.py +169 -0
- gaia/apps/summarize/app.py +633 -0
- gaia/apps/summarize/html_viewer.py +133 -0
- gaia/apps/summarize/pdf_formatter.py +284 -0
- gaia/audio/__init__.py +2 -0
- gaia/audio/audio_client.py +439 -0
- gaia/audio/audio_recorder.py +269 -0
- gaia/audio/kokoro_tts.py +599 -0
- gaia/audio/whisper_asr.py +432 -0
- gaia/chat/__init__.py +16 -0
- gaia/chat/app.py +430 -0
- gaia/chat/prompts.py +522 -0
- gaia/chat/sdk.py +1200 -0
- gaia/cli.py +5621 -0
- gaia/eval/batch_experiment.py +2332 -0
- gaia/eval/claude.py +542 -0
- gaia/eval/config.py +37 -0
- gaia/eval/email_generator.py +512 -0
- gaia/eval/eval.py +3179 -0
- gaia/eval/groundtruth.py +1130 -0
- gaia/eval/transcript_generator.py +582 -0
- gaia/eval/webapp/README.md +168 -0
- gaia/eval/webapp/node_modules/.bin/mime +16 -0
- gaia/eval/webapp/node_modules/.bin/mime.cmd +17 -0
- gaia/eval/webapp/node_modules/.bin/mime.ps1 +28 -0
- gaia/eval/webapp/node_modules/.package-lock.json +865 -0
- gaia/eval/webapp/node_modules/accepts/HISTORY.md +243 -0
- gaia/eval/webapp/node_modules/accepts/LICENSE +23 -0
- gaia/eval/webapp/node_modules/accepts/README.md +140 -0
- gaia/eval/webapp/node_modules/accepts/index.js +238 -0
- gaia/eval/webapp/node_modules/accepts/package.json +47 -0
- gaia/eval/webapp/node_modules/array-flatten/LICENSE +21 -0
- gaia/eval/webapp/node_modules/array-flatten/README.md +43 -0
- gaia/eval/webapp/node_modules/array-flatten/array-flatten.js +64 -0
- gaia/eval/webapp/node_modules/array-flatten/package.json +39 -0
- gaia/eval/webapp/node_modules/body-parser/HISTORY.md +672 -0
- gaia/eval/webapp/node_modules/body-parser/LICENSE +23 -0
- gaia/eval/webapp/node_modules/body-parser/README.md +476 -0
- gaia/eval/webapp/node_modules/body-parser/SECURITY.md +25 -0
- gaia/eval/webapp/node_modules/body-parser/index.js +156 -0
- gaia/eval/webapp/node_modules/body-parser/lib/read.js +205 -0
- gaia/eval/webapp/node_modules/body-parser/lib/types/json.js +247 -0
- gaia/eval/webapp/node_modules/body-parser/lib/types/raw.js +101 -0
- gaia/eval/webapp/node_modules/body-parser/lib/types/text.js +121 -0
- gaia/eval/webapp/node_modules/body-parser/lib/types/urlencoded.js +307 -0
- gaia/eval/webapp/node_modules/body-parser/package.json +56 -0
- gaia/eval/webapp/node_modules/bytes/History.md +97 -0
- gaia/eval/webapp/node_modules/bytes/LICENSE +23 -0
- gaia/eval/webapp/node_modules/bytes/Readme.md +152 -0
- gaia/eval/webapp/node_modules/bytes/index.js +170 -0
- gaia/eval/webapp/node_modules/bytes/package.json +42 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/.eslintrc +17 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/.nycrc +9 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/CHANGELOG.md +30 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/LICENSE +21 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/README.md +62 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/actualApply.d.ts +1 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/actualApply.js +10 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/applyBind.d.ts +19 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/applyBind.js +10 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/functionApply.d.ts +1 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/functionApply.js +4 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/functionCall.d.ts +1 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/functionCall.js +4 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/index.d.ts +64 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/index.js +15 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/package.json +85 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/reflectApply.d.ts +3 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/reflectApply.js +4 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/test/index.js +63 -0
- gaia/eval/webapp/node_modules/call-bind-apply-helpers/tsconfig.json +9 -0
- gaia/eval/webapp/node_modules/call-bound/.eslintrc +13 -0
- gaia/eval/webapp/node_modules/call-bound/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/call-bound/.nycrc +9 -0
- gaia/eval/webapp/node_modules/call-bound/CHANGELOG.md +42 -0
- gaia/eval/webapp/node_modules/call-bound/LICENSE +21 -0
- gaia/eval/webapp/node_modules/call-bound/README.md +53 -0
- gaia/eval/webapp/node_modules/call-bound/index.d.ts +94 -0
- gaia/eval/webapp/node_modules/call-bound/index.js +19 -0
- gaia/eval/webapp/node_modules/call-bound/package.json +99 -0
- gaia/eval/webapp/node_modules/call-bound/test/index.js +61 -0
- gaia/eval/webapp/node_modules/call-bound/tsconfig.json +10 -0
- gaia/eval/webapp/node_modules/content-disposition/HISTORY.md +60 -0
- gaia/eval/webapp/node_modules/content-disposition/LICENSE +22 -0
- gaia/eval/webapp/node_modules/content-disposition/README.md +142 -0
- gaia/eval/webapp/node_modules/content-disposition/index.js +458 -0
- gaia/eval/webapp/node_modules/content-disposition/package.json +44 -0
- gaia/eval/webapp/node_modules/content-type/HISTORY.md +29 -0
- gaia/eval/webapp/node_modules/content-type/LICENSE +22 -0
- gaia/eval/webapp/node_modules/content-type/README.md +94 -0
- gaia/eval/webapp/node_modules/content-type/index.js +225 -0
- gaia/eval/webapp/node_modules/content-type/package.json +42 -0
- gaia/eval/webapp/node_modules/cookie/LICENSE +24 -0
- gaia/eval/webapp/node_modules/cookie/README.md +317 -0
- gaia/eval/webapp/node_modules/cookie/SECURITY.md +25 -0
- gaia/eval/webapp/node_modules/cookie/index.js +334 -0
- gaia/eval/webapp/node_modules/cookie/package.json +44 -0
- gaia/eval/webapp/node_modules/cookie-signature/.npmignore +4 -0
- gaia/eval/webapp/node_modules/cookie-signature/History.md +38 -0
- gaia/eval/webapp/node_modules/cookie-signature/Readme.md +42 -0
- gaia/eval/webapp/node_modules/cookie-signature/index.js +51 -0
- gaia/eval/webapp/node_modules/cookie-signature/package.json +18 -0
- gaia/eval/webapp/node_modules/debug/.coveralls.yml +1 -0
- gaia/eval/webapp/node_modules/debug/.eslintrc +11 -0
- gaia/eval/webapp/node_modules/debug/.npmignore +9 -0
- gaia/eval/webapp/node_modules/debug/.travis.yml +14 -0
- gaia/eval/webapp/node_modules/debug/CHANGELOG.md +362 -0
- gaia/eval/webapp/node_modules/debug/LICENSE +19 -0
- gaia/eval/webapp/node_modules/debug/Makefile +50 -0
- gaia/eval/webapp/node_modules/debug/README.md +312 -0
- gaia/eval/webapp/node_modules/debug/component.json +19 -0
- gaia/eval/webapp/node_modules/debug/karma.conf.js +70 -0
- gaia/eval/webapp/node_modules/debug/node.js +1 -0
- gaia/eval/webapp/node_modules/debug/package.json +49 -0
- gaia/eval/webapp/node_modules/debug/src/browser.js +185 -0
- gaia/eval/webapp/node_modules/debug/src/debug.js +202 -0
- gaia/eval/webapp/node_modules/debug/src/index.js +10 -0
- gaia/eval/webapp/node_modules/debug/src/inspector-log.js +15 -0
- gaia/eval/webapp/node_modules/debug/src/node.js +248 -0
- gaia/eval/webapp/node_modules/depd/History.md +103 -0
- gaia/eval/webapp/node_modules/depd/LICENSE +22 -0
- gaia/eval/webapp/node_modules/depd/Readme.md +280 -0
- gaia/eval/webapp/node_modules/depd/index.js +538 -0
- gaia/eval/webapp/node_modules/depd/lib/browser/index.js +77 -0
- gaia/eval/webapp/node_modules/depd/package.json +45 -0
- gaia/eval/webapp/node_modules/destroy/LICENSE +23 -0
- gaia/eval/webapp/node_modules/destroy/README.md +63 -0
- gaia/eval/webapp/node_modules/destroy/index.js +209 -0
- gaia/eval/webapp/node_modules/destroy/package.json +48 -0
- gaia/eval/webapp/node_modules/dunder-proto/.eslintrc +5 -0
- gaia/eval/webapp/node_modules/dunder-proto/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/dunder-proto/.nycrc +13 -0
- gaia/eval/webapp/node_modules/dunder-proto/CHANGELOG.md +24 -0
- gaia/eval/webapp/node_modules/dunder-proto/LICENSE +21 -0
- gaia/eval/webapp/node_modules/dunder-proto/README.md +54 -0
- gaia/eval/webapp/node_modules/dunder-proto/get.d.ts +5 -0
- gaia/eval/webapp/node_modules/dunder-proto/get.js +30 -0
- gaia/eval/webapp/node_modules/dunder-proto/package.json +76 -0
- gaia/eval/webapp/node_modules/dunder-proto/set.d.ts +5 -0
- gaia/eval/webapp/node_modules/dunder-proto/set.js +35 -0
- gaia/eval/webapp/node_modules/dunder-proto/test/get.js +34 -0
- gaia/eval/webapp/node_modules/dunder-proto/test/index.js +4 -0
- gaia/eval/webapp/node_modules/dunder-proto/test/set.js +50 -0
- gaia/eval/webapp/node_modules/dunder-proto/tsconfig.json +9 -0
- gaia/eval/webapp/node_modules/ee-first/LICENSE +22 -0
- gaia/eval/webapp/node_modules/ee-first/README.md +80 -0
- gaia/eval/webapp/node_modules/ee-first/index.js +95 -0
- gaia/eval/webapp/node_modules/ee-first/package.json +29 -0
- gaia/eval/webapp/node_modules/encodeurl/LICENSE +22 -0
- gaia/eval/webapp/node_modules/encodeurl/README.md +109 -0
- gaia/eval/webapp/node_modules/encodeurl/index.js +60 -0
- gaia/eval/webapp/node_modules/encodeurl/package.json +40 -0
- gaia/eval/webapp/node_modules/es-define-property/.eslintrc +13 -0
- gaia/eval/webapp/node_modules/es-define-property/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/es-define-property/.nycrc +9 -0
- gaia/eval/webapp/node_modules/es-define-property/CHANGELOG.md +29 -0
- gaia/eval/webapp/node_modules/es-define-property/LICENSE +21 -0
- gaia/eval/webapp/node_modules/es-define-property/README.md +49 -0
- gaia/eval/webapp/node_modules/es-define-property/index.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-define-property/index.js +14 -0
- gaia/eval/webapp/node_modules/es-define-property/package.json +81 -0
- gaia/eval/webapp/node_modules/es-define-property/test/index.js +56 -0
- gaia/eval/webapp/node_modules/es-define-property/tsconfig.json +10 -0
- gaia/eval/webapp/node_modules/es-errors/.eslintrc +5 -0
- gaia/eval/webapp/node_modules/es-errors/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/es-errors/CHANGELOG.md +40 -0
- gaia/eval/webapp/node_modules/es-errors/LICENSE +21 -0
- gaia/eval/webapp/node_modules/es-errors/README.md +55 -0
- gaia/eval/webapp/node_modules/es-errors/eval.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-errors/eval.js +4 -0
- gaia/eval/webapp/node_modules/es-errors/index.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-errors/index.js +4 -0
- gaia/eval/webapp/node_modules/es-errors/package.json +80 -0
- gaia/eval/webapp/node_modules/es-errors/range.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-errors/range.js +4 -0
- gaia/eval/webapp/node_modules/es-errors/ref.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-errors/ref.js +4 -0
- gaia/eval/webapp/node_modules/es-errors/syntax.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-errors/syntax.js +4 -0
- gaia/eval/webapp/node_modules/es-errors/test/index.js +19 -0
- gaia/eval/webapp/node_modules/es-errors/tsconfig.json +49 -0
- gaia/eval/webapp/node_modules/es-errors/type.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-errors/type.js +4 -0
- gaia/eval/webapp/node_modules/es-errors/uri.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-errors/uri.js +4 -0
- gaia/eval/webapp/node_modules/es-object-atoms/.eslintrc +16 -0
- gaia/eval/webapp/node_modules/es-object-atoms/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/es-object-atoms/CHANGELOG.md +37 -0
- gaia/eval/webapp/node_modules/es-object-atoms/LICENSE +21 -0
- gaia/eval/webapp/node_modules/es-object-atoms/README.md +63 -0
- gaia/eval/webapp/node_modules/es-object-atoms/RequireObjectCoercible.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-object-atoms/RequireObjectCoercible.js +11 -0
- gaia/eval/webapp/node_modules/es-object-atoms/ToObject.d.ts +7 -0
- gaia/eval/webapp/node_modules/es-object-atoms/ToObject.js +10 -0
- gaia/eval/webapp/node_modules/es-object-atoms/index.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-object-atoms/index.js +4 -0
- gaia/eval/webapp/node_modules/es-object-atoms/isObject.d.ts +3 -0
- gaia/eval/webapp/node_modules/es-object-atoms/isObject.js +6 -0
- gaia/eval/webapp/node_modules/es-object-atoms/package.json +80 -0
- gaia/eval/webapp/node_modules/es-object-atoms/test/index.js +38 -0
- gaia/eval/webapp/node_modules/es-object-atoms/tsconfig.json +6 -0
- gaia/eval/webapp/node_modules/escape-html/LICENSE +24 -0
- gaia/eval/webapp/node_modules/escape-html/Readme.md +43 -0
- gaia/eval/webapp/node_modules/escape-html/index.js +78 -0
- gaia/eval/webapp/node_modules/escape-html/package.json +24 -0
- gaia/eval/webapp/node_modules/etag/HISTORY.md +83 -0
- gaia/eval/webapp/node_modules/etag/LICENSE +22 -0
- gaia/eval/webapp/node_modules/etag/README.md +159 -0
- gaia/eval/webapp/node_modules/etag/index.js +131 -0
- gaia/eval/webapp/node_modules/etag/package.json +47 -0
- gaia/eval/webapp/node_modules/express/History.md +3656 -0
- gaia/eval/webapp/node_modules/express/LICENSE +24 -0
- gaia/eval/webapp/node_modules/express/Readme.md +260 -0
- gaia/eval/webapp/node_modules/express/index.js +11 -0
- gaia/eval/webapp/node_modules/express/lib/application.js +661 -0
- gaia/eval/webapp/node_modules/express/lib/express.js +116 -0
- gaia/eval/webapp/node_modules/express/lib/middleware/init.js +43 -0
- gaia/eval/webapp/node_modules/express/lib/middleware/query.js +47 -0
- gaia/eval/webapp/node_modules/express/lib/request.js +525 -0
- gaia/eval/webapp/node_modules/express/lib/response.js +1179 -0
- gaia/eval/webapp/node_modules/express/lib/router/index.js +673 -0
- gaia/eval/webapp/node_modules/express/lib/router/layer.js +181 -0
- gaia/eval/webapp/node_modules/express/lib/router/route.js +230 -0
- gaia/eval/webapp/node_modules/express/lib/utils.js +303 -0
- gaia/eval/webapp/node_modules/express/lib/view.js +182 -0
- gaia/eval/webapp/node_modules/express/package.json +102 -0
- gaia/eval/webapp/node_modules/finalhandler/HISTORY.md +210 -0
- gaia/eval/webapp/node_modules/finalhandler/LICENSE +22 -0
- gaia/eval/webapp/node_modules/finalhandler/README.md +147 -0
- gaia/eval/webapp/node_modules/finalhandler/SECURITY.md +25 -0
- gaia/eval/webapp/node_modules/finalhandler/index.js +341 -0
- gaia/eval/webapp/node_modules/finalhandler/package.json +47 -0
- gaia/eval/webapp/node_modules/forwarded/HISTORY.md +21 -0
- gaia/eval/webapp/node_modules/forwarded/LICENSE +22 -0
- gaia/eval/webapp/node_modules/forwarded/README.md +57 -0
- gaia/eval/webapp/node_modules/forwarded/index.js +90 -0
- gaia/eval/webapp/node_modules/forwarded/package.json +45 -0
- gaia/eval/webapp/node_modules/fresh/HISTORY.md +70 -0
- gaia/eval/webapp/node_modules/fresh/LICENSE +23 -0
- gaia/eval/webapp/node_modules/fresh/README.md +119 -0
- gaia/eval/webapp/node_modules/fresh/index.js +137 -0
- gaia/eval/webapp/node_modules/fresh/package.json +46 -0
- gaia/eval/webapp/node_modules/fs/README.md +9 -0
- gaia/eval/webapp/node_modules/fs/package.json +20 -0
- gaia/eval/webapp/node_modules/function-bind/.eslintrc +21 -0
- gaia/eval/webapp/node_modules/function-bind/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/function-bind/.github/SECURITY.md +3 -0
- gaia/eval/webapp/node_modules/function-bind/.nycrc +13 -0
- gaia/eval/webapp/node_modules/function-bind/CHANGELOG.md +136 -0
- gaia/eval/webapp/node_modules/function-bind/LICENSE +20 -0
- gaia/eval/webapp/node_modules/function-bind/README.md +46 -0
- gaia/eval/webapp/node_modules/function-bind/implementation.js +84 -0
- gaia/eval/webapp/node_modules/function-bind/index.js +5 -0
- gaia/eval/webapp/node_modules/function-bind/package.json +87 -0
- gaia/eval/webapp/node_modules/function-bind/test/.eslintrc +9 -0
- gaia/eval/webapp/node_modules/function-bind/test/index.js +252 -0
- gaia/eval/webapp/node_modules/get-intrinsic/.eslintrc +42 -0
- gaia/eval/webapp/node_modules/get-intrinsic/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/get-intrinsic/.nycrc +9 -0
- gaia/eval/webapp/node_modules/get-intrinsic/CHANGELOG.md +186 -0
- gaia/eval/webapp/node_modules/get-intrinsic/LICENSE +21 -0
- gaia/eval/webapp/node_modules/get-intrinsic/README.md +71 -0
- gaia/eval/webapp/node_modules/get-intrinsic/index.js +378 -0
- gaia/eval/webapp/node_modules/get-intrinsic/package.json +97 -0
- gaia/eval/webapp/node_modules/get-intrinsic/test/GetIntrinsic.js +274 -0
- gaia/eval/webapp/node_modules/get-proto/.eslintrc +10 -0
- gaia/eval/webapp/node_modules/get-proto/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/get-proto/.nycrc +9 -0
- gaia/eval/webapp/node_modules/get-proto/CHANGELOG.md +21 -0
- gaia/eval/webapp/node_modules/get-proto/LICENSE +21 -0
- gaia/eval/webapp/node_modules/get-proto/Object.getPrototypeOf.d.ts +5 -0
- gaia/eval/webapp/node_modules/get-proto/Object.getPrototypeOf.js +6 -0
- gaia/eval/webapp/node_modules/get-proto/README.md +50 -0
- gaia/eval/webapp/node_modules/get-proto/Reflect.getPrototypeOf.d.ts +3 -0
- gaia/eval/webapp/node_modules/get-proto/Reflect.getPrototypeOf.js +4 -0
- gaia/eval/webapp/node_modules/get-proto/index.d.ts +5 -0
- gaia/eval/webapp/node_modules/get-proto/index.js +27 -0
- gaia/eval/webapp/node_modules/get-proto/package.json +81 -0
- gaia/eval/webapp/node_modules/get-proto/test/index.js +68 -0
- gaia/eval/webapp/node_modules/get-proto/tsconfig.json +9 -0
- gaia/eval/webapp/node_modules/gopd/.eslintrc +16 -0
- gaia/eval/webapp/node_modules/gopd/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/gopd/CHANGELOG.md +45 -0
- gaia/eval/webapp/node_modules/gopd/LICENSE +21 -0
- gaia/eval/webapp/node_modules/gopd/README.md +40 -0
- gaia/eval/webapp/node_modules/gopd/gOPD.d.ts +1 -0
- gaia/eval/webapp/node_modules/gopd/gOPD.js +4 -0
- gaia/eval/webapp/node_modules/gopd/index.d.ts +5 -0
- gaia/eval/webapp/node_modules/gopd/index.js +15 -0
- gaia/eval/webapp/node_modules/gopd/package.json +77 -0
- gaia/eval/webapp/node_modules/gopd/test/index.js +36 -0
- gaia/eval/webapp/node_modules/gopd/tsconfig.json +9 -0
- gaia/eval/webapp/node_modules/has-symbols/.eslintrc +11 -0
- gaia/eval/webapp/node_modules/has-symbols/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/has-symbols/.nycrc +9 -0
- gaia/eval/webapp/node_modules/has-symbols/CHANGELOG.md +91 -0
- gaia/eval/webapp/node_modules/has-symbols/LICENSE +21 -0
- gaia/eval/webapp/node_modules/has-symbols/README.md +46 -0
- gaia/eval/webapp/node_modules/has-symbols/index.d.ts +3 -0
- gaia/eval/webapp/node_modules/has-symbols/index.js +14 -0
- gaia/eval/webapp/node_modules/has-symbols/package.json +111 -0
- gaia/eval/webapp/node_modules/has-symbols/shams.d.ts +3 -0
- gaia/eval/webapp/node_modules/has-symbols/shams.js +45 -0
- gaia/eval/webapp/node_modules/has-symbols/test/index.js +22 -0
- gaia/eval/webapp/node_modules/has-symbols/test/shams/core-js.js +29 -0
- gaia/eval/webapp/node_modules/has-symbols/test/shams/get-own-property-symbols.js +29 -0
- gaia/eval/webapp/node_modules/has-symbols/test/tests.js +58 -0
- gaia/eval/webapp/node_modules/has-symbols/tsconfig.json +10 -0
- gaia/eval/webapp/node_modules/hasown/.eslintrc +5 -0
- gaia/eval/webapp/node_modules/hasown/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/hasown/.nycrc +13 -0
- gaia/eval/webapp/node_modules/hasown/CHANGELOG.md +40 -0
- gaia/eval/webapp/node_modules/hasown/LICENSE +21 -0
- gaia/eval/webapp/node_modules/hasown/README.md +40 -0
- gaia/eval/webapp/node_modules/hasown/index.d.ts +3 -0
- gaia/eval/webapp/node_modules/hasown/index.js +8 -0
- gaia/eval/webapp/node_modules/hasown/package.json +92 -0
- gaia/eval/webapp/node_modules/hasown/tsconfig.json +6 -0
- gaia/eval/webapp/node_modules/http-errors/HISTORY.md +180 -0
- gaia/eval/webapp/node_modules/http-errors/LICENSE +23 -0
- gaia/eval/webapp/node_modules/http-errors/README.md +169 -0
- gaia/eval/webapp/node_modules/http-errors/index.js +289 -0
- gaia/eval/webapp/node_modules/http-errors/package.json +50 -0
- gaia/eval/webapp/node_modules/iconv-lite/Changelog.md +162 -0
- gaia/eval/webapp/node_modules/iconv-lite/LICENSE +21 -0
- gaia/eval/webapp/node_modules/iconv-lite/README.md +156 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/dbcs-codec.js +555 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/dbcs-data.js +176 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/index.js +22 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/internal.js +188 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/sbcs-codec.js +72 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/sbcs-data-generated.js +451 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/sbcs-data.js +174 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/tables/big5-added.json +122 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/tables/cp936.json +264 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/tables/cp949.json +273 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/tables/cp950.json +177 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/tables/eucjp.json +182 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/tables/gb18030-ranges.json +1 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/tables/gbk-added.json +55 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/tables/shiftjis.json +125 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/utf16.js +177 -0
- gaia/eval/webapp/node_modules/iconv-lite/encodings/utf7.js +290 -0
- gaia/eval/webapp/node_modules/iconv-lite/lib/bom-handling.js +52 -0
- gaia/eval/webapp/node_modules/iconv-lite/lib/extend-node.js +217 -0
- gaia/eval/webapp/node_modules/iconv-lite/lib/index.d.ts +24 -0
- gaia/eval/webapp/node_modules/iconv-lite/lib/index.js +153 -0
- gaia/eval/webapp/node_modules/iconv-lite/lib/streams.js +121 -0
- gaia/eval/webapp/node_modules/iconv-lite/package.json +46 -0
- gaia/eval/webapp/node_modules/inherits/LICENSE +16 -0
- gaia/eval/webapp/node_modules/inherits/README.md +42 -0
- gaia/eval/webapp/node_modules/inherits/inherits.js +9 -0
- gaia/eval/webapp/node_modules/inherits/inherits_browser.js +27 -0
- gaia/eval/webapp/node_modules/inherits/package.json +29 -0
- gaia/eval/webapp/node_modules/ipaddr.js/LICENSE +19 -0
- gaia/eval/webapp/node_modules/ipaddr.js/README.md +233 -0
- gaia/eval/webapp/node_modules/ipaddr.js/ipaddr.min.js +1 -0
- gaia/eval/webapp/node_modules/ipaddr.js/lib/ipaddr.js +673 -0
- gaia/eval/webapp/node_modules/ipaddr.js/lib/ipaddr.js.d.ts +68 -0
- gaia/eval/webapp/node_modules/ipaddr.js/package.json +35 -0
- gaia/eval/webapp/node_modules/math-intrinsics/.eslintrc +16 -0
- gaia/eval/webapp/node_modules/math-intrinsics/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/math-intrinsics/CHANGELOG.md +24 -0
- gaia/eval/webapp/node_modules/math-intrinsics/LICENSE +21 -0
- gaia/eval/webapp/node_modules/math-intrinsics/README.md +50 -0
- gaia/eval/webapp/node_modules/math-intrinsics/abs.d.ts +1 -0
- gaia/eval/webapp/node_modules/math-intrinsics/abs.js +4 -0
- gaia/eval/webapp/node_modules/math-intrinsics/constants/maxArrayLength.d.ts +3 -0
- gaia/eval/webapp/node_modules/math-intrinsics/constants/maxArrayLength.js +4 -0
- gaia/eval/webapp/node_modules/math-intrinsics/constants/maxSafeInteger.d.ts +3 -0
- gaia/eval/webapp/node_modules/math-intrinsics/constants/maxSafeInteger.js +5 -0
- gaia/eval/webapp/node_modules/math-intrinsics/constants/maxValue.d.ts +3 -0
- gaia/eval/webapp/node_modules/math-intrinsics/constants/maxValue.js +5 -0
- gaia/eval/webapp/node_modules/math-intrinsics/floor.d.ts +1 -0
- gaia/eval/webapp/node_modules/math-intrinsics/floor.js +4 -0
- gaia/eval/webapp/node_modules/math-intrinsics/isFinite.d.ts +3 -0
- gaia/eval/webapp/node_modules/math-intrinsics/isFinite.js +12 -0
- gaia/eval/webapp/node_modules/math-intrinsics/isInteger.d.ts +3 -0
- gaia/eval/webapp/node_modules/math-intrinsics/isInteger.js +16 -0
- gaia/eval/webapp/node_modules/math-intrinsics/isNaN.d.ts +1 -0
- gaia/eval/webapp/node_modules/math-intrinsics/isNaN.js +6 -0
- gaia/eval/webapp/node_modules/math-intrinsics/isNegativeZero.d.ts +3 -0
- gaia/eval/webapp/node_modules/math-intrinsics/isNegativeZero.js +6 -0
- gaia/eval/webapp/node_modules/math-intrinsics/max.d.ts +1 -0
- gaia/eval/webapp/node_modules/math-intrinsics/max.js +4 -0
- gaia/eval/webapp/node_modules/math-intrinsics/min.d.ts +1 -0
- gaia/eval/webapp/node_modules/math-intrinsics/min.js +4 -0
- gaia/eval/webapp/node_modules/math-intrinsics/mod.d.ts +3 -0
- gaia/eval/webapp/node_modules/math-intrinsics/mod.js +9 -0
- gaia/eval/webapp/node_modules/math-intrinsics/package.json +86 -0
- gaia/eval/webapp/node_modules/math-intrinsics/pow.d.ts +1 -0
- gaia/eval/webapp/node_modules/math-intrinsics/pow.js +4 -0
- gaia/eval/webapp/node_modules/math-intrinsics/round.d.ts +1 -0
- gaia/eval/webapp/node_modules/math-intrinsics/round.js +4 -0
- gaia/eval/webapp/node_modules/math-intrinsics/sign.d.ts +3 -0
- gaia/eval/webapp/node_modules/math-intrinsics/sign.js +11 -0
- gaia/eval/webapp/node_modules/math-intrinsics/test/index.js +192 -0
- gaia/eval/webapp/node_modules/math-intrinsics/tsconfig.json +3 -0
- gaia/eval/webapp/node_modules/media-typer/HISTORY.md +22 -0
- gaia/eval/webapp/node_modules/media-typer/LICENSE +22 -0
- gaia/eval/webapp/node_modules/media-typer/README.md +81 -0
- gaia/eval/webapp/node_modules/media-typer/index.js +270 -0
- gaia/eval/webapp/node_modules/media-typer/package.json +26 -0
- gaia/eval/webapp/node_modules/merge-descriptors/HISTORY.md +21 -0
- gaia/eval/webapp/node_modules/merge-descriptors/LICENSE +23 -0
- gaia/eval/webapp/node_modules/merge-descriptors/README.md +49 -0
- gaia/eval/webapp/node_modules/merge-descriptors/index.js +60 -0
- gaia/eval/webapp/node_modules/merge-descriptors/package.json +39 -0
- gaia/eval/webapp/node_modules/methods/HISTORY.md +29 -0
- gaia/eval/webapp/node_modules/methods/LICENSE +24 -0
- gaia/eval/webapp/node_modules/methods/README.md +51 -0
- gaia/eval/webapp/node_modules/methods/index.js +69 -0
- gaia/eval/webapp/node_modules/methods/package.json +36 -0
- gaia/eval/webapp/node_modules/mime/.npmignore +0 -0
- gaia/eval/webapp/node_modules/mime/CHANGELOG.md +164 -0
- gaia/eval/webapp/node_modules/mime/LICENSE +21 -0
- gaia/eval/webapp/node_modules/mime/README.md +90 -0
- gaia/eval/webapp/node_modules/mime/cli.js +8 -0
- gaia/eval/webapp/node_modules/mime/mime.js +108 -0
- gaia/eval/webapp/node_modules/mime/package.json +44 -0
- gaia/eval/webapp/node_modules/mime/src/build.js +53 -0
- gaia/eval/webapp/node_modules/mime/src/test.js +60 -0
- gaia/eval/webapp/node_modules/mime/types.json +1 -0
- gaia/eval/webapp/node_modules/mime-db/HISTORY.md +507 -0
- gaia/eval/webapp/node_modules/mime-db/LICENSE +23 -0
- gaia/eval/webapp/node_modules/mime-db/README.md +100 -0
- gaia/eval/webapp/node_modules/mime-db/db.json +8519 -0
- gaia/eval/webapp/node_modules/mime-db/index.js +12 -0
- gaia/eval/webapp/node_modules/mime-db/package.json +60 -0
- gaia/eval/webapp/node_modules/mime-types/HISTORY.md +397 -0
- gaia/eval/webapp/node_modules/mime-types/LICENSE +23 -0
- gaia/eval/webapp/node_modules/mime-types/README.md +113 -0
- gaia/eval/webapp/node_modules/mime-types/index.js +188 -0
- gaia/eval/webapp/node_modules/mime-types/package.json +44 -0
- gaia/eval/webapp/node_modules/ms/index.js +152 -0
- gaia/eval/webapp/node_modules/ms/license.md +21 -0
- gaia/eval/webapp/node_modules/ms/package.json +37 -0
- gaia/eval/webapp/node_modules/ms/readme.md +51 -0
- gaia/eval/webapp/node_modules/negotiator/HISTORY.md +108 -0
- gaia/eval/webapp/node_modules/negotiator/LICENSE +24 -0
- gaia/eval/webapp/node_modules/negotiator/README.md +203 -0
- gaia/eval/webapp/node_modules/negotiator/index.js +82 -0
- gaia/eval/webapp/node_modules/negotiator/lib/charset.js +169 -0
- gaia/eval/webapp/node_modules/negotiator/lib/encoding.js +184 -0
- gaia/eval/webapp/node_modules/negotiator/lib/language.js +179 -0
- gaia/eval/webapp/node_modules/negotiator/lib/mediaType.js +294 -0
- gaia/eval/webapp/node_modules/negotiator/package.json +42 -0
- gaia/eval/webapp/node_modules/object-inspect/.eslintrc +53 -0
- gaia/eval/webapp/node_modules/object-inspect/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/object-inspect/.nycrc +13 -0
- gaia/eval/webapp/node_modules/object-inspect/CHANGELOG.md +424 -0
- gaia/eval/webapp/node_modules/object-inspect/LICENSE +21 -0
- gaia/eval/webapp/node_modules/object-inspect/example/all.js +23 -0
- gaia/eval/webapp/node_modules/object-inspect/example/circular.js +6 -0
- gaia/eval/webapp/node_modules/object-inspect/example/fn.js +5 -0
- gaia/eval/webapp/node_modules/object-inspect/example/inspect.js +10 -0
- gaia/eval/webapp/node_modules/object-inspect/index.js +544 -0
- gaia/eval/webapp/node_modules/object-inspect/package-support.json +20 -0
- gaia/eval/webapp/node_modules/object-inspect/package.json +105 -0
- gaia/eval/webapp/node_modules/object-inspect/readme.markdown +84 -0
- gaia/eval/webapp/node_modules/object-inspect/test/bigint.js +58 -0
- gaia/eval/webapp/node_modules/object-inspect/test/browser/dom.js +15 -0
- gaia/eval/webapp/node_modules/object-inspect/test/circular.js +16 -0
- gaia/eval/webapp/node_modules/object-inspect/test/deep.js +12 -0
- gaia/eval/webapp/node_modules/object-inspect/test/element.js +53 -0
- gaia/eval/webapp/node_modules/object-inspect/test/err.js +48 -0
- gaia/eval/webapp/node_modules/object-inspect/test/fakes.js +29 -0
- gaia/eval/webapp/node_modules/object-inspect/test/fn.js +76 -0
- gaia/eval/webapp/node_modules/object-inspect/test/global.js +17 -0
- gaia/eval/webapp/node_modules/object-inspect/test/has.js +15 -0
- gaia/eval/webapp/node_modules/object-inspect/test/holes.js +15 -0
- gaia/eval/webapp/node_modules/object-inspect/test/indent-option.js +271 -0
- gaia/eval/webapp/node_modules/object-inspect/test/inspect.js +139 -0
- gaia/eval/webapp/node_modules/object-inspect/test/lowbyte.js +12 -0
- gaia/eval/webapp/node_modules/object-inspect/test/number.js +58 -0
- gaia/eval/webapp/node_modules/object-inspect/test/quoteStyle.js +26 -0
- gaia/eval/webapp/node_modules/object-inspect/test/toStringTag.js +40 -0
- gaia/eval/webapp/node_modules/object-inspect/test/undef.js +12 -0
- gaia/eval/webapp/node_modules/object-inspect/test/values.js +261 -0
- gaia/eval/webapp/node_modules/object-inspect/test-core-js.js +26 -0
- gaia/eval/webapp/node_modules/object-inspect/util.inspect.js +1 -0
- gaia/eval/webapp/node_modules/on-finished/HISTORY.md +98 -0
- gaia/eval/webapp/node_modules/on-finished/LICENSE +23 -0
- gaia/eval/webapp/node_modules/on-finished/README.md +162 -0
- gaia/eval/webapp/node_modules/on-finished/index.js +234 -0
- gaia/eval/webapp/node_modules/on-finished/package.json +39 -0
- gaia/eval/webapp/node_modules/parseurl/HISTORY.md +58 -0
- gaia/eval/webapp/node_modules/parseurl/LICENSE +24 -0
- gaia/eval/webapp/node_modules/parseurl/README.md +133 -0
- gaia/eval/webapp/node_modules/parseurl/index.js +158 -0
- gaia/eval/webapp/node_modules/parseurl/package.json +40 -0
- gaia/eval/webapp/node_modules/path/.npmignore +1 -0
- gaia/eval/webapp/node_modules/path/LICENSE +18 -0
- gaia/eval/webapp/node_modules/path/README.md +15 -0
- gaia/eval/webapp/node_modules/path/package.json +24 -0
- gaia/eval/webapp/node_modules/path/path.js +628 -0
- gaia/eval/webapp/node_modules/path-to-regexp/LICENSE +21 -0
- gaia/eval/webapp/node_modules/path-to-regexp/Readme.md +35 -0
- gaia/eval/webapp/node_modules/path-to-regexp/index.js +156 -0
- gaia/eval/webapp/node_modules/path-to-regexp/package.json +30 -0
- gaia/eval/webapp/node_modules/process/.eslintrc +21 -0
- gaia/eval/webapp/node_modules/process/LICENSE +22 -0
- gaia/eval/webapp/node_modules/process/README.md +26 -0
- gaia/eval/webapp/node_modules/process/browser.js +184 -0
- gaia/eval/webapp/node_modules/process/index.js +2 -0
- gaia/eval/webapp/node_modules/process/package.json +27 -0
- gaia/eval/webapp/node_modules/process/test.js +199 -0
- gaia/eval/webapp/node_modules/proxy-addr/HISTORY.md +161 -0
- gaia/eval/webapp/node_modules/proxy-addr/LICENSE +22 -0
- gaia/eval/webapp/node_modules/proxy-addr/README.md +139 -0
- gaia/eval/webapp/node_modules/proxy-addr/index.js +327 -0
- gaia/eval/webapp/node_modules/proxy-addr/package.json +47 -0
- gaia/eval/webapp/node_modules/qs/.editorconfig +46 -0
- gaia/eval/webapp/node_modules/qs/.eslintrc +38 -0
- gaia/eval/webapp/node_modules/qs/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/qs/.nycrc +13 -0
- gaia/eval/webapp/node_modules/qs/CHANGELOG.md +600 -0
- gaia/eval/webapp/node_modules/qs/LICENSE.md +29 -0
- gaia/eval/webapp/node_modules/qs/README.md +709 -0
- gaia/eval/webapp/node_modules/qs/dist/qs.js +90 -0
- gaia/eval/webapp/node_modules/qs/lib/formats.js +23 -0
- gaia/eval/webapp/node_modules/qs/lib/index.js +11 -0
- gaia/eval/webapp/node_modules/qs/lib/parse.js +296 -0
- gaia/eval/webapp/node_modules/qs/lib/stringify.js +351 -0
- gaia/eval/webapp/node_modules/qs/lib/utils.js +265 -0
- gaia/eval/webapp/node_modules/qs/package.json +91 -0
- gaia/eval/webapp/node_modules/qs/test/empty-keys-cases.js +267 -0
- gaia/eval/webapp/node_modules/qs/test/parse.js +1170 -0
- gaia/eval/webapp/node_modules/qs/test/stringify.js +1298 -0
- gaia/eval/webapp/node_modules/qs/test/utils.js +136 -0
- gaia/eval/webapp/node_modules/range-parser/HISTORY.md +56 -0
- gaia/eval/webapp/node_modules/range-parser/LICENSE +23 -0
- gaia/eval/webapp/node_modules/range-parser/README.md +84 -0
- gaia/eval/webapp/node_modules/range-parser/index.js +162 -0
- gaia/eval/webapp/node_modules/range-parser/package.json +44 -0
- gaia/eval/webapp/node_modules/raw-body/HISTORY.md +308 -0
- gaia/eval/webapp/node_modules/raw-body/LICENSE +22 -0
- gaia/eval/webapp/node_modules/raw-body/README.md +223 -0
- gaia/eval/webapp/node_modules/raw-body/SECURITY.md +24 -0
- gaia/eval/webapp/node_modules/raw-body/index.d.ts +87 -0
- gaia/eval/webapp/node_modules/raw-body/index.js +336 -0
- gaia/eval/webapp/node_modules/raw-body/package.json +49 -0
- gaia/eval/webapp/node_modules/safe-buffer/LICENSE +21 -0
- gaia/eval/webapp/node_modules/safe-buffer/README.md +584 -0
- gaia/eval/webapp/node_modules/safe-buffer/index.d.ts +187 -0
- gaia/eval/webapp/node_modules/safe-buffer/index.js +65 -0
- gaia/eval/webapp/node_modules/safe-buffer/package.json +51 -0
- gaia/eval/webapp/node_modules/safer-buffer/LICENSE +21 -0
- gaia/eval/webapp/node_modules/safer-buffer/Porting-Buffer.md +268 -0
- gaia/eval/webapp/node_modules/safer-buffer/Readme.md +156 -0
- gaia/eval/webapp/node_modules/safer-buffer/dangerous.js +58 -0
- gaia/eval/webapp/node_modules/safer-buffer/package.json +34 -0
- gaia/eval/webapp/node_modules/safer-buffer/safer.js +77 -0
- gaia/eval/webapp/node_modules/safer-buffer/tests.js +406 -0
- gaia/eval/webapp/node_modules/send/HISTORY.md +526 -0
- gaia/eval/webapp/node_modules/send/LICENSE +23 -0
- gaia/eval/webapp/node_modules/send/README.md +327 -0
- gaia/eval/webapp/node_modules/send/SECURITY.md +24 -0
- gaia/eval/webapp/node_modules/send/index.js +1142 -0
- gaia/eval/webapp/node_modules/send/node_modules/encodeurl/HISTORY.md +14 -0
- gaia/eval/webapp/node_modules/send/node_modules/encodeurl/LICENSE +22 -0
- gaia/eval/webapp/node_modules/send/node_modules/encodeurl/README.md +128 -0
- gaia/eval/webapp/node_modules/send/node_modules/encodeurl/index.js +60 -0
- gaia/eval/webapp/node_modules/send/node_modules/encodeurl/package.json +40 -0
- gaia/eval/webapp/node_modules/send/node_modules/ms/index.js +162 -0
- gaia/eval/webapp/node_modules/send/node_modules/ms/license.md +21 -0
- gaia/eval/webapp/node_modules/send/node_modules/ms/package.json +38 -0
- gaia/eval/webapp/node_modules/send/node_modules/ms/readme.md +59 -0
- gaia/eval/webapp/node_modules/send/package.json +62 -0
- gaia/eval/webapp/node_modules/serve-static/HISTORY.md +487 -0
- gaia/eval/webapp/node_modules/serve-static/LICENSE +25 -0
- gaia/eval/webapp/node_modules/serve-static/README.md +257 -0
- gaia/eval/webapp/node_modules/serve-static/index.js +209 -0
- gaia/eval/webapp/node_modules/serve-static/package.json +42 -0
- gaia/eval/webapp/node_modules/setprototypeof/LICENSE +13 -0
- gaia/eval/webapp/node_modules/setprototypeof/README.md +31 -0
- gaia/eval/webapp/node_modules/setprototypeof/index.d.ts +2 -0
- gaia/eval/webapp/node_modules/setprototypeof/index.js +17 -0
- gaia/eval/webapp/node_modules/setprototypeof/package.json +38 -0
- gaia/eval/webapp/node_modules/setprototypeof/test/index.js +24 -0
- gaia/eval/webapp/node_modules/side-channel/.editorconfig +9 -0
- gaia/eval/webapp/node_modules/side-channel/.eslintrc +12 -0
- gaia/eval/webapp/node_modules/side-channel/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/side-channel/.nycrc +13 -0
- gaia/eval/webapp/node_modules/side-channel/CHANGELOG.md +110 -0
- gaia/eval/webapp/node_modules/side-channel/LICENSE +21 -0
- gaia/eval/webapp/node_modules/side-channel/README.md +61 -0
- gaia/eval/webapp/node_modules/side-channel/index.d.ts +14 -0
- gaia/eval/webapp/node_modules/side-channel/index.js +43 -0
- gaia/eval/webapp/node_modules/side-channel/package.json +85 -0
- gaia/eval/webapp/node_modules/side-channel/test/index.js +104 -0
- gaia/eval/webapp/node_modules/side-channel/tsconfig.json +9 -0
- gaia/eval/webapp/node_modules/side-channel-list/.editorconfig +9 -0
- gaia/eval/webapp/node_modules/side-channel-list/.eslintrc +11 -0
- gaia/eval/webapp/node_modules/side-channel-list/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/side-channel-list/.nycrc +13 -0
- gaia/eval/webapp/node_modules/side-channel-list/CHANGELOG.md +15 -0
- gaia/eval/webapp/node_modules/side-channel-list/LICENSE +21 -0
- gaia/eval/webapp/node_modules/side-channel-list/README.md +62 -0
- gaia/eval/webapp/node_modules/side-channel-list/index.d.ts +13 -0
- gaia/eval/webapp/node_modules/side-channel-list/index.js +113 -0
- gaia/eval/webapp/node_modules/side-channel-list/list.d.ts +14 -0
- gaia/eval/webapp/node_modules/side-channel-list/package.json +77 -0
- gaia/eval/webapp/node_modules/side-channel-list/test/index.js +104 -0
- gaia/eval/webapp/node_modules/side-channel-list/tsconfig.json +9 -0
- gaia/eval/webapp/node_modules/side-channel-map/.editorconfig +9 -0
- gaia/eval/webapp/node_modules/side-channel-map/.eslintrc +11 -0
- gaia/eval/webapp/node_modules/side-channel-map/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/side-channel-map/.nycrc +13 -0
- gaia/eval/webapp/node_modules/side-channel-map/CHANGELOG.md +22 -0
- gaia/eval/webapp/node_modules/side-channel-map/LICENSE +21 -0
- gaia/eval/webapp/node_modules/side-channel-map/README.md +62 -0
- gaia/eval/webapp/node_modules/side-channel-map/index.d.ts +15 -0
- gaia/eval/webapp/node_modules/side-channel-map/index.js +68 -0
- gaia/eval/webapp/node_modules/side-channel-map/package.json +80 -0
- gaia/eval/webapp/node_modules/side-channel-map/test/index.js +114 -0
- gaia/eval/webapp/node_modules/side-channel-map/tsconfig.json +9 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/.editorconfig +9 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/.eslintrc +12 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/.github/FUNDING.yml +12 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/.nycrc +13 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/CHANGELOG.md +28 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/LICENSE +21 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/README.md +62 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/index.d.ts +15 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/index.js +84 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/package.json +87 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/test/index.js +114 -0
- gaia/eval/webapp/node_modules/side-channel-weakmap/tsconfig.json +9 -0
- gaia/eval/webapp/node_modules/statuses/HISTORY.md +82 -0
- gaia/eval/webapp/node_modules/statuses/LICENSE +23 -0
- gaia/eval/webapp/node_modules/statuses/README.md +136 -0
- gaia/eval/webapp/node_modules/statuses/codes.json +65 -0
- gaia/eval/webapp/node_modules/statuses/index.js +146 -0
- gaia/eval/webapp/node_modules/statuses/package.json +49 -0
- gaia/eval/webapp/node_modules/toidentifier/HISTORY.md +9 -0
- gaia/eval/webapp/node_modules/toidentifier/LICENSE +21 -0
- gaia/eval/webapp/node_modules/toidentifier/README.md +61 -0
- gaia/eval/webapp/node_modules/toidentifier/index.js +32 -0
- gaia/eval/webapp/node_modules/toidentifier/package.json +38 -0
- gaia/eval/webapp/node_modules/type-is/HISTORY.md +259 -0
- gaia/eval/webapp/node_modules/type-is/LICENSE +23 -0
- gaia/eval/webapp/node_modules/type-is/README.md +170 -0
- gaia/eval/webapp/node_modules/type-is/index.js +266 -0
- gaia/eval/webapp/node_modules/type-is/package.json +45 -0
- gaia/eval/webapp/node_modules/unpipe/HISTORY.md +4 -0
- gaia/eval/webapp/node_modules/unpipe/LICENSE +22 -0
- gaia/eval/webapp/node_modules/unpipe/README.md +43 -0
- gaia/eval/webapp/node_modules/unpipe/index.js +69 -0
- gaia/eval/webapp/node_modules/unpipe/package.json +27 -0
- gaia/eval/webapp/node_modules/util/LICENSE +18 -0
- gaia/eval/webapp/node_modules/util/README.md +15 -0
- gaia/eval/webapp/node_modules/util/node_modules/inherits/LICENSE +16 -0
- gaia/eval/webapp/node_modules/util/node_modules/inherits/README.md +42 -0
- gaia/eval/webapp/node_modules/util/node_modules/inherits/inherits.js +7 -0
- gaia/eval/webapp/node_modules/util/node_modules/inherits/inherits_browser.js +23 -0
- gaia/eval/webapp/node_modules/util/node_modules/inherits/package.json +29 -0
- gaia/eval/webapp/node_modules/util/package.json +35 -0
- gaia/eval/webapp/node_modules/util/support/isBuffer.js +3 -0
- gaia/eval/webapp/node_modules/util/support/isBufferBrowser.js +6 -0
- gaia/eval/webapp/node_modules/util/util.js +586 -0
- gaia/eval/webapp/node_modules/utils-merge/.npmignore +9 -0
- gaia/eval/webapp/node_modules/utils-merge/LICENSE +20 -0
- gaia/eval/webapp/node_modules/utils-merge/README.md +34 -0
- gaia/eval/webapp/node_modules/utils-merge/index.js +23 -0
- gaia/eval/webapp/node_modules/utils-merge/package.json +40 -0
- gaia/eval/webapp/node_modules/vary/HISTORY.md +39 -0
- gaia/eval/webapp/node_modules/vary/LICENSE +22 -0
- gaia/eval/webapp/node_modules/vary/README.md +101 -0
- gaia/eval/webapp/node_modules/vary/index.js +149 -0
- gaia/eval/webapp/node_modules/vary/package.json +43 -0
- gaia/eval/webapp/package-lock.json +875 -0
- gaia/eval/webapp/package.json +21 -0
- gaia/eval/webapp/public/app.js +3403 -0
- gaia/eval/webapp/public/index.html +88 -0
- gaia/eval/webapp/public/styles.css +3661 -0
- gaia/eval/webapp/server.js +416 -0
- gaia/eval/webapp/test-setup.js +73 -0
- gaia/llm/__init__.py +2 -0
- gaia/llm/lemonade_client.py +3083 -0
- gaia/llm/lemonade_manager.py +269 -0
- gaia/llm/llm_client.py +729 -0
- gaia/llm/vlm_client.py +307 -0
- gaia/logger.py +189 -0
- gaia/mcp/agent_mcp_server.py +245 -0
- gaia/mcp/blender_mcp_client.py +138 -0
- gaia/mcp/blender_mcp_server.py +648 -0
- gaia/mcp/context7_cache.py +332 -0
- gaia/mcp/external_services.py +518 -0
- gaia/mcp/mcp_bridge.py +550 -0
- gaia/mcp/servers/__init__.py +6 -0
- gaia/mcp/servers/docker_mcp.py +83 -0
- gaia/rag/__init__.py +10 -0
- gaia/rag/app.py +293 -0
- gaia/rag/demo.py +304 -0
- gaia/rag/pdf_utils.py +235 -0
- gaia/rag/sdk.py +2194 -0
- gaia/security.py +163 -0
- gaia/talk/app.py +289 -0
- gaia/talk/sdk.py +538 -0
- gaia/util.py +46 -0
- gaia/version.py +100 -0
|
@@ -0,0 +1,1925 @@
|
|
|
1
|
+
# Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Code generation patterns for web applications.
|
|
4
|
+
|
|
5
|
+
This module contains reusable code patterns for generating functional
|
|
6
|
+
web application code. Patterns are framework-agnostic where possible,
|
|
7
|
+
with framework-specific variants where needed.
|
|
8
|
+
|
|
9
|
+
Patterns are stored as template strings that can be formatted with
|
|
10
|
+
resource-specific context (model names, fields, etc.).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# ========== App-Wide Layout and Styling ==========
|
|
14
|
+
|
|
15
|
+
APP_LAYOUT = """import type {{ Metadata }} from "next";
|
|
16
|
+
import {{ Inter }} from "next/font/google";
|
|
17
|
+
import "./globals.css";
|
|
18
|
+
|
|
19
|
+
const inter = Inter({{ subsets: ["latin"] }});
|
|
20
|
+
|
|
21
|
+
export const metadata: Metadata = {{
|
|
22
|
+
title: "{app_title}",
|
|
23
|
+
description: "{app_description}",
|
|
24
|
+
}};
|
|
25
|
+
|
|
26
|
+
export default function RootLayout({{
|
|
27
|
+
children,
|
|
28
|
+
}}: Readonly<{{
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
}}>) {{
|
|
31
|
+
return (
|
|
32
|
+
<html lang="en" className="dark">
|
|
33
|
+
<body className={{`${{inter.className}} antialiased`}}>
|
|
34
|
+
<div className="fixed inset-0 bg-gradient-to-br from-slate-900 via-purple-900/20 to-slate-900 -z-10" />
|
|
35
|
+
<div className="fixed inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-indigo-900/20 via-transparent to-transparent -z-10" />
|
|
36
|
+
{{children}}
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
);
|
|
40
|
+
}}"""
|
|
41
|
+
|
|
42
|
+
APP_GLOBALS_CSS = """@tailwind base;
|
|
43
|
+
@tailwind components;
|
|
44
|
+
@tailwind utilities;
|
|
45
|
+
|
|
46
|
+
:root {{
|
|
47
|
+
--background: #0f0f1a;
|
|
48
|
+
--foreground: #e2e8f0;
|
|
49
|
+
}}
|
|
50
|
+
|
|
51
|
+
body {{
|
|
52
|
+
color: var(--foreground);
|
|
53
|
+
background: var(--background);
|
|
54
|
+
min-height: 100vh;
|
|
55
|
+
}}
|
|
56
|
+
|
|
57
|
+
/* Custom scrollbar */
|
|
58
|
+
::-webkit-scrollbar {{
|
|
59
|
+
width: 8px;
|
|
60
|
+
}}
|
|
61
|
+
|
|
62
|
+
::-webkit-scrollbar-track {{
|
|
63
|
+
background: rgba(30, 41, 59, 0.3);
|
|
64
|
+
}}
|
|
65
|
+
|
|
66
|
+
::-webkit-scrollbar-thumb {{
|
|
67
|
+
background: rgba(99, 102, 241, 0.5);
|
|
68
|
+
border-radius: 4px;
|
|
69
|
+
}}
|
|
70
|
+
|
|
71
|
+
::-webkit-scrollbar-thumb:hover {{
|
|
72
|
+
background: rgba(99, 102, 241, 0.7);
|
|
73
|
+
}}
|
|
74
|
+
|
|
75
|
+
@layer base {{
|
|
76
|
+
/* Dark mode color scheme */
|
|
77
|
+
html {{
|
|
78
|
+
color-scheme: dark;
|
|
79
|
+
}}
|
|
80
|
+
|
|
81
|
+
/* Better form defaults for dark theme */
|
|
82
|
+
input[type="checkbox"] {{
|
|
83
|
+
color-scheme: dark;
|
|
84
|
+
}}
|
|
85
|
+
}}
|
|
86
|
+
|
|
87
|
+
@layer components {{
|
|
88
|
+
/* Glass card effect */
|
|
89
|
+
.glass-card {{
|
|
90
|
+
@apply bg-slate-800/50 backdrop-blur-xl border border-slate-700/50 rounded-2xl shadow-2xl;
|
|
91
|
+
}}
|
|
92
|
+
|
|
93
|
+
/* Button variants */
|
|
94
|
+
.btn-primary {{
|
|
95
|
+
@apply bg-gradient-to-r from-indigo-500 to-purple-500 text-white px-6 py-3 rounded-xl font-medium
|
|
96
|
+
hover:from-indigo-600 hover:to-purple-600 transition-all duration-300
|
|
97
|
+
hover:shadow-lg hover:shadow-indigo-500/25 active:scale-95 disabled:opacity-50;
|
|
98
|
+
}}
|
|
99
|
+
|
|
100
|
+
.btn-secondary {{
|
|
101
|
+
@apply bg-slate-700/50 text-slate-200 px-6 py-3 rounded-xl font-medium border border-slate-600/50
|
|
102
|
+
hover:bg-slate-700 transition-all duration-300 active:scale-95;
|
|
103
|
+
}}
|
|
104
|
+
|
|
105
|
+
.btn-danger {{
|
|
106
|
+
@apply bg-gradient-to-r from-red-500 to-rose-500 text-white px-6 py-3 rounded-xl font-medium
|
|
107
|
+
hover:from-red-600 hover:to-rose-600 transition-all duration-300
|
|
108
|
+
hover:shadow-lg hover:shadow-red-500/25 active:scale-95 disabled:opacity-50;
|
|
109
|
+
}}
|
|
110
|
+
|
|
111
|
+
/* Input styling */
|
|
112
|
+
.input-field {{
|
|
113
|
+
@apply w-full px-4 py-3 bg-slate-900/50 border border-slate-700/50 rounded-xl
|
|
114
|
+
text-slate-100 placeholder-slate-500
|
|
115
|
+
focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50
|
|
116
|
+
transition-all duration-300;
|
|
117
|
+
}}
|
|
118
|
+
|
|
119
|
+
/* Modern checkbox */
|
|
120
|
+
.checkbox-modern {{
|
|
121
|
+
@apply appearance-none w-6 h-6 rounded-lg border-2 border-slate-600 bg-slate-800/50
|
|
122
|
+
checked:bg-gradient-to-r checked:from-indigo-500 checked:to-purple-500
|
|
123
|
+
checked:border-transparent cursor-pointer transition-all duration-300
|
|
124
|
+
hover:border-indigo-400 focus:ring-2 focus:ring-indigo-500/50;
|
|
125
|
+
}}
|
|
126
|
+
|
|
127
|
+
/* Page title with gradient */
|
|
128
|
+
.page-title {{
|
|
129
|
+
@apply text-4xl font-bold bg-gradient-to-r from-indigo-400 via-purple-400 to-pink-400
|
|
130
|
+
bg-clip-text text-transparent;
|
|
131
|
+
}}
|
|
132
|
+
|
|
133
|
+
/* Back link styling */
|
|
134
|
+
.link-back {{
|
|
135
|
+
@apply inline-flex items-center gap-2 text-slate-400 hover:text-indigo-400
|
|
136
|
+
transition-colors duration-300;
|
|
137
|
+
}}
|
|
138
|
+
}}
|
|
139
|
+
|
|
140
|
+
@layer utilities {{
|
|
141
|
+
.text-balance {{
|
|
142
|
+
text-wrap: balance;
|
|
143
|
+
}}
|
|
144
|
+
}}
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
# ========== Landing Page Pattern ==========
|
|
148
|
+
|
|
149
|
+
LANDING_PAGE_WITH_LINKS = """import Link from "next/link";
|
|
150
|
+
|
|
151
|
+
export default function Home() {{
|
|
152
|
+
return (
|
|
153
|
+
<main className="min-h-screen">
|
|
154
|
+
<div className="container mx-auto px-4 py-12 max-w-4xl">
|
|
155
|
+
<h1 className="page-title mb-8">Welcome</h1>
|
|
156
|
+
|
|
157
|
+
<div className="grid gap-6">
|
|
158
|
+
<Link
|
|
159
|
+
href="/{resource_plural}"
|
|
160
|
+
className="glass-card p-6 block hover:border-indigo-500/50 transition-all duration-300 group"
|
|
161
|
+
>
|
|
162
|
+
<div className="flex items-center justify-between">
|
|
163
|
+
<div>
|
|
164
|
+
<h2 className="text-2xl font-semibold text-slate-100 mb-2 group-hover:text-indigo-400 transition-colors">{Resource}s</h2>
|
|
165
|
+
<p className="text-slate-400">{link_description}</p>
|
|
166
|
+
</div>
|
|
167
|
+
<svg className="w-6 h-6 text-slate-500 group-hover:text-indigo-400 group-hover:translate-x-1 transition-all" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
168
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{2}} d="M9 5l7 7-7 7" />
|
|
169
|
+
</svg>
|
|
170
|
+
</div>
|
|
171
|
+
</Link>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</main>
|
|
175
|
+
);
|
|
176
|
+
}}
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
# ========== API Route Patterns (Next.js) ==========
|
|
180
|
+
|
|
181
|
+
API_ROUTE_GET = """export async function GET() {{
|
|
182
|
+
try {{
|
|
183
|
+
const {resource_plural} = await prisma.{resource}.findMany({{
|
|
184
|
+
orderBy: {{ id: 'desc' }},
|
|
185
|
+
take: 50
|
|
186
|
+
}});
|
|
187
|
+
return NextResponse.json({resource_plural});
|
|
188
|
+
}} catch (error) {{
|
|
189
|
+
console.error('GET /{resource}s error:', error);
|
|
190
|
+
return NextResponse.json(
|
|
191
|
+
{{ error: 'Failed to fetch {resource}s' }},
|
|
192
|
+
{{ status: 500 }}
|
|
193
|
+
);
|
|
194
|
+
}}
|
|
195
|
+
}}"""
|
|
196
|
+
|
|
197
|
+
API_ROUTE_GET_PAGINATED = """export async function GET(request: Request) {{
|
|
198
|
+
try {{
|
|
199
|
+
const {{ searchParams }} = new URL(request.url);
|
|
200
|
+
const page = parseInt(searchParams.get('page') || '1');
|
|
201
|
+
const limit = parseInt(searchParams.get('limit') || '10');
|
|
202
|
+
const skip = (page - 1) * limit;
|
|
203
|
+
|
|
204
|
+
const [{resource_plural}, total] = await Promise.all([
|
|
205
|
+
prisma.{resource}.findMany({{
|
|
206
|
+
skip,
|
|
207
|
+
take: limit,
|
|
208
|
+
orderBy: {{ id: 'desc' }}
|
|
209
|
+
}}),
|
|
210
|
+
prisma.{resource}.count()
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
return NextResponse.json({{
|
|
214
|
+
{resource_plural},
|
|
215
|
+
pagination: {{
|
|
216
|
+
page,
|
|
217
|
+
limit,
|
|
218
|
+
total,
|
|
219
|
+
pages: Math.ceil(total / limit)
|
|
220
|
+
}}
|
|
221
|
+
}});
|
|
222
|
+
}} catch (error) {{
|
|
223
|
+
console.error('GET /{resource}s error:', error);
|
|
224
|
+
return NextResponse.json(
|
|
225
|
+
{{ error: 'Failed to fetch {resource}s' }},
|
|
226
|
+
{{ status: 500 }}
|
|
227
|
+
);
|
|
228
|
+
}}
|
|
229
|
+
}}"""
|
|
230
|
+
|
|
231
|
+
API_ROUTE_POST = """export async function POST(request: Request) {{
|
|
232
|
+
try {{
|
|
233
|
+
const body = await request.json();
|
|
234
|
+
|
|
235
|
+
// Validate request body
|
|
236
|
+
const validatedData = {Resource}Schema.parse(body);
|
|
237
|
+
|
|
238
|
+
const {resource} = await prisma.{resource}.create({{
|
|
239
|
+
data: validatedData
|
|
240
|
+
}});
|
|
241
|
+
|
|
242
|
+
return NextResponse.json({resource}, {{ status: 201 }});
|
|
243
|
+
}} catch (error) {{
|
|
244
|
+
if (error instanceof z.ZodError) {{
|
|
245
|
+
return NextResponse.json(
|
|
246
|
+
{{ error: 'Invalid request data', details: error.issues }},
|
|
247
|
+
{{ status: 400 }}
|
|
248
|
+
);
|
|
249
|
+
}}
|
|
250
|
+
|
|
251
|
+
console.error('POST /{resource}s error:', error);
|
|
252
|
+
return NextResponse.json(
|
|
253
|
+
{{ error: 'Failed to create {resource}' }},
|
|
254
|
+
{{ status: 500 }}
|
|
255
|
+
);
|
|
256
|
+
}}
|
|
257
|
+
}}"""
|
|
258
|
+
|
|
259
|
+
API_ROUTE_DYNAMIC_GET = """export async function GET(
|
|
260
|
+
request: Request,
|
|
261
|
+
{{ params }}: {{ params: {{ id: string }} }}
|
|
262
|
+
) {{
|
|
263
|
+
try {{
|
|
264
|
+
const id = parseInt(params.id);
|
|
265
|
+
|
|
266
|
+
const {resource} = await prisma.{resource}.findUnique({{
|
|
267
|
+
where: {{ id }}
|
|
268
|
+
}});
|
|
269
|
+
|
|
270
|
+
if (!{resource}) {{
|
|
271
|
+
return NextResponse.json(
|
|
272
|
+
{{ error: '{Resource} not found' }},
|
|
273
|
+
{{ status: 404 }}
|
|
274
|
+
);
|
|
275
|
+
}}
|
|
276
|
+
|
|
277
|
+
return NextResponse.json({resource});
|
|
278
|
+
}} catch (error) {{
|
|
279
|
+
console.error('GET /{resource}/[id] error:', error);
|
|
280
|
+
return NextResponse.json(
|
|
281
|
+
{{ error: 'Failed to fetch {resource}' }},
|
|
282
|
+
{{ status: 500 }}
|
|
283
|
+
);
|
|
284
|
+
}}
|
|
285
|
+
}}"""
|
|
286
|
+
|
|
287
|
+
API_ROUTE_DYNAMIC_PATCH = """export async function PATCH(
|
|
288
|
+
request: Request,
|
|
289
|
+
{{ params }}: {{ params: {{ id: string }} }}
|
|
290
|
+
) {{
|
|
291
|
+
try {{
|
|
292
|
+
const id = parseInt(params.id);
|
|
293
|
+
const body = await request.json();
|
|
294
|
+
|
|
295
|
+
const validatedData = {Resource}UpdateSchema.parse(body);
|
|
296
|
+
|
|
297
|
+
const {resource} = await prisma.{resource}.update({{
|
|
298
|
+
where: {{ id }},
|
|
299
|
+
data: validatedData
|
|
300
|
+
}});
|
|
301
|
+
|
|
302
|
+
return NextResponse.json({resource});
|
|
303
|
+
}} catch (error) {{
|
|
304
|
+
if (error instanceof z.ZodError) {{
|
|
305
|
+
return NextResponse.json(
|
|
306
|
+
{{ error: 'Invalid update data', details: error.issues }},
|
|
307
|
+
{{ status: 400 }}
|
|
308
|
+
);
|
|
309
|
+
}}
|
|
310
|
+
|
|
311
|
+
console.error('PATCH /{resource}/[id] error:', error);
|
|
312
|
+
return NextResponse.json(
|
|
313
|
+
{{ error: 'Failed to update {resource}' }},
|
|
314
|
+
{{ status: 500 }}
|
|
315
|
+
);
|
|
316
|
+
}}
|
|
317
|
+
}}"""
|
|
318
|
+
|
|
319
|
+
API_ROUTE_DYNAMIC_DELETE = """export async function DELETE(
|
|
320
|
+
request: Request,
|
|
321
|
+
{{ params }}: {{ params: {{ id: string }} }}
|
|
322
|
+
) {{
|
|
323
|
+
try {{
|
|
324
|
+
const id = parseInt(params.id);
|
|
325
|
+
|
|
326
|
+
await prisma.{resource}.delete({{
|
|
327
|
+
where: {{ id }}
|
|
328
|
+
}});
|
|
329
|
+
|
|
330
|
+
return NextResponse.json({{ success: true }});
|
|
331
|
+
}} catch (error) {{
|
|
332
|
+
console.error('DELETE /{resource}/[id] error:', error);
|
|
333
|
+
return NextResponse.json(
|
|
334
|
+
{{ error: 'Failed to delete {resource}' }},
|
|
335
|
+
{{ status: 500 }}
|
|
336
|
+
);
|
|
337
|
+
}}
|
|
338
|
+
}}"""
|
|
339
|
+
|
|
340
|
+
# ========== Validation Schema Patterns ==========
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def generate_zod_schema(resource_name: str, fields: dict) -> str:
|
|
344
|
+
"""Generate Zod validation schema for a resource.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
resource_name: Name of the resource (e.g., "todo", "user")
|
|
348
|
+
fields: Dictionary of field names to types
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
TypeScript code for Zod schema
|
|
352
|
+
"""
|
|
353
|
+
schema_fields = []
|
|
354
|
+
for field_name, field_type in fields.items():
|
|
355
|
+
if field_name in ["id", "createdAt", "updatedAt"]:
|
|
356
|
+
continue # Skip auto-generated fields
|
|
357
|
+
|
|
358
|
+
zod_type = _map_type_to_zod(field_type)
|
|
359
|
+
schema_fields.append(f" {field_name}: {zod_type}")
|
|
360
|
+
|
|
361
|
+
resource_capitalized = resource_name.capitalize()
|
|
362
|
+
|
|
363
|
+
return f"""const {resource_capitalized}Schema = z.object({{
|
|
364
|
+
{','.join(schema_fields)}
|
|
365
|
+
}});
|
|
366
|
+
|
|
367
|
+
const {resource_capitalized}UpdateSchema = {resource_capitalized}Schema.partial();
|
|
368
|
+
|
|
369
|
+
type {resource_capitalized} = z.infer<typeof {resource_capitalized}Schema>;"""
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _map_type_to_zod(field_type: str) -> str:
|
|
373
|
+
"""Map field type to Zod validation type."""
|
|
374
|
+
# Normalize to lowercase for consistent lookup
|
|
375
|
+
normalized = field_type.lower()
|
|
376
|
+
|
|
377
|
+
type_mapping = {
|
|
378
|
+
"string": "z.string().min(1)",
|
|
379
|
+
"text": "z.string()",
|
|
380
|
+
"int": "z.number().int()",
|
|
381
|
+
"number": "z.number().int()",
|
|
382
|
+
"float": "z.number()",
|
|
383
|
+
"boolean": "z.boolean()",
|
|
384
|
+
"date": "z.coerce.date()",
|
|
385
|
+
"datetime": "z.coerce.date()",
|
|
386
|
+
"timestamp": "z.coerce.date()",
|
|
387
|
+
"email": "z.string().email()",
|
|
388
|
+
"url": "z.string().url()",
|
|
389
|
+
}
|
|
390
|
+
return type_mapping.get(normalized, "z.string()")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# ========== React Component Patterns ==========
|
|
394
|
+
|
|
395
|
+
SERVER_COMPONENT_LIST = """import {{ prisma }} from "@/lib/prisma";
|
|
396
|
+
import Link from "next/link";
|
|
397
|
+
|
|
398
|
+
async function get{Resource}s() {{
|
|
399
|
+
const {resource_plural} = await prisma.{resource}.findMany({{
|
|
400
|
+
orderBy: {{ id: "desc" }},
|
|
401
|
+
take: 50
|
|
402
|
+
}});
|
|
403
|
+
return {resource_plural};
|
|
404
|
+
}}
|
|
405
|
+
|
|
406
|
+
export default async function {Resource}sPage() {{
|
|
407
|
+
const {resource_plural} = await get{Resource}s();
|
|
408
|
+
|
|
409
|
+
return (
|
|
410
|
+
<div className="min-h-screen">
|
|
411
|
+
<div className="container mx-auto px-4 py-12 max-w-4xl">
|
|
412
|
+
{{/* Header */}}
|
|
413
|
+
<div className="mb-10">
|
|
414
|
+
<h1 className="text-4xl font-bold bg-gradient-to-r from-indigo-400 via-purple-400 to-pink-400 bg-clip-text text-transparent mb-2">
|
|
415
|
+
{Resource}s
|
|
416
|
+
</h1>
|
|
417
|
+
<p className="text-slate-400">
|
|
418
|
+
{{{resource_plural}.length === 0
|
|
419
|
+
? "No items yet. Create your first one!"
|
|
420
|
+
: `${{({resource_plural} as any[]).filter(t => !(t as any).completed).length}} pending items`}}
|
|
421
|
+
</p>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
{{/* Add Button */}}
|
|
425
|
+
<div className="mb-8">
|
|
426
|
+
<Link
|
|
427
|
+
href="/{resource}s/new"
|
|
428
|
+
className="inline-flex items-center gap-2 bg-gradient-to-r from-indigo-500 to-purple-500 text-white px-6 py-3 rounded-xl font-medium hover:from-indigo-600 hover:to-purple-600 transition-all duration-300 hover:shadow-lg hover:shadow-indigo-500/25"
|
|
429
|
+
>
|
|
430
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
431
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{2}} d="M12 4v16m8-8H4" />
|
|
432
|
+
</svg>
|
|
433
|
+
Add New {Resource}
|
|
434
|
+
</Link>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
{{/* List */}}
|
|
438
|
+
<div className="bg-slate-800/50 backdrop-blur-xl border border-slate-700/50 rounded-2xl shadow-2xl p-6">
|
|
439
|
+
{{{resource_plural}.length === 0 ? (
|
|
440
|
+
<div className="text-center py-16">
|
|
441
|
+
<div className="w-20 h-20 mx-auto mb-6 rounded-full bg-slate-800/50 flex items-center justify-center">
|
|
442
|
+
<svg className="w-10 h-10 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
443
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{1.5}} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
444
|
+
</svg>
|
|
445
|
+
</div>
|
|
446
|
+
<h3 className="text-xl font-medium text-slate-300 mb-2">No {resource}s yet</h3>
|
|
447
|
+
<p className="text-slate-500 mb-6">Create your first item to get started</p>
|
|
448
|
+
<Link
|
|
449
|
+
href="/{resource}s/new"
|
|
450
|
+
className="inline-flex items-center gap-2 bg-gradient-to-r from-indigo-500 to-purple-500 text-white px-6 py-3 rounded-xl font-medium hover:from-indigo-600 hover:to-purple-600 transition-all duration-300"
|
|
451
|
+
>
|
|
452
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
453
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{2}} d="M12 4v16m8-8H4" />
|
|
454
|
+
</svg>
|
|
455
|
+
Create {Resource}
|
|
456
|
+
</Link>
|
|
457
|
+
</div>
|
|
458
|
+
) : (
|
|
459
|
+
<div className="space-y-3">
|
|
460
|
+
{{{resource_plural}.map((item) => (
|
|
461
|
+
<Link
|
|
462
|
+
key={{item.id}}
|
|
463
|
+
href={{`/{resource}s/${{item.id}}`}}
|
|
464
|
+
className="block p-5 rounded-xl bg-slate-800/30 border border-slate-700/30 hover:bg-slate-800/50 hover:border-indigo-500/30 transition-all duration-300"
|
|
465
|
+
>
|
|
466
|
+
{field_display}
|
|
467
|
+
</Link>
|
|
468
|
+
))}}
|
|
469
|
+
</div>
|
|
470
|
+
)}}
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
);
|
|
475
|
+
}}"""
|
|
476
|
+
|
|
477
|
+
CLIENT_COMPONENT_FORM = """"use client";
|
|
478
|
+
|
|
479
|
+
import {{ useState, useEffect }} from "react";
|
|
480
|
+
import {{ useRouter }} from "next/navigation";
|
|
481
|
+
import type {{ {Resource} }} from "@prisma/client";
|
|
482
|
+
|
|
483
|
+
interface {Resource}FormProps {{
|
|
484
|
+
initialData?: Partial<{Resource}>;
|
|
485
|
+
mode?: "create" | "edit";
|
|
486
|
+
}}
|
|
487
|
+
|
|
488
|
+
export function {Resource}Form({{ initialData, mode = "create" }}: {Resource}FormProps) {{
|
|
489
|
+
const router = useRouter();
|
|
490
|
+
const [loading, setLoading] = useState(false);
|
|
491
|
+
const [error, setError] = useState<string | null>(null);
|
|
492
|
+
|
|
493
|
+
const [formData, setFormData] = useState({{
|
|
494
|
+
{form_state_fields}
|
|
495
|
+
}});
|
|
496
|
+
const dateFields = {date_fields};
|
|
497
|
+
|
|
498
|
+
const normalizePayload = (data: typeof formData) => {{
|
|
499
|
+
if (dateFields.length === 0) {{
|
|
500
|
+
return data;
|
|
501
|
+
}}
|
|
502
|
+
|
|
503
|
+
const normalized = {{ ...data }};
|
|
504
|
+
|
|
505
|
+
dateFields.forEach((field) => {{
|
|
506
|
+
const value = normalized[field as keyof typeof normalized];
|
|
507
|
+
if (!value) {{
|
|
508
|
+
return;
|
|
509
|
+
}}
|
|
510
|
+
|
|
511
|
+
const parsedValue = new Date(value as string | number | Date);
|
|
512
|
+
if (!Number.isNaN(parsedValue.getTime())) {{
|
|
513
|
+
(normalized as any)[field] = parsedValue.toISOString();
|
|
514
|
+
}}
|
|
515
|
+
}});
|
|
516
|
+
|
|
517
|
+
return normalized;
|
|
518
|
+
}};
|
|
519
|
+
|
|
520
|
+
// Initialize form with initialData when in edit mode
|
|
521
|
+
useEffect(() => {{
|
|
522
|
+
if (initialData && mode === "edit") {{
|
|
523
|
+
setFormData(prev => ({{
|
|
524
|
+
...prev,
|
|
525
|
+
...Object.fromEntries(
|
|
526
|
+
Object.entries(initialData).filter(([key]) =>
|
|
527
|
+
!["id", "createdAt", "updatedAt"].includes(key)
|
|
528
|
+
)
|
|
529
|
+
)
|
|
530
|
+
}}));
|
|
531
|
+
}}
|
|
532
|
+
}}, [initialData, mode]);
|
|
533
|
+
|
|
534
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {{
|
|
535
|
+
const {{ name, value, type }} = e.target;
|
|
536
|
+
const checked = (e.target as HTMLInputElement).checked;
|
|
537
|
+
|
|
538
|
+
setFormData(prev => ({{
|
|
539
|
+
...prev,
|
|
540
|
+
[name]: type === "checkbox" ? checked : type === "number" ? parseFloat(value) : value
|
|
541
|
+
}}));
|
|
542
|
+
}};
|
|
543
|
+
|
|
544
|
+
const handleSubmit = async (e: React.FormEvent) => {{
|
|
545
|
+
e.preventDefault();
|
|
546
|
+
setLoading(true);
|
|
547
|
+
setError(null);
|
|
548
|
+
|
|
549
|
+
try {{
|
|
550
|
+
const url = mode === "create"
|
|
551
|
+
? "/api/{resource}s"
|
|
552
|
+
: `/api/{resource}s/${{initialData?.id}}`;
|
|
553
|
+
|
|
554
|
+
const method = mode === "create" ? "POST" : "PATCH";
|
|
555
|
+
const payload = normalizePayload(formData);
|
|
556
|
+
|
|
557
|
+
const response = await fetch(url, {{
|
|
558
|
+
method,
|
|
559
|
+
headers: {{ "Content-Type": "application/json" }},
|
|
560
|
+
body: JSON.stringify(payload)
|
|
561
|
+
}});
|
|
562
|
+
|
|
563
|
+
if (!response.ok) {{
|
|
564
|
+
const data = await response.json();
|
|
565
|
+
throw new Error(data.error || "Operation failed");
|
|
566
|
+
}}
|
|
567
|
+
|
|
568
|
+
router.push("/{resource}s");
|
|
569
|
+
router.refresh();
|
|
570
|
+
}} catch (err) {{
|
|
571
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
572
|
+
}} finally {{
|
|
573
|
+
setLoading(false);
|
|
574
|
+
}}
|
|
575
|
+
}};
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
<form onSubmit={{handleSubmit}} className="space-y-6">
|
|
579
|
+
{form_fields}
|
|
580
|
+
|
|
581
|
+
{{error && (
|
|
582
|
+
<div className="bg-red-500/10 border border-red-500/20 text-red-400 p-4 rounded-xl">
|
|
583
|
+
{{error}}
|
|
584
|
+
</div>
|
|
585
|
+
)}}
|
|
586
|
+
|
|
587
|
+
<div className="flex gap-4 pt-4">
|
|
588
|
+
<button
|
|
589
|
+
type="submit"
|
|
590
|
+
disabled={{loading}}
|
|
591
|
+
className="flex-1 bg-gradient-to-r from-indigo-500 to-purple-500 text-white py-3 px-6 rounded-xl font-medium hover:from-indigo-600 hover:to-purple-600 transition-all duration-300 hover:shadow-lg hover:shadow-indigo-500/25 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
592
|
+
>
|
|
593
|
+
{{loading ? "Saving..." : mode === "create" ? "Create {Resource}" : "Save Changes"}}
|
|
594
|
+
</button>
|
|
595
|
+
<button
|
|
596
|
+
type="button"
|
|
597
|
+
onClick={{() => router.back()}}
|
|
598
|
+
className="px-6 py-3 bg-slate-700/50 text-slate-200 rounded-xl font-medium border border-slate-600/50 hover:bg-slate-700 transition-all duration-300"
|
|
599
|
+
>
|
|
600
|
+
Cancel
|
|
601
|
+
</button>
|
|
602
|
+
</div>
|
|
603
|
+
</form>
|
|
604
|
+
);
|
|
605
|
+
}}"""
|
|
606
|
+
|
|
607
|
+
CLIENT_COMPONENT_NEW_PAGE = """"use client";
|
|
608
|
+
|
|
609
|
+
import {{ {Resource}Form }} from "@/components/{Resource}Form";
|
|
610
|
+
import Link from "next/link";
|
|
611
|
+
|
|
612
|
+
export default function New{Resource}Page() {{
|
|
613
|
+
return (
|
|
614
|
+
<div className="min-h-screen">
|
|
615
|
+
<div className="container mx-auto px-4 py-12 max-w-2xl">
|
|
616
|
+
<div className="mb-8">
|
|
617
|
+
<Link
|
|
618
|
+
href="/{resource}s"
|
|
619
|
+
className="link-back group"
|
|
620
|
+
>
|
|
621
|
+
<svg className="w-5 h-5 transition-transform duration-300 group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
622
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{2}} d="M15 19l-7-7 7-7" />
|
|
623
|
+
</svg>
|
|
624
|
+
Back to {Resource}s
|
|
625
|
+
</Link>
|
|
626
|
+
</div>
|
|
627
|
+
|
|
628
|
+
<div className="glass-card p-8">
|
|
629
|
+
<h1 className="page-title mb-8">
|
|
630
|
+
Create New {Resource}
|
|
631
|
+
</h1>
|
|
632
|
+
|
|
633
|
+
<{Resource}Form mode="create" />
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
);
|
|
638
|
+
}}"""
|
|
639
|
+
|
|
640
|
+
SERVER_COMPONENT_DETAIL = """"use client";
|
|
641
|
+
|
|
642
|
+
import {{ useRouter }} from "next/navigation";
|
|
643
|
+
import {{ useState, useEffect }} from "react";
|
|
644
|
+
import Link from "next/link";
|
|
645
|
+
|
|
646
|
+
interface {Resource}Data {{
|
|
647
|
+
id: number;
|
|
648
|
+
{interface_fields}
|
|
649
|
+
createdAt: string;
|
|
650
|
+
updatedAt: string;
|
|
651
|
+
}}
|
|
652
|
+
|
|
653
|
+
export default function {Resource}EditPage({{
|
|
654
|
+
params
|
|
655
|
+
}}: {{
|
|
656
|
+
params: {{ id: string }}
|
|
657
|
+
}}) {{
|
|
658
|
+
const router = useRouter();
|
|
659
|
+
const id = parseInt(params.id);
|
|
660
|
+
const [loading, setLoading] = useState(true);
|
|
661
|
+
const [saving, setSaving] = useState(false);
|
|
662
|
+
const [deleting, setDeleting] = useState(false);
|
|
663
|
+
const [error, setError] = useState<string | null>(null);
|
|
664
|
+
const [{resource}, set{Resource}] = useState<{Resource}Data | null>(null);
|
|
665
|
+
|
|
666
|
+
// Form state - populated from API
|
|
667
|
+
{form_state}
|
|
668
|
+
|
|
669
|
+
// Fetch data on mount
|
|
670
|
+
useEffect(() => {{
|
|
671
|
+
async function fetchData() {{
|
|
672
|
+
try {{
|
|
673
|
+
const response = await fetch(`/api/{resource}s/${{id}}`);
|
|
674
|
+
if (!response.ok) {{
|
|
675
|
+
if (response.status === 404) {{
|
|
676
|
+
router.push("/{resource}s");
|
|
677
|
+
return;
|
|
678
|
+
}}
|
|
679
|
+
throw new Error("Failed to fetch {resource}");
|
|
680
|
+
}}
|
|
681
|
+
const data = await response.json();
|
|
682
|
+
set{Resource}(data);
|
|
683
|
+
// Populate form fields
|
|
684
|
+
{populate_fields}
|
|
685
|
+
setLoading(false);
|
|
686
|
+
}} catch (err) {{
|
|
687
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
688
|
+
setLoading(false);
|
|
689
|
+
}}
|
|
690
|
+
}}
|
|
691
|
+
fetchData();
|
|
692
|
+
}}, [id, router]);
|
|
693
|
+
|
|
694
|
+
const handleSave = async (e: React.FormEvent) => {{
|
|
695
|
+
e.preventDefault();
|
|
696
|
+
setSaving(true);
|
|
697
|
+
setError(null);
|
|
698
|
+
|
|
699
|
+
try {{
|
|
700
|
+
const response = await fetch(`/api/{resource}s/${{id}}`, {{
|
|
701
|
+
method: "PATCH",
|
|
702
|
+
headers: {{ "Content-Type": "application/json" }},
|
|
703
|
+
body: JSON.stringify({{
|
|
704
|
+
{save_body}
|
|
705
|
+
}}),
|
|
706
|
+
}});
|
|
707
|
+
|
|
708
|
+
if (!response.ok) {{
|
|
709
|
+
const data = await response.json();
|
|
710
|
+
throw new Error(data.error || "Failed to update {resource}");
|
|
711
|
+
}}
|
|
712
|
+
|
|
713
|
+
router.push("/{resource}s");
|
|
714
|
+
router.refresh();
|
|
715
|
+
}} catch (err) {{
|
|
716
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
717
|
+
setSaving(false);
|
|
718
|
+
}}
|
|
719
|
+
}};
|
|
720
|
+
|
|
721
|
+
const handleDelete = async () => {{
|
|
722
|
+
if (!confirm("Are you sure you want to delete this {resource}?")) {{
|
|
723
|
+
return;
|
|
724
|
+
}}
|
|
725
|
+
|
|
726
|
+
setDeleting(true);
|
|
727
|
+
setError(null);
|
|
728
|
+
|
|
729
|
+
try {{
|
|
730
|
+
const response = await fetch(`/api/{resource}s/${{id}}`, {{
|
|
731
|
+
method: "DELETE"
|
|
732
|
+
}});
|
|
733
|
+
|
|
734
|
+
if (!response.ok) {{
|
|
735
|
+
throw new Error("Failed to delete {resource}");
|
|
736
|
+
}}
|
|
737
|
+
|
|
738
|
+
router.push("/{resource}s");
|
|
739
|
+
router.refresh();
|
|
740
|
+
}} catch (err) {{
|
|
741
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
742
|
+
setDeleting(false);
|
|
743
|
+
}}
|
|
744
|
+
}};
|
|
745
|
+
|
|
746
|
+
if (loading) {{
|
|
747
|
+
return (
|
|
748
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
749
|
+
<div className="text-slate-400">Loading...</div>
|
|
750
|
+
</div>
|
|
751
|
+
);
|
|
752
|
+
}}
|
|
753
|
+
|
|
754
|
+
if (!{resource}) {{
|
|
755
|
+
return (
|
|
756
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
757
|
+
<div className="text-slate-400">{Resource} not found</div>
|
|
758
|
+
</div>
|
|
759
|
+
);
|
|
760
|
+
}}
|
|
761
|
+
|
|
762
|
+
return (
|
|
763
|
+
<div className="min-h-screen">
|
|
764
|
+
<div className="container mx-auto px-4 py-12 max-w-2xl">
|
|
765
|
+
<div className="mb-8">
|
|
766
|
+
<Link
|
|
767
|
+
href="/{resource}s"
|
|
768
|
+
className="link-back group"
|
|
769
|
+
>
|
|
770
|
+
<svg className="w-5 h-5 transition-transform duration-300 group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
771
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{2}} d="M15 19l-7-7 7-7" />
|
|
772
|
+
</svg>
|
|
773
|
+
Back to {Resource}s
|
|
774
|
+
</Link>
|
|
775
|
+
</div>
|
|
776
|
+
|
|
777
|
+
<div className="glass-card p-8">
|
|
778
|
+
<h1 className="page-title mb-8">
|
|
779
|
+
Edit {Resource}
|
|
780
|
+
</h1>
|
|
781
|
+
|
|
782
|
+
{{error && (
|
|
783
|
+
<div className="mb-6 p-4 bg-red-500/10 border border-red-500/20 rounded-xl text-red-400">
|
|
784
|
+
{{error}}
|
|
785
|
+
</div>
|
|
786
|
+
)}}
|
|
787
|
+
|
|
788
|
+
<form onSubmit={{handleSave}}>
|
|
789
|
+
{form_fields}
|
|
790
|
+
|
|
791
|
+
<div className="flex gap-4 mt-8">
|
|
792
|
+
<button
|
|
793
|
+
type="submit"
|
|
794
|
+
disabled={{saving}}
|
|
795
|
+
className="btn-primary flex-1"
|
|
796
|
+
>
|
|
797
|
+
{{saving ? "Saving..." : "Save Changes"}}
|
|
798
|
+
</button>
|
|
799
|
+
<button
|
|
800
|
+
type="button"
|
|
801
|
+
onClick={{handleDelete}}
|
|
802
|
+
disabled={{deleting}}
|
|
803
|
+
className="btn-danger"
|
|
804
|
+
>
|
|
805
|
+
{{deleting ? "Deleting..." : "Delete"}}
|
|
806
|
+
</button>
|
|
807
|
+
</div>
|
|
808
|
+
</form>
|
|
809
|
+
|
|
810
|
+
<div className="mt-8 pt-6 border-t border-slate-700/50 text-sm text-slate-500">
|
|
811
|
+
<p><strong className="text-slate-400">Created:</strong> {{new Date({resource}.createdAt).toLocaleString()}}</p>
|
|
812
|
+
<p className="mt-1"><strong className="text-slate-400">Updated:</strong> {{new Date({resource}.updatedAt).toLocaleString()}}</p>
|
|
813
|
+
</div>
|
|
814
|
+
</div>
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
);
|
|
818
|
+
}}"""
|
|
819
|
+
|
|
820
|
+
CLIENT_COMPONENT_ACTIONS = """"use client";
|
|
821
|
+
|
|
822
|
+
import {{ useRouter }} from "next/navigation";
|
|
823
|
+
import {{ useState }} from "react";
|
|
824
|
+
import {{ {Resource}Form }} from "@/components/{Resource}Form";
|
|
825
|
+
import type {{ {Resource} }} from "@prisma/client";
|
|
826
|
+
|
|
827
|
+
interface {Resource}ActionsProps {{
|
|
828
|
+
{resource}Id: number;
|
|
829
|
+
{resource}Data?: {Resource};
|
|
830
|
+
}}
|
|
831
|
+
|
|
832
|
+
export function {Resource}Actions({{ {resource}Id, {resource}Data }}: {Resource}ActionsProps) {{
|
|
833
|
+
const router = useRouter();
|
|
834
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
835
|
+
const [deleting, setDeleting] = useState(false);
|
|
836
|
+
const [error, setError] = useState<string | null>(null);
|
|
837
|
+
|
|
838
|
+
const handleDelete = async () => {{
|
|
839
|
+
if (!confirm("Are you sure you want to delete this {resource}?")) {{
|
|
840
|
+
return;
|
|
841
|
+
}}
|
|
842
|
+
|
|
843
|
+
setDeleting(true);
|
|
844
|
+
setError(null);
|
|
845
|
+
|
|
846
|
+
try {{
|
|
847
|
+
const response = await fetch(`/api/{resource}s/${{{resource}Id}}`, {{
|
|
848
|
+
method: "DELETE"
|
|
849
|
+
}});
|
|
850
|
+
|
|
851
|
+
if (!response.ok) {{
|
|
852
|
+
throw new Error("Failed to delete {resource}");
|
|
853
|
+
}}
|
|
854
|
+
|
|
855
|
+
router.push("/{resource}s");
|
|
856
|
+
router.refresh();
|
|
857
|
+
}} catch (err) {{
|
|
858
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
859
|
+
setDeleting(false);
|
|
860
|
+
}}
|
|
861
|
+
}};
|
|
862
|
+
|
|
863
|
+
if (isEditing && {resource}Data) {{
|
|
864
|
+
return (
|
|
865
|
+
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
|
866
|
+
<div className="bg-slate-800 border border-slate-700 rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto">
|
|
867
|
+
<div className="p-6 border-b border-slate-700">
|
|
868
|
+
<div className="flex justify-between items-center">
|
|
869
|
+
<h2 className="text-xl font-bold text-slate-100">Edit {Resource}</h2>
|
|
870
|
+
<button
|
|
871
|
+
onClick={{() => setIsEditing(false)}}
|
|
872
|
+
className="text-slate-400 hover:text-slate-200 transition-colors"
|
|
873
|
+
>
|
|
874
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
875
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{2}} d="M6 18L18 6M6 6l12 12" />
|
|
876
|
+
</svg>
|
|
877
|
+
</button>
|
|
878
|
+
</div>
|
|
879
|
+
</div>
|
|
880
|
+
<div className="p-6">
|
|
881
|
+
<{Resource}Form initialData={{{resource}Data}} mode="edit" />
|
|
882
|
+
</div>
|
|
883
|
+
</div>
|
|
884
|
+
</div>
|
|
885
|
+
);
|
|
886
|
+
}}
|
|
887
|
+
|
|
888
|
+
return (
|
|
889
|
+
<div className="flex items-center gap-3">
|
|
890
|
+
{{error && (
|
|
891
|
+
<div className="absolute top-full right-0 mt-2 bg-red-500/10 border border-red-500/20 text-red-400 p-3 rounded-xl text-sm whitespace-nowrap">
|
|
892
|
+
{{error}}
|
|
893
|
+
</div>
|
|
894
|
+
)}}
|
|
895
|
+
<button
|
|
896
|
+
onClick={{() => setIsEditing(true)}}
|
|
897
|
+
className="inline-flex items-center gap-2 bg-slate-700/50 text-slate-200 px-4 py-2 rounded-xl font-medium border border-slate-600/50 hover:bg-slate-700 transition-all duration-300"
|
|
898
|
+
>
|
|
899
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
900
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{2}} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
901
|
+
</svg>
|
|
902
|
+
Edit
|
|
903
|
+
</button>
|
|
904
|
+
<button
|
|
905
|
+
onClick={{handleDelete}}
|
|
906
|
+
disabled={{deleting}}
|
|
907
|
+
className="inline-flex items-center gap-2 bg-gradient-to-r from-red-500 to-rose-500 text-white px-4 py-2 rounded-xl font-medium hover:from-red-600 hover:to-rose-600 transition-all duration-300 hover:shadow-lg hover:shadow-red-500/25 disabled:opacity-50"
|
|
908
|
+
>
|
|
909
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
910
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{2}} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
911
|
+
</svg>
|
|
912
|
+
{{deleting ? "Deleting..." : "Delete"}}
|
|
913
|
+
</button>
|
|
914
|
+
</div>
|
|
915
|
+
);
|
|
916
|
+
}}"""
|
|
917
|
+
|
|
918
|
+
CLIENT_COMPONENT_DETAIL_PAGE = """"use client";
|
|
919
|
+
|
|
920
|
+
import {{ useState, useEffect }} from "react";
|
|
921
|
+
import {{ useRouter }} from "next/navigation";
|
|
922
|
+
import {{ {Resource}Form }} from "@/components/{Resource}Form";
|
|
923
|
+
import Link from "next/link";
|
|
924
|
+
|
|
925
|
+
interface {Resource} {{
|
|
926
|
+
id: number;
|
|
927
|
+
{type_fields}
|
|
928
|
+
createdAt: Date;
|
|
929
|
+
updatedAt: Date;
|
|
930
|
+
}}
|
|
931
|
+
|
|
932
|
+
export default function {Resource}DetailPage({{ params }}: {{ params: {{ id: string }} }}) {{
|
|
933
|
+
const router = useRouter();
|
|
934
|
+
const [{resource}, set{Resource}] = useState<{Resource} | null>(null);
|
|
935
|
+
const [error, setError] = useState<string | null>(null);
|
|
936
|
+
const [loading, setLoading] = useState(true);
|
|
937
|
+
const [deleting, setDeleting] = useState(false);
|
|
938
|
+
|
|
939
|
+
useEffect(() => {{
|
|
940
|
+
fetch{Resource}();
|
|
941
|
+
}}, [params.id]);
|
|
942
|
+
|
|
943
|
+
const fetch{Resource} = async () => {{
|
|
944
|
+
try {{
|
|
945
|
+
const response = await fetch(`/api/{resource}s/${{params.id}}`);
|
|
946
|
+
if (!response.ok) {{
|
|
947
|
+
throw new Error("Failed to fetch {resource}");
|
|
948
|
+
}}
|
|
949
|
+
const data = await response.json();
|
|
950
|
+
set{Resource}(data);
|
|
951
|
+
}} catch (err) {{
|
|
952
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
953
|
+
}} finally {{
|
|
954
|
+
setLoading(false);
|
|
955
|
+
}}
|
|
956
|
+
}};
|
|
957
|
+
|
|
958
|
+
const handleDelete = async () => {{
|
|
959
|
+
if (!confirm("Are you sure you want to delete this {resource}?")) return;
|
|
960
|
+
|
|
961
|
+
setDeleting(true);
|
|
962
|
+
try {{
|
|
963
|
+
const response = await fetch(`/api/{resource}s/${{params.id}}`, {{
|
|
964
|
+
method: "DELETE"
|
|
965
|
+
}});
|
|
966
|
+
|
|
967
|
+
if (!response.ok) {{
|
|
968
|
+
throw new Error("Failed to delete {resource}");
|
|
969
|
+
}}
|
|
970
|
+
|
|
971
|
+
router.push("/{resource}s");
|
|
972
|
+
router.refresh();
|
|
973
|
+
}} catch (err) {{
|
|
974
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
975
|
+
setDeleting(false);
|
|
976
|
+
}}
|
|
977
|
+
}};
|
|
978
|
+
|
|
979
|
+
if (loading) {{
|
|
980
|
+
return (
|
|
981
|
+
<div className="container mx-auto p-8 max-w-2xl">
|
|
982
|
+
<div className="text-center py-12">Loading...</div>
|
|
983
|
+
</div>
|
|
984
|
+
);
|
|
985
|
+
}}
|
|
986
|
+
|
|
987
|
+
if (error || !{resource}) {{
|
|
988
|
+
return (
|
|
989
|
+
<div className="container mx-auto p-8 max-w-2xl">
|
|
990
|
+
<div className="text-center py-12">
|
|
991
|
+
<p className="text-red-500 mb-4">{{error || "{Resource} not found"}}</p>
|
|
992
|
+
<Link href="/{resource}s" className="text-blue-500 hover:underline">
|
|
993
|
+
Back to {Resource}s
|
|
994
|
+
</Link>
|
|
995
|
+
</div>
|
|
996
|
+
</div>
|
|
997
|
+
);
|
|
998
|
+
}}
|
|
999
|
+
|
|
1000
|
+
return (
|
|
1001
|
+
<div className="container mx-auto p-8 max-w-2xl">
|
|
1002
|
+
<div className="mb-6">
|
|
1003
|
+
<Link href="/{resource}s" className="text-blue-500 hover:underline">
|
|
1004
|
+
← Back to {Resource}s
|
|
1005
|
+
</Link>
|
|
1006
|
+
</div>
|
|
1007
|
+
|
|
1008
|
+
<h1 className="text-3xl font-bold mb-6">Edit {Resource}</h1>
|
|
1009
|
+
|
|
1010
|
+
<{Resource}Form initialData={{{resource}}} mode="edit" />
|
|
1011
|
+
|
|
1012
|
+
<div className="mt-6 pt-6 border-t border-gray-200">
|
|
1013
|
+
<button
|
|
1014
|
+
onClick={{handleDelete}}
|
|
1015
|
+
disabled={{deleting}}
|
|
1016
|
+
className="bg-red-500 text-white px-6 py-2 rounded-md hover:bg-red-600 disabled:opacity-50"
|
|
1017
|
+
>
|
|
1018
|
+
{{deleting ? "Deleting..." : "Delete {Resource}"}}
|
|
1019
|
+
</button>
|
|
1020
|
+
</div>
|
|
1021
|
+
|
|
1022
|
+
<div className="mt-6 bg-gray-50 rounded-lg p-4 text-sm text-gray-600">
|
|
1023
|
+
<p><strong>Created:</strong> {{new Date({resource}.createdAt).toLocaleString()}}</p>
|
|
1024
|
+
<p><strong>Updated:</strong> {{new Date({resource}.updatedAt).toLocaleString()}}</p>
|
|
1025
|
+
</div>
|
|
1026
|
+
</div>
|
|
1027
|
+
);
|
|
1028
|
+
}}"""
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def generate_form_field(field_name: str, field_type: str) -> str:
|
|
1032
|
+
"""Generate a form field based on type with modern styling."""
|
|
1033
|
+
input_type = {
|
|
1034
|
+
"string": "text",
|
|
1035
|
+
"text": "textarea",
|
|
1036
|
+
"number": "number",
|
|
1037
|
+
"email": "email",
|
|
1038
|
+
"url": "url",
|
|
1039
|
+
"boolean": "checkbox",
|
|
1040
|
+
"date": "date",
|
|
1041
|
+
}.get(field_type.lower(), "text")
|
|
1042
|
+
|
|
1043
|
+
label = field_name.replace("_", " ").title()
|
|
1044
|
+
|
|
1045
|
+
if input_type == "textarea":
|
|
1046
|
+
return f""" <div>
|
|
1047
|
+
<label htmlFor="{field_name}" className="block text-sm font-medium text-slate-300 mb-2">
|
|
1048
|
+
{label}
|
|
1049
|
+
</label>
|
|
1050
|
+
<textarea
|
|
1051
|
+
id="{field_name}"
|
|
1052
|
+
name="{field_name}"
|
|
1053
|
+
value={{formData.{field_name}}}
|
|
1054
|
+
onChange={{handleChange}}
|
|
1055
|
+
rows={{4}}
|
|
1056
|
+
className="w-full px-4 py-3 bg-slate-900/50 border border-slate-700/50 rounded-xl text-slate-100 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all duration-300"
|
|
1057
|
+
required
|
|
1058
|
+
/>
|
|
1059
|
+
</div>"""
|
|
1060
|
+
elif input_type == "checkbox":
|
|
1061
|
+
return f""" <div className="flex items-center gap-3 p-4 bg-slate-900/30 rounded-xl border border-slate-700/30">
|
|
1062
|
+
<input
|
|
1063
|
+
type="checkbox"
|
|
1064
|
+
id="{field_name}"
|
|
1065
|
+
name="{field_name}"
|
|
1066
|
+
checked={{formData.{field_name}}}
|
|
1067
|
+
onChange={{handleChange}}
|
|
1068
|
+
className="w-5 h-5 rounded-lg border-2 border-slate-600 bg-slate-800/50 checked:bg-gradient-to-r checked:from-indigo-500 checked:to-purple-500 checked:border-transparent focus:ring-2 focus:ring-indigo-500/50 cursor-pointer transition-all duration-300"
|
|
1069
|
+
/>
|
|
1070
|
+
<label htmlFor="{field_name}" className="text-slate-300 cursor-pointer">
|
|
1071
|
+
{label}
|
|
1072
|
+
</label>
|
|
1073
|
+
</div>"""
|
|
1074
|
+
else:
|
|
1075
|
+
return f""" <div>
|
|
1076
|
+
<label htmlFor="{field_name}" className="block text-sm font-medium text-slate-300 mb-2">
|
|
1077
|
+
{label}
|
|
1078
|
+
</label>
|
|
1079
|
+
<input
|
|
1080
|
+
type="{input_type}"
|
|
1081
|
+
id="{field_name}"
|
|
1082
|
+
name="{field_name}"
|
|
1083
|
+
value={{formData.{field_name}}}
|
|
1084
|
+
onChange={{handleChange}}
|
|
1085
|
+
className="w-full px-4 py-3 bg-slate-900/50 border border-slate-700/50 rounded-xl text-slate-100 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all duration-300"
|
|
1086
|
+
required
|
|
1087
|
+
/>
|
|
1088
|
+
</div>"""
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
# ========== Import Generation ==========
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
def generate_api_imports(_operations: list, uses_validation: bool = True) -> str:
|
|
1095
|
+
"""Generate appropriate imports for API routes."""
|
|
1096
|
+
imports = [
|
|
1097
|
+
'import { NextResponse } from "next/server";',
|
|
1098
|
+
'import { prisma } from "@/lib/prisma";',
|
|
1099
|
+
]
|
|
1100
|
+
|
|
1101
|
+
if uses_validation:
|
|
1102
|
+
imports.append('import { z } from "zod";')
|
|
1103
|
+
|
|
1104
|
+
return "\n".join(imports)
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
def generate_component_imports(component_type: str, uses_data: bool = False) -> str:
|
|
1108
|
+
"""Generate appropriate imports for React components."""
|
|
1109
|
+
imports = []
|
|
1110
|
+
|
|
1111
|
+
if component_type == "client":
|
|
1112
|
+
imports.extend(
|
|
1113
|
+
[
|
|
1114
|
+
'"use client";',
|
|
1115
|
+
"",
|
|
1116
|
+
'import { useState } from "react";',
|
|
1117
|
+
'import { useRouter } from "next/navigation";',
|
|
1118
|
+
]
|
|
1119
|
+
)
|
|
1120
|
+
elif uses_data:
|
|
1121
|
+
imports.append('import { prisma } from "@/lib/prisma";')
|
|
1122
|
+
|
|
1123
|
+
imports.append('import Link from "next/link";')
|
|
1124
|
+
|
|
1125
|
+
return "\n".join(imports)
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
# ========== Helper Functions ==========
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
def pluralize(word: str) -> str:
|
|
1132
|
+
"""Simple pluralization (can be enhanced)."""
|
|
1133
|
+
if word.endswith("y"):
|
|
1134
|
+
return word[:-1] + "ies"
|
|
1135
|
+
elif word.endswith(("s", "x", "z", "ch", "sh")):
|
|
1136
|
+
return word + "es"
|
|
1137
|
+
else:
|
|
1138
|
+
return word + "s"
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
def generate_field_display(fields: dict, max_fields: int = 3) -> str:
|
|
1142
|
+
"""Generate JSX for displaying resource fields with type-aware rendering.
|
|
1143
|
+
|
|
1144
|
+
Boolean fields render as checkboxes with strikethrough styling for completed items.
|
|
1145
|
+
String fields render as text with proper hierarchy. Uses modern dark theme styling.
|
|
1146
|
+
|
|
1147
|
+
Args:
|
|
1148
|
+
fields: Dictionary mapping field names to their types (e.g., {"title": "string", "completed": "boolean"})
|
|
1149
|
+
max_fields: Maximum number of fields to display (default: 3)
|
|
1150
|
+
|
|
1151
|
+
Returns:
|
|
1152
|
+
JSX string for field display
|
|
1153
|
+
"""
|
|
1154
|
+
display_fields = []
|
|
1155
|
+
title_field = None
|
|
1156
|
+
boolean_field = None
|
|
1157
|
+
|
|
1158
|
+
# Find primary title field and boolean field
|
|
1159
|
+
for field_name, field_type in fields.items():
|
|
1160
|
+
if field_name.lower() in {"id", "createdat", "updatedat"}:
|
|
1161
|
+
continue
|
|
1162
|
+
if field_name.lower() in {"title", "name"} and not title_field:
|
|
1163
|
+
title_field = field_name
|
|
1164
|
+
if field_type.lower() == "boolean" and not boolean_field:
|
|
1165
|
+
boolean_field = field_name
|
|
1166
|
+
|
|
1167
|
+
# Generate checkbox + title combo for boolean fields (e.g., completed todo)
|
|
1168
|
+
if boolean_field and title_field:
|
|
1169
|
+
# Render checkbox with title that has strikethrough when boolean is true
|
|
1170
|
+
display_fields.append(
|
|
1171
|
+
f"""<div className="flex items-center gap-4">
|
|
1172
|
+
<div className="relative">
|
|
1173
|
+
<input
|
|
1174
|
+
type="checkbox"
|
|
1175
|
+
checked={{item.{boolean_field}}}
|
|
1176
|
+
readOnly
|
|
1177
|
+
className="w-6 h-6 rounded-lg border-2 border-slate-600 bg-slate-800/50 checked:bg-gradient-to-r checked:from-indigo-500 checked:to-purple-500 checked:border-transparent appearance-none cursor-pointer transition-all duration-300"
|
|
1178
|
+
/>
|
|
1179
|
+
{{item.{boolean_field} && (
|
|
1180
|
+
<svg className="absolute inset-0 w-6 h-6 text-white pointer-events-none p-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1181
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{3}} d="M5 13l4 4L19 7" />
|
|
1182
|
+
</svg>
|
|
1183
|
+
)}}
|
|
1184
|
+
</div>
|
|
1185
|
+
<h3 className={{`font-semibold text-lg ${{item.{boolean_field} ? "line-through text-slate-500" : "text-slate-100"}}`}}>
|
|
1186
|
+
{{item.{title_field}}}
|
|
1187
|
+
</h3>
|
|
1188
|
+
</div>"""
|
|
1189
|
+
)
|
|
1190
|
+
elif title_field:
|
|
1191
|
+
# Just render title without checkbox
|
|
1192
|
+
display_fields.append(
|
|
1193
|
+
f'<h3 className="font-semibold text-lg text-slate-100">{{item.{title_field}}}</h3>'
|
|
1194
|
+
)
|
|
1195
|
+
|
|
1196
|
+
# Add remaining non-boolean, non-title fields as secondary text
|
|
1197
|
+
for field_name, field_type in list(fields.items())[:max_fields]:
|
|
1198
|
+
if field_name.lower() in {"id", "createdat", "updatedat"}:
|
|
1199
|
+
continue
|
|
1200
|
+
if field_name == title_field or field_name == boolean_field:
|
|
1201
|
+
continue
|
|
1202
|
+
if field_type.lower() != "boolean":
|
|
1203
|
+
display_fields.append(
|
|
1204
|
+
f'<p className="text-slate-400 text-sm mt-1">{{item.{field_name}}}</p>'
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1207
|
+
return (
|
|
1208
|
+
"\n ".join(display_fields)
|
|
1209
|
+
if display_fields
|
|
1210
|
+
else '<p className="text-slate-400">{{item.id}}</p>'
|
|
1211
|
+
)
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
def generate_new_page(resource_name: str) -> str:
|
|
1215
|
+
"""Generate a 'new' page component that uses the form component.
|
|
1216
|
+
|
|
1217
|
+
Args:
|
|
1218
|
+
resource_name: Name of the resource (e.g., "todo", "product")
|
|
1219
|
+
|
|
1220
|
+
Returns:
|
|
1221
|
+
Complete TypeScript/React page component code
|
|
1222
|
+
"""
|
|
1223
|
+
resource = resource_name.lower()
|
|
1224
|
+
Resource = resource_name.capitalize()
|
|
1225
|
+
|
|
1226
|
+
return CLIENT_COMPONENT_NEW_PAGE.format(resource=resource, Resource=Resource)
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
def generate_detail_page(resource_name: str, fields: dict) -> str:
|
|
1230
|
+
"""Generate an edit page with pre-populated form fields.
|
|
1231
|
+
|
|
1232
|
+
Args:
|
|
1233
|
+
resource_name: Name of the resource (e.g., "todo", "product")
|
|
1234
|
+
fields: Dictionary of field names to types
|
|
1235
|
+
|
|
1236
|
+
Returns:
|
|
1237
|
+
Complete TypeScript/React page component code
|
|
1238
|
+
"""
|
|
1239
|
+
resource = resource_name.lower()
|
|
1240
|
+
Resource = resource_name.capitalize()
|
|
1241
|
+
|
|
1242
|
+
# Generate TypeScript interface fields
|
|
1243
|
+
interface_lines = []
|
|
1244
|
+
form_state_lines = []
|
|
1245
|
+
populate_lines = []
|
|
1246
|
+
save_body_lines = []
|
|
1247
|
+
form_field_lines = []
|
|
1248
|
+
|
|
1249
|
+
for field_name, field_type in fields.items():
|
|
1250
|
+
if field_name.lower() in {"id", "createdat", "updatedat"}:
|
|
1251
|
+
continue
|
|
1252
|
+
|
|
1253
|
+
# TypeScript types
|
|
1254
|
+
ts_type = _get_typescript_type(field_type)
|
|
1255
|
+
interface_lines.append(f" {field_name}: {ts_type};")
|
|
1256
|
+
|
|
1257
|
+
# useState declarations
|
|
1258
|
+
default_val = _get_default_value(field_type)
|
|
1259
|
+
form_state_lines.append(
|
|
1260
|
+
f" const [{field_name}, set{field_name.capitalize()}] = useState<{ts_type}>({default_val});"
|
|
1261
|
+
)
|
|
1262
|
+
|
|
1263
|
+
# Populate from API response
|
|
1264
|
+
populate_lines.append(
|
|
1265
|
+
f" set{field_name.capitalize()}(data.{field_name});"
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
# Save body
|
|
1269
|
+
save_body_lines.append(f" {field_name},")
|
|
1270
|
+
|
|
1271
|
+
# Form field JSX
|
|
1272
|
+
label = field_name.replace("_", " ").title()
|
|
1273
|
+
form_field_lines.append(
|
|
1274
|
+
_generate_edit_form_field(field_name, field_type, label)
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
return SERVER_COMPONENT_DETAIL.format(
|
|
1278
|
+
resource=resource,
|
|
1279
|
+
Resource=Resource,
|
|
1280
|
+
interface_fields="\n".join(interface_lines),
|
|
1281
|
+
form_state="\n".join(form_state_lines),
|
|
1282
|
+
populate_fields="\n".join(populate_lines),
|
|
1283
|
+
save_body="\n".join(save_body_lines),
|
|
1284
|
+
form_fields="\n\n".join(form_field_lines),
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
def _get_typescript_type(field_type: str) -> str:
|
|
1289
|
+
"""Convert field type to TypeScript type."""
|
|
1290
|
+
type_lower = field_type.lower()
|
|
1291
|
+
if type_lower == "boolean":
|
|
1292
|
+
return "boolean"
|
|
1293
|
+
if type_lower in ["number", "int", "integer", "float"]:
|
|
1294
|
+
return "number"
|
|
1295
|
+
return "string"
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
def _get_default_value(field_type: str) -> str:
|
|
1299
|
+
"""Get default value for useState based on field type."""
|
|
1300
|
+
type_lower = field_type.lower()
|
|
1301
|
+
if type_lower == "boolean":
|
|
1302
|
+
return "false"
|
|
1303
|
+
if type_lower in ["number", "int", "integer", "float"]:
|
|
1304
|
+
return "0"
|
|
1305
|
+
return '""'
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
def _generate_edit_form_field(field_name: str, field_type: str, label: str) -> str:
|
|
1309
|
+
"""Generate a single form field for editing."""
|
|
1310
|
+
type_lower = field_type.lower()
|
|
1311
|
+
|
|
1312
|
+
if type_lower == "boolean":
|
|
1313
|
+
return f""" <div className="mb-6">
|
|
1314
|
+
<label className="flex items-center gap-3 cursor-pointer">
|
|
1315
|
+
<input
|
|
1316
|
+
type="checkbox"
|
|
1317
|
+
checked={{{field_name}}}
|
|
1318
|
+
onChange={{(e) => set{field_name.capitalize()}(e.target.checked)}}
|
|
1319
|
+
className="w-6 h-6 rounded-lg border-2 border-slate-600 bg-slate-800/50 checked:bg-gradient-to-r checked:from-indigo-500 checked:to-purple-500 checked:border-transparent appearance-none cursor-pointer transition-all duration-300"
|
|
1320
|
+
/>
|
|
1321
|
+
<span className="text-slate-200 font-medium">{label}</span>
|
|
1322
|
+
</label>
|
|
1323
|
+
</div>"""
|
|
1324
|
+
elif type_lower in ["number", "int", "integer", "float"]:
|
|
1325
|
+
return f""" <div className="mb-6">
|
|
1326
|
+
<label className="block text-sm font-medium text-slate-400 mb-2">{label}</label>
|
|
1327
|
+
<input
|
|
1328
|
+
type="number"
|
|
1329
|
+
value={{{field_name}}}
|
|
1330
|
+
onChange={{(e) => set{field_name.capitalize()}(parseFloat(e.target.value) || 0)}}
|
|
1331
|
+
className="input-field"
|
|
1332
|
+
/>
|
|
1333
|
+
</div>"""
|
|
1334
|
+
else:
|
|
1335
|
+
# String/text fields
|
|
1336
|
+
return f""" <div className="mb-6">
|
|
1337
|
+
<label className="block text-sm font-medium text-slate-400 mb-2">{label}</label>
|
|
1338
|
+
<input
|
|
1339
|
+
type="text"
|
|
1340
|
+
value={{{field_name}}}
|
|
1341
|
+
onChange={{(e) => set{field_name.capitalize()}(e.target.value)}}
|
|
1342
|
+
className="input-field"
|
|
1343
|
+
required
|
|
1344
|
+
/>
|
|
1345
|
+
</div>"""
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
def generate_actions_component(resource_name: str) -> str:
|
|
1349
|
+
"""Generate the actions component for delete functionality.
|
|
1350
|
+
|
|
1351
|
+
Args:
|
|
1352
|
+
resource_name: Name of the resource (e.g., "todo", "product")
|
|
1353
|
+
|
|
1354
|
+
Returns:
|
|
1355
|
+
Complete TypeScript/React client component code
|
|
1356
|
+
"""
|
|
1357
|
+
resource = resource_name.lower()
|
|
1358
|
+
Resource = resource_name.capitalize()
|
|
1359
|
+
|
|
1360
|
+
return CLIENT_COMPONENT_ACTIONS.format(resource=resource, Resource=Resource)
|
|
1361
|
+
|
|
1362
|
+
|
|
1363
|
+
def _generate_detail_field_display(resource: str, fields: dict) -> str:
|
|
1364
|
+
"""Generate JSX for displaying resource fields in detail view.
|
|
1365
|
+
|
|
1366
|
+
Boolean fields render as visual checkboxes with strikethrough for completed items.
|
|
1367
|
+
Uses modern dark theme styling.
|
|
1368
|
+
|
|
1369
|
+
Args:
|
|
1370
|
+
resource: Resource variable name (e.g., "todo")
|
|
1371
|
+
fields: Dictionary mapping field names to their types
|
|
1372
|
+
|
|
1373
|
+
Returns:
|
|
1374
|
+
JSX string for detail field display
|
|
1375
|
+
"""
|
|
1376
|
+
display_fields = []
|
|
1377
|
+
|
|
1378
|
+
# Find title and boolean fields for special combined rendering
|
|
1379
|
+
title_field = None
|
|
1380
|
+
boolean_field = None
|
|
1381
|
+
for field_name, field_type in fields.items():
|
|
1382
|
+
if field_name.lower() in {"title", "name"} and not title_field:
|
|
1383
|
+
title_field = field_name
|
|
1384
|
+
if field_type.lower() == "boolean" and not boolean_field:
|
|
1385
|
+
boolean_field = field_name
|
|
1386
|
+
|
|
1387
|
+
for field_name, field_type in fields.items():
|
|
1388
|
+
if field_name.lower() in {"id", "createdat", "updatedat"}:
|
|
1389
|
+
continue
|
|
1390
|
+
|
|
1391
|
+
label = field_name.replace("_", " ").title()
|
|
1392
|
+
|
|
1393
|
+
if field_type.lower() == "boolean":
|
|
1394
|
+
# Render checkbox with visual feedback
|
|
1395
|
+
display_fields.append(
|
|
1396
|
+
f' <div className="mb-6 p-4 bg-slate-900/30 rounded-xl border border-slate-700/30">\n'
|
|
1397
|
+
f' <label className="block text-sm font-medium text-slate-400 mb-3">{label}</label>\n'
|
|
1398
|
+
f' <div className="flex items-center gap-3">\n'
|
|
1399
|
+
f' <div className="relative">\n'
|
|
1400
|
+
f" <input\n"
|
|
1401
|
+
f' type="checkbox"\n'
|
|
1402
|
+
f" checked={{{resource}.{field_name}}}\n"
|
|
1403
|
+
f" readOnly\n"
|
|
1404
|
+
f' className="w-6 h-6 rounded-lg border-2 border-slate-600 bg-slate-800/50 checked:bg-gradient-to-r checked:from-indigo-500 checked:to-purple-500 checked:border-transparent appearance-none cursor-default transition-all duration-300"\n'
|
|
1405
|
+
f" />\n"
|
|
1406
|
+
f" {{{resource}.{field_name} && (\n"
|
|
1407
|
+
f' <svg className="absolute inset-0 w-6 h-6 text-white pointer-events-none p-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n'
|
|
1408
|
+
f' <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={{3}} d="M5 13l4 4L19 7" />\n'
|
|
1409
|
+
f" </svg>\n"
|
|
1410
|
+
f" )}}\n"
|
|
1411
|
+
f" </div>\n"
|
|
1412
|
+
f' <span className={{{resource}.{field_name} ? "text-emerald-400 font-medium" : "text-slate-500"}}>\n'
|
|
1413
|
+
f' {{{resource}.{field_name} ? "Yes" : "No"}}\n'
|
|
1414
|
+
f" </span>\n"
|
|
1415
|
+
f" </div>\n"
|
|
1416
|
+
f" </div>"
|
|
1417
|
+
)
|
|
1418
|
+
elif field_type.lower() in ["date", "datetime", "timestamp"]:
|
|
1419
|
+
display_fields.append(
|
|
1420
|
+
f' <div className="mb-6">\n'
|
|
1421
|
+
f' <label className="block text-sm font-medium text-slate-400 mb-2">{label}</label>\n'
|
|
1422
|
+
f' <p className="text-lg text-slate-200">{{new Date({resource}.{field_name}).toLocaleDateString()}}</p>\n'
|
|
1423
|
+
f" </div>"
|
|
1424
|
+
)
|
|
1425
|
+
else:
|
|
1426
|
+
# For title field with boolean, add strikethrough styling
|
|
1427
|
+
if field_name == title_field and boolean_field:
|
|
1428
|
+
class_expr = f'{{{resource}.{boolean_field} ? "text-xl text-slate-500 line-through" : "text-xl text-slate-100"}}'
|
|
1429
|
+
display_fields.append(
|
|
1430
|
+
f' <div className="mb-6">\n'
|
|
1431
|
+
f' <label className="block text-sm font-medium text-slate-400 mb-2">{label}</label>\n'
|
|
1432
|
+
f" <p className={class_expr}>{{{resource}.{field_name}}}</p>\n"
|
|
1433
|
+
f" </div>"
|
|
1434
|
+
)
|
|
1435
|
+
else:
|
|
1436
|
+
display_fields.append(
|
|
1437
|
+
f' <div className="mb-6">\n'
|
|
1438
|
+
f' <label className="block text-sm font-medium text-slate-400 mb-2">{label}</label>\n'
|
|
1439
|
+
f' <p className="text-xl text-slate-100">{{{resource}.{field_name}}}</p>\n'
|
|
1440
|
+
f" </div>"
|
|
1441
|
+
)
|
|
1442
|
+
|
|
1443
|
+
return (
|
|
1444
|
+
"\n".join(display_fields)
|
|
1445
|
+
if display_fields
|
|
1446
|
+
else ' <p className="text-slate-400">No fields to display</p>'
|
|
1447
|
+
)
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
def _map_type_to_typescript(field_type: str) -> str:
|
|
1451
|
+
"""Map field type to TypeScript type."""
|
|
1452
|
+
type_mapping = {
|
|
1453
|
+
"string": "string",
|
|
1454
|
+
"text": "string",
|
|
1455
|
+
"number": "number",
|
|
1456
|
+
"float": "number",
|
|
1457
|
+
"boolean": "boolean",
|
|
1458
|
+
"date": "Date",
|
|
1459
|
+
"datetime": "Date",
|
|
1460
|
+
"timestamp": "Date",
|
|
1461
|
+
"email": "string",
|
|
1462
|
+
"url": "string",
|
|
1463
|
+
}
|
|
1464
|
+
return type_mapping.get(field_type.lower(), "string")
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
# ========== Test Templates (Vitest) ==========
|
|
1468
|
+
|
|
1469
|
+
VITEST_CONFIG = """import { defineConfig } from 'vitest/config';
|
|
1470
|
+
import react from '@vitejs/plugin-react';
|
|
1471
|
+
import path from 'path';
|
|
1472
|
+
|
|
1473
|
+
export default defineConfig({
|
|
1474
|
+
plugins: [react()],
|
|
1475
|
+
test: {
|
|
1476
|
+
environment: 'jsdom',
|
|
1477
|
+
globals: true,
|
|
1478
|
+
setupFiles: ['./tests/setup.ts'],
|
|
1479
|
+
include: ['**/__tests__/**/*.test.{ts,tsx}'],
|
|
1480
|
+
coverage: {
|
|
1481
|
+
provider: 'v8',
|
|
1482
|
+
reporter: ['text', 'json', 'html'],
|
|
1483
|
+
},
|
|
1484
|
+
},
|
|
1485
|
+
resolve: {
|
|
1486
|
+
alias: {
|
|
1487
|
+
'@': path.resolve(__dirname, './src'),
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
});
|
|
1491
|
+
"""
|
|
1492
|
+
|
|
1493
|
+
TEST_SETUP = """import '@testing-library/jest-dom';
|
|
1494
|
+
import {{ vi }} from 'vitest';
|
|
1495
|
+
|
|
1496
|
+
// Mock next/navigation
|
|
1497
|
+
vi.mock('next/navigation', () => ({{
|
|
1498
|
+
useRouter: () => ({{
|
|
1499
|
+
push: vi.fn(),
|
|
1500
|
+
back: vi.fn(),
|
|
1501
|
+
refresh: vi.fn(),
|
|
1502
|
+
replace: vi.fn(),
|
|
1503
|
+
}}),
|
|
1504
|
+
usePathname: () => '/',
|
|
1505
|
+
useSearchParams: () => new URLSearchParams(),
|
|
1506
|
+
}}));
|
|
1507
|
+
|
|
1508
|
+
// Mock Prisma client
|
|
1509
|
+
vi.mock('@/lib/prisma', () => ({{
|
|
1510
|
+
prisma: {{
|
|
1511
|
+
{resource}: {{
|
|
1512
|
+
findMany: vi.fn(),
|
|
1513
|
+
findUnique: vi.fn(),
|
|
1514
|
+
create: vi.fn(),
|
|
1515
|
+
update: vi.fn(),
|
|
1516
|
+
delete: vi.fn(),
|
|
1517
|
+
count: vi.fn(),
|
|
1518
|
+
}},
|
|
1519
|
+
}},
|
|
1520
|
+
}}));
|
|
1521
|
+
"""
|
|
1522
|
+
|
|
1523
|
+
|
|
1524
|
+
COMPONENT_TEST_FORM = """import {{ describe, it, expect, vi, beforeEach }} from 'vitest';
|
|
1525
|
+
import {{ render, screen, fireEvent, waitFor }} from '@testing-library/react';
|
|
1526
|
+
import {{ {Resource}Form }} from '../{Resource}Form';
|
|
1527
|
+
|
|
1528
|
+
// Mock fetch
|
|
1529
|
+
global.fetch = vi.fn();
|
|
1530
|
+
|
|
1531
|
+
describe('{Resource}Form', () => {{
|
|
1532
|
+
beforeEach(() => {{
|
|
1533
|
+
vi.clearAllMocks();
|
|
1534
|
+
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({{
|
|
1535
|
+
ok: true,
|
|
1536
|
+
json: () => Promise.resolve({{ id: 1 }}),
|
|
1537
|
+
}});
|
|
1538
|
+
}});
|
|
1539
|
+
|
|
1540
|
+
it('renders all form fields', () => {{
|
|
1541
|
+
render(<{Resource}Form />);
|
|
1542
|
+
|
|
1543
|
+
{form_field_assertions}
|
|
1544
|
+
}});
|
|
1545
|
+
|
|
1546
|
+
it('submits form data correctly in create mode', async () => {{
|
|
1547
|
+
render(<{Resource}Form mode="create" />);
|
|
1548
|
+
|
|
1549
|
+
{form_fill_actions}
|
|
1550
|
+
|
|
1551
|
+
fireEvent.click(screen.getByRole('button', {{ name: /create/i }}));
|
|
1552
|
+
|
|
1553
|
+
await waitFor(() => {{
|
|
1554
|
+
expect(fetch).toHaveBeenCalledWith('/api/{resource_plural}', expect.objectContaining({{
|
|
1555
|
+
method: 'POST',
|
|
1556
|
+
headers: {{ 'Content-Type': 'application/json' }},
|
|
1557
|
+
}}));
|
|
1558
|
+
}});
|
|
1559
|
+
}});
|
|
1560
|
+
|
|
1561
|
+
it('submits form data correctly in edit mode', async () => {{
|
|
1562
|
+
const initialData = {{ id: 1, {test_data_fields} }};
|
|
1563
|
+
render(<{Resource}Form initialData={{initialData}} mode="edit" />);
|
|
1564
|
+
|
|
1565
|
+
fireEvent.click(screen.getByRole('button', {{ name: /update/i }}));
|
|
1566
|
+
|
|
1567
|
+
await waitFor(() => {{
|
|
1568
|
+
expect(fetch).toHaveBeenCalledWith('/api/{resource_plural}/1', expect.objectContaining({{
|
|
1569
|
+
method: 'PATCH',
|
|
1570
|
+
}}));
|
|
1571
|
+
}});
|
|
1572
|
+
}});
|
|
1573
|
+
|
|
1574
|
+
it('displays error message on failed submission', async () => {{
|
|
1575
|
+
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({{
|
|
1576
|
+
ok: false,
|
|
1577
|
+
json: () => Promise.resolve({{ error: 'Validation failed' }}),
|
|
1578
|
+
}});
|
|
1579
|
+
|
|
1580
|
+
render(<{Resource}Form />);
|
|
1581
|
+
|
|
1582
|
+
{form_fill_actions}
|
|
1583
|
+
|
|
1584
|
+
fireEvent.click(screen.getByRole('button', {{ name: /create/i }}));
|
|
1585
|
+
|
|
1586
|
+
await waitFor(() => {{
|
|
1587
|
+
expect(screen.getByText(/validation failed/i)).toBeInTheDocument();
|
|
1588
|
+
}});
|
|
1589
|
+
}});
|
|
1590
|
+
}});
|
|
1591
|
+
"""
|
|
1592
|
+
|
|
1593
|
+
COMPONENT_TEST_ACTIONS = """import {{ describe, it, expect, vi, beforeEach }} from 'vitest';
|
|
1594
|
+
import {{ render, screen, fireEvent, waitFor }} from '@testing-library/react';
|
|
1595
|
+
import {{ {Resource}Actions }} from '../{Resource}Actions';
|
|
1596
|
+
|
|
1597
|
+
// Mock fetch and confirm
|
|
1598
|
+
global.fetch = vi.fn();
|
|
1599
|
+
global.confirm = vi.fn();
|
|
1600
|
+
|
|
1601
|
+
describe('{Resource}Actions', () => {{
|
|
1602
|
+
beforeEach(() => {{
|
|
1603
|
+
vi.clearAllMocks();
|
|
1604
|
+
(global.confirm as ReturnType<typeof vi.fn>).mockReturnValue(true);
|
|
1605
|
+
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({{
|
|
1606
|
+
ok: true,
|
|
1607
|
+
json: () => Promise.resolve({{ success: true }}),
|
|
1608
|
+
}});
|
|
1609
|
+
}});
|
|
1610
|
+
|
|
1611
|
+
it('renders delete button', () => {{
|
|
1612
|
+
render(<{Resource}Actions {resource}Id={{1}} />);
|
|
1613
|
+
|
|
1614
|
+
expect(screen.getByRole('button', {{ name: /delete/i }})).toBeInTheDocument();
|
|
1615
|
+
}});
|
|
1616
|
+
|
|
1617
|
+
it('confirms before deleting', async () => {{
|
|
1618
|
+
render(<{Resource}Actions {resource}Id={{1}} />);
|
|
1619
|
+
|
|
1620
|
+
fireEvent.click(screen.getByRole('button', {{ name: /delete/i }}));
|
|
1621
|
+
|
|
1622
|
+
expect(confirm).toHaveBeenCalled();
|
|
1623
|
+
}});
|
|
1624
|
+
|
|
1625
|
+
it('calls delete API on confirmation', async () => {{
|
|
1626
|
+
render(<{Resource}Actions {resource}Id={{1}} />);
|
|
1627
|
+
|
|
1628
|
+
fireEvent.click(screen.getByRole('button', {{ name: /delete/i }}));
|
|
1629
|
+
|
|
1630
|
+
await waitFor(() => {{
|
|
1631
|
+
expect(fetch).toHaveBeenCalledWith('/api/{resource_plural}/1', {{
|
|
1632
|
+
method: 'DELETE',
|
|
1633
|
+
}});
|
|
1634
|
+
}});
|
|
1635
|
+
}});
|
|
1636
|
+
|
|
1637
|
+
it('does not call API when delete is cancelled', () => {{
|
|
1638
|
+
(global.confirm as ReturnType<typeof vi.fn>).mockReturnValue(false);
|
|
1639
|
+
|
|
1640
|
+
render(<{Resource}Actions {resource}Id={{1}} />);
|
|
1641
|
+
|
|
1642
|
+
fireEvent.click(screen.getByRole('button', {{ name: /delete/i }}));
|
|
1643
|
+
|
|
1644
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
1645
|
+
}});
|
|
1646
|
+
|
|
1647
|
+
it('displays error message on failed deletion', async () => {{
|
|
1648
|
+
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({{
|
|
1649
|
+
ok: false,
|
|
1650
|
+
}});
|
|
1651
|
+
|
|
1652
|
+
render(<{Resource}Actions {resource}Id={{1}} />);
|
|
1653
|
+
|
|
1654
|
+
fireEvent.click(screen.getByRole('button', {{ name: /delete/i }}));
|
|
1655
|
+
|
|
1656
|
+
await waitFor(() => {{
|
|
1657
|
+
expect(screen.getByText(/failed to delete/i)).toBeInTheDocument();
|
|
1658
|
+
}});
|
|
1659
|
+
}});
|
|
1660
|
+
}});
|
|
1661
|
+
"""
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
def generate_test_data_fields(fields: dict, variant: int = 1) -> str:
|
|
1665
|
+
"""Generate test data fields for test templates.
|
|
1666
|
+
|
|
1667
|
+
Args:
|
|
1668
|
+
fields: Dictionary of field names to types
|
|
1669
|
+
variant: Variant number for different test data
|
|
1670
|
+
|
|
1671
|
+
Returns:
|
|
1672
|
+
String of test data field assignments
|
|
1673
|
+
"""
|
|
1674
|
+
test_values = []
|
|
1675
|
+
for field_name, field_type in fields.items():
|
|
1676
|
+
if field_name in ["id", "createdAt", "updatedAt"]:
|
|
1677
|
+
continue
|
|
1678
|
+
|
|
1679
|
+
normalized_type = field_type.lower()
|
|
1680
|
+
if normalized_type == "boolean":
|
|
1681
|
+
value = "true" if variant == 1 else "false"
|
|
1682
|
+
elif normalized_type in ["number", "int", "float"]:
|
|
1683
|
+
value = str(variant * 10)
|
|
1684
|
+
elif normalized_type == "email":
|
|
1685
|
+
value = f'"test{variant}@example.com"'
|
|
1686
|
+
elif normalized_type == "url":
|
|
1687
|
+
value = f'"https://example{variant}.com"'
|
|
1688
|
+
else:
|
|
1689
|
+
value = f'"{field_name.title()} {variant}"'
|
|
1690
|
+
|
|
1691
|
+
test_values.append(f"{field_name}: {value}")
|
|
1692
|
+
|
|
1693
|
+
return ", ".join(test_values)
|
|
1694
|
+
|
|
1695
|
+
|
|
1696
|
+
def generate_form_field_assertions(fields: dict) -> str:
|
|
1697
|
+
"""Generate test assertions for form field presence.
|
|
1698
|
+
|
|
1699
|
+
Args:
|
|
1700
|
+
fields: Dictionary of field names to types
|
|
1701
|
+
|
|
1702
|
+
Returns:
|
|
1703
|
+
String of expect statements
|
|
1704
|
+
"""
|
|
1705
|
+
assertions = []
|
|
1706
|
+
for field_name, field_type in fields.items():
|
|
1707
|
+
if field_name in ["id", "createdAt", "updatedAt"]:
|
|
1708
|
+
continue
|
|
1709
|
+
|
|
1710
|
+
label = field_name.replace("_", " ").title()
|
|
1711
|
+
if field_type.lower() == "boolean":
|
|
1712
|
+
assertions.append(
|
|
1713
|
+
f"expect(screen.getByLabelText(/{label}/i)).toBeInTheDocument();"
|
|
1714
|
+
)
|
|
1715
|
+
else:
|
|
1716
|
+
assertions.append(
|
|
1717
|
+
f"expect(screen.getByLabelText(/{label}/i)).toBeInTheDocument();"
|
|
1718
|
+
)
|
|
1719
|
+
|
|
1720
|
+
return "\n ".join(assertions)
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
def generate_form_fill_actions(fields: dict) -> str:
|
|
1724
|
+
"""Generate test actions to fill form fields.
|
|
1725
|
+
|
|
1726
|
+
Args:
|
|
1727
|
+
fields: Dictionary of field names to types
|
|
1728
|
+
|
|
1729
|
+
Returns:
|
|
1730
|
+
String of fireEvent calls
|
|
1731
|
+
"""
|
|
1732
|
+
actions = []
|
|
1733
|
+
for field_name, field_type in fields.items():
|
|
1734
|
+
if field_name in ["id", "createdAt", "updatedAt"]:
|
|
1735
|
+
continue
|
|
1736
|
+
|
|
1737
|
+
label = field_name.replace("_", " ").title()
|
|
1738
|
+
normalized_type = field_type.lower()
|
|
1739
|
+
|
|
1740
|
+
if normalized_type == "boolean":
|
|
1741
|
+
actions.append(f"fireEvent.click(screen.getByLabelText(/{label}/i));")
|
|
1742
|
+
else:
|
|
1743
|
+
test_value = "Test Value"
|
|
1744
|
+
if normalized_type in ["number", "int", "float"]:
|
|
1745
|
+
test_value = "42"
|
|
1746
|
+
elif normalized_type == "email":
|
|
1747
|
+
test_value = "test@example.com"
|
|
1748
|
+
elif normalized_type == "url":
|
|
1749
|
+
test_value = "https://example.com"
|
|
1750
|
+
|
|
1751
|
+
actions.append(
|
|
1752
|
+
f"fireEvent.change(screen.getByLabelText(/{label}/i), {{ target: {{ value: '{test_value}' }} }});"
|
|
1753
|
+
)
|
|
1754
|
+
|
|
1755
|
+
return "\n ".join(actions)
|
|
1756
|
+
|
|
1757
|
+
|
|
1758
|
+
# ========== Style Test Templates (Issue #1002) ==========
|
|
1759
|
+
|
|
1760
|
+
STYLE_TEST_TEMPLATE = """import { describe, it, expect, beforeAll } from 'vitest';
|
|
1761
|
+
import * as fs from 'fs';
|
|
1762
|
+
import * as path from 'path';
|
|
1763
|
+
|
|
1764
|
+
describe('Global CSS Integrity', () => {
|
|
1765
|
+
const globalsPath = path.join(process.cwd(), 'src/app/globals.css');
|
|
1766
|
+
let cssContent: string;
|
|
1767
|
+
|
|
1768
|
+
beforeAll(() => {
|
|
1769
|
+
cssContent = fs.readFileSync(globalsPath, 'utf-8');
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
describe('File Content Type (CRITICAL - Issue #1002)', () => {
|
|
1773
|
+
it('is valid CSS, not TypeScript/JavaScript', () => {
|
|
1774
|
+
// These patterns indicate wrong file content - always invalid
|
|
1775
|
+
expect(cssContent).not.toMatch(/^\\s*import\\s+.*from/m);
|
|
1776
|
+
expect(cssContent).not.toMatch(/^\\s*export\\s+(default|const|function|class)/m);
|
|
1777
|
+
expect(cssContent).not.toMatch(/"use client"|'use client'/);
|
|
1778
|
+
expect(cssContent).not.toMatch(/^\\s*interface\\s+\\w+/m);
|
|
1779
|
+
expect(cssContent).not.toMatch(/^\\s*type\\s+\\w+\\s*=/m);
|
|
1780
|
+
expect(cssContent).not.toMatch(/<[A-Z][a-zA-Z]*[\\s/>]/); // JSX tags
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
it('has balanced CSS braces', () => {
|
|
1784
|
+
const open = (cssContent.match(/\\{/g) || []).length;
|
|
1785
|
+
const close = (cssContent.match(/\\}/g) || []).length;
|
|
1786
|
+
expect(open).toBe(close);
|
|
1787
|
+
});
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
describe('Tailwind Framework', () => {
|
|
1791
|
+
it('includes Tailwind directives', () => {
|
|
1792
|
+
// At minimum, CSS should have Tailwind setup
|
|
1793
|
+
const hasTailwind =
|
|
1794
|
+
cssContent.includes('@tailwind') ||
|
|
1795
|
+
cssContent.includes('@import "tailwindcss');
|
|
1796
|
+
expect(hasTailwind).toBe(true);
|
|
1797
|
+
});
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
describe('Design System Classes', () => {
|
|
1801
|
+
it('defines glass-card class', () => {
|
|
1802
|
+
expect(cssContent).toContain('.glass-card');
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1805
|
+
it('defines btn-primary class', () => {
|
|
1806
|
+
expect(cssContent).toContain('.btn-primary');
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1809
|
+
it('defines page-title class', () => {
|
|
1810
|
+
expect(cssContent).toContain('.page-title');
|
|
1811
|
+
});
|
|
1812
|
+
});
|
|
1813
|
+
});
|
|
1814
|
+
"""
|
|
1815
|
+
|
|
1816
|
+
ROUTES_TEST_TEMPLATE = """import {{ describe, it, expect }} from 'vitest';
|
|
1817
|
+
import * as fs from 'fs';
|
|
1818
|
+
import * as path from 'path';
|
|
1819
|
+
import {{ glob }} from 'glob';
|
|
1820
|
+
|
|
1821
|
+
describe('Next.js App Router Structure', () => {{
|
|
1822
|
+
const appDir = path.join(process.cwd(), 'src/app');
|
|
1823
|
+
|
|
1824
|
+
describe('Root Layout (Global Styles Entry Point)', () => {{
|
|
1825
|
+
it('layout.tsx exists', () => {{
|
|
1826
|
+
const layoutPath = path.join(appDir, 'layout.tsx');
|
|
1827
|
+
expect(fs.existsSync(layoutPath)).toBe(true);
|
|
1828
|
+
}});
|
|
1829
|
+
|
|
1830
|
+
it('layout imports globals.css', () => {{
|
|
1831
|
+
const layoutPath = path.join(appDir, 'layout.tsx');
|
|
1832
|
+
const content = fs.readFileSync(layoutPath, 'utf-8');
|
|
1833
|
+
// Should import globals.css (various import patterns)
|
|
1834
|
+
expect(content).toMatch(/import\\s+['"]\\.\\/globals\\.css['"]|import\\s+['"]@\\/app\\/globals\\.css['"]/);
|
|
1835
|
+
}});
|
|
1836
|
+
}});
|
|
1837
|
+
|
|
1838
|
+
describe('Page Structure', () => {{
|
|
1839
|
+
it('all page.tsx files are valid React components', () => {{
|
|
1840
|
+
const pages = glob.sync('**/page.tsx', {{ cwd: appDir }});
|
|
1841
|
+
|
|
1842
|
+
for (const page of pages) {{
|
|
1843
|
+
const content = fs.readFileSync(path.join(appDir, page), 'utf-8');
|
|
1844
|
+
|
|
1845
|
+
// Should have an export (default or named)
|
|
1846
|
+
expect(content).toMatch(/export\\s+(default\\s+)?(async\\s+)?function|export\\s+default/);
|
|
1847
|
+
|
|
1848
|
+
// Should not be empty
|
|
1849
|
+
expect(content.trim().length).toBeGreaterThan(50);
|
|
1850
|
+
}}
|
|
1851
|
+
}});
|
|
1852
|
+
|
|
1853
|
+
it('dynamic routes have params handling', () => {{
|
|
1854
|
+
const dynamicPages = glob.sync('**/\\\\[*\\\\]/**/page.tsx', {{ cwd: appDir }});
|
|
1855
|
+
|
|
1856
|
+
for (const page of dynamicPages) {{
|
|
1857
|
+
const content = fs.readFileSync(path.join(appDir, page), 'utf-8');
|
|
1858
|
+
// Should reference params somewhere
|
|
1859
|
+
expect(content).toMatch(/params|searchParams/);
|
|
1860
|
+
}}
|
|
1861
|
+
}});
|
|
1862
|
+
}});
|
|
1863
|
+
|
|
1864
|
+
describe('Styling Consistency', () => {{
|
|
1865
|
+
it('pages use className attributes (styled, not unstyled)', () => {{
|
|
1866
|
+
const pages = glob.sync('**/page.tsx', {{ cwd: appDir, ignore: '**/api/**' }});
|
|
1867
|
+
|
|
1868
|
+
for (const page of pages) {{
|
|
1869
|
+
const content = fs.readFileSync(path.join(appDir, page), 'utf-8');
|
|
1870
|
+
// Each page should have some styling
|
|
1871
|
+
const classNameCount = (content.match(/className=/g) || []).length;
|
|
1872
|
+
expect(classNameCount).toBeGreaterThan(0);
|
|
1873
|
+
}}
|
|
1874
|
+
}});
|
|
1875
|
+
}});
|
|
1876
|
+
|
|
1877
|
+
describe('API Routes Exist', () => {{
|
|
1878
|
+
it('has API routes for CRUD operations', () => {{
|
|
1879
|
+
const apiRoutes = glob.sync('**/route.ts', {{ cwd: path.join(appDir, 'api') }});
|
|
1880
|
+
expect(apiRoutes.length).toBeGreaterThan(0);
|
|
1881
|
+
}});
|
|
1882
|
+
|
|
1883
|
+
it('API routes export HTTP methods', () => {{
|
|
1884
|
+
const apiDir = path.join(appDir, 'api');
|
|
1885
|
+
if (!fs.existsSync(apiDir)) return; // Skip if no API dir
|
|
1886
|
+
|
|
1887
|
+
const apiRoutes = glob.sync('**/route.ts', {{ cwd: apiDir }});
|
|
1888
|
+
|
|
1889
|
+
for (const route of apiRoutes) {{
|
|
1890
|
+
const content = fs.readFileSync(path.join(apiDir, route), 'utf-8');
|
|
1891
|
+
// Should export at least one HTTP method
|
|
1892
|
+
expect(content).toMatch(/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE)/);
|
|
1893
|
+
}}
|
|
1894
|
+
}});
|
|
1895
|
+
}});
|
|
1896
|
+
}});
|
|
1897
|
+
"""
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
def generate_style_test_content(_resource_name: str = "Item") -> str:
|
|
1901
|
+
"""Generate the content for styles.test.ts.
|
|
1902
|
+
|
|
1903
|
+
Args:
|
|
1904
|
+
_resource_name: Resource name for component checks (unused, kept for API compatibility)
|
|
1905
|
+
|
|
1906
|
+
Returns:
|
|
1907
|
+
Complete test file content
|
|
1908
|
+
"""
|
|
1909
|
+
return STYLE_TEST_TEMPLATE
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
def generate_routes_test_content(resource_name: str = "Item") -> str:
|
|
1913
|
+
"""Generate the content for routes.test.ts.
|
|
1914
|
+
|
|
1915
|
+
Args:
|
|
1916
|
+
resource_name: Resource name for route checks
|
|
1917
|
+
|
|
1918
|
+
Returns:
|
|
1919
|
+
Complete test file content
|
|
1920
|
+
"""
|
|
1921
|
+
return ROUTES_TEST_TEMPLATE.format(
|
|
1922
|
+
resource=resource_name.lower(),
|
|
1923
|
+
Resource=resource_name.capitalize(),
|
|
1924
|
+
resource_plural=pluralize(resource_name.lower()),
|
|
1925
|
+
)
|