framework-m-core 0.4.1__tar.gz → 0.5.0__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.
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/.gitignore +3 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/CHANGELOG.md +25 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/PKG-INFO +1 -1
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/pyproject.toml +1 -1
- framework_m_core-0.5.0/src/framework_m_core/cli/init.py +137 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/cli/main.py +4 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/cli/plugin_loader.py +5 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/domain/outbox.py +2 -2
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/pii.py +3 -3
- framework_m_core-0.5.0/tests/cli/test_cli_commands.py +301 -0
- framework_m_core-0.5.0/tests/cli/test_init.py +150 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/README.md +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/__init__.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/cli/__init__.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/cli/config.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/cli/utility.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/config.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/container.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/decorators.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/__init__.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/activity_log.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/api_key.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/custom_permission.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/document_share.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/email_queue.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/error_log.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/file.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/job_log.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/notification.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/print_format.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/recent_document.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/report.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/scheduled_job.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/session.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/social_account.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/system_settings.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/tenant_translation.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/todo.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/translation.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/user.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/webhook.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/webhook_log.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/workflow.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/workflow_state.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/doctypes/workflow_transition.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/domain/__init__.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/domain/base_controller.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/domain/base_doctype.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/domain/mixins.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/domain/naming_counter.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/events/__init__.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/exceptions.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/__init__.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/audit.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/auth_context.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/authentication.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/base_doctype.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/bootstrap.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/cache.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/controller.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/email_queue.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/email_sender.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/event_bus.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/i18n.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/identity.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/job_queue.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/notification.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/oauth.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/permission.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/print.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/read_model.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/report_engine.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/repository.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/schema_mapper.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/search.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/session.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/socket.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/storage.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/tenant.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/interfaces/workflow.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/permission_lookup.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/permissions.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/py.typed +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/registry.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/rls.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/rpc_registry.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/security.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/services/__init__.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/services/user_manager.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/system_context.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/types/job_context.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/src/framework_m_core/unit_of_work.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/cli/test_cli_main.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/cli/test_config.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/cli/test_plugin_loader.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/cli/test_utility.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/conftest.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/doctypes/test_api_key.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/doctypes/test_job_log.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/doctypes/test_report.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/doctypes/test_scheduled_job.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/doctypes/test_social_account.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/doctypes/test_user.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/doctypes/test_webhook.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/doctypes/test_webhook_log.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/events/test_doc_events.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/interfaces/test_cache.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/interfaces/test_email_queue_protocol.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/interfaces/test_identity.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/interfaces/test_socket.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/interfaces/test_workflow.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/services/test_user_manager.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_activity_log.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_child_table_permissions.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_container.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_custom_permission.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_custom_protocols.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_document_share.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_email_queue.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_error_log.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_exceptions.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_file.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_link_field_leakage.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_meta_registry_overrides.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_notification.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_object_level_permissions.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_outbox.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_permission_config.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_permission_conveniences.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_permission_lookup.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_pii.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_print_format.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_rls.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_rpc_decorator.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_schema_extension.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_session.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_share_lookup.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_system_context.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_system_settings.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_table_alteration.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_team_based_access.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_tenant.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_unit_of_work.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/test_whitelist_decorator.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/core/types/test_job_context.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/domain/test_base_controller.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/domain/test_base_doctype_implements_protocol.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/domain/test_mixins.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_audit.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_auth_context.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_base_doctype_protocol.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_bootstrap_protocol.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_controller_protocol.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_event_bus.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_i18n.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_job_queue.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_permission.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_print.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_repository.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_schema_mapper_protocol.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_search.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/interfaces/test_storage.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/core/test_registry.py +0 -0
- {framework_m_core-0.4.1 → framework_m_core-0.5.0}/tests/unit/test_base_doctype.py +0 -0
|
@@ -77,6 +77,9 @@ docker-compose.override.yml
|
|
|
77
77
|
# Studio UI built assets
|
|
78
78
|
apps/studio/src/framework_m_studio/static/
|
|
79
79
|
|
|
80
|
+
# Desk UI built assets (bundled with framework-m package)
|
|
81
|
+
libs/framework-m/src/framework_m/static/assets/
|
|
82
|
+
|
|
80
83
|
#doctypes
|
|
81
84
|
doctypes/*.py
|
|
82
85
|
apps/studio/src/doctypes/*.py
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## framework-m-core v0.5.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- implement m start and m dev commands (4c80fc1)
|
|
8
|
+
- implement bundled Desk distribution with npm package (20b3be4)
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
- lint errors (9b6ab60)
|
|
13
|
+
- lint-import (8738781)
|
|
14
|
+
- switch tabs, doctype name spacing and foreign key field for doctypes (036f34c)
|
|
15
|
+
- Fix GitLab CI test failures (589d783)
|
|
16
|
+
- add framework-m-desk to pnpm-workspace.yaml (43deefa)
|
|
17
|
+
- lint format and import (9116aec)
|
|
18
|
+
- lint errors and rename publish-npm-package job to publish-npm-package-gitlab (77e2dce)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## framework-m-core v0.4.2
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
- add TestPyPI badge to README (8e62bea)
|
|
26
|
+
|
|
27
|
+
|
|
3
28
|
## framework-m-core v0.4.1
|
|
4
29
|
|
|
5
30
|
### Bug Fixes
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Init CLI Command - Scaffold frontend from template.
|
|
2
|
+
|
|
3
|
+
This module provides commands to initialize new components of Framework M.
|
|
4
|
+
|
|
5
|
+
Architecture:
|
|
6
|
+
- No global state - all paths passed explicitly
|
|
7
|
+
- Template resources via importlib.resources (Python 3.9+)
|
|
8
|
+
- Explicit error handling for file operations
|
|
9
|
+
- User feedback with print statements
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
m init:frontend # Initialize in ./frontend
|
|
13
|
+
m init:frontend ../my-frontend # Initialize in custom location
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
from importlib.resources import files
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Annotated
|
|
23
|
+
|
|
24
|
+
import cyclopts
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _copy_template(source_template: Path, target: Path) -> None:
|
|
28
|
+
"""Copy template directory to target location.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
source_template: Source template directory
|
|
32
|
+
target: Target directory
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
FileExistsError: If target already exists
|
|
36
|
+
RuntimeError: If copy fails
|
|
37
|
+
"""
|
|
38
|
+
if target.exists():
|
|
39
|
+
msg = f"Target directory already exists: {target}"
|
|
40
|
+
raise FileExistsError(msg)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
shutil.copytree(source_template, target)
|
|
44
|
+
print(f"✓ Copied template to {target}")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
msg = f"Failed to copy template: {e}"
|
|
47
|
+
raise RuntimeError(msg) from e
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _install_dependencies(target: Path) -> None:
|
|
51
|
+
"""Install pnpm dependencies in target directory.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
target: Frontend directory
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
RuntimeError: If pnpm install fails
|
|
58
|
+
"""
|
|
59
|
+
print("\nInstalling dependencies with pnpm...")
|
|
60
|
+
try:
|
|
61
|
+
subprocess.run(
|
|
62
|
+
["pnpm", "install"],
|
|
63
|
+
cwd=str(target),
|
|
64
|
+
check=True,
|
|
65
|
+
)
|
|
66
|
+
print("✓ Dependencies installed")
|
|
67
|
+
except subprocess.CalledProcessError as e:
|
|
68
|
+
msg = f"Failed to install dependencies: {e}"
|
|
69
|
+
raise RuntimeError(msg) from e
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def init_frontend_command(
|
|
73
|
+
target: Annotated[
|
|
74
|
+
Path | None,
|
|
75
|
+
cyclopts.Parameter(
|
|
76
|
+
name=["target"],
|
|
77
|
+
help="Target directory for frontend (default: ./frontend)",
|
|
78
|
+
),
|
|
79
|
+
] = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Initialize a new frontend from template.
|
|
82
|
+
|
|
83
|
+
This scaffolds the bundled Desk frontend template to a target directory
|
|
84
|
+
and installs dependencies. The template includes:
|
|
85
|
+
- React + TypeScript + Vite
|
|
86
|
+
- @framework-m/desk npm package integration
|
|
87
|
+
- Refine.dev setup with authProvider, dataProvider, liveProvider
|
|
88
|
+
- Tailwind CSS + shadcn/ui components
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
m init:frontend # Initialize in ./frontend
|
|
92
|
+
m init:frontend ../my-frontend # Custom location
|
|
93
|
+
|
|
94
|
+
Architecture Notes:
|
|
95
|
+
- Uses apps/studio/frontend as template source
|
|
96
|
+
- No global state - all paths explicit
|
|
97
|
+
- Validates target doesn't exist (prevents overwrites)
|
|
98
|
+
- Runs pnpm install automatically
|
|
99
|
+
"""
|
|
100
|
+
# Default to ./frontend
|
|
101
|
+
if target is None:
|
|
102
|
+
target = Path("frontend")
|
|
103
|
+
|
|
104
|
+
# Resolve to absolute path
|
|
105
|
+
target = target.resolve()
|
|
106
|
+
|
|
107
|
+
# Find template source from framework_m package
|
|
108
|
+
try:
|
|
109
|
+
template_resource = files("framework_m") / "templates" / "frontend"
|
|
110
|
+
if not template_resource.is_dir():
|
|
111
|
+
msg = "Frontend template not found in framework_m package"
|
|
112
|
+
raise RuntimeError(msg)
|
|
113
|
+
|
|
114
|
+
# Convert to Path for file operations
|
|
115
|
+
template_source = Path(str(template_resource))
|
|
116
|
+
except Exception as e:
|
|
117
|
+
msg = f"Failed to locate template: {e}"
|
|
118
|
+
raise RuntimeError(msg) from e
|
|
119
|
+
|
|
120
|
+
print(f"Initializing frontend at {target}")
|
|
121
|
+
print(f"Template source: {template_source}")
|
|
122
|
+
|
|
123
|
+
# Copy template
|
|
124
|
+
_copy_template(template_source, target)
|
|
125
|
+
|
|
126
|
+
# Install dependencies
|
|
127
|
+
_install_dependencies(target)
|
|
128
|
+
|
|
129
|
+
print("\n✓ Frontend initialized successfully!")
|
|
130
|
+
print("\nNext steps:")
|
|
131
|
+
print(f" cd {target}")
|
|
132
|
+
print(" pnpm dev")
|
|
133
|
+
print("\nOr start with:")
|
|
134
|
+
print(" m start --with-frontend")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
__all__ = ["init_frontend_command"]
|
|
@@ -2,6 +2,7 @@ import cyclopts
|
|
|
2
2
|
|
|
3
3
|
from framework_m_core import __version__
|
|
4
4
|
from framework_m_core.cli.config import config_set_command, config_show_command
|
|
5
|
+
from framework_m_core.cli.init import init_frontend_command
|
|
5
6
|
from framework_m_core.cli.plugin_loader import load_plugins
|
|
6
7
|
from framework_m_core.cli.utility import info_command
|
|
7
8
|
|
|
@@ -18,6 +19,9 @@ app.command(info_command, name="info")
|
|
|
18
19
|
app.command(config_show_command, name="config:show")
|
|
19
20
|
app.command(config_set_command, name="config:set")
|
|
20
21
|
|
|
22
|
+
# Init commands
|
|
23
|
+
app.command(init_frontend_command, name="init:frontend")
|
|
24
|
+
|
|
21
25
|
# Load plugins
|
|
22
26
|
load_plugins(app)
|
|
23
27
|
|
|
@@ -94,6 +94,11 @@ def register_plugins(app: cyclopts.App, plugins: list[EntryPoint]) -> None:
|
|
|
94
94
|
logger.warning(
|
|
95
95
|
f"Plugin '{ep.name}' is not a cyclopts App or callable, skipping"
|
|
96
96
|
)
|
|
97
|
+
except cyclopts.exceptions.CommandCollisionError:
|
|
98
|
+
# Command already registered (core takes precedence over plugins)
|
|
99
|
+
logger.debug(
|
|
100
|
+
f"CLI plugin '{ep.name}' skipped - command already registered by core"
|
|
101
|
+
)
|
|
97
102
|
except Exception as e:
|
|
98
103
|
logger.warning(
|
|
99
104
|
f"Failed to register CLI plugin '{ep.name}': {e}",
|
|
@@ -27,7 +27,7 @@ Example:
|
|
|
27
27
|
from __future__ import annotations
|
|
28
28
|
|
|
29
29
|
from datetime import UTC, datetime
|
|
30
|
-
from enum import
|
|
30
|
+
from enum import StrEnum
|
|
31
31
|
from typing import Any
|
|
32
32
|
from uuid import UUID, uuid4
|
|
33
33
|
|
|
@@ -39,7 +39,7 @@ def _utc_now() -> datetime:
|
|
|
39
39
|
return datetime.now(UTC)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
class OutboxStatus(
|
|
42
|
+
class OutboxStatus(StrEnum):
|
|
43
43
|
"""Status of an outbox entry.
|
|
44
44
|
|
|
45
45
|
Attributes:
|
|
@@ -23,7 +23,7 @@ Example:
|
|
|
23
23
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
-
from enum import
|
|
26
|
+
from enum import StrEnum
|
|
27
27
|
from typing import Any
|
|
28
28
|
|
|
29
29
|
from pydantic import BaseModel, Field
|
|
@@ -35,7 +35,7 @@ from framework_m_core.config import load_config
|
|
|
35
35
|
# =============================================================================
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
class AuthMode(
|
|
38
|
+
class AuthMode(StrEnum):
|
|
39
39
|
"""Available authentication modes.
|
|
40
40
|
|
|
41
41
|
Attributes:
|
|
@@ -142,7 +142,7 @@ def is_sensitive_pii(field_name: str) -> bool:
|
|
|
142
142
|
# =============================================================================
|
|
143
143
|
|
|
144
144
|
|
|
145
|
-
class DeletionMode(
|
|
145
|
+
class DeletionMode(StrEnum):
|
|
146
146
|
"""How to handle user data on account deletion.
|
|
147
147
|
|
|
148
148
|
Attributes:
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Fast CLI command tests.
|
|
2
|
+
|
|
3
|
+
Tests CLI commands without heavy npm operations.
|
|
4
|
+
Focus on command structure, error handling, and fast validations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestInitFrontendCommand:
|
|
16
|
+
"""Test m init:frontend command."""
|
|
17
|
+
|
|
18
|
+
def test_command_available(self) -> None:
|
|
19
|
+
"""Test that init:frontend command is registered."""
|
|
20
|
+
result = subprocess.run(
|
|
21
|
+
["python", "-m", "framework_m_core.cli.main", "init:frontend", "--help"],
|
|
22
|
+
capture_output=True,
|
|
23
|
+
text=True,
|
|
24
|
+
)
|
|
25
|
+
assert result.returncode == 0
|
|
26
|
+
assert "init:frontend" in result.stdout.lower() or "Initialize" in result.stdout
|
|
27
|
+
|
|
28
|
+
def test_fails_without_template(self, tmp_path: Path) -> None:
|
|
29
|
+
"""Test fails gracefully when template not found."""
|
|
30
|
+
# Test by trying to initialize with a non-existent template via import failure
|
|
31
|
+
# The init command will fail during dependency installation if pnpm can't find packages
|
|
32
|
+
# This is effectively tested by the error handling in test_raises_runtime_error_when_template_not_found
|
|
33
|
+
# Just verify the command exists and accepts arguments
|
|
34
|
+
result = subprocess.run(
|
|
35
|
+
["python", "-m", "framework_m_core.cli.main", "init:frontend", "--help"],
|
|
36
|
+
capture_output=True,
|
|
37
|
+
text=True,
|
|
38
|
+
)
|
|
39
|
+
assert result.returncode == 0
|
|
40
|
+
|
|
41
|
+
def test_fails_if_target_exists(self, tmp_path: Path) -> None:
|
|
42
|
+
"""Test fails when target directory already exists."""
|
|
43
|
+
target = tmp_path / "existing"
|
|
44
|
+
target.mkdir()
|
|
45
|
+
|
|
46
|
+
result = subprocess.run(
|
|
47
|
+
["python", "-m", "framework_m_core.cli.main", "init:frontend", str(target)],
|
|
48
|
+
capture_output=True,
|
|
49
|
+
text=True,
|
|
50
|
+
timeout=5,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
assert result.returncode != 0
|
|
54
|
+
assert (
|
|
55
|
+
"already exists" in result.stderr.lower()
|
|
56
|
+
or "already exists" in result.stdout.lower()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestStartCommand:
|
|
61
|
+
"""Test m start command."""
|
|
62
|
+
|
|
63
|
+
def test_command_available(self) -> None:
|
|
64
|
+
"""Test that start command is registered."""
|
|
65
|
+
result = subprocess.run(
|
|
66
|
+
["python", "-m", "framework_m_core.cli.main", "start", "--help"],
|
|
67
|
+
capture_output=True,
|
|
68
|
+
text=True,
|
|
69
|
+
)
|
|
70
|
+
assert result.returncode == 0
|
|
71
|
+
assert "start" in result.stdout.lower() or "Start" in result.stdout
|
|
72
|
+
|
|
73
|
+
def test_accepts_port_parameter(self) -> None:
|
|
74
|
+
"""Test that --port parameter is accepted."""
|
|
75
|
+
result = subprocess.run(
|
|
76
|
+
["python", "-m", "framework_m_core.cli.main", "start", "--help"],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
)
|
|
80
|
+
assert "--port" in result.stdout.lower()
|
|
81
|
+
|
|
82
|
+
def test_accepts_with_frontend_parameter(self) -> None:
|
|
83
|
+
"""Test that --with-frontend parameter is accepted."""
|
|
84
|
+
result = subprocess.run(
|
|
85
|
+
["python", "-m", "framework_m_core.cli.main", "start", "--help"],
|
|
86
|
+
capture_output=True,
|
|
87
|
+
text=True,
|
|
88
|
+
)
|
|
89
|
+
assert "--with-frontend" in result.stdout.lower()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestCLIStructure:
|
|
93
|
+
"""Test overall CLI structure."""
|
|
94
|
+
|
|
95
|
+
def test_main_cli_runs(self) -> None:
|
|
96
|
+
"""Test that main CLI runs without errors."""
|
|
97
|
+
result = subprocess.run(
|
|
98
|
+
["python", "-m", "framework_m_core.cli.main", "--help"],
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
timeout=5,
|
|
102
|
+
)
|
|
103
|
+
assert result.returncode == 0
|
|
104
|
+
assert "framework" in result.stdout.lower() or "usage" in result.stdout.lower()
|
|
105
|
+
|
|
106
|
+
def test_all_commands_listed(self) -> None:
|
|
107
|
+
"""Test that key commands are listed in help."""
|
|
108
|
+
result = subprocess.run(
|
|
109
|
+
["python", "-m", "framework_m_core.cli.main", "--help"],
|
|
110
|
+
capture_output=True,
|
|
111
|
+
text=True,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Should list major commands
|
|
115
|
+
help_text = result.stdout.lower()
|
|
116
|
+
assert "start" in help_text
|
|
117
|
+
assert "init" in help_text or "frontend" in help_text
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.skipif(
|
|
121
|
+
not Path("frontend").exists(),
|
|
122
|
+
reason="Frontend template not available",
|
|
123
|
+
)
|
|
124
|
+
class TestTemplateStructure:
|
|
125
|
+
"""Test frontend template structure (fast validation)."""
|
|
126
|
+
|
|
127
|
+
def test_template_has_package_json(self) -> None:
|
|
128
|
+
"""Test template has package.json."""
|
|
129
|
+
template = Path("frontend")
|
|
130
|
+
assert (template / "package.json").exists()
|
|
131
|
+
|
|
132
|
+
def test_template_has_vite_config(self) -> None:
|
|
133
|
+
"""Test template has vite.config.ts."""
|
|
134
|
+
template = Path("frontend")
|
|
135
|
+
assert (template / "vite.config.ts").exists()
|
|
136
|
+
|
|
137
|
+
def test_template_has_tsconfig(self) -> None:
|
|
138
|
+
"""Test template has tsconfig.json."""
|
|
139
|
+
template = Path("frontend")
|
|
140
|
+
assert (template / "tsconfig.json").exists()
|
|
141
|
+
|
|
142
|
+
def test_template_has_src_directory(self) -> None:
|
|
143
|
+
"""Test template has src directory with essential files."""
|
|
144
|
+
template = Path("frontend")
|
|
145
|
+
assert (template / "src").exists()
|
|
146
|
+
assert (template / "src" / "App.tsx").exists()
|
|
147
|
+
assert (template / "src" / "index.tsx").exists()
|
|
148
|
+
|
|
149
|
+
def test_template_has_npmrc(self) -> None:
|
|
150
|
+
"""Test template has .npmrc for pnpm configuration."""
|
|
151
|
+
template = Path("frontend")
|
|
152
|
+
assert (template / ".npmrc").exists()
|
|
153
|
+
|
|
154
|
+
def test_package_json_has_required_deps(self) -> None:
|
|
155
|
+
"""Test package.json has required dependencies."""
|
|
156
|
+
template = Path("frontend")
|
|
157
|
+
package_json = template / "package.json"
|
|
158
|
+
content = package_json.read_text()
|
|
159
|
+
|
|
160
|
+
# Should have Refine and React
|
|
161
|
+
assert "@refinedev/core" in content
|
|
162
|
+
assert "react" in content
|
|
163
|
+
assert "vite" in content
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@pytest.mark.skipif(
|
|
167
|
+
not Path("libs/framework-m-desk").exists(),
|
|
168
|
+
reason="@framework-m/desk package not available",
|
|
169
|
+
)
|
|
170
|
+
class TestDeskPackageStructure:
|
|
171
|
+
"""Test @framework-m/desk package structure (fast validation)."""
|
|
172
|
+
|
|
173
|
+
def test_package_has_package_json(self) -> None:
|
|
174
|
+
"""Test package has package.json."""
|
|
175
|
+
package = Path("libs/framework-m-desk")
|
|
176
|
+
assert (package / "package.json").exists()
|
|
177
|
+
|
|
178
|
+
def test_package_has_src_directory(self) -> None:
|
|
179
|
+
"""Test package has src directory."""
|
|
180
|
+
package = Path("libs/framework-m-desk")
|
|
181
|
+
assert (package / "src").exists()
|
|
182
|
+
assert (package / "src" / "index.ts").exists()
|
|
183
|
+
|
|
184
|
+
def test_package_exports_providers(self) -> None:
|
|
185
|
+
"""Test index.ts exports required providers."""
|
|
186
|
+
package = Path("libs/framework-m-desk")
|
|
187
|
+
index_file = package / "src" / "index.ts"
|
|
188
|
+
content = index_file.read_text()
|
|
189
|
+
|
|
190
|
+
expected_exports = [
|
|
191
|
+
"frameworkMDataProvider",
|
|
192
|
+
"authProvider",
|
|
193
|
+
"liveProvider",
|
|
194
|
+
"API_URL",
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
for export in expected_exports:
|
|
198
|
+
assert export in content, f"Missing export: {export}"
|
|
199
|
+
|
|
200
|
+
def test_package_json_has_correct_config(self) -> None:
|
|
201
|
+
"""Test package.json has correct configuration."""
|
|
202
|
+
package = Path("libs/framework-m-desk")
|
|
203
|
+
package_json = package / "package.json"
|
|
204
|
+
content = package_json.read_text()
|
|
205
|
+
|
|
206
|
+
assert "@framework-m/desk" in content
|
|
207
|
+
assert "dist/index.js" in content
|
|
208
|
+
assert "peerDependencies" in content
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestFrontendBuildConfig:
|
|
212
|
+
"""Test frontend build configuration (no actual build)."""
|
|
213
|
+
|
|
214
|
+
@pytest.mark.skipif(
|
|
215
|
+
not Path("frontend").exists(),
|
|
216
|
+
reason="Frontend template not available",
|
|
217
|
+
)
|
|
218
|
+
def test_vite_config_exists_and_valid(self) -> None:
|
|
219
|
+
"""Test vite config file exists and has basic structure."""
|
|
220
|
+
vite_config = Path("frontend/vite.config.ts")
|
|
221
|
+
assert vite_config.exists()
|
|
222
|
+
|
|
223
|
+
content = vite_config.read_text()
|
|
224
|
+
assert "defineConfig" in content
|
|
225
|
+
assert "plugins" in content
|
|
226
|
+
|
|
227
|
+
@pytest.mark.skipif(
|
|
228
|
+
not Path("frontend").exists(),
|
|
229
|
+
reason="Frontend template not available",
|
|
230
|
+
)
|
|
231
|
+
def test_tsconfig_exists_and_valid(self) -> None:
|
|
232
|
+
"""Test TypeScript config exists and has basic structure."""
|
|
233
|
+
tsconfig = Path("frontend/tsconfig.json")
|
|
234
|
+
assert tsconfig.exists()
|
|
235
|
+
|
|
236
|
+
content = tsconfig.read_text()
|
|
237
|
+
assert "compilerOptions" in content
|
|
238
|
+
|
|
239
|
+
@pytest.mark.skipif(
|
|
240
|
+
not Path("frontend").exists(),
|
|
241
|
+
reason="Frontend template not available",
|
|
242
|
+
)
|
|
243
|
+
def test_npmrc_has_peer_dependency_config(self) -> None:
|
|
244
|
+
"""Test .npmrc has peer dependency configuration."""
|
|
245
|
+
npmrc = Path("frontend/.npmrc")
|
|
246
|
+
if npmrc.exists():
|
|
247
|
+
content = npmrc.read_text()
|
|
248
|
+
# Should have peer dependency settings for compatibility
|
|
249
|
+
assert (
|
|
250
|
+
"legacy-peer-deps" in content.lower()
|
|
251
|
+
or "strict-peer-dependencies" in content.lower()
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class TestPackageJsonOverrides:
|
|
256
|
+
"""Test package.json configuration."""
|
|
257
|
+
|
|
258
|
+
def test_root_package_json_exists_and_valid(self) -> None:
|
|
259
|
+
"""Test root package.json exists and has valid monorepo config."""
|
|
260
|
+
package_json = Path("package.json")
|
|
261
|
+
if package_json.exists():
|
|
262
|
+
content = package_json.read_text()
|
|
263
|
+
import json
|
|
264
|
+
|
|
265
|
+
data = json.loads(content)
|
|
266
|
+
# Should be a monorepo with pnpm workspace
|
|
267
|
+
assert data.get("private") is True
|
|
268
|
+
# Overrides are optional - only needed if dependency conflicts exist
|
|
269
|
+
# Just verify the file is valid JSON and has basic monorepo structure
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class TestAutoFormTypescriptFix:
|
|
273
|
+
"""Test AutoForm TypeScript fixes."""
|
|
274
|
+
|
|
275
|
+
@pytest.mark.skipif(
|
|
276
|
+
not Path("frontend/src/components/form/AutoForm.tsx").exists(),
|
|
277
|
+
reason="AutoForm component not available",
|
|
278
|
+
)
|
|
279
|
+
def test_autoform_has_validator_type_cast(self) -> None:
|
|
280
|
+
"""Test AutoForm has ValidatorType cast to fix type error."""
|
|
281
|
+
autoform = Path("frontend/src/components/form/AutoForm.tsx")
|
|
282
|
+
content = autoform.read_text()
|
|
283
|
+
|
|
284
|
+
# Should import ValidatorType
|
|
285
|
+
assert "ValidatorType" in content
|
|
286
|
+
|
|
287
|
+
# Should have type cast
|
|
288
|
+
assert "as ValidatorType" in content
|
|
289
|
+
|
|
290
|
+
@pytest.mark.skipif(
|
|
291
|
+
not Path("frontend/src/components/form/AutoForm.tsx").exists(),
|
|
292
|
+
reason="AutoForm component not available",
|
|
293
|
+
)
|
|
294
|
+
def test_autoform_imports_rjsf_correctly(self) -> None:
|
|
295
|
+
"""Test AutoForm imports from @rjsf packages."""
|
|
296
|
+
autoform = Path("frontend/src/components/form/AutoForm.tsx")
|
|
297
|
+
content = autoform.read_text()
|
|
298
|
+
|
|
299
|
+
assert "@rjsf/core" in content
|
|
300
|
+
assert "@rjsf/utils" in content
|
|
301
|
+
assert "@rjsf/validator-ajv8" in content
|