ntc-dev-kit 1.2.1__tar.gz → 1.3.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.
- {ntc_dev_kit-1.2.1/ntc_dev_kit.egg-info → ntc_dev_kit-1.3.0}/PKG-INFO +81 -2
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/README.md +79 -1
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/__init__.py +1 -1
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/cli_enhanced.py +61 -26
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/main.py +12 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/prompts.py +179 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/wizard.py +33 -15
- ntc_dev_kit-1.3.0/ntc/workspace/__init__.py +2 -0
- ntc_dev_kit-1.3.0/ntc/workspace/clean.py +214 -0
- ntc_dev_kit-1.3.0/ntc/workspace/config.py +342 -0
- ntc_dev_kit-1.3.0/ntc/workspace/create.py +449 -0
- ntc_dev_kit-1.3.0/ntc/workspace/processes.py +307 -0
- ntc_dev_kit-1.3.0/ntc/workspace/screen.py +217 -0
- ntc_dev_kit-1.3.0/ntc/workspace/setup.py +108 -0
- ntc_dev_kit-1.3.0/ntc/workspace/start.py +112 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0/ntc_dev_kit.egg-info}/PKG-INFO +81 -2
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/SOURCES.txt +10 -1
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/requires.txt +3 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/pyproject.toml +2 -1
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/setup.py +2 -1
- ntc_dev_kit-1.3.0/tests/test_workspace.py +96 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/LICENSE +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/MANIFEST.in +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/app_creator.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/cli.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/composer/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/composer/main.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/deployment/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/main.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/admin.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/apps.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/migrations/0001_initial.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/models.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/serializers.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/tasks.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/templates/authentication/password_reset.html +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/templates/authentication/password_reset.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/tests.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/urls.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/views.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/drf_structure.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/structure.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/.entry/start_core_backend.sh +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/.entry/start_tasks_backend.sh +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/CLAUDE.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/README.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_admin.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_apps.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_models.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_urls.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_views.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/common_apps.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/common_checks.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/common_models.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/ci/github_ci.yml +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/ci/pre_commit.yaml +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/config/gunicorn_config.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/config/http.conf +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/exceptions/handler.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/formatter.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/health.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/logger.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/pagination.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/renderers.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/response_wrapper.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/schema.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/services/azure/email/backend.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/services/azure/email/client.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/services/azure/email/notifier.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/services/object_storage.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/tests.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/utils/request.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/asgi.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/celery.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/dev_settings.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/django_settings.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/prod_settings.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/project_init.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/schema.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/test_settings.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/urls.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/wsgi.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/docker/Dockerfile +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/docker/captain-definition +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/docker/docker-compose.yml +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/knowledge/knowledge_description.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/knowledge/knowledge_design_pattern.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/knowledge/knowledge_structure.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/README.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/django_env.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/django_requirements.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/django_requirements_dev.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/dockerignore.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/gitattributes.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/gitignore.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/manage.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/pytest_ini.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/ruff_toml.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/.gitignore +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/.knowledge/knowledge_description.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/.knowledge/knowledge_design_pattern.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/.knowledge/knowledge_structure.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/CLAUDE.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/analysis_options.yaml +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/core/config/app_config.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/core/config/environment.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/core/constants/app_constants.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/core/network/dio_client.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/features/home/presentation/home_screen.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/l10n/app_localizations.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/main.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/services/api_service.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/shared/localization/locale_provider.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/shared/theme/app_theme.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/shared/theme/theme_provider.dart +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/main.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/pubspec.yaml +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/structure.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/.env.example +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/.gitignore +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/CLAUDE.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/README.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/eslint.config.mjs +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/knowledge/knowledge_description.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/knowledge/knowledge_design_pattern.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/knowledge/knowledge_structure.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/main.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/next.config.js +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/package.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/app/globals.css +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/app/layout.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/app/page.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/components/Header.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/services/api.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/services/server.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/utils/constants.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/structure.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/tsconfig.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/.env.example +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/.gitignore +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/CLAUDE.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/README.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/eslint.config.js +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/index.html +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/knowledge/knowledge_description.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/knowledge/knowledge_design_pattern.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/knowledge/knowledge_structure.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/main.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/package.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/App.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/components/ProtectedRoute.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/components/Sidebar.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/components/TopNav.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/context/AuthContext.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/context/ThemeContext.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/context/WorkspaceContext.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/hooks/useApi.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/index.css +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/layouts/AuthLayout.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/layouts/DashboardLayout.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/main.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/pages/DashboardPage.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/pages/auth/LoginPage.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/services/api.service.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/services/base.service.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/services/token.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/vite-env.d.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/structure.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/tsconfig.app.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/tsconfig.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/tsconfig.node.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/vite.config.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/App.css +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/CLAUDE.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/index.css +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/index.html +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/knowledge/knowledge_description.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/knowledge/knowledge_design_pattern.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/knowledge/knowledge_structure.md +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/main.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/package.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/App.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/components/Header.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/main.tsx +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/services/api.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/utils/constants.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/vite-env.d.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/structure.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/tsconfig.app.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/tsconfig.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/tsconfig.node.json +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/vite.config.ts +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/ui/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/ui/app.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/__init__.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/environment.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/output.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/shell.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/validation.py +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/dependency_links.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/entry_points.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/top_level.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/requirements.txt +0 -0
- {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ntc-dev-kit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Nexent Toolkit Console - A CLI tool for scaffolding Django REST Framework, React, Next.js, and Flutter projects
|
|
5
5
|
Home-page: https://github.com/nexent/ntc
|
|
6
6
|
Author: Nexent
|
|
@@ -27,6 +27,7 @@ License-File: LICENSE
|
|
|
27
27
|
Requires-Dist: colorama>=0.4.6
|
|
28
28
|
Requires-Dist: pyyaml>=6.0
|
|
29
29
|
Requires-Dist: textual>=0.47.0
|
|
30
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
30
31
|
Provides-Extra: dev
|
|
31
32
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
32
33
|
Requires-Dist: twine>=4.0; extra == "dev"
|
|
@@ -121,7 +122,85 @@ ntc init -t nextjs --name site --install --yes
|
|
|
121
122
|
| `--with-auth` / `--no-auth` | Django: include the sign-in app (custom User model, JWT, migration included) |
|
|
122
123
|
| `--env` | Django: `venv`, `conda`, `existing` (the active virtualenv) or `skip` |
|
|
123
124
|
| `--install` / `--no-install` | Others: run `npm install` / `flutter pub get` |
|
|
124
|
-
| `--
|
|
125
|
+
| `--apps` | A workspace instead of one app: `backend,website,dashboard,admin,mobile` (any of them) |
|
|
126
|
+
| `--git` | Workspace: `per-app` (default), `single` or `none` |
|
|
127
|
+
| `--yes`, `-y` | Ask nothing; needs `--name` and `--technology` (or `--apps`) |
|
|
128
|
+
|
|
129
|
+
### Workspaces: several apps at once
|
|
130
|
+
|
|
131
|
+
Tick more than one app at **What are you building?** (Space ticks) - say a backend, a website and
|
|
132
|
+
an admin dashboard - and they're created side by side in one folder, already connected:
|
|
133
|
+
|
|
134
|
+
- each app gets its own port, the frontends point at the backend's API, and the backend allows
|
|
135
|
+
the frontends' origins (CORS);
|
|
136
|
+
- dependencies install for all apps at once, one line each;
|
|
137
|
+
- git: a repository per app (plus a small one at the root for `ntc.toml`), one for everything,
|
|
138
|
+
or none;
|
|
139
|
+
- `ntc.toml` at the root says how each app is set up and run.
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
kaya/
|
|
143
|
+
├── ntc.toml # the apps, their ports, setup and run commands, env
|
|
144
|
+
├── README.md
|
|
145
|
+
├── backend/ # Django REST API http://127.0.0.1:8000
|
|
146
|
+
├── website/ # Next.js http://127.0.0.1:3000
|
|
147
|
+
└── admin/ # React admin http://127.0.0.1:5173
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Then, from anywhere inside the workspace:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
ntc start # every app in this terminal: a tab each plus "All"
|
|
154
|
+
ntc start backend admin # just these (and what they need)
|
|
155
|
+
ntc start --plain # interleaved output instead (CI, logs)
|
|
156
|
+
ntc setup # environments, .env files, dependencies - after a clone or a deep clean
|
|
157
|
+
ntc clean # build output and caches; shows the space freed
|
|
158
|
+
ntc clean --deep # also node_modules and Python environments
|
|
159
|
+
ntc add # another app, wired in like the rest
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
In `ntc start`: **1-9** (or the tabs) switch app, **0** shows all, **r** restarts, **s** stops or
|
|
163
|
+
starts, **o** opens it in the browser, **i** types into it (Flutter's `r` hot reload), **q** quits
|
|
164
|
+
and stops everything. Apps wait for the ones they `need` (the backend) before starting, and a port
|
|
165
|
+
that's already taken is reported instead of crashing.
|
|
166
|
+
|
|
167
|
+
`ntc clean` only removes what the tools rebuild (`.next`, `dist`, `__pycache__`, caches; with
|
|
168
|
+
`--deep` also `node_modules` and `env`), never anything git tracks, and skips apps that are running.
|
|
169
|
+
`--dry-run` shows what would go.
|
|
170
|
+
|
|
171
|
+
`ntc.toml` is plain TOML and meant to be edited and committed (no secrets in it - those stay in
|
|
172
|
+
each app's `.env`):
|
|
173
|
+
|
|
174
|
+
```toml
|
|
175
|
+
name = "kaya"
|
|
176
|
+
|
|
177
|
+
[apps.backend]
|
|
178
|
+
kind = "django"
|
|
179
|
+
path = "backend"
|
|
180
|
+
port = 8000
|
|
181
|
+
python = "env"
|
|
182
|
+
setup = ["{python} -m pip install -r requirements-dev.txt", "{python} manage.py migrate"]
|
|
183
|
+
run = "{python} manage.py runserver 127.0.0.1:{port}"
|
|
184
|
+
# also = { worker = "{python} -m celery -A kaya worker -B -l INFO" }
|
|
185
|
+
|
|
186
|
+
[apps.backend.env]
|
|
187
|
+
CORS_ALLOWED_ORIGINS = "{frontends.origins}"
|
|
188
|
+
|
|
189
|
+
[apps.website]
|
|
190
|
+
kind = "nextjs"
|
|
191
|
+
path = "website"
|
|
192
|
+
port = 3000
|
|
193
|
+
needs = ["backend"]
|
|
194
|
+
run = "npm run dev -- --port {port}"
|
|
195
|
+
repo = "git@github.com:you/website.git" # `ntc setup` clones it when the folder is missing
|
|
196
|
+
|
|
197
|
+
[apps.website.env]
|
|
198
|
+
NEXT_PUBLIC_API_BASE_URL = "{backend.url}/api/v1"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Placeholders: `{port}`, `{url}`, `{python}` (this app's), `{<app>.port}`, `{<app>.url}`,
|
|
202
|
+
`{frontends.origins}`. Commands are one program with its arguments (no `&&`), so they run the
|
|
203
|
+
same on Windows and Unix.
|
|
125
204
|
|
|
126
205
|
### Technology Options
|
|
127
206
|
|
|
@@ -81,7 +81,85 @@ ntc init -t nextjs --name site --install --yes
|
|
|
81
81
|
| `--with-auth` / `--no-auth` | Django: include the sign-in app (custom User model, JWT, migration included) |
|
|
82
82
|
| `--env` | Django: `venv`, `conda`, `existing` (the active virtualenv) or `skip` |
|
|
83
83
|
| `--install` / `--no-install` | Others: run `npm install` / `flutter pub get` |
|
|
84
|
-
| `--
|
|
84
|
+
| `--apps` | A workspace instead of one app: `backend,website,dashboard,admin,mobile` (any of them) |
|
|
85
|
+
| `--git` | Workspace: `per-app` (default), `single` or `none` |
|
|
86
|
+
| `--yes`, `-y` | Ask nothing; needs `--name` and `--technology` (or `--apps`) |
|
|
87
|
+
|
|
88
|
+
### Workspaces: several apps at once
|
|
89
|
+
|
|
90
|
+
Tick more than one app at **What are you building?** (Space ticks) - say a backend, a website and
|
|
91
|
+
an admin dashboard - and they're created side by side in one folder, already connected:
|
|
92
|
+
|
|
93
|
+
- each app gets its own port, the frontends point at the backend's API, and the backend allows
|
|
94
|
+
the frontends' origins (CORS);
|
|
95
|
+
- dependencies install for all apps at once, one line each;
|
|
96
|
+
- git: a repository per app (plus a small one at the root for `ntc.toml`), one for everything,
|
|
97
|
+
or none;
|
|
98
|
+
- `ntc.toml` at the root says how each app is set up and run.
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
kaya/
|
|
102
|
+
├── ntc.toml # the apps, their ports, setup and run commands, env
|
|
103
|
+
├── README.md
|
|
104
|
+
├── backend/ # Django REST API http://127.0.0.1:8000
|
|
105
|
+
├── website/ # Next.js http://127.0.0.1:3000
|
|
106
|
+
└── admin/ # React admin http://127.0.0.1:5173
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Then, from anywhere inside the workspace:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
ntc start # every app in this terminal: a tab each plus "All"
|
|
113
|
+
ntc start backend admin # just these (and what they need)
|
|
114
|
+
ntc start --plain # interleaved output instead (CI, logs)
|
|
115
|
+
ntc setup # environments, .env files, dependencies - after a clone or a deep clean
|
|
116
|
+
ntc clean # build output and caches; shows the space freed
|
|
117
|
+
ntc clean --deep # also node_modules and Python environments
|
|
118
|
+
ntc add # another app, wired in like the rest
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
In `ntc start`: **1-9** (or the tabs) switch app, **0** shows all, **r** restarts, **s** stops or
|
|
122
|
+
starts, **o** opens it in the browser, **i** types into it (Flutter's `r` hot reload), **q** quits
|
|
123
|
+
and stops everything. Apps wait for the ones they `need` (the backend) before starting, and a port
|
|
124
|
+
that's already taken is reported instead of crashing.
|
|
125
|
+
|
|
126
|
+
`ntc clean` only removes what the tools rebuild (`.next`, `dist`, `__pycache__`, caches; with
|
|
127
|
+
`--deep` also `node_modules` and `env`), never anything git tracks, and skips apps that are running.
|
|
128
|
+
`--dry-run` shows what would go.
|
|
129
|
+
|
|
130
|
+
`ntc.toml` is plain TOML and meant to be edited and committed (no secrets in it - those stay in
|
|
131
|
+
each app's `.env`):
|
|
132
|
+
|
|
133
|
+
```toml
|
|
134
|
+
name = "kaya"
|
|
135
|
+
|
|
136
|
+
[apps.backend]
|
|
137
|
+
kind = "django"
|
|
138
|
+
path = "backend"
|
|
139
|
+
port = 8000
|
|
140
|
+
python = "env"
|
|
141
|
+
setup = ["{python} -m pip install -r requirements-dev.txt", "{python} manage.py migrate"]
|
|
142
|
+
run = "{python} manage.py runserver 127.0.0.1:{port}"
|
|
143
|
+
# also = { worker = "{python} -m celery -A kaya worker -B -l INFO" }
|
|
144
|
+
|
|
145
|
+
[apps.backend.env]
|
|
146
|
+
CORS_ALLOWED_ORIGINS = "{frontends.origins}"
|
|
147
|
+
|
|
148
|
+
[apps.website]
|
|
149
|
+
kind = "nextjs"
|
|
150
|
+
path = "website"
|
|
151
|
+
port = 3000
|
|
152
|
+
needs = ["backend"]
|
|
153
|
+
run = "npm run dev -- --port {port}"
|
|
154
|
+
repo = "git@github.com:you/website.git" # `ntc setup` clones it when the folder is missing
|
|
155
|
+
|
|
156
|
+
[apps.website.env]
|
|
157
|
+
NEXT_PUBLIC_API_BASE_URL = "{backend.url}/api/v1"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Placeholders: `{port}`, `{url}`, `{python}` (this app's), `{<app>.port}`, `{<app>.url}`,
|
|
161
|
+
`{frontends.origins}`. Commands are one program with its arguments (no `&&`), so they run the
|
|
162
|
+
same on Windows and Unix.
|
|
85
163
|
|
|
86
164
|
### Technology Options
|
|
87
165
|
|
|
@@ -284,8 +284,10 @@ Examples:
|
|
|
284
284
|
ntc init -t django --name shop-api --with-auth --env venv --yes
|
|
285
285
|
# No questions at all (scripts, CI, AI agents)
|
|
286
286
|
ntc init -t nextjs --name site --install --yes
|
|
287
|
-
ntc
|
|
288
|
-
|
|
287
|
+
ntc init --name shop --apps backend,website,admin --yes
|
|
288
|
+
# A workspace: several apps wired together, plus ntc.toml
|
|
289
|
+
ntc start # Run the workspace's apps in this terminal
|
|
290
|
+
ntc setup / ntc clean / ntc add
|
|
289
291
|
ntc create app myapp # Create new Django app
|
|
290
292
|
ntc drf init path name # Legacy Django REST Framework initialization
|
|
291
293
|
"""
|
|
@@ -321,7 +323,11 @@ Examples:
|
|
|
321
323
|
help="Next.js/React/Flutter: don't install dependencies")
|
|
322
324
|
init_parser.add_argument("--yes", "-y", action="store_true",
|
|
323
325
|
help="Don't ask: use the options given and defaults for the rest "
|
|
324
|
-
"(requires --name and --technology)")
|
|
326
|
+
"(requires --name and --technology or --apps)")
|
|
327
|
+
init_parser.add_argument("--apps", help="A workspace of several apps, comma-separated: "
|
|
328
|
+
"backend, website, dashboard, admin, mobile")
|
|
329
|
+
init_parser.add_argument("--git", choices=["per-app", "single", "none"],
|
|
330
|
+
help="Workspace: a repository per app (default), one for everything, or none")
|
|
325
331
|
|
|
326
332
|
# Create command for apps
|
|
327
333
|
create_parser = subparsers.add_parser("create", help="Create new components")
|
|
@@ -345,13 +351,32 @@ Examples:
|
|
|
345
351
|
drf_init_parser.add_argument("name", help="Project name")
|
|
346
352
|
|
|
347
353
|
# Composer command for full-stack project composition
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
354
|
+
subparsers.add_parser("compose", help="Replaced by `ntc init` (pick several apps)")
|
|
355
|
+
add_parser = subparsers.add_parser("add", help="Add apps to the workspace")
|
|
356
|
+
add_parser.add_argument("apps", nargs="*", help="backend, website, dashboard, admin, mobile")
|
|
357
|
+
add_parser.add_argument("--name", help="Folder name (when adding one app)")
|
|
358
|
+
add_parser.add_argument("--env", choices=["venv", "conda", "existing", "skip"], help="Backend: Python environment")
|
|
359
|
+
add_auth = add_parser.add_mutually_exclusive_group()
|
|
360
|
+
add_auth.add_argument("--with-auth", dest="with_auth", action="store_true", default=None)
|
|
361
|
+
add_auth.add_argument("--no-auth", dest="with_auth", action="store_false")
|
|
362
|
+
add_install = add_parser.add_mutually_exclusive_group()
|
|
363
|
+
add_install.add_argument("--install", dest="install", action="store_true", default=None)
|
|
364
|
+
add_install.add_argument("--no-install", dest="install", action="store_false")
|
|
365
|
+
add_parser.add_argument("--yes", "-y", action="store_true", help="Don't ask")
|
|
366
|
+
setup_parser = subparsers.add_parser("setup", help="Install the workspace's apps (after a clone or a deep clean)")
|
|
367
|
+
setup_parser.add_argument("apps", nargs="*", help="Only these apps")
|
|
368
|
+
clean_parser = subparsers.add_parser("clean", help="Delete build output and caches; say how much space it freed")
|
|
369
|
+
clean_parser.add_argument("apps", nargs="*", help="Only these apps")
|
|
370
|
+
clean_parser.add_argument("--deep", action="store_true",
|
|
371
|
+
help="Also dependencies: node_modules, Python environments (`ntc setup` reinstalls)")
|
|
372
|
+
clean_parser.add_argument("--dry-run", action="store_true", help="Show what would go, delete nothing")
|
|
373
|
+
clean_parser.add_argument("--yes", "-y", action="store_true", help="Don't ask before deleting")
|
|
351
374
|
|
|
352
375
|
# Start services command
|
|
353
|
-
start_parser = subparsers.add_parser("start", help="
|
|
354
|
-
start_parser.add_argument("
|
|
376
|
+
start_parser = subparsers.add_parser("start", help="Run the workspace's apps together in this terminal")
|
|
377
|
+
start_parser.add_argument("apps", nargs="*", help="Only these apps (and the ones they need)")
|
|
378
|
+
start_parser.add_argument("--plain", action="store_true",
|
|
379
|
+
help="Interleaved output instead of the full-screen view (CI, logs)")
|
|
355
380
|
|
|
356
381
|
# Doctor command for setup diagnostics
|
|
357
382
|
doctor_parser = subparsers.add_parser("doctor", help="Check setup and fix common issues")
|
|
@@ -364,12 +389,20 @@ Examples:
|
|
|
364
389
|
if args.command == "init":
|
|
365
390
|
technology = args.technology or args.frontend or args.backend
|
|
366
391
|
technology = {"drf": "django", "laravel": "django"}.get(technology, technology)
|
|
367
|
-
|
|
368
|
-
|
|
392
|
+
apps = [a.strip() for a in args.apps.split(",") if a.strip()] if args.apps else None
|
|
393
|
+
if apps and technology:
|
|
394
|
+
print_error("Give either --technology (one app) or --apps (a workspace), not both.")
|
|
369
395
|
sys.exit(2)
|
|
396
|
+
if args.yes and not (args.name and (technology or apps)):
|
|
397
|
+
print_error("--yes needs --name and --technology (or --apps), so there is nothing left to ask.")
|
|
398
|
+
sys.exit(2)
|
|
399
|
+
if apps and len(apps) == 1:
|
|
400
|
+
from ntc.workspace.config import ROLE_KINDS
|
|
401
|
+
technology, apps = ROLE_KINDS.get(apps[0], apps[0]), None
|
|
370
402
|
from ntc.wizard import run as run_wizard
|
|
371
403
|
created = run_wizard(technology=technology, name=args.name, path=args.path, with_auth=args.with_auth,
|
|
372
|
-
env_choice=args.env, install=args.install, assume_yes=args.yes
|
|
404
|
+
env_choice=args.env, install=args.install, assume_yes=args.yes,
|
|
405
|
+
apps=apps, git=args.git)
|
|
373
406
|
sys.exit(0 if created else 1)
|
|
374
407
|
elif args.command == "create" and args.create_type == "app":
|
|
375
408
|
if not is_valid_project_name(args.app_name):
|
|
@@ -385,22 +418,23 @@ Examples:
|
|
|
385
418
|
from ntc.templates.drf_structure.main import init_drf_project
|
|
386
419
|
init_drf_project(args.path, args.name)
|
|
387
420
|
elif args.command == "compose":
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
sys.exit(1)
|
|
392
|
-
from ntc.composer.main import compose_project
|
|
393
|
-
if args.path:
|
|
394
|
-
compose_project(args.project_name, args.path)
|
|
395
|
-
else:
|
|
396
|
-
compose_project(args.project_name)
|
|
421
|
+
print_info("`ntc compose` is now part of `ntc init`: pick several apps (Space ticks them) and they're "
|
|
422
|
+
"created together as a workspace, with an ntc.toml for `ntc start`.")
|
|
423
|
+
sys.exit(2)
|
|
397
424
|
elif args.command == "start":
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
else
|
|
403
|
-
|
|
425
|
+
from ntc.workspace.start import run as run_start
|
|
426
|
+
sys.exit(0 if run_start(args.apps, plain=args.plain) else 1)
|
|
427
|
+
elif args.command == "setup":
|
|
428
|
+
from ntc.workspace.setup import run as run_setup
|
|
429
|
+
sys.exit(0 if run_setup(args.apps) else 1)
|
|
430
|
+
elif args.command == "clean":
|
|
431
|
+
from ntc.workspace.clean import run as run_clean
|
|
432
|
+
sys.exit(0 if run_clean(args.apps, deep=args.deep, assume_yes=args.yes, dry_run=args.dry_run) else 1)
|
|
433
|
+
elif args.command == "add":
|
|
434
|
+
from ntc.workspace.create import run_add
|
|
435
|
+
env_choice = {"existing": "active"}.get(args.env, args.env)
|
|
436
|
+
sys.exit(0 if run_add(args.apps, name=args.name, with_auth=args.with_auth, env_choice=env_choice,
|
|
437
|
+
install=args.install, assume_yes=args.yes) else 1)
|
|
404
438
|
elif args.command == "doctor":
|
|
405
439
|
run_doctor()
|
|
406
440
|
elif args.command == "tui":
|
|
@@ -412,6 +446,7 @@ Examples:
|
|
|
412
446
|
print(f"\n {Fore.LIGHTBLACK_EX}Quick Start:{Style.RESET_ALL}")
|
|
413
447
|
print(f" {Fore.WHITE}ntc tui{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}interactive terminal UI{Style.RESET_ALL}")
|
|
414
448
|
print(f" {Fore.WHITE}ntc init{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}interactive setup{Style.RESET_ALL}")
|
|
449
|
+
print(f" {Fore.WHITE}ntc start{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}run a workspace's apps{Style.RESET_ALL}")
|
|
415
450
|
print(f" {Fore.WHITE}ntc init -t django{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}Django project{Style.RESET_ALL}")
|
|
416
451
|
print(f" {Fore.WHITE}ntc doctor{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}check setup{Style.RESET_ALL}")
|
|
417
452
|
print()
|
|
@@ -373,6 +373,18 @@ def _render_project(project_root, project_name, original_name, with_auth):
|
|
|
373
373
|
return True
|
|
374
374
|
|
|
375
375
|
|
|
376
|
+
def write_env_file(project_root, project_name, original_name=None):
|
|
377
|
+
"""Write the project's .env (development defaults, a new SECRET_KEY). It's gitignored, so a
|
|
378
|
+
fresh clone has none; `ntc setup` uses this to recreate it."""
|
|
379
|
+
structure_file = os.path.join(os.path.dirname(__file__), "structure.json")
|
|
380
|
+
with open(structure_file, "r", encoding="utf-8") as f:
|
|
381
|
+
template = json.load(f)[".env"]["template"]
|
|
382
|
+
variables = {"project_name": project_name, "original_name": original_name or project_name,
|
|
383
|
+
"secret_key": secrets.token_urlsafe(50)}
|
|
384
|
+
with open(os.path.join(project_root, ".env"), "w", encoding="utf-8") as f:
|
|
385
|
+
f.write(get_template_content(template, variables))
|
|
386
|
+
|
|
387
|
+
|
|
376
388
|
def init_drf_project(base_path, project_name, with_auth=False, env_choice=None):
|
|
377
389
|
"""Create a DRF project in base_path/<sanitized name> and set up its environment.
|
|
378
390
|
|
|
@@ -189,6 +189,82 @@ def text(question, default=None, validate=None):
|
|
|
189
189
|
return value
|
|
190
190
|
|
|
191
191
|
|
|
192
|
+
def multiselect(question, options, default=(), hint=None):
|
|
193
|
+
"""Pick any of options: (label, value) or (label, value, description) tuples.
|
|
194
|
+
|
|
195
|
+
↑/↓ move, Space (or the option's number) ticks/unticks, Enter confirms, Esc cancels.
|
|
196
|
+
Returns the ticked values in option order. Without a terminal: the defaults.
|
|
197
|
+
"""
|
|
198
|
+
options = [opt if len(opt) == 3 else (opt[0], opt[1], "") for opt in options]
|
|
199
|
+
ticked = {value for _, value, _ in options if value in default}
|
|
200
|
+
if not interactive():
|
|
201
|
+
chosen = [value for _, value, _ in options if value in ticked]
|
|
202
|
+
answered(question, ", ".join(label for label, value, _ in options if value in ticked) or "none")
|
|
203
|
+
return chosen
|
|
204
|
+
|
|
205
|
+
cursor = 0
|
|
206
|
+
error = ""
|
|
207
|
+
lines = len(options) + 2
|
|
208
|
+
|
|
209
|
+
def draw(first=False):
|
|
210
|
+
width = _width()
|
|
211
|
+
if not first:
|
|
212
|
+
_write(_UP.format(lines))
|
|
213
|
+
hint_text = hint or "Space to tick, Enter to confirm"
|
|
214
|
+
if len(question) + len(hint_text) + 3 > width:
|
|
215
|
+
hint_text = ""
|
|
216
|
+
_write(f"{_CLEAR_LINE}{QUESTION} {Style.BRIGHT}{_truncate(question, width - 2)}{RESET} "
|
|
217
|
+
f"{DIM}{hint_text}{RESET}\n")
|
|
218
|
+
for i, (label, value, description) in enumerate(options):
|
|
219
|
+
pointer = POINTER if i == cursor else " "
|
|
220
|
+
box = f"{Fore.GREEN}◉{RESET}" if value in ticked else f"{DIM}◯{RESET}"
|
|
221
|
+
room = width - 8 - len(str(i + 1)) # " ❯ ◉ 1 "
|
|
222
|
+
if len(label) + 2 + len(description) > room:
|
|
223
|
+
description = ""
|
|
224
|
+
label = _truncate(label, room)
|
|
225
|
+
text = f"{Fore.CYAN}{Style.BRIGHT}{label}{RESET}" if i == cursor else label
|
|
226
|
+
extra = f" {DIM}{description}{RESET}" if description else ""
|
|
227
|
+
_write(f"{_CLEAR_LINE} {pointer} {box} {DIM}{i + 1}{RESET} {text}{extra}\n")
|
|
228
|
+
_write(f"{_CLEAR_LINE} {Fore.RED}{_truncate(error, width - 2)}{RESET}\n" if error else f"{_CLEAR_LINE}\n")
|
|
229
|
+
|
|
230
|
+
def collapse():
|
|
231
|
+
_write(_UP.format(lines))
|
|
232
|
+
for _ in range(lines):
|
|
233
|
+
_write(f"{_CLEAR_LINE}\n")
|
|
234
|
+
_write(_UP.format(lines))
|
|
235
|
+
|
|
236
|
+
draw(first=True)
|
|
237
|
+
try:
|
|
238
|
+
while True:
|
|
239
|
+
key = _read_key()
|
|
240
|
+
error = ""
|
|
241
|
+
if key == "up":
|
|
242
|
+
cursor = (cursor - 1) % len(options)
|
|
243
|
+
elif key == "down":
|
|
244
|
+
cursor = (cursor + 1) % len(options)
|
|
245
|
+
elif key == " " or (key.isdigit() and 1 <= int(key) <= len(options)):
|
|
246
|
+
if key != " ":
|
|
247
|
+
cursor = int(key) - 1
|
|
248
|
+
value = options[cursor][1]
|
|
249
|
+
ticked.symmetric_difference_update({value})
|
|
250
|
+
elif key == "enter":
|
|
251
|
+
if ticked:
|
|
252
|
+
break
|
|
253
|
+
error = "Tick at least one (Space)"
|
|
254
|
+
elif key == "escape":
|
|
255
|
+
raise Cancelled
|
|
256
|
+
else:
|
|
257
|
+
continue
|
|
258
|
+
draw()
|
|
259
|
+
except (Cancelled, KeyboardInterrupt):
|
|
260
|
+
collapse()
|
|
261
|
+
answered(question, "cancelled", mark=CROSS)
|
|
262
|
+
raise Cancelled
|
|
263
|
+
collapse()
|
|
264
|
+
answered(question, ", ".join(label for label, value, _ in options if value in ticked))
|
|
265
|
+
return [value for _, value, _ in options if value in ticked]
|
|
266
|
+
|
|
267
|
+
|
|
192
268
|
def confirm(question, default=True):
|
|
193
269
|
"""Yes or no; Enter takes the default."""
|
|
194
270
|
return select(question, [("Yes", True), ("No", False)], default=0 if default else 1,
|
|
@@ -265,6 +341,109 @@ def step(label, command, cwd=None, env=None):
|
|
|
265
341
|
return False, output
|
|
266
342
|
|
|
267
343
|
|
|
344
|
+
class Task:
|
|
345
|
+
"""One line of work for tasks(): commands run one after another in cwd. A command is a list
|
|
346
|
+
(run directly) or a callable returning None on success or an error message."""
|
|
347
|
+
|
|
348
|
+
def __init__(self, label, commands, cwd=None, env=None):
|
|
349
|
+
self.label = label
|
|
350
|
+
self.commands = commands
|
|
351
|
+
self.cwd = cwd
|
|
352
|
+
self.env = env
|
|
353
|
+
self.state = "waiting" # waiting / running / done / failed
|
|
354
|
+
self.latest = ""
|
|
355
|
+
self.lines = []
|
|
356
|
+
self.failed_command = None # the command that failed, as text
|
|
357
|
+
self.started = None
|
|
358
|
+
self.elapsed = 0.0
|
|
359
|
+
|
|
360
|
+
def run(self):
|
|
361
|
+
self.state = "running"
|
|
362
|
+
self.started = time.monotonic()
|
|
363
|
+
for command in self.commands:
|
|
364
|
+
if callable(command):
|
|
365
|
+
# A step decided when it runs: returns None (done), an error message, or the
|
|
366
|
+
# command to run now (e.g. one naming an interpreter an earlier step created).
|
|
367
|
+
try:
|
|
368
|
+
result = command()
|
|
369
|
+
except Exception as error: # noqa: BLE001
|
|
370
|
+
result = str(error)
|
|
371
|
+
if isinstance(result, str):
|
|
372
|
+
self.lines.append(result)
|
|
373
|
+
self.failed_command = getattr(command, "description", "")
|
|
374
|
+
break
|
|
375
|
+
if result is None:
|
|
376
|
+
continue
|
|
377
|
+
command = result
|
|
378
|
+
command = list(command)
|
|
379
|
+
command[0] = shutil.which(command[0]) or command[0]
|
|
380
|
+
try:
|
|
381
|
+
process = subprocess.Popen(
|
|
382
|
+
command, cwd=self.cwd, env=self.env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
383
|
+
stdin=subprocess.DEVNULL, text=True, encoding="utf-8", errors="replace", bufsize=1,
|
|
384
|
+
)
|
|
385
|
+
except OSError as error:
|
|
386
|
+
self.lines.append(f"couldn't run {os.path.basename(command[0])} ({error.strerror or error})")
|
|
387
|
+
self.failed_command = " ".join([os.path.basename(command[0])] + command[1:])
|
|
388
|
+
break
|
|
389
|
+
for line in process.stdout:
|
|
390
|
+
line = line.strip()
|
|
391
|
+
if line:
|
|
392
|
+
self.lines.append(line)
|
|
393
|
+
self.latest = line
|
|
394
|
+
if process.wait() != 0:
|
|
395
|
+
self.failed_command = " ".join([os.path.splitext(os.path.basename(command[0]))[0]] + command[1:])
|
|
396
|
+
break
|
|
397
|
+
self.elapsed = time.monotonic() - self.started
|
|
398
|
+
self.state = "failed" if self.failed_command is not None else "done"
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def tasks(items):
|
|
402
|
+
"""Run Tasks side by side, one live line each (spinner, label, latest output, time). The
|
|
403
|
+
full output of a failed task is printed once everything has finished. Returns the tasks."""
|
|
404
|
+
threads = [threading.Thread(target=task.run, daemon=True) for task in items]
|
|
405
|
+
for thread in threads:
|
|
406
|
+
thread.start()
|
|
407
|
+
show = interactive()
|
|
408
|
+
frames = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
409
|
+
label_width = max(len(task.label) for task in items)
|
|
410
|
+
|
|
411
|
+
def line(task, frame):
|
|
412
|
+
width = _width()
|
|
413
|
+
label = task.label.ljust(label_width)
|
|
414
|
+
if task.state == "done":
|
|
415
|
+
return f"{CHECK} {label} {DIM}({task.elapsed:.0f}s){RESET}"
|
|
416
|
+
if task.state == "failed":
|
|
417
|
+
return f"{CROSS} {label} {Fore.RED}failed{RESET} {DIM}({task.elapsed:.0f}s){RESET}"
|
|
418
|
+
if task.state == "waiting":
|
|
419
|
+
return f"{DIM}·{RESET} {label}"
|
|
420
|
+
elapsed = f"{int(time.monotonic() - task.started)}s"
|
|
421
|
+
room = width - label_width - len(elapsed) - 6
|
|
422
|
+
latest = _truncate(task.latest, room) if task.latest and room > 10 else ""
|
|
423
|
+
return f"{Fore.CYAN}{frame}{RESET} {label} {DIM}{elapsed} {latest}{RESET}"
|
|
424
|
+
|
|
425
|
+
i = 0
|
|
426
|
+
if show:
|
|
427
|
+
_write("\n" * len(items))
|
|
428
|
+
while any(thread.is_alive() for thread in threads):
|
|
429
|
+
if show:
|
|
430
|
+
_write(_UP.format(len(items)))
|
|
431
|
+
for task in items:
|
|
432
|
+
_write(f"{_CLEAR_LINE}{line(task, frames[i % len(frames)])}\n")
|
|
433
|
+
i += 1
|
|
434
|
+
time.sleep(0.08)
|
|
435
|
+
if show:
|
|
436
|
+
_write(_UP.format(len(items)))
|
|
437
|
+
for task in items:
|
|
438
|
+
_write(f"{_CLEAR_LINE}{line(task, '')}\n")
|
|
439
|
+
for task in items:
|
|
440
|
+
if task.state == "failed":
|
|
441
|
+
_write(f"\n{CROSS} {Style.BRIGHT}{task.label}{RESET}: {task.failed_command or 'failed'}\n")
|
|
442
|
+
for output in task.lines[-15:]:
|
|
443
|
+
_write(f" {DIM}{output}{RESET}\n")
|
|
444
|
+
return items
|
|
445
|
+
|
|
446
|
+
|
|
268
447
|
def _truncate(value, limit):
|
|
269
448
|
return value if len(value) <= limit else value[: max(limit - 1, 1)] + "…"
|
|
270
449
|
|
|
@@ -5,7 +5,6 @@ a one-line answer, the defaults are already selected (Enter, Enter, Enter works)
|
|
|
5
5
|
pauses for effect. The only spinner is for real work - installing dependencies - and it shows
|
|
6
6
|
what that work is doing. Every answer can also be given as a flag; with --yes nothing is asked.
|
|
7
7
|
"""
|
|
8
|
-
import json
|
|
9
8
|
import os
|
|
10
9
|
import shutil
|
|
11
10
|
import sys
|
|
@@ -190,16 +189,9 @@ FRONTENDS = {
|
|
|
190
189
|
|
|
191
190
|
|
|
192
191
|
def _create_frontend(plan):
|
|
193
|
-
import
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
with open(os.path.join(os.path.dirname(module.__file__), "structure.json"), encoding="utf-8") as f:
|
|
197
|
-
structure = json.load(f)
|
|
198
|
-
variables = {"project_name": plan.name}
|
|
199
|
-
if plan.technology == "flutter":
|
|
200
|
-
variables["project_camel"] = module.convert_to_camel_case(plan.name)
|
|
201
|
-
os.makedirs(plan.root, exist_ok=True)
|
|
202
|
-
module.create_from_json(plan.root, structure["{project_name}"], variables)
|
|
192
|
+
from ntc.workspace.create import render_frontend
|
|
193
|
+
tool = FRONTENDS[plan.technology][1]
|
|
194
|
+
render_frontend(plan.technology, plan.root, plan.name)
|
|
203
195
|
prompts.success(f"Created {_count_files(plan.root)} files")
|
|
204
196
|
if plan.install:
|
|
205
197
|
command = ["npm", "install", "--no-audit", "--no-fund"] if tool == "npm" else ["flutter", "pub", "get"]
|
|
@@ -251,10 +243,18 @@ def _next_steps(plan):
|
|
|
251
243
|
|
|
252
244
|
# ── The wizard ──────────────────────────────────────────────────────────
|
|
253
245
|
|
|
254
|
-
def run(technology=None, name=None, path=None, with_auth=None, env_choice=None, install=None, assume_yes=False
|
|
246
|
+
def run(technology=None, name=None, path=None, with_auth=None, env_choice=None, install=None, assume_yes=False,
|
|
247
|
+
apps=None, git=None):
|
|
255
248
|
"""Ask what isn't given, show the plan, create the project. Returns the project folder, or
|
|
256
|
-
None if cancelled. Esc or Ctrl+C at any question cancels without writing anything.
|
|
249
|
+
None if cancelled. Esc or Ctrl+C at any question cancels without writing anything.
|
|
250
|
+
|
|
251
|
+
Picking several apps (or passing apps=[roles]) makes a workspace instead: the apps side by
|
|
252
|
+
side in one folder, wired to each other, with an ntc.toml for `ntc start`."""
|
|
253
|
+
from ntc.workspace import config as workspace_config
|
|
257
254
|
print(f"\n{Style.BRIGHT}ntc{RESET} {DIM}— new project{RESET}\n")
|
|
255
|
+
inside = workspace_config.find_root()
|
|
256
|
+
if inside and not apps and technology is None:
|
|
257
|
+
prompts.info(f"You're in the {os.path.basename(inside)} workspace - `ntc add` adds an app to it.")
|
|
258
258
|
plan = Plan()
|
|
259
259
|
try:
|
|
260
260
|
if name is None:
|
|
@@ -267,10 +267,20 @@ def run(technology=None, name=None, path=None, with_auth=None, env_choice=None,
|
|
|
267
267
|
prompts.answered("Project name", name)
|
|
268
268
|
plan.name = name
|
|
269
269
|
|
|
270
|
-
if technology is None:
|
|
271
|
-
|
|
270
|
+
if technology is None and not apps:
|
|
271
|
+
options = [(label, role, workspace_config.KINDS[kind]["label"])
|
|
272
|
+
for role, label, kind in workspace_config.ROLES]
|
|
273
|
+
apps = prompts.multiselect("What are you building?", options,
|
|
274
|
+
hint="Space to tick (several = a workspace), Enter to confirm")
|
|
275
|
+
if len(apps) == 1:
|
|
276
|
+
technology = workspace_config.ROLE_KINDS[apps[0]]
|
|
277
|
+
apps = None
|
|
278
|
+
elif apps:
|
|
279
|
+
prompts.answered("What are you building?", ", ".join(apps))
|
|
272
280
|
else:
|
|
273
281
|
prompts.answered("What are you building?", LABELS.get(technology, technology))
|
|
282
|
+
if apps:
|
|
283
|
+
raise _Workspace # a workspace asks its own questions from here
|
|
274
284
|
plan.technology = technology
|
|
275
285
|
|
|
276
286
|
_ask_location(plan, path, assume_yes)
|
|
@@ -294,6 +304,10 @@ def run(technology=None, name=None, path=None, with_auth=None, env_choice=None,
|
|
|
294
304
|
except prompts.Cancelled:
|
|
295
305
|
print(f"\n{DIM}Cancelled - nothing was written.{RESET}")
|
|
296
306
|
return None
|
|
307
|
+
except _Workspace:
|
|
308
|
+
from ntc.workspace.create import run_init
|
|
309
|
+
return run_init(plan.name, apps, path=path, with_auth=with_auth, env_choice=env_choice,
|
|
310
|
+
install=install, git=git, assume_yes=assume_yes)
|
|
297
311
|
|
|
298
312
|
print()
|
|
299
313
|
started = time.monotonic()
|
|
@@ -312,6 +326,10 @@ def run(technology=None, name=None, path=None, with_auth=None, env_choice=None,
|
|
|
312
326
|
return plan.root
|
|
313
327
|
|
|
314
328
|
|
|
329
|
+
class _Workspace(Exception):
|
|
330
|
+
"""Several apps were picked: the workspace flow takes over."""
|
|
331
|
+
|
|
332
|
+
|
|
315
333
|
def _is_valid_location_name(plan):
|
|
316
334
|
"""A project created in an existing folder takes that folder's name; make sure it's usable."""
|
|
317
335
|
if plan.technology == "django" and not is_valid_project_name(plan.name):
|