quadkit-cli 0.0.44__tar.gz → 0.0.45__tar.gz
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.
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/PKG-INFO +5 -5
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/README.md +4 -4
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/pyproject.toml +1 -1
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/new.py +4 -6
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/__init__.py +2 -11
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/_lazy_exports.py +2 -11
- quadkit_cli-0.0.45/src/quadkit/cli/registry/presets.py +104 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/template.py +9 -86
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/scaffold/__init__.py +12 -4
- quadkit_cli-0.0.45/src/quadkit/cli/scaffold/metadata.py +204 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/scaffold/module_files.py +65 -24
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/scaffold/render.py +85 -3
- quadkit_cli-0.0.45/src/quadkit/cli/scaffold/specs.py +90 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/scaffold/support_files.py +18 -4
- quadkit_cli-0.0.45/tests/unit/registry/test_presets.py +87 -0
- quadkit_cli-0.0.45/tests/unit/registry/test_template.py +134 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_new.py +5 -6
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_project_layout.py +6 -8
- quadkit_cli-0.0.45/tests/unit/test_project_metadata.py +178 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_scaffold_and_validators.py +37 -28
- quadkit_cli-0.0.44/src/quadkit/cli/registry/presets.py +0 -219
- quadkit_cli-0.0.44/src/quadkit/cli/scaffold/specs.py +0 -203
- quadkit_cli-0.0.44/tests/unit/registry/test_presets.py +0 -120
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/.gitignore +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/LICENSE +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/NOTICE +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/example.yaml +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/assembly/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/assembly/assembler.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/add.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/config.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/config_utils.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/contrib.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/db.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/db_bootstrap.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/db_commands_data.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/db_commands_migrations.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/db_commands_ops.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/dev.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/events.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/gen.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/init.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/inspect.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/meta.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/project.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/py.typed +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/run.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/shell.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/commands/system.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/config.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/constants.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/contributors/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/contributors/base.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/contributors/core.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/contributors/discovery.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/contributors/registry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/contributors/runtime.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/decorators.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/di/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/di/provider.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/di/sub_providers/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/di/sub_providers/contributor.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/events.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/exceptions.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/generators/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/generators/base.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/generators/field_parser.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/generators/provider.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/generators/test.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/hooks.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/layout.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/lib/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/lib/config_loader.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/lib/entry_point.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/lib/naming.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/lib/templates.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/module.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/output/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/output/manager.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/package_names.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/protocols.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/py.typed +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/backend_base.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/command.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/completion.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/config.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/database.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/database_backends.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/environment.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/formatter.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/generator.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health_checks/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health_checks/base.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health_checks/deps.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health_checks/project.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health_checks/providers.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health_checks/python_env.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health_checks/tools.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/health_checks/vcs_docker.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/hook.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/inspect.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/migration.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/provider.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/provider_catalog.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/provider_registry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/secrets.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/serializer.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/server.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/task.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/telemetry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/version.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/registry/watcher.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/runtime/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/runtime/context.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/runtime/error_handler.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/runtime/main.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/scaffold/app_files.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/scaffold_package.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/templates/provider.py.jinja2 +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/templates/py.typed +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/templates/test_unit.py.jinja2 +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/cli/types.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/src/quadkit/py.typed +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/conftest.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/integration/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/integration/test_cli_lifecycle.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/test_package_structure_p1.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/_test_cli_contribution_system_support.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_config.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_config_happy_paths.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_contrib.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_events_cmd.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_inspect.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_meta.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_run.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_shell.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/commands/test_system.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/di/test_config_alignment.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/lib/test_templates.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/__init__.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_completion.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_database.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_environment.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_formatter.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_health.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_hook.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_inspect_registry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_migration.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_secrets.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_serializer.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_task.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_telemetry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_version.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/registry/test_watcher.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_base_contributor.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_constants.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_context.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_contribution_system.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_contribution_system__testclicontributorsubprovider.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_errors.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_exceptions.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_import_isolation.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_module.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_cli_types.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_config_async.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_config_loader.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_config_save.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_config_validate.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_contributor_registry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_contributor_runtime.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_core_contributor.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_db.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_decorators.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_dev_command.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_error_handler.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_events.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_exception_handling.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_field_parser.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_gen.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_gen_controller.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_gen_provider.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_generator_base.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_generator_option_help.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_generator_registry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_generators_contract.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_health_checks.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_hooks.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_init_command.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_layout.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_main.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_output.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_output_json.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_project.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_provider_registry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_round40_hardening.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_run_detection.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_server_registry.py +0 -0
- {quadkit_cli-0.0.44 → quadkit_cli-0.0.45}/tests/unit/test_storage_driver_generator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quadkit-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.45
|
|
4
4
|
Summary: Command-line interface for Quadkit Framework - Project scaffolding, code generation, and development tools
|
|
5
5
|
Project-URL: Homepage, https://dbtinoy-.github.io/quadkit/
|
|
6
6
|
Project-URL: Repository, https://github.com/dbtinoy-/quadkit
|
|
@@ -94,7 +94,7 @@ Requires **Python >= 3.11**.
|
|
|
94
94
|
There is no code to write — the product is the command line:
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
|
-
# Create a new project (
|
|
97
|
+
# Create a new project (single `api` template); the package
|
|
98
98
|
# inside is my_api/ with app.py exposing create_app()
|
|
99
99
|
quadkit new project my-api
|
|
100
100
|
cd my-api
|
|
@@ -167,10 +167,10 @@ review release notes on upgrade. Report vulnerabilities privately per
|
|
|
167
167
|
|
|
168
168
|
## Stability
|
|
169
169
|
|
|
170
|
-
Version `0.0.
|
|
170
|
+
Version `0.0.45` in the `0.x` series, released in lockstep with the
|
|
171
171
|
other four distributions; APIs may change between minor versions until
|
|
172
|
-
1.0 — pin an exact version (`quadkit-cli==0.0.
|
|
173
|
-
(`>=0.0.
|
|
172
|
+
1.0 — pin an exact version (`quadkit-cli==0.0.45`) or a tight range
|
|
173
|
+
(`>=0.0.45,<0.1.0`). Full policy:
|
|
174
174
|
[stability and compatibility](https://dbtinoy-.github.io/quadkit/reference/stability/).
|
|
175
175
|
|
|
176
176
|
## Links
|
|
@@ -38,7 +38,7 @@ Requires **Python >= 3.11**.
|
|
|
38
38
|
There is no code to write — the product is the command line:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
# Create a new project (
|
|
41
|
+
# Create a new project (single `api` template); the package
|
|
42
42
|
# inside is my_api/ with app.py exposing create_app()
|
|
43
43
|
quadkit new project my-api
|
|
44
44
|
cd my-api
|
|
@@ -111,10 +111,10 @@ review release notes on upgrade. Report vulnerabilities privately per
|
|
|
111
111
|
|
|
112
112
|
## Stability
|
|
113
113
|
|
|
114
|
-
Version `0.0.
|
|
114
|
+
Version `0.0.45` in the `0.x` series, released in lockstep with the
|
|
115
115
|
other four distributions; APIs may change between minor versions until
|
|
116
|
-
1.0 — pin an exact version (`quadkit-cli==0.0.
|
|
117
|
-
(`>=0.0.
|
|
116
|
+
1.0 — pin an exact version (`quadkit-cli==0.0.45`) or a tight range
|
|
117
|
+
(`>=0.0.45,<0.1.0`). Full policy:
|
|
118
118
|
[stability and compatibility](https://dbtinoy-.github.io/quadkit/reference/stability/).
|
|
119
119
|
|
|
120
120
|
## Links
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "quadkit-cli"
|
|
9
|
-
version = "0.0.
|
|
9
|
+
version = "0.0.45"
|
|
10
10
|
description = "Command-line interface for Quadkit Framework - Project scaffolding, code generation, and development tools"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.11"
|
|
@@ -35,10 +35,7 @@ def main(
|
|
|
35
35
|
"api",
|
|
36
36
|
"--template",
|
|
37
37
|
"-t",
|
|
38
|
-
help=(
|
|
39
|
-
"Project template "
|
|
40
|
-
f"(available in this install: {', '.join(template_names())})"
|
|
41
|
-
),
|
|
38
|
+
help=f"Project template (the only template: {', '.join(template_names())})",
|
|
42
39
|
),
|
|
43
40
|
directory: str = typer.Option(".", "--directory", "-d", help="Target directory"),
|
|
44
41
|
interactive: bool = typer.Option(
|
|
@@ -53,13 +50,14 @@ def main(
|
|
|
53
50
|
project_name = _valid_name(name, "Project")
|
|
54
51
|
selected_template = template
|
|
55
52
|
|
|
56
|
-
|
|
53
|
+
names = template_names()
|
|
54
|
+
if interactive and len(names) > 1:
|
|
57
55
|
import click
|
|
58
56
|
|
|
59
57
|
selected_template = typer.prompt(
|
|
60
58
|
"Select a template",
|
|
61
59
|
default="api",
|
|
62
|
-
type=click.Choice(
|
|
60
|
+
type=click.Choice(names),
|
|
63
61
|
)
|
|
64
62
|
|
|
65
63
|
try:
|
|
@@ -128,15 +128,10 @@ if TYPE_CHECKING:
|
|
|
128
128
|
TasksInspector,
|
|
129
129
|
)
|
|
130
130
|
from quadkit.cli.registry.presets import (
|
|
131
|
-
|
|
132
|
-
GraphQLPreset,
|
|
133
|
-
MicroservicePreset,
|
|
134
|
-
MinimalPreset,
|
|
131
|
+
ApiPreset,
|
|
135
132
|
Preset,
|
|
136
133
|
PresetDefinition,
|
|
137
134
|
PresetRegistry,
|
|
138
|
-
WebAPIPreset,
|
|
139
|
-
WorkerPreset,
|
|
140
135
|
)
|
|
141
136
|
from quadkit.cli.registry.provider import (
|
|
142
137
|
AdminProvider,
|
|
@@ -200,15 +195,11 @@ if TYPE_CHECKING:
|
|
|
200
195
|
get_telemetry_status,
|
|
201
196
|
)
|
|
202
197
|
from quadkit.cli.registry.template import (
|
|
203
|
-
|
|
204
|
-
GraphQLTemplate,
|
|
205
|
-
MinimalTemplate,
|
|
198
|
+
ApiTemplate,
|
|
206
199
|
ProjectBuilder,
|
|
207
200
|
ProjectTemplate,
|
|
208
201
|
TemplateConfig,
|
|
209
202
|
TemplateRegistry,
|
|
210
|
-
WebAPITemplate,
|
|
211
|
-
WorkerTemplate,
|
|
212
203
|
)
|
|
213
204
|
from quadkit.cli.registry.version import (
|
|
214
205
|
VersionInfo,
|
|
@@ -11,6 +11,8 @@ from __future__ import annotations
|
|
|
11
11
|
LAZY_IMPORTS: dict[str, str] = {
|
|
12
12
|
"AIProvider": "quadkit.cli.registry.provider",
|
|
13
13
|
"AdminProvider": "quadkit.cli.registry.provider",
|
|
14
|
+
"ApiPreset": "quadkit.cli.registry.presets",
|
|
15
|
+
"ApiTemplate": "quadkit.cli.registry.template",
|
|
14
16
|
"AuthConfigValidator": "quadkit.cli.registry.config",
|
|
15
17
|
"AuthProvider": "quadkit.cli.registry.provider",
|
|
16
18
|
"BashCompletionGenerator": "quadkit.cli.registry.completion",
|
|
@@ -46,16 +48,12 @@ LAZY_IMPORTS: dict[str, str] = {
|
|
|
46
48
|
"FishCompletionGenerator": "quadkit.cli.registry.completion",
|
|
47
49
|
"FormatOptions": "quadkit.cli.registry.formatter",
|
|
48
50
|
"FormatterRegistry": "quadkit.cli.registry.formatter",
|
|
49
|
-
"FullStackPreset": "quadkit.cli.registry.presets",
|
|
50
|
-
"FullStackTemplate": "quadkit.cli.registry.template",
|
|
51
51
|
"GeneratorAdapter": "quadkit.cli.registry.generator",
|
|
52
52
|
"GeneratorRegistry": "quadkit.cli.registry.generator",
|
|
53
53
|
"GeneratorResult": "quadkit.cli.registry.generator",
|
|
54
54
|
"GitCheck": "quadkit.cli.registry.health",
|
|
55
55
|
"GranianBackend": "quadkit.cli.registry.server",
|
|
56
|
-
"GraphQLPreset": "quadkit.cli.registry.presets",
|
|
57
56
|
"GraphQLProvider": "quadkit.cli.registry.provider",
|
|
58
|
-
"GraphQLTemplate": "quadkit.cli.registry.template",
|
|
59
57
|
"GunicornBackend": "quadkit.cli.registry.server",
|
|
60
58
|
"HealthCheck": "quadkit.cli.registry.health",
|
|
61
59
|
"HealthCheckRegistry": "quadkit.cli.registry.health",
|
|
@@ -73,10 +71,7 @@ LAZY_IMPORTS: dict[str, str] = {
|
|
|
73
71
|
"JSONFormatter": "quadkit.cli.registry.formatter",
|
|
74
72
|
"LoggingHook": "quadkit.cli.registry.hook",
|
|
75
73
|
"MessagingProvider": "quadkit.cli.registry.provider",
|
|
76
|
-
"MicroservicePreset": "quadkit.cli.registry.presets",
|
|
77
74
|
"MiddlewareInspector": "quadkit.cli.registry.inspect",
|
|
78
|
-
"MinimalPreset": "quadkit.cli.registry.presets",
|
|
79
|
-
"MinimalTemplate": "quadkit.cli.registry.template",
|
|
80
75
|
"MonitorProvider": "quadkit.cli.registry.provider",
|
|
81
76
|
"MonitoringProvider": "quadkit.cli.registry.provider",
|
|
82
77
|
"MySQLBackend": "quadkit.cli.registry.database",
|
|
@@ -141,11 +136,7 @@ LAZY_IMPORTS: dict[str, str] = {
|
|
|
141
136
|
"VersionInfo": "quadkit.cli.registry.version",
|
|
142
137
|
"VersionRegistry": "quadkit.cli.registry.version",
|
|
143
138
|
"VersionSource": "quadkit.cli.registry.version",
|
|
144
|
-
"WebAPIPreset": "quadkit.cli.registry.presets",
|
|
145
|
-
"WebAPITemplate": "quadkit.cli.registry.template",
|
|
146
139
|
"WebProvider": "quadkit.cli.registry.provider",
|
|
147
|
-
"WorkerPreset": "quadkit.cli.registry.presets",
|
|
148
|
-
"WorkerTemplate": "quadkit.cli.registry.template",
|
|
149
140
|
"YAMLFormatter": "quadkit.cli.registry.formatter",
|
|
150
141
|
"ZshCompletionGenerator": "quadkit.cli.registry.completion",
|
|
151
142
|
"create_database_connection": "quadkit.cli.registry.database",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Presets registry for application presets.
|
|
2
|
+
|
|
3
|
+
This module provides a registry pattern for application presets.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import abc
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, ClassVar
|
|
11
|
+
|
|
12
|
+
from quadkit.cli.package_names import packages_for
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Preset:
|
|
17
|
+
"""A preset configuration of providers."""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
description: str
|
|
21
|
+
packages: list[str] = field(default_factory=list)
|
|
22
|
+
config: dict[str, Any] = field(default_factory=dict)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PresetDefinition(abc.ABC):
|
|
26
|
+
"""Abstract base class for preset definitions."""
|
|
27
|
+
|
|
28
|
+
name: ClassVar[str]
|
|
29
|
+
description: ClassVar[str]
|
|
30
|
+
packages: ClassVar[list[str]] = []
|
|
31
|
+
config: ClassVar[dict[str, Any]] = {}
|
|
32
|
+
|
|
33
|
+
@abc.abstractmethod
|
|
34
|
+
def get_preset(self) -> Preset:
|
|
35
|
+
"""Get the preset configuration."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ApiPreset(PresetDefinition):
|
|
39
|
+
"""The single application preset: REST API with the web framework."""
|
|
40
|
+
|
|
41
|
+
name = "api"
|
|
42
|
+
description = "REST API with the web framework"
|
|
43
|
+
packages = packages_for("web")
|
|
44
|
+
config = {
|
|
45
|
+
"web": {"host": "0.0.0.0", "port": 8000}, # noqa: S104 — preset template default
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def get_preset(self) -> Preset:
|
|
49
|
+
return Preset(
|
|
50
|
+
name=self.name,
|
|
51
|
+
description=self.description,
|
|
52
|
+
packages=self.packages.copy(),
|
|
53
|
+
config=self.config.copy(),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PresetRegistry:
|
|
58
|
+
"""Registry for application presets.
|
|
59
|
+
|
|
60
|
+
Instances are always empty — use :meth:`with_defaults` for the
|
|
61
|
+
in-package built-ins or :meth:`register` for plugin presets.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
self._presets: dict[str, Preset] = {}
|
|
66
|
+
|
|
67
|
+
def register(self, preset_class: type[PresetDefinition]) -> None:
|
|
68
|
+
"""Register a preset class."""
|
|
69
|
+
instance = preset_class()
|
|
70
|
+
preset = instance.get_preset()
|
|
71
|
+
self._presets[preset.name] = preset
|
|
72
|
+
|
|
73
|
+
def get(self, name: str) -> Preset | None:
|
|
74
|
+
"""Get a preset by name."""
|
|
75
|
+
return self._presets.get(name)
|
|
76
|
+
|
|
77
|
+
def get_all(self) -> dict[str, Preset]:
|
|
78
|
+
"""Get all registered presets."""
|
|
79
|
+
return self._presets.copy()
|
|
80
|
+
|
|
81
|
+
def get_choices(self) -> list[str]:
|
|
82
|
+
"""Get list of available preset names."""
|
|
83
|
+
return list(self._presets.keys())
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def _default_entries(cls) -> tuple[type[PresetDefinition], ...]:
|
|
87
|
+
"""Return the built-in presets (a single one today)."""
|
|
88
|
+
return (ApiPreset,)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def with_defaults(cls) -> PresetRegistry:
|
|
92
|
+
"""Return an instance populated with the built-in presets."""
|
|
93
|
+
registry = cls()
|
|
94
|
+
for entry in cls._default_entries():
|
|
95
|
+
registry.register(entry)
|
|
96
|
+
return registry
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__all__ = [
|
|
100
|
+
"ApiPreset",
|
|
101
|
+
"Preset",
|
|
102
|
+
"PresetDefinition",
|
|
103
|
+
"PresetRegistry",
|
|
104
|
+
]
|
|
@@ -9,7 +9,7 @@ import abc
|
|
|
9
9
|
from dataclasses import dataclass, field
|
|
10
10
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
11
11
|
|
|
12
|
-
from quadkit.cli.package_names import packages_for
|
|
12
|
+
from quadkit.cli.package_names import packages_for
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from pathlib import Path
|
|
@@ -43,76 +43,12 @@ class ProjectTemplate(abc.ABC):
|
|
|
43
43
|
return self.packages.copy()
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
class
|
|
47
|
-
"""
|
|
46
|
+
class ApiTemplate(ProjectTemplate):
|
|
47
|
+
"""The single project template: REST API with the web framework."""
|
|
48
48
|
|
|
49
|
-
name = "
|
|
50
|
-
description = "
|
|
51
|
-
packages =
|
|
52
|
-
"quadkit",
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
def get_config(self) -> TemplateConfig:
|
|
56
|
-
return TemplateConfig(
|
|
57
|
-
name=self.name,
|
|
58
|
-
description=self.description,
|
|
59
|
-
packages=self.packages,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class WebAPITemplate(ProjectTemplate):
|
|
64
|
-
"""REST API template."""
|
|
65
|
-
|
|
66
|
-
name = "web-api"
|
|
67
|
-
description = "REST API with web framework"
|
|
68
|
-
packages = packages_for("web", "sql")
|
|
69
|
-
|
|
70
|
-
def get_config(self) -> TemplateConfig:
|
|
71
|
-
return TemplateConfig(
|
|
72
|
-
name=self.name,
|
|
73
|
-
description=self.description,
|
|
74
|
-
packages=self.packages,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class GraphQLTemplate(ProjectTemplate):
|
|
79
|
-
"""GraphQL API template."""
|
|
80
|
-
|
|
81
|
-
name = "graphql"
|
|
82
|
-
description = "GraphQL API with web framework"
|
|
83
|
-
packages = packages_for("web", "sql", "graphql")
|
|
84
|
-
|
|
85
|
-
def get_config(self) -> TemplateConfig:
|
|
86
|
-
return TemplateConfig(
|
|
87
|
-
name=self.name,
|
|
88
|
-
description=self.description,
|
|
89
|
-
packages=self.packages,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class WorkerTemplate(ProjectTemplate):
|
|
94
|
-
"""Background worker template."""
|
|
95
|
-
|
|
96
|
-
name = "worker"
|
|
97
|
-
description = "Background job processor"
|
|
98
|
-
packages = packages_for("sql", "tasks", "queue")
|
|
99
|
-
|
|
100
|
-
def get_config(self) -> TemplateConfig:
|
|
101
|
-
return TemplateConfig(
|
|
102
|
-
name=self.name,
|
|
103
|
-
description=self.description,
|
|
104
|
-
packages=self.packages,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
class FullStackTemplate(ProjectTemplate):
|
|
109
|
-
"""Full-stack application template."""
|
|
110
|
-
|
|
111
|
-
name = "fullstack"
|
|
112
|
-
description = "Full application: auth, cache, storage, events, tasks, HTTP, monitoring"
|
|
113
|
-
packages = packages_for(
|
|
114
|
-
"web", "sql", "auth", "cache", "storage", "events", "tasks", "http", "monitor"
|
|
115
|
-
)
|
|
49
|
+
name = "api"
|
|
50
|
+
description = "REST API with the web framework"
|
|
51
|
+
packages = packages_for("web")
|
|
116
52
|
|
|
117
53
|
def get_config(self) -> TemplateConfig:
|
|
118
54
|
return TemplateConfig(
|
|
@@ -151,17 +87,8 @@ class TemplateRegistry:
|
|
|
151
87
|
|
|
152
88
|
@classmethod
|
|
153
89
|
def _default_entries(cls) -> tuple[type[ProjectTemplate], ...]:
|
|
154
|
-
"""Return templates
|
|
155
|
-
|
|
156
|
-
optional = (
|
|
157
|
-
(GraphQLTemplate, ("sql", "graphql")),
|
|
158
|
-
(WorkerTemplate, ("sql", "tasks", "queue")),
|
|
159
|
-
(FullStackTemplate, ("web", "sql", "auth", "cache", "storage", "events", "tasks", "http", "monitor")),
|
|
160
|
-
)
|
|
161
|
-
for entry, features in optional:
|
|
162
|
-
if all(provider_available(feature) for feature in features):
|
|
163
|
-
entries.append(entry)
|
|
164
|
-
return tuple(entries)
|
|
90
|
+
"""Return the built-in templates (a single one today)."""
|
|
91
|
+
return (ApiTemplate,)
|
|
165
92
|
|
|
166
93
|
@classmethod
|
|
167
94
|
def with_defaults(cls) -> TemplateRegistry:
|
|
@@ -214,13 +141,9 @@ class ProjectBuilder:
|
|
|
214
141
|
|
|
215
142
|
|
|
216
143
|
__all__ = [
|
|
217
|
-
"
|
|
218
|
-
"GraphQLTemplate",
|
|
219
|
-
"MinimalTemplate",
|
|
144
|
+
"ApiTemplate",
|
|
220
145
|
"ProjectBuilder",
|
|
221
146
|
"ProjectTemplate",
|
|
222
147
|
"TemplateConfig",
|
|
223
148
|
"TemplateRegistry",
|
|
224
|
-
"WebAPITemplate",
|
|
225
|
-
"WorkerTemplate",
|
|
226
149
|
]
|
|
@@ -4,9 +4,12 @@ Single source of truth for project bootstrapping — the catalogue lives in
|
|
|
4
4
|
:mod:`quadkit.cli.scaffold.specs`, the rendered files in
|
|
5
5
|
:mod:`quadkit.cli.scaffold.app_files`, :mod:`quadkit.cli.scaffold.module_files`
|
|
6
6
|
and :mod:`quadkit.cli.scaffold.support_files`, and the writers in
|
|
7
|
-
:mod:`quadkit.cli.scaffold.render
|
|
7
|
+
:mod:`quadkit.cli.scaffold.render`. Project metadata is rendered by
|
|
8
|
+
:func:`render_project_metadata` from an explicit :class:`ProjectMetadataContext`;
|
|
9
|
+
downstream builders can use this side-effect-free API without depending on
|
|
10
|
+
private template functions:
|
|
8
11
|
|
|
9
|
-
- ``quadkit new project
|
|
12
|
+
- ``quadkit new project`` (``commands/new.py``)
|
|
10
13
|
- ``ProjectBuilder.create_project`` (``registry/template.py``)
|
|
11
14
|
- scaffold alignment tests (``tests/unit/test_scaffold_alignment.py``)
|
|
12
15
|
|
|
@@ -15,7 +18,7 @@ Every generated file targets the **real** quadkit APIs (``Application``,
|
|
|
15
18
|
generator map** in :mod:`quadkit.cli.layout`, so a scaffolded project and
|
|
16
19
|
its generators stay aligned:
|
|
17
20
|
|
|
18
|
-
quadkit new project myapp
|
|
21
|
+
quadkit new project myapp
|
|
19
22
|
cd myapp && pip install -e .
|
|
20
23
|
quadkit gen controller users # src/myapp/controllers/users_controller.py
|
|
21
24
|
quadkit dev # WebModule auto-discovers it
|
|
@@ -36,10 +39,13 @@ and directly in pytest fixtures.
|
|
|
36
39
|
|
|
37
40
|
from __future__ import annotations
|
|
38
41
|
|
|
42
|
+
from quadkit.cli.scaffold.metadata import (
|
|
43
|
+
ProjectMetadataContext,
|
|
44
|
+
render_project_metadata,
|
|
45
|
+
)
|
|
39
46
|
from quadkit.cli.scaffold.render import render_module, render_project
|
|
40
47
|
from quadkit.cli.scaffold.specs import (
|
|
41
48
|
_TEMPLATE_SPECS,
|
|
42
|
-
TEMPLATE_ALIASES,
|
|
43
49
|
TEMPLATES,
|
|
44
50
|
ProjectTemplateSpec,
|
|
45
51
|
component_packages,
|
|
@@ -50,9 +56,11 @@ from quadkit.cli.scaffold.specs import (
|
|
|
50
56
|
|
|
51
57
|
__all__ = [
|
|
52
58
|
"TEMPLATES",
|
|
59
|
+
"ProjectMetadataContext",
|
|
53
60
|
"component_packages",
|
|
54
61
|
"render_module",
|
|
55
62
|
"render_project",
|
|
63
|
+
"render_project_metadata",
|
|
56
64
|
"resolve_template",
|
|
57
65
|
"root_output_dirs",
|
|
58
66
|
"template_names",
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Project-level metadata renderers shared by CLI and downstream builders.
|
|
2
|
+
|
|
3
|
+
These renderers are intentionally side-effect free. Consumers provide the
|
|
4
|
+
facts from the project they are creating; the renderer returns a deterministic
|
|
5
|
+
map of files to add to that project. The generated-app profile is separate
|
|
6
|
+
from any framework website or release metadata profile.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
__all__ = ["ProjectMetadataContext", "render_project_metadata"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class ProjectMetadataContext:
|
|
20
|
+
"""Facts needed to render concise, project-specific agent instructions.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
project_name: Human-facing project name.
|
|
24
|
+
package_name: Importable Python package name.
|
|
25
|
+
summary: Short project description.
|
|
26
|
+
template_name: Scaffold/template identifier, not a release version.
|
|
27
|
+
features: Framework features actually present in the generated project.
|
|
28
|
+
dependencies: Direct requirements emitted into the project's manifest.
|
|
29
|
+
layout: Existing important paths and their purpose, relative to project
|
|
30
|
+
root. Paths must refer to this generated project, not the framework
|
|
31
|
+
checkout.
|
|
32
|
+
commands: Commands valid for the generated project and their purpose.
|
|
33
|
+
notes: Project-specific operational notes, e.g. a generated dev secret
|
|
34
|
+
must be replaced before deployment.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
project_name: str
|
|
38
|
+
package_name: str
|
|
39
|
+
summary: str
|
|
40
|
+
template_name: str
|
|
41
|
+
features: tuple[str, ...]
|
|
42
|
+
dependencies: tuple[str, ...]
|
|
43
|
+
layout: tuple[tuple[str, str], ...]
|
|
44
|
+
commands: tuple[tuple[str, str], ...]
|
|
45
|
+
notes: tuple[str, ...] = ()
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
"""Reject unsafe or malformed values before rendering Markdown."""
|
|
49
|
+
if not re.fullmatch(r"[A-Za-z][A-Za-z0-9_-]*", self.project_name):
|
|
50
|
+
raise ValueError("project_name must be a safe project slug")
|
|
51
|
+
if not re.fullmatch(r"[A-Za-z][A-Za-z0-9_]*", self.package_name):
|
|
52
|
+
raise ValueError("package_name must be a Python package identifier")
|
|
53
|
+
_require_single_line("summary", self.summary, allow_backticks=True)
|
|
54
|
+
_require_single_line("template_name", self.template_name)
|
|
55
|
+
for item in self.features:
|
|
56
|
+
_require_single_line("feature", item)
|
|
57
|
+
for item in self.dependencies:
|
|
58
|
+
_require_single_line("dependency", item)
|
|
59
|
+
if not self.summary.strip():
|
|
60
|
+
raise ValueError("summary must not be empty")
|
|
61
|
+
if not self.template_name.strip():
|
|
62
|
+
raise ValueError("template_name must not be empty")
|
|
63
|
+
if len(set(self.features)) != len(self.features):
|
|
64
|
+
raise ValueError("features must not contain duplicates")
|
|
65
|
+
if len(set(self.dependencies)) != len(self.dependencies):
|
|
66
|
+
raise ValueError("dependencies must not contain duplicates")
|
|
67
|
+
for path, purpose in self.layout:
|
|
68
|
+
_validate_project_path(path)
|
|
69
|
+
_require_single_line("layout purpose", purpose, allow_backticks=True)
|
|
70
|
+
for command, purpose in self.commands:
|
|
71
|
+
_require_single_line("command", command)
|
|
72
|
+
_require_single_line("command purpose", purpose, allow_backticks=True)
|
|
73
|
+
if not command.strip():
|
|
74
|
+
raise ValueError("commands must not contain empty commands")
|
|
75
|
+
for note in self.notes:
|
|
76
|
+
_require_single_line("note", note, allow_backticks=True)
|
|
77
|
+
if len({path for path, _ in self.layout}) != len(self.layout):
|
|
78
|
+
raise ValueError("layout paths must be unique")
|
|
79
|
+
if len({command for command, _ in self.commands}) != len(self.commands):
|
|
80
|
+
raise ValueError("commands must be unique")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _require_single_line(
|
|
84
|
+
label: str, value: str, *, allow_backticks: bool = False
|
|
85
|
+
) -> None:
|
|
86
|
+
if "\n" in value or "\r" in value or ("`" in value and not allow_backticks):
|
|
87
|
+
detail = "single line"
|
|
88
|
+
if not allow_backticks:
|
|
89
|
+
detail += " without backticks"
|
|
90
|
+
raise ValueError(f"{label} must be {detail}")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _validate_project_path(path: str) -> None:
|
|
94
|
+
"""Require a normalized, relative path that remains inside the project."""
|
|
95
|
+
_require_single_line("layout path", path)
|
|
96
|
+
if not path or path.startswith(("/", "\\")) or "\\" in path:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f"layout path must be a non-empty relative POSIX path: {path!r}"
|
|
99
|
+
)
|
|
100
|
+
normalized = path[:-1] if path.endswith("/") else path
|
|
101
|
+
parts = normalized.split("/")
|
|
102
|
+
if not normalized or any(part in {"", ".", ".."} for part in parts):
|
|
103
|
+
raise ValueError(f"layout path is not normalized: {path!r}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _escape_markdown(value: str) -> str:
|
|
107
|
+
"""Escape inline Markdown punctuation in caller-supplied prose."""
|
|
108
|
+
return re.sub(r"([\\`*_{}\[\]()#+.!|>])", r"\\\1", value)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _inline_code(value: str) -> str:
|
|
112
|
+
"""Render one validated single-line value as inline code."""
|
|
113
|
+
return f"`{value}`"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _render_agents_md(context: ProjectMetadataContext) -> str:
|
|
117
|
+
features = ", ".join(_inline_code(feature) for feature in context.features)
|
|
118
|
+
if not features:
|
|
119
|
+
features = "none declared by the selected template"
|
|
120
|
+
|
|
121
|
+
lines = [
|
|
122
|
+
f"# Agent Guide: {context.project_name}",
|
|
123
|
+
"",
|
|
124
|
+
"This file is project-specific guidance for coding agents. `README.md` is",
|
|
125
|
+
"the human quickstart; this file adds the project map, commands, and the",
|
|
126
|
+
"QuadKit conventions relevant to this scaffold. Update it when the project",
|
|
127
|
+
"layout or workflow changes.",
|
|
128
|
+
"",
|
|
129
|
+
"## Project summary",
|
|
130
|
+
"",
|
|
131
|
+
_escape_markdown(context.summary),
|
|
132
|
+
"",
|
|
133
|
+
f"- Scaffold: {_inline_code(context.template_name)}",
|
|
134
|
+
f"- Python package: {_inline_code(context.package_name)}",
|
|
135
|
+
f"- Framework features: {features}",
|
|
136
|
+
"",
|
|
137
|
+
"## Start here",
|
|
138
|
+
"",
|
|
139
|
+
"1. Read `README.md` for the project overview and quickstart.",
|
|
140
|
+
"2. Read `application.yaml` before changing runtime configuration.",
|
|
141
|
+
f"3. Treat `src/{context.package_name}/app.py` as the composition root; inspect it before changing module wiring.",
|
|
142
|
+
"4. Consult the linked QuadKit docs when a framework convention is unclear.",
|
|
143
|
+
"",
|
|
144
|
+
"## Useful commands",
|
|
145
|
+
"",
|
|
146
|
+
]
|
|
147
|
+
if context.commands:
|
|
148
|
+
for command, purpose in context.commands:
|
|
149
|
+
lines.append(f"- {_inline_code(command)} — {_escape_markdown(purpose)}")
|
|
150
|
+
else:
|
|
151
|
+
lines.append("- No project commands were supplied by the scaffold context.")
|
|
152
|
+
|
|
153
|
+
lines += ["", "## Project map", ""]
|
|
154
|
+
for path, purpose in context.layout:
|
|
155
|
+
lines.append(f"- {_inline_code(path)} — {_escape_markdown(purpose)}")
|
|
156
|
+
|
|
157
|
+
lines += [
|
|
158
|
+
"",
|
|
159
|
+
"## QuadKit conventions",
|
|
160
|
+
"",
|
|
161
|
+
"- Preserve the existing project tree: feature code belongs in its feature",
|
|
162
|
+
" package; cross-cutting code belongs in `shared/`; bounded contexts live",
|
|
163
|
+
" under `modules/<name>/` when this project uses modules.",
|
|
164
|
+
"- Keep the composition root focused on assembling configuration, providers,",
|
|
165
|
+
" and modules. Put business behavior in services/domain types rather than",
|
|
166
|
+
" growing app wiring into a second service locator.",
|
|
167
|
+
"- Prefer the framework's typed protocols and constructor injection. Use",
|
|
168
|
+
" `Result[T, E]` for expected domain failures and exceptions for unexpected",
|
|
169
|
+
" infrastructure failures; follow the existing local patterns first.",
|
|
170
|
+
"- Keep I/O asynchronous where the surrounding API is asynchronous. Do not",
|
|
171
|
+
" introduce a parallel `models/` tree; follow the generated `domains/`",
|
|
172
|
+
" layout and the canonical QuadKit project-structure guide.",
|
|
173
|
+
"- Add or update tests with behavior changes. Keep configuration examples",
|
|
174
|
+
" and tests aligned with the actual package set in `pyproject.toml`.",
|
|
175
|
+
"",
|
|
176
|
+
"## QuadKit references",
|
|
177
|
+
"",
|
|
178
|
+
"- [For coding agents](https://oridecon.dev/quadkit/getting-started/for-coding-agents/)",
|
|
179
|
+
"- [Project structure](https://oridecon.dev/quadkit/getting-started/project-structure/)",
|
|
180
|
+
"- [Testing guide](https://oridecon.dev/quadkit/guides/testing/)",
|
|
181
|
+
"- [Common mistakes](https://oridecon.dev/quadkit/getting-started/common-mistakes/)",
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
if context.dependencies:
|
|
185
|
+
lines += ["", "## Declared dependencies", ""]
|
|
186
|
+
lines.extend(f"- {_inline_code(item)}" for item in context.dependencies)
|
|
187
|
+
|
|
188
|
+
if context.notes:
|
|
189
|
+
lines += ["", "## Important project notes", ""]
|
|
190
|
+
lines.extend(f"- {_escape_markdown(note)}" for note in context.notes)
|
|
191
|
+
|
|
192
|
+
return "\n".join(lines) + "\n"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def render_project_metadata(
|
|
196
|
+
context: ProjectMetadataContext,
|
|
197
|
+
) -> Mapping[str, str]:
|
|
198
|
+
"""Return deterministic project metadata files for *context*.
|
|
199
|
+
|
|
200
|
+
The initial generated-app profile emits only ``AGENTS.md``. It deliberately
|
|
201
|
+
does not create website-only resources such as ``llms.txt``, crawler rules,
|
|
202
|
+
or an Agent Skills pack.
|
|
203
|
+
"""
|
|
204
|
+
return {"AGENTS.md": _render_agents_md(context)}
|