quoll-cli 0.2.0__tar.gz → 0.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.
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/PKG-INFO +31 -6
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/README.md +30 -5
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/pyproject.toml +3 -1
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/cd/config.py +7 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/ci/custom.py +4 -2
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/_common.py +19 -11
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/add.py +6 -3
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/cd.py +66 -8
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/new.py +60 -21
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/config/store.py +8 -6
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/base.py +9 -3
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/go/__init__.py +2 -0
- quoll_cli-0.3.0/quoll/languages/go/starter.py +29 -0
- quoll_cli-0.3.0/quoll/languages/go/templates/http/README.md +9 -0
- quoll_cli-0.3.0/quoll/languages/go/templates/http/gitignore +5 -0
- quoll_cli-0.3.0/quoll/languages/go/templates/http/go.mod.tmpl +3 -0
- quoll_cli-0.3.0/quoll/languages/go/templates/http/main.go +84 -0
- quoll_cli-0.3.0/quoll/languages/go/templates/http/main_test.go +102 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/Dockerfile.j2 +1 -1
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/__init__.py +2 -1
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/ci.py +14 -2
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/detect.py +3 -3
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/docker.py +15 -1
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/angular/starter.py +10 -13
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nextjs/__init__.py +4 -1
- quoll_cli-0.3.0/quoll/languages/node/frameworks/nextjs/starter.py +110 -0
- quoll_cli-0.3.0/quoll/languages/node/react.py +73 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/starter.py +4 -3
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/ci.py +11 -1
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/ci.py +1 -1
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/fastapi/__init__.py +4 -1
- quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/starter.py +41 -0
- quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/template/README.md +10 -0
- quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/template/app/main.py +13 -0
- quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/template/env.example +1 -0
- quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/template/gitignore +7 -0
- quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/template/pyproject +16 -0
- quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/template/python-version +1 -0
- quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/template/tests/test_main.py +15 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/base.py +8 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/github/actions.py +2 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/github/api.py +29 -1
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/github/cd.py +11 -1
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/github/ci.py +18 -0
- quoll_cli-0.3.0/quoll/platforms/github/release.py +141 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/starters.py +20 -4
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/conftest.py +9 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/docker/test_build.py +7 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/docker/test_images.py +18 -1
- quoll_cli-0.3.0/tests/languages/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_php.py +3 -1
- quoll_cli-0.3.0/tests/platforms/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/platforms/test_api.py +17 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/platforms/test_gitlab_ci.py +6 -3
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_cd.py +148 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_ci_render.py +11 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_custom.py +11 -2
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_new.py +209 -10
- quoll_cli-0.3.0/tests/test_store.py +30 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/.gitignore +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/LICENSE +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/cd/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/ci/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/ci/config.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/ci/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/ci/steps.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/ci/tools.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/_workspace.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/add_secret.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/k8s.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/commands/remove.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/config/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/config/quoll_toml.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/config/settings.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/console.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/docker/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/docker/config.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/docker/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/docker/render.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/files.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/chart/templates/_helpers.tpl +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/chart/templates/deployment.yaml +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/chart/templates/hpa.yaml +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/chart/templates/ingress.yaml +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/chart/templates/pdb.yaml +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/chart/templates/service.yaml +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/chart/templates/serviceaccount.yaml +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/config.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/deploy.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/k8s/render.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/cpp/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/cpp/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/cpp/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/cpp/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/cpp/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/dotnet/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/dotnet/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/dotnet/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/dotnet/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/dotnet/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/go/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/go/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/go/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/go/docker.py +0 -0
- /quoll_cli-0.2.0/tests/__init__.py → /quoll_cli-0.3.0/quoll/languages/go/templates/http/go.sum +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/frameworks/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/frameworks/spring_boot/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/frameworks/spring_boot/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/frameworks/spring_boot/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/frameworks/spring_boot/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/java/frameworks/spring_boot/services.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/angular/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/angular/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/angular/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/angular/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/astro/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/astro/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/astro/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/astro/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nestjs/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nestjs/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nestjs/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nextjs/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nextjs/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nextjs/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nextjs/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nuxt/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nuxt/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nuxt/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/nuxt/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/sveltekit/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/sveltekit/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/sveltekit/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/frameworks/sveltekit/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/README.md +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/env.example +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/eslint.config.js +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/gitignore +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/nvmrc +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/package.json +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/prettierignore +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/src/app.ts +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/src/server.ts +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/test/app.test.ts +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/tsconfig.build.json +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/tsconfig.json +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/node/templates/express/vitest.config.ts +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/laravel/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/laravel/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/laravel/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/laravel/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/laravel/services.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/symfony/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/symfony/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/symfony/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/php/frameworks/symfony/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/django/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/django/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/django/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/django/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/fastapi/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/fastapi/docker.py +0 -0
- {quoll_cli-0.2.0/tests/docker → quoll_cli-0.3.0/quoll/languages/python/frameworks/fastapi/template/app}/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/flask/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/flask/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/python/frameworks/flask/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/frameworks/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/frameworks/rails/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/frameworks/rails/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/frameworks/rails/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/frameworks/rails/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/frameworks/rails/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/ruby/frameworks/rails/services.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/rust/Dockerfile.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/rust/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/rust/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/rust/detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/languages/rust/docker.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/main.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/github/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/github/dependabot.yml.j2 +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/github/platform.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/github/workflow.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/gitlab/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/gitlab/api.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/gitlab/cd.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/gitlab/ci.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/gitlab/pipeline.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/platforms/gitlab/platform.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/project.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/services.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/workspace.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/quoll/yaml_dump.py +0 -0
- {quoll_cli-0.2.0/tests/k8s → quoll_cli-0.3.0/tests}/__init__.py +0 -0
- {quoll_cli-0.2.0/tests/languages → quoll_cli-0.3.0/tests/docker}/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/docker/test_cli.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/helpers.py +0 -0
- {quoll_cli-0.2.0/tests/platforms → quoll_cli-0.3.0/tests/k8s}/__init__.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/k8s/test_kind.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_contract.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_cpp.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_dotnet.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_go.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_java.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_node.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_python.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_registry.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_ruby.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/languages/test_rust.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/platforms/test_add_secret.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/platforms/test_gitlab_run.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/platforms/test_platforms.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/samples.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_ci_cli.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_ci_detect.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_k8s.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_services.py +0 -0
- {quoll_cli-0.2.0 → quoll_cli-0.3.0}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quoll-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Production-grade CI, Dockerfiles, Helm charts and delivery for any repository, kept in sync with one quoll.toml
|
|
5
5
|
Project-URL: Homepage, https://github.com/theo-mrn/Quoll
|
|
6
6
|
Project-URL: Repository, https://github.com/theo-mrn/Quoll
|
|
@@ -254,7 +254,7 @@ quoll ci add-step -d back # in a workspace: the project's directory; asks wh
|
|
|
254
254
|
# .pre-commit-config.yaml
|
|
255
255
|
repos:
|
|
256
256
|
- repo: https://github.com/theo-mrn/Quoll
|
|
257
|
-
rev: v0.
|
|
257
|
+
rev: v0.3.0
|
|
258
258
|
hooks:
|
|
259
259
|
- id: quoll-ci # quoll ci update; also quoll-docker, quoll-cd
|
|
260
260
|
```
|
|
@@ -411,7 +411,7 @@ Every push to the default branch and every `v*` tag:
|
|
|
411
411
|
| Runs after CI | through branch protection | always: its `publish` stage starts once every CI job passed |
|
|
412
412
|
| Default registry (no secret) | GHCR, with the workflow's token | the project's container registry, with the job token |
|
|
413
413
|
| Build | Buildx, GitHub Actions cache | Docker-in-Docker, a Buildx container builder; Trivy runs next to the image |
|
|
414
|
-
| Signature | artifact attestations (public repositories, or GitHub Enterprise Cloud for private ones) | cosign keyless with the job's OIDC token (gitlab.com: Sigstore's public instance doesn't trust self-managed issuers) |
|
|
414
|
+
| Signature | artifact attestations (public repositories, or GitHub Enterprise Cloud for private ones: `quoll cd init` turns signing off on a private repository, `--sign` keeps it) | cosign keyless with the job's OIDC token (gitlab.com: Sigstore's public instance doesn't trust self-managed issuers) |
|
|
415
415
|
|
|
416
416
|
Docker Hub (`registry = "dockerhub"`) needs `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN`. Multi-arch images are a `platforms` entry away.
|
|
417
417
|
|
|
@@ -421,16 +421,35 @@ registry = "ghcr" # ghcr (GitHub) | gitlab (GitLab) | dockerhub
|
|
|
421
421
|
image = "ghcr.io/acme/api"
|
|
422
422
|
branches = ["main"]
|
|
423
423
|
release_tags = true
|
|
424
|
+
release_pr = true # GitHub: release-please opens the release PR that tags v* (below)
|
|
424
425
|
platforms = ["linux/amd64"] # e.g. ["linux/amd64", "linux/arm64"]
|
|
425
426
|
scan = true
|
|
426
427
|
severity = ["CRITICAL", "HIGH"]
|
|
427
428
|
ignore_unfixed = true
|
|
428
429
|
sbom = true # SBOM of the image: artifact + release asset
|
|
429
|
-
sign = true # Sigstore signature and attestations (
|
|
430
|
+
sign = true # Sigstore signature and attestations (`cd init` writes false on a private GitHub repo)
|
|
430
431
|
```
|
|
431
432
|
|
|
432
433
|
On GitHub, branch protection (set by `quoll ci init`) makes sure only commits that passed CI reach the default branch, and therefore the registry.
|
|
433
434
|
|
|
435
|
+
### Releases
|
|
436
|
+
|
|
437
|
+
On GitHub, nobody has to push the `v*` tags: with `release_pr` (the default of `quoll cd init`), [release-please](https://github.com/googleapis/release-please) reads the [Conventional Commits](https://www.conventionalcommits.org/) on the default branch and keeps a **release PR** open with the next version and its `CHANGELOG.md`. Merging it tags `v<version>` and creates the GitHub release, which publishes the image and, with a chart, deploys to production after the approval.
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
feat: … fix: … feat!: … → release PR "chore(main): release 1.3.0" → merge → v1.3.0 → CD
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
- `fix:` bumps the patch, `feat:` the minor, a breaking change (`feat!:`, `BREAKING CHANGE:`) the major. Before `1.0.0`, a breaking change bumps the minor.
|
|
444
|
+
- Node projects get their `package.json` (and `package-lock.json`) version bumped, Rust their `Cargo.toml` and `Cargo.lock`. The other stacks keep their version in the tag: release-please can't update every lockfile (`uv.lock` records the project's version).
|
|
445
|
+
- A workspace releases as a whole, under one tag, like a `v*` tag pushed by hand.
|
|
446
|
+
- `release-please-config.json` and `.release-please-manifest.json` are written once, starting from the latest `v*` tag (`0.1.0` for a project never released), and are yours afterwards: release-please updates the manifest at each release.
|
|
447
|
+
- The release PR and the tag are created with the workflow's own token, so they start no workflow by themselves (GitHub's rule against recursive runs). The release workflow dispatches the CI on the release PR and the delivery on the tag. A dispatched run isn't attached to the pull request, so its `CI result` job also reports to the commit as a status, which branch protection reads.
|
|
448
|
+
- On a public repository, GitHub also creates the release PR's `pull_request` run but waits for an approval, since the bot counts as a first-time contributor. It isn't required: the dispatched run already reported.
|
|
449
|
+
- GitHub must let Actions open pull requests (Settings → Actions → General). `quoll new --remote` turns it on.
|
|
450
|
+
|
|
451
|
+
GitLab has no release PR: push the `v*` tags there.
|
|
452
|
+
|
|
434
453
|
## Kubernetes
|
|
435
454
|
|
|
436
455
|
```bash
|
|
@@ -497,6 +516,8 @@ In a **workspace**, every project deploys to the same namespaces and reaches the
|
|
|
497
516
|
```bash
|
|
498
517
|
quoll new # interactive
|
|
499
518
|
quoll new shop --front angular --back express
|
|
519
|
+
quoll new shop --front react --back fastapi --python 3.13
|
|
520
|
+
quoll new shop --front nextjs --back go
|
|
500
521
|
quoll new shop --front angular@18 --back express@4 --node 20 --platform gitlab --cd --k8s --remote
|
|
501
522
|
quoll new shop --front angular --back none --service orders=express --service users=express@4
|
|
502
523
|
```
|
|
@@ -510,9 +531,13 @@ From nothing to a repository whose pipeline passes on its first push:
|
|
|
510
531
|
| Starter | Role | How |
|
|
511
532
|
|---|---|---|
|
|
512
533
|
| `angular` | front | Angular's own CLI (`ng new`) at the chosen major — `angular@18` — plus ESLint (angular-eslint) |
|
|
534
|
+
| `react` | front | Vite's own generator (`create-vite`, TypeScript), React's major with `react@18`; Vitest + Testing Library, Prettier |
|
|
535
|
+
| `nextjs` | front | Next.js' own generator (`create-next-app`: App Router, ESLint, Tailwind), `nextjs@15`; standalone build, Vitest, Prettier |
|
|
513
536
|
| `express` | back | quoll's template: Express (`express@4` for 4) and TypeScript, Vitest + Supertest, ESLint, `/health`, graceful shutdown |
|
|
537
|
+
| `fastapi` | back | quoll's template: FastAPI with uv, pytest (TestClient), ruff, `/health` |
|
|
538
|
+
| `go` | back | quoll's template: the standard library's `net/http` (Go 1.22+ routing), httptest, graceful shutdown, `/health` |
|
|
514
539
|
|
|
515
|
-
**Versions.** `name@version` picks the framework's major; `--node
|
|
540
|
+
**Versions.** `name@version` picks the framework's major; `--node 22`, `--python 3.13`, `--go 1.26` the runtime: the generators then run in that runtime's image (so the dependencies and the lockfile match it), and `.nvmrc`, `.python-version` or `go.mod` make the CI and the Dockerfile use it too. Asked interactively, each framework offers its latest version and the older majors its maintainers still fix (Angular 22 to 20, React 19 and 18, Next.js 16 and 15, Express 5 and 4), or any other. A version they no longer fix is flagged at creation: the CI's dependency audit would fail on its known vulnerabilities.
|
|
516
541
|
|
|
517
542
|
### Microservices
|
|
518
543
|
|
|
@@ -530,7 +555,7 @@ quoll remove service billing --keep-files # only takes it out of the workspac
|
|
|
530
555
|
|
|
531
556
|
`quoll remove service` does the opposite: the project leaves the members, its jobs, its compose service and `BILLING_URL`, its delivery and setup action are removed. It refuses to delete a directory with uncommitted changes.
|
|
532
557
|
|
|
533
|
-
**Front-end → API.** With a front-end and a back-end, `quoll new` makes the front-end call the API on its own origin, at `/api/` (no CORS, no URL baked into the build)
|
|
558
|
+
**Front-end → API.** With a front-end and a back-end, `quoll new` makes the front-end call the API on its own origin, at `/api/` (no CORS, no URL baked into the build). In development, the dev server forwards there (`proxy.conf.json` for Angular, Vite's `server.proxy`); deployed, to the URL in `BACK_URL`, which the root `compose.yaml` and the charts set — through nginx for a static site, through Next.js' own `proxy.ts` (read on each request) for Next.js:
|
|
534
559
|
|
|
535
560
|
```toml
|
|
536
561
|
# front/quoll.toml
|
|
@@ -205,7 +205,7 @@ quoll ci add-step -d back # in a workspace: the project's directory; asks wh
|
|
|
205
205
|
# .pre-commit-config.yaml
|
|
206
206
|
repos:
|
|
207
207
|
- repo: https://github.com/theo-mrn/Quoll
|
|
208
|
-
rev: v0.
|
|
208
|
+
rev: v0.3.0
|
|
209
209
|
hooks:
|
|
210
210
|
- id: quoll-ci # quoll ci update; also quoll-docker, quoll-cd
|
|
211
211
|
```
|
|
@@ -362,7 +362,7 @@ Every push to the default branch and every `v*` tag:
|
|
|
362
362
|
| Runs after CI | through branch protection | always: its `publish` stage starts once every CI job passed |
|
|
363
363
|
| Default registry (no secret) | GHCR, with the workflow's token | the project's container registry, with the job token |
|
|
364
364
|
| Build | Buildx, GitHub Actions cache | Docker-in-Docker, a Buildx container builder; Trivy runs next to the image |
|
|
365
|
-
| Signature | artifact attestations (public repositories, or GitHub Enterprise Cloud for private ones) | cosign keyless with the job's OIDC token (gitlab.com: Sigstore's public instance doesn't trust self-managed issuers) |
|
|
365
|
+
| Signature | artifact attestations (public repositories, or GitHub Enterprise Cloud for private ones: `quoll cd init` turns signing off on a private repository, `--sign` keeps it) | cosign keyless with the job's OIDC token (gitlab.com: Sigstore's public instance doesn't trust self-managed issuers) |
|
|
366
366
|
|
|
367
367
|
Docker Hub (`registry = "dockerhub"`) needs `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN`. Multi-arch images are a `platforms` entry away.
|
|
368
368
|
|
|
@@ -372,16 +372,35 @@ registry = "ghcr" # ghcr (GitHub) | gitlab (GitLab) | dockerhub
|
|
|
372
372
|
image = "ghcr.io/acme/api"
|
|
373
373
|
branches = ["main"]
|
|
374
374
|
release_tags = true
|
|
375
|
+
release_pr = true # GitHub: release-please opens the release PR that tags v* (below)
|
|
375
376
|
platforms = ["linux/amd64"] # e.g. ["linux/amd64", "linux/arm64"]
|
|
376
377
|
scan = true
|
|
377
378
|
severity = ["CRITICAL", "HIGH"]
|
|
378
379
|
ignore_unfixed = true
|
|
379
380
|
sbom = true # SBOM of the image: artifact + release asset
|
|
380
|
-
sign = true # Sigstore signature and attestations (
|
|
381
|
+
sign = true # Sigstore signature and attestations (`cd init` writes false on a private GitHub repo)
|
|
381
382
|
```
|
|
382
383
|
|
|
383
384
|
On GitHub, branch protection (set by `quoll ci init`) makes sure only commits that passed CI reach the default branch, and therefore the registry.
|
|
384
385
|
|
|
386
|
+
### Releases
|
|
387
|
+
|
|
388
|
+
On GitHub, nobody has to push the `v*` tags: with `release_pr` (the default of `quoll cd init`), [release-please](https://github.com/googleapis/release-please) reads the [Conventional Commits](https://www.conventionalcommits.org/) on the default branch and keeps a **release PR** open with the next version and its `CHANGELOG.md`. Merging it tags `v<version>` and creates the GitHub release, which publishes the image and, with a chart, deploys to production after the approval.
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
feat: … fix: … feat!: … → release PR "chore(main): release 1.3.0" → merge → v1.3.0 → CD
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
- `fix:` bumps the patch, `feat:` the minor, a breaking change (`feat!:`, `BREAKING CHANGE:`) the major. Before `1.0.0`, a breaking change bumps the minor.
|
|
395
|
+
- Node projects get their `package.json` (and `package-lock.json`) version bumped, Rust their `Cargo.toml` and `Cargo.lock`. The other stacks keep their version in the tag: release-please can't update every lockfile (`uv.lock` records the project's version).
|
|
396
|
+
- A workspace releases as a whole, under one tag, like a `v*` tag pushed by hand.
|
|
397
|
+
- `release-please-config.json` and `.release-please-manifest.json` are written once, starting from the latest `v*` tag (`0.1.0` for a project never released), and are yours afterwards: release-please updates the manifest at each release.
|
|
398
|
+
- The release PR and the tag are created with the workflow's own token, so they start no workflow by themselves (GitHub's rule against recursive runs). The release workflow dispatches the CI on the release PR and the delivery on the tag. A dispatched run isn't attached to the pull request, so its `CI result` job also reports to the commit as a status, which branch protection reads.
|
|
399
|
+
- On a public repository, GitHub also creates the release PR's `pull_request` run but waits for an approval, since the bot counts as a first-time contributor. It isn't required: the dispatched run already reported.
|
|
400
|
+
- GitHub must let Actions open pull requests (Settings → Actions → General). `quoll new --remote` turns it on.
|
|
401
|
+
|
|
402
|
+
GitLab has no release PR: push the `v*` tags there.
|
|
403
|
+
|
|
385
404
|
## Kubernetes
|
|
386
405
|
|
|
387
406
|
```bash
|
|
@@ -448,6 +467,8 @@ In a **workspace**, every project deploys to the same namespaces and reaches the
|
|
|
448
467
|
```bash
|
|
449
468
|
quoll new # interactive
|
|
450
469
|
quoll new shop --front angular --back express
|
|
470
|
+
quoll new shop --front react --back fastapi --python 3.13
|
|
471
|
+
quoll new shop --front nextjs --back go
|
|
451
472
|
quoll new shop --front angular@18 --back express@4 --node 20 --platform gitlab --cd --k8s --remote
|
|
452
473
|
quoll new shop --front angular --back none --service orders=express --service users=express@4
|
|
453
474
|
```
|
|
@@ -461,9 +482,13 @@ From nothing to a repository whose pipeline passes on its first push:
|
|
|
461
482
|
| Starter | Role | How |
|
|
462
483
|
|---|---|---|
|
|
463
484
|
| `angular` | front | Angular's own CLI (`ng new`) at the chosen major — `angular@18` — plus ESLint (angular-eslint) |
|
|
485
|
+
| `react` | front | Vite's own generator (`create-vite`, TypeScript), React's major with `react@18`; Vitest + Testing Library, Prettier |
|
|
486
|
+
| `nextjs` | front | Next.js' own generator (`create-next-app`: App Router, ESLint, Tailwind), `nextjs@15`; standalone build, Vitest, Prettier |
|
|
464
487
|
| `express` | back | quoll's template: Express (`express@4` for 4) and TypeScript, Vitest + Supertest, ESLint, `/health`, graceful shutdown |
|
|
488
|
+
| `fastapi` | back | quoll's template: FastAPI with uv, pytest (TestClient), ruff, `/health` |
|
|
489
|
+
| `go` | back | quoll's template: the standard library's `net/http` (Go 1.22+ routing), httptest, graceful shutdown, `/health` |
|
|
465
490
|
|
|
466
|
-
**Versions.** `name@version` picks the framework's major; `--node
|
|
491
|
+
**Versions.** `name@version` picks the framework's major; `--node 22`, `--python 3.13`, `--go 1.26` the runtime: the generators then run in that runtime's image (so the dependencies and the lockfile match it), and `.nvmrc`, `.python-version` or `go.mod` make the CI and the Dockerfile use it too. Asked interactively, each framework offers its latest version and the older majors its maintainers still fix (Angular 22 to 20, React 19 and 18, Next.js 16 and 15, Express 5 and 4), or any other. A version they no longer fix is flagged at creation: the CI's dependency audit would fail on its known vulnerabilities.
|
|
467
492
|
|
|
468
493
|
### Microservices
|
|
469
494
|
|
|
@@ -481,7 +506,7 @@ quoll remove service billing --keep-files # only takes it out of the workspac
|
|
|
481
506
|
|
|
482
507
|
`quoll remove service` does the opposite: the project leaves the members, its jobs, its compose service and `BILLING_URL`, its delivery and setup action are removed. It refuses to delete a directory with uncommitted changes.
|
|
483
508
|
|
|
484
|
-
**Front-end → API.** With a front-end and a back-end, `quoll new` makes the front-end call the API on its own origin, at `/api/` (no CORS, no URL baked into the build)
|
|
509
|
+
**Front-end → API.** With a front-end and a back-end, `quoll new` makes the front-end call the API on its own origin, at `/api/` (no CORS, no URL baked into the build). In development, the dev server forwards there (`proxy.conf.json` for Angular, Vite's `server.proxy`); deployed, to the URL in `BACK_URL`, which the root `compose.yaml` and the charts set — through nginx for a static site, through Next.js' own `proxy.ts` (read on each request) for Next.js:
|
|
485
510
|
|
|
486
511
|
```toml
|
|
487
512
|
# front/quoll.toml
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quoll-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Production-grade CI, Dockerfiles, Helm charts and delivery for any repository, kept in sync with one quoll.toml"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -78,6 +78,8 @@ testpaths = ["tests"]
|
|
|
78
78
|
[tool.ruff]
|
|
79
79
|
line-length = 120
|
|
80
80
|
target-version = "py39"
|
|
81
|
+
# Starters' templates are the generated projects' code, checked there (with their own settings).
|
|
82
|
+
extend-exclude = ["quoll/languages/*/frameworks/*/template", "quoll/languages/*/templates"]
|
|
81
83
|
|
|
82
84
|
[tool.ruff.lint]
|
|
83
85
|
select = ["E", "F", "I", "B"]
|
|
@@ -27,6 +27,9 @@ class CDConfig(Section, PlatformField):
|
|
|
27
27
|
branches: List[str] = Field(default_factory=lambda: ["main"])
|
|
28
28
|
# Git tags v1.2.3 publish :1.2.3 and :1.2.
|
|
29
29
|
release_tags: bool = True
|
|
30
|
+
# GitHub: release-please keeps a release pull request open from the Conventional Commits on the default branch
|
|
31
|
+
# (next version, CHANGELOG.md); merging it tags v<version>, which publishes the image and deploys to production.
|
|
32
|
+
release_pr: bool = False
|
|
30
33
|
platforms: List[str] = Field(default_factory=lambda: ["linux/amd64"])
|
|
31
34
|
# Trivy scans the image before it is pushed; findings of these severities block the push.
|
|
32
35
|
scan: bool = True
|
|
@@ -60,6 +63,10 @@ class CDConfig(Section, PlatformField):
|
|
|
60
63
|
raise ValueError(f"unknown severity '{unknown[0]}' (expected some of: {', '.join(SEVERITIES)})")
|
|
61
64
|
if self.scan and not self.severity:
|
|
62
65
|
raise ValueError("severity must list at least one level when scan is true")
|
|
66
|
+
if self.release_pr and self.platform != "github":
|
|
67
|
+
raise ValueError("release_pr is GitHub only (release-please): push the v* tags yourself")
|
|
68
|
+
if self.release_pr and not self.release_tags:
|
|
69
|
+
raise ValueError("release_pr needs release_tags: the release it tags is what publishes")
|
|
63
70
|
if not self.branches and not self.release_tags:
|
|
64
71
|
raise ValueError("nothing would publish the image: set branches or release_tags")
|
|
65
72
|
if not self.platforms:
|
|
@@ -16,8 +16,10 @@ def extend(cfg: CIConfig, job: str, items: List[Item]) -> List[Item]:
|
|
|
16
16
|
after = [_step(s) for s in cfg.steps if s.job == job and s.when == "after"]
|
|
17
17
|
if not before and not after:
|
|
18
18
|
return items
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# By name: a job may run its own variant of the install step (C/C++ configures the test build with
|
|
20
|
+
# coverage and sanitizers, the release build optimised), still the step that installs the dependencies.
|
|
21
|
+
installed = {item.name for item in cfg.language.ci.install(cfg)} - {""}
|
|
22
|
+
last = max((i for i, item in enumerate(items) if item.name in installed), default=-1)
|
|
21
23
|
return [*items[: last + 1], *before, *items[last + 1 :], *after]
|
|
22
24
|
|
|
23
25
|
|
|
@@ -38,22 +38,30 @@ def check_platform(name: Optional[str]) -> None:
|
|
|
38
38
|
raise typer.Exit(1)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def
|
|
42
|
-
"""A token for `platform` in settings: from the environment, the platform's CLI (gh, glab: the
|
|
43
|
-
keeps fresh)
|
|
41
|
+
def find_token(platform: str, host: Optional[str] = None) -> bool:
|
|
42
|
+
"""A token for `platform` in settings, without asking: from the environment, the platform's CLI (gh, glab: the
|
|
43
|
+
session the user keeps fresh) or the one stored in ~/.config/quoll."""
|
|
44
44
|
attr = f"{platform}_token"
|
|
45
45
|
if getattr(settings, attr):
|
|
46
46
|
return True
|
|
47
47
|
token = _cli_token(platform, host) or load_stored_token(platform)
|
|
48
|
+
if token:
|
|
49
|
+
setattr(settings, attr, token)
|
|
50
|
+
return bool(token)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def ensure_token(platform: str, host: Optional[str] = None) -> bool:
|
|
54
|
+
"""A token for `platform` in settings: found (see `find_token`), or asked."""
|
|
55
|
+
if find_token(platform, host):
|
|
56
|
+
return True
|
|
57
|
+
label = platforms.get(platform).label
|
|
58
|
+
scope = "repo" if platform == "github" else "api"
|
|
59
|
+
token = ask(questionary.password(f"{label} token ({scope} scope)"))
|
|
48
60
|
if not token:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return False
|
|
54
|
-
save_token(token, platform)
|
|
55
|
-
console.print("[dim]Token saved to ~/.config/quoll/config.toml[/dim]")
|
|
56
|
-
setattr(settings, attr, token)
|
|
61
|
+
return False
|
|
62
|
+
save_token(token, platform)
|
|
63
|
+
console.print("[dim]Token saved to ~/.config/quoll/config.toml[/dim]")
|
|
64
|
+
setattr(settings, f"{platform}_token", token)
|
|
57
65
|
return True
|
|
58
66
|
|
|
59
67
|
|
|
@@ -37,7 +37,9 @@ def service(
|
|
|
37
37
|
spec: str = typer.Argument(..., help="NAME=STARTER[@VERSION], e.g. orders=express or orders=express@4."),
|
|
38
38
|
project_dir: Path = DirOption,
|
|
39
39
|
within: str = typer.Option(SERVICES_DIRECTORY, "--in", help="Directory holding the services."),
|
|
40
|
-
node: Optional[str] = typer.Option(None, "--node", help="Node.js version, e.g.
|
|
40
|
+
node: Optional[str] = typer.Option(None, "--node", help="Node.js version, e.g. 22."),
|
|
41
|
+
python: Optional[str] = typer.Option(None, "--python", help="Python version, e.g. 3.13."),
|
|
42
|
+
go: Optional[str] = typer.Option(None, "--go", help="Go version, e.g. 1.26."),
|
|
41
43
|
) -> None:
|
|
42
44
|
"""Create a service in services/<name>, and add it to the workspace's CI, compose.yaml and delivery."""
|
|
43
45
|
root = find_root(resolve_dir(project_dir))
|
|
@@ -67,7 +69,8 @@ def service(
|
|
|
67
69
|
platform = configured_platform(root) or platforms.detect(root, platforms.remote(root))
|
|
68
70
|
before = snapshot(root, platform) if workspace else Snapshot(set(), None)
|
|
69
71
|
with Progress(SpinnerColumn(), TextColumn("{task.description}"), console=console) as progress:
|
|
70
|
-
|
|
72
|
+
runtimes = {"node": node or "", "python": python or "", "go": go or ""}
|
|
73
|
+
notes = create_part(root, root.name, Part(directory, starter, version, name), runtimes, progress)
|
|
71
74
|
quiet("Docker", docker_init(root / directory), progress)
|
|
72
75
|
|
|
73
76
|
task = progress.add_task("Adding it to the workspace...")
|
|
@@ -85,7 +88,7 @@ def service(
|
|
|
85
88
|
reference = next(load_cd_config(root / m) for m in members if "cd" in quoll_toml.read(root / m))
|
|
86
89
|
quiet("CD", lambda: cd_command._configure(
|
|
87
90
|
root / directory, root, member_name(directory), yes=True, force=False, platform=platform,
|
|
88
|
-
registry=reference.registry, image=None,
|
|
91
|
+
registry=reference.registry, image=None, release_pr=reference.release_pr, sign=reference.sign,
|
|
89
92
|
), progress)
|
|
90
93
|
|
|
91
94
|
# And its chart, deployed with the others: same namespaces.
|
|
@@ -16,6 +16,7 @@ from quoll.commands._common import (
|
|
|
16
16
|
check_files,
|
|
17
17
|
check_platform,
|
|
18
18
|
confirm_overwrite,
|
|
19
|
+
find_token,
|
|
19
20
|
platform_option,
|
|
20
21
|
resolve_dir,
|
|
21
22
|
update_files,
|
|
@@ -39,6 +40,15 @@ def init(
|
|
|
39
40
|
None, help="ghcr (GitHub) or gitlab (GitLab) — the default —, or dockerhub."
|
|
40
41
|
),
|
|
41
42
|
image: Optional[str] = typer.Option(None, help="Image name, e.g. ghcr.io/acme/api."),
|
|
43
|
+
release_pr: Optional[bool] = typer.Option(
|
|
44
|
+
None, "--release-pr/--no-release-pr",
|
|
45
|
+
help="GitHub: a release PR from the Conventional Commits (release-please) tags the releases (default: yes).",
|
|
46
|
+
),
|
|
47
|
+
sign: Optional[bool] = typer.Option(
|
|
48
|
+
None, "--sign/--no-sign",
|
|
49
|
+
help="Sign the image and its SBOM with Sigstore (default: yes, but no for a private GitHub repository: "
|
|
50
|
+
"artifact attestations need GitHub Enterprise Cloud there).",
|
|
51
|
+
),
|
|
42
52
|
) -> None:
|
|
43
53
|
"""Write [cd] in quoll.toml and generate the delivery pipeline.
|
|
44
54
|
|
|
@@ -53,7 +63,7 @@ def init(
|
|
|
53
63
|
raise typer.Exit(1)
|
|
54
64
|
if workspace is None:
|
|
55
65
|
cfg = _configure(project_dir, root, "", yes=yes, force=force, platform=platform, registry=registry,
|
|
56
|
-
image=image)
|
|
66
|
+
image=image, release_pr=release_pr, sign=sign)
|
|
57
67
|
if cfg is None:
|
|
58
68
|
raise typer.Exit(0)
|
|
59
69
|
deliveries: List[Delivery] = [Delivery("", ".", cfg)]
|
|
@@ -70,11 +80,16 @@ def init(
|
|
|
70
80
|
raise typer.Exit(1)
|
|
71
81
|
for member in members:
|
|
72
82
|
_configure(root / member, root, member_name(member), yes=yes, force=force, platform=platform,
|
|
73
|
-
registry=registry, image=image)
|
|
83
|
+
registry=registry, image=image, release_pr=release_pr, sign=sign)
|
|
84
|
+
# A workspace releases as a whole, from one repository: asked once.
|
|
85
|
+
if release_pr is None:
|
|
86
|
+
release_pr = _read(root / member).get("cd", {}).get("release_pr", False)
|
|
87
|
+
if sign is None and "cd" in _read(root / member):
|
|
88
|
+
sign = _read(root / member)["cd"].get("sign", True)
|
|
74
89
|
deliveries = _deliveries(root)
|
|
75
90
|
|
|
76
91
|
cfg = deliveries[0].cfg
|
|
77
|
-
files = _plan(root, deliveries)
|
|
92
|
+
files = _writable(_plan(root, deliveries))
|
|
78
93
|
if not confirm_overwrite(files, yes=yes, force=force):
|
|
79
94
|
raise typer.Exit(1)
|
|
80
95
|
for delivery in deliveries:
|
|
@@ -94,6 +109,16 @@ def init(
|
|
|
94
109
|
console.print(f"Set the {names} {target.label} {target.secrets_label}.")
|
|
95
110
|
if cfg.platform == "gitlab":
|
|
96
111
|
_check_gitlab_include(root)
|
|
112
|
+
if any(d.cfg.release_pr for d in deliveries):
|
|
113
|
+
console.print(RELEASE_NOTE)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
RELEASE_NOTE = (
|
|
117
|
+
"\nReleases: commit with Conventional Commits ([bold]feat:[/bold], [bold]fix:[/bold], [bold]feat!:[/bold]); "
|
|
118
|
+
"release-please keeps a release PR open, and merging it tags the version, which publishes and deploys.\n"
|
|
119
|
+
"GitHub must let Actions open pull requests: Settings → Actions → General → "
|
|
120
|
+
"[bold]Allow GitHub Actions to create and approve pull requests[/bold] (`quoll new --remote` turns it on)."
|
|
121
|
+
)
|
|
97
122
|
|
|
98
123
|
|
|
99
124
|
def _configure(
|
|
@@ -106,6 +131,8 @@ def _configure(
|
|
|
106
131
|
platform: Optional[str],
|
|
107
132
|
registry: Optional[str],
|
|
108
133
|
image: Optional[str],
|
|
134
|
+
release_pr: Optional[bool] = None,
|
|
135
|
+
sign: Optional[bool] = None,
|
|
109
136
|
) -> Optional[CDConfig]:
|
|
110
137
|
"""The [cd] of one project, asked or defaulted; saved in its quoll.toml. None when kept as it was."""
|
|
111
138
|
console.print(f"\n[bold]quoll cd[/bold] — {member or project_dir.name}\n")
|
|
@@ -134,12 +161,27 @@ def _configure(
|
|
|
134
161
|
if image is None:
|
|
135
162
|
image = default_image if yes else ask(questionary.text("Image", default=default_image))
|
|
136
163
|
|
|
137
|
-
|
|
138
|
-
|
|
164
|
+
if sign is None:
|
|
165
|
+
private = _private_repo(name, remote)
|
|
166
|
+
if private:
|
|
167
|
+
console.print("[dim]Private GitHub repository: artifact attestations need GitHub Enterprise Cloud there, "
|
|
168
|
+
"so signing defaults to off (--sign turns it on).[/dim]")
|
|
169
|
+
note = " (private GitHub repositories need GitHub Enterprise Cloud)" if name == "github" else ""
|
|
170
|
+
sign = not private if yes else ask(
|
|
171
|
+
questionary.confirm(f"Sign the image and its SBOM with Sigstore{note}?", default=not private)
|
|
172
|
+
)
|
|
173
|
+
if name != "github":
|
|
174
|
+
release_pr = False
|
|
175
|
+
elif release_pr is None:
|
|
176
|
+
release_pr = True if yes else ask(questionary.confirm(
|
|
177
|
+
"Open a release PR from the Conventional Commits (release-please)? Merging it tags the version.",
|
|
178
|
+
default=True,
|
|
179
|
+
))
|
|
139
180
|
|
|
140
181
|
try:
|
|
141
182
|
cfg = CDConfig(
|
|
142
|
-
platform=name, registry=registry, image=image.lower(), branches=[default_branch(root)], sign=sign
|
|
183
|
+
platform=name, registry=registry, image=image.lower(), branches=[default_branch(root)], sign=sign,
|
|
184
|
+
release_pr=release_pr,
|
|
143
185
|
)
|
|
144
186
|
except ValueError as e:
|
|
145
187
|
console.print(f"[red]Error:[/red] {e}")
|
|
@@ -150,6 +192,15 @@ def _configure(
|
|
|
150
192
|
return cfg
|
|
151
193
|
|
|
152
194
|
|
|
195
|
+
def _private_repo(name: str, remote: Optional[platforms.Remote]) -> bool:
|
|
196
|
+
"""True when the remote is a private repository on github.com, where a signed delivery would fail: artifact
|
|
197
|
+
attestations need GitHub Enterprise Cloud on private repositories. Looked up only with a token already at hand
|
|
198
|
+
(never asked); unknown counts as public."""
|
|
199
|
+
if name != "github" or remote is None or remote.host != "github.com" or not find_token("github"):
|
|
200
|
+
return False
|
|
201
|
+
return platforms.get("github").api().is_private(remote.path) is True
|
|
202
|
+
|
|
203
|
+
|
|
153
204
|
@app.command()
|
|
154
205
|
def update(
|
|
155
206
|
project_dir: Path = DirOption,
|
|
@@ -158,14 +209,14 @@ def update(
|
|
|
158
209
|
) -> None:
|
|
159
210
|
"""Regenerate the delivery workflow from quoll.toml (the workspace's, from any of its directories)."""
|
|
160
211
|
root = find_root(resolve_dir(project_dir))
|
|
161
|
-
update_files(root, _plan(root, _deliveries(root)), yes=yes, force=force, what="CD is")
|
|
212
|
+
update_files(root, _writable(_plan(root, _deliveries(root))), yes=yes, force=force, what="CD is")
|
|
162
213
|
|
|
163
214
|
|
|
164
215
|
@app.command()
|
|
165
216
|
def check(project_dir: Path = DirOption) -> None:
|
|
166
217
|
"""Fail if the delivery pipeline differs from what quoll.toml describes."""
|
|
167
218
|
root = find_root(resolve_dir(project_dir))
|
|
168
|
-
check_files(_plan(root, _deliveries(root)), "cd")
|
|
219
|
+
check_files([f for f in _plan(root, _deliveries(root)) if not f.create_only], "cd")
|
|
169
220
|
|
|
170
221
|
|
|
171
222
|
def _plan(root: Path, deliveries: List[Delivery]) -> List[PlannedFile]:
|
|
@@ -173,6 +224,11 @@ def _plan(root: Path, deliveries: List[Delivery]) -> List[PlannedFile]:
|
|
|
173
224
|
return platforms.get(deliveries[0].cfg.platform).cd_files(root, target)
|
|
174
225
|
|
|
175
226
|
|
|
227
|
+
def _writable(files: List[PlannedFile]) -> List[PlannedFile]:
|
|
228
|
+
"""Without release-please's configuration and manifest once written: they are the project's then."""
|
|
229
|
+
return [f for f in files if not (f.create_only and f.existing is not None)]
|
|
230
|
+
|
|
231
|
+
|
|
176
232
|
def _deliveries(root: Path) -> List[Delivery]:
|
|
177
233
|
try:
|
|
178
234
|
return load_deliveries(root)
|
|
@@ -204,6 +260,8 @@ def _print_config(cfg: CDConfig) -> None:
|
|
|
204
260
|
table.add_row("Platforms", ", ".join(cfg.platforms))
|
|
205
261
|
table.add_row("SBOM", "artifact + release asset" if cfg.sbom else "off")
|
|
206
262
|
table.add_row("Signature", "Sigstore" if cfg.sign else "off")
|
|
263
|
+
if cfg.platform == "github":
|
|
264
|
+
table.add_row("Releases", "release PR (release-please)" if cfg.release_pr else "v* tags pushed by hand")
|
|
207
265
|
console.print(table)
|
|
208
266
|
|
|
209
267
|
|
|
@@ -29,6 +29,12 @@ from quoll.workspace import member_name
|
|
|
29
29
|
# Where each part goes when the repository holds several.
|
|
30
30
|
DIRECTORIES = {"front": "front", "back": "back"}
|
|
31
31
|
SERVICES_DIRECTORY = "services"
|
|
32
|
+
# Runtime versions offered when none is given: (label, versions), the default first.
|
|
33
|
+
RUNTIME_CHOICES = {
|
|
34
|
+
"node": ("Node.js", ["26", "24", "22"]),
|
|
35
|
+
"python": ("Python", ["3.14", "3.13", "3.12"]),
|
|
36
|
+
"go": ("Go", ["1.27", "1.26"]),
|
|
37
|
+
}
|
|
32
38
|
|
|
33
39
|
@dataclass
|
|
34
40
|
class Part:
|
|
@@ -64,7 +70,9 @@ def new(
|
|
|
64
70
|
services: List[str] = typer.Option(
|
|
65
71
|
[], "--service", help="A service in services/<name>: NAME=STARTER[@VERSION], e.g. orders=express (repeatable)."
|
|
66
72
|
),
|
|
67
|
-
node: Optional[str] = typer.Option(None, "--node", help="Node.js version of the Node-based parts, e.g.
|
|
73
|
+
node: Optional[str] = typer.Option(None, "--node", help="Node.js version of the Node-based parts, e.g. 22."),
|
|
74
|
+
python: Optional[str] = typer.Option(None, "--python", help="Python version of the Python parts, e.g. 3.13."),
|
|
75
|
+
go: Optional[str] = typer.Option(None, "--go", help="Go version of the Go parts, e.g. 1.26."),
|
|
68
76
|
platform: Optional[str] = platform_option(),
|
|
69
77
|
delivery: Optional[bool] = typer.Option(None, "--cd/--no-cd", help="Also publish the images (quoll cd)."),
|
|
70
78
|
kubernetes: Optional[bool] = typer.Option(
|
|
@@ -101,11 +109,14 @@ def new(
|
|
|
101
109
|
Part(DIRECTORIES[role] if several else ".", starter, version, f"{name}-{role}" if several else name)
|
|
102
110
|
for role, starter, version in picked
|
|
103
111
|
] + [Part(f"{SERVICES_DIRECTORY}/{n}" if several else ".", s, v, n) for n, s, v in picked_services]
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
runtimes = {"node": node, "python": python, "go": go}
|
|
113
|
+
for runtime, version in runtimes.items():
|
|
114
|
+
used = [p.starter for p in parts if p.starter.runtime == runtime]
|
|
115
|
+
if version is None and not yes and used:
|
|
116
|
+
label, choices = RUNTIME_CHOICES[runtime]
|
|
117
|
+
runtimes[runtime] = ask(questionary.select(f"{label} version", choices=choices,
|
|
118
|
+
default=used[0].runtime_default or choices[0]))
|
|
119
|
+
runtimes = {runtime: version or "" for runtime, version in runtimes.items()}
|
|
109
120
|
if platform is None:
|
|
110
121
|
platform = "github" if yes else ask(questionary.select(
|
|
111
122
|
"Hosting platform",
|
|
@@ -168,8 +179,12 @@ def new(
|
|
|
168
179
|
# Before the delivery: it deploys the charts.
|
|
169
180
|
steps.append(("Kubernetes", lambda: k8s_command.init(project_dir=root, yes=True, force=False)))
|
|
170
181
|
if delivery:
|
|
182
|
+
# The repository just created says its visibility: a private one on GitHub can't hold attestations
|
|
183
|
+
# (without Enterprise Cloud). Otherwise cd init looks it up from the remote.
|
|
184
|
+
sign = False if repo and private and platform == "github" else None
|
|
171
185
|
steps.append(("CD", lambda: cd_command.init(
|
|
172
|
-
project_dir=root, yes=True, force=False, platform=platform, registry=None, image=None
|
|
186
|
+
project_dir=root, yes=True, force=False, platform=platform, registry=None, image=None, release_pr=None,
|
|
187
|
+
sign=sign,
|
|
173
188
|
)))
|
|
174
189
|
for label, run in steps:
|
|
175
190
|
quiet(label, run, progress)
|
|
@@ -186,6 +201,8 @@ def new(
|
|
|
186
201
|
try:
|
|
187
202
|
api = target.api()
|
|
188
203
|
warnings = api.protect_branch(repo.path, "main", required_checks=target.required_checks)
|
|
204
|
+
if delivery:
|
|
205
|
+
warnings += api.allow_release_prs(repo.path)
|
|
189
206
|
if target.schedules_via_api:
|
|
190
207
|
api.ensure_schedule(repo.path, "main", "0 6 * * 1")
|
|
191
208
|
except PlatformError as e:
|
|
@@ -207,11 +224,12 @@ def create_part(root: Path, name: str, part: Part, runtimes: Dict[str, str], pro
|
|
|
207
224
|
where = root / part.directory
|
|
208
225
|
where.mkdir(parents=True, exist_ok=True)
|
|
209
226
|
runtime = runtimes.get(part.starter.runtime, "")
|
|
210
|
-
details = f" (
|
|
227
|
+
details = f" ({RUNTIME_CHOICES[part.starter.runtime][0]} {runtime})" if runtime else ""
|
|
211
228
|
shown = f"{name}/" if part.directory == "." else f"{name}/{part.directory}/"
|
|
212
229
|
task = progress.add_task(f"Creating {part.label}{details} in {shown}...")
|
|
213
230
|
try:
|
|
214
231
|
notes = part.starter.create(where, StarterOptions(part.package, part.version, runtime)) or []
|
|
232
|
+
notes += [n for n in [unmaintained(part.starter, part.version)] if n]
|
|
215
233
|
except StarterError as e:
|
|
216
234
|
progress.stop()
|
|
217
235
|
console.print(f"\n[red]Error:[/red] {e}")
|
|
@@ -225,19 +243,20 @@ def docker_init(directory: Path) -> Callable[[], None]:
|
|
|
225
243
|
|
|
226
244
|
|
|
227
245
|
def proxy_api(root: Path, front: Part, back: Part) -> Callable[[], None]:
|
|
228
|
-
"""The front-end calls the back-end at /api/, on its own origin (no CORS):
|
|
229
|
-
|
|
246
|
+
"""The front-end calls the back-end at /api/, on its own origin (no CORS): in development, through its dev
|
|
247
|
+
server; deployed, to BACK_URL (set by the workspace's compose.yaml and charts) — through nginx for a static
|
|
248
|
+
site, the app's own server otherwise."""
|
|
230
249
|
|
|
231
250
|
def run() -> None:
|
|
232
|
-
|
|
233
|
-
if not docker.static: # a server-rendered front-end calls the API itself
|
|
234
|
-
return
|
|
251
|
+
variable = f"{member_name(back.directory).upper().replace('-', '_')}_URL"
|
|
235
252
|
if front.starter.proxy:
|
|
236
253
|
port = docker_command._load(root / back.directory).port
|
|
237
|
-
front.starter.proxy(root / front.directory, "/api", f"http://localhost:{port}")
|
|
238
|
-
docker
|
|
239
|
-
|
|
240
|
-
|
|
254
|
+
front.starter.proxy(root / front.directory, "/api", f"http://localhost:{port}", variable)
|
|
255
|
+
docker = docker_command._load(root / front.directory)
|
|
256
|
+
if docker.static:
|
|
257
|
+
docker.proxy = {"/api/": variable}
|
|
258
|
+
save_docker_config(root / front.directory, docker)
|
|
259
|
+
docker_command.update(project_dir=root / front.directory, yes=True, force=False)
|
|
241
260
|
|
|
242
261
|
return run
|
|
243
262
|
|
|
@@ -288,11 +307,31 @@ def _pick(role: str, choice: Optional[str], yes: bool) -> Optional[Tuple[str, St
|
|
|
288
307
|
console.print(f"[red]Error:[/red] unknown {role}-end '{starter_name}' (expected one of: {', '.join(by_name)}).")
|
|
289
308
|
raise typer.Exit(1)
|
|
290
309
|
starter = by_name[starter_name]
|
|
291
|
-
if asked:
|
|
292
|
-
version = ask(questionary.
|
|
310
|
+
if asked and starter.versions:
|
|
311
|
+
version = ask(questionary.select(f"{starter.label} version", choices=[
|
|
312
|
+
questionary.Choice(f"Latest ({starter.versions[0]})", ""),
|
|
313
|
+
*(questionary.Choice(v, v) for v in starter.versions[1:]),
|
|
314
|
+
questionary.Choice("Other…", OTHER),
|
|
315
|
+
]))
|
|
316
|
+
if version == OTHER:
|
|
317
|
+
version = ask(questionary.text(f"{starter.label} version, e.g. {starter.versions[-1]}"))
|
|
293
318
|
return role, starter, version.strip()
|
|
294
319
|
|
|
295
320
|
|
|
321
|
+
OTHER = "other"
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def unmaintained(starter: Starter, version: str) -> str:
|
|
325
|
+
"""A note when `version` is older than the majors `starter`'s maintainers still fix."""
|
|
326
|
+
major = version.split(".")[0]
|
|
327
|
+
if not starter.versions or not major.isdigit() or int(major) >= int(starter.versions[-1]):
|
|
328
|
+
return ""
|
|
329
|
+
return (
|
|
330
|
+
f"{starter.label} {major} no longer gets security fixes: the CI's dependency audit will fail on its known "
|
|
331
|
+
f"vulnerabilities. Maintained: {', '.join(starter.versions)}."
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
296
335
|
def _valid_name(value: str):
|
|
297
336
|
if re.fullmatch(r"[a-z][a-z0-9-]*", value or ""):
|
|
298
337
|
return True
|
|
@@ -329,8 +368,8 @@ def _summary(root: Path, name: str, parts: List[Part], repo) -> None:
|
|
|
329
368
|
console.print("\n[dim]Next steps:[/dim]")
|
|
330
369
|
for part in parts:
|
|
331
370
|
where = name if part.directory == "." else f"{name}/{part.directory}"
|
|
332
|
-
|
|
333
|
-
|
|
371
|
+
if part.starter.dev:
|
|
372
|
+
console.print(f" cd {where} && {part.starter.dev}")
|
|
334
373
|
if len(parts) > 1:
|
|
335
374
|
console.print(f" cd {name} && docker compose up --build # everything, with the databases")
|
|
336
375
|
else:
|