xtr-cli 5.1.0__tar.gz → 5.2.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.
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/PKG-INFO +29 -101
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/README.md +28 -100
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/pyproject.toml +1 -1
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/pyproject.toml.orig +1 -1
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/__init__.py +6 -15
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/command/init_command.py +11 -19
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/command/new_command.py +12 -21
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/command_support.py +12 -41
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/__init__.py +0 -2
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/unknown_template_error.py +5 -5
- xtr_cli-5.2.0/src/xtr_cli/project_settings.py +52 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/scaffolder.py +1 -3
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/template_catalog.py +8 -13
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/template.toml +1 -2
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/README.md.tmpl +7 -3
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/kernel.py.tmpl +3 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/template.toml +11 -2
- xtr_cli-5.1.0/src/xtr_cli/add_on.py +0 -53
- xtr_cli-5.1.0/src/xtr_cli/add_on_catalog.py +0 -179
- xtr_cli-5.1.0/src/xtr_cli/add_on_choice.py +0 -40
- xtr_cli-5.1.0/src/xtr_cli/exception/unknown_add_on_error.py +0 -28
- xtr_cli-5.1.0/src/xtr_cli/project_questions.py +0 -108
- xtr_cli-5.1.0/src/xtr_cli/project_settings.py +0 -89
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/LICENSE +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/__main__.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/command/__init__.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/command/serve_command.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/ambiguous_web_entry_point_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/cli_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/directory_not_empty_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/invalid_project_name_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/invalid_template_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/project_not_found_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/step_failed_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/unsupported_python_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/exception/web_entry_point_not_found_error.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/process_runner_interface.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/project_name.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/py.typed +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/scaffold_plan.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/subprocess_runner.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/template.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/__init__.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/README.md.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/app/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/app/__main__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/app/command/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/app/command/hello_command.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/app/kernel.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/app/py.typed.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/gitignore.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/pyproject.toml.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/python-version.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/tests/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/cli/files/tests/test_kernel.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/__main__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/command/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/command/hello_command.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/config/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/config/packages/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/config/packages/http_kernel.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/py.typed.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/web/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/web/__main__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/web/app.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/app/web/routes.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/gitignore.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/pyproject.toml.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/python-version.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/tests/__init__.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/tests/test_kernel.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/templates/webapp/files/tests/test_web.py.tmpl +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/web_entry_point.py +0 -0
- {xtr_cli-5.1.0 → xtr_cli-5.2.0}/src/xtr_cli/web_entry_point_finder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xtr-cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2.0
|
|
4
4
|
Summary: Scaffolds a new xtr application — a console application or a web application — in one command, and serves it.
|
|
5
5
|
Keywords: scaffold,new project,template,console,web,xtr-dependency-injection
|
|
6
6
|
Author: Xterr
|
|
@@ -67,27 +67,14 @@ Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/) on the `PATH`, and `g
|
|
|
67
67
|
## Quick start
|
|
68
68
|
|
|
69
69
|
```console
|
|
70
|
-
$ uvx xtr-cli new shop
|
|
71
|
-
• cli — A console application: a kernel, its commands, the layered .env files and logging.
|
|
72
|
-
• webapp — A web application: FastAPI served from the kernel, with the console beside it.
|
|
73
|
-
Template [cli/webapp] (cli): webapp
|
|
74
|
-
• orm — Database engines, sessions per unit of work and versioned migrations.
|
|
75
|
-
• messenger — A message bus: handlers, middleware, transports and consumers.
|
|
76
|
-
…
|
|
77
|
-
Add-ons, comma-separated (empty for none): orm, messenger
|
|
78
|
-
Database drivers for orm — sqlite, postgres, mysql, joined with + (sqlite): sqlite+postgres
|
|
79
|
-
Transports and serializers for messenger — amqp, pydantic, joined with +:
|
|
70
|
+
$ uvx xtr-cli new shop --webapp
|
|
80
71
|
|
|
81
72
|
shop — the webapp template
|
|
82
73
|
--------------------------
|
|
83
74
|
|
|
84
75
|
• directory: /home/ada/shop
|
|
85
|
-
• add-ons: orm (sqlite, postgres), messenger
|
|
86
76
|
• python: 3.13
|
|
87
|
-
The same, with no question asked:
|
|
88
|
-
uvx xtr-cli new shop --template=webapp --with=orm:sqlite+postgres,messenger --python=3.13 -n
|
|
89
77
|
|
|
90
|
-
Create it? [y/n] (y):
|
|
91
78
|
write pyproject.toml
|
|
92
79
|
…
|
|
93
80
|
run uv add -- 'xtr-dependency-injection>=5.0,<6' …
|
|
@@ -108,26 +95,23 @@ uv run pytest
|
|
|
108
95
|
## Commands
|
|
109
96
|
|
|
110
97
|
```sh
|
|
111
|
-
xtr project:new
|
|
112
|
-
xtr project:init [options]
|
|
113
|
-
xtr server:start [options]
|
|
98
|
+
xtr project:new <directory> [options] # alias: new — into a new or empty directory
|
|
99
|
+
xtr project:init [options] # alias: init — into the current directory
|
|
100
|
+
xtr server:start [options] # alias: serve — run the project's web application
|
|
114
101
|
```
|
|
115
102
|
|
|
116
103
|
| Option | Default | |
|
|
117
104
|
|---|---|---|
|
|
118
|
-
| `--
|
|
105
|
+
| `--webapp` | | a web application rather than a console application — see [Templates](#templates) |
|
|
119
106
|
| `--name` | the directory's name | the distribution and console script; dashes become underscores for the import package |
|
|
120
|
-
| `--with` | asked; none with `-n` | add-ons, comma-separated: `orm:sqlite+postgres,messenger` |
|
|
121
107
|
| `--python` | the Python running this tool | the `3.<minor>` the project is pinned to; 3.11 or later |
|
|
122
108
|
| `--no-git` | | do not run `git init`; `init` never re-initialises an existing repository |
|
|
123
109
|
| `--dry-run` | | print every file and command, and write nothing |
|
|
124
110
|
| `--local-packages` | | install the xtr packages, editable, from a directory of their sources |
|
|
125
|
-
| `-n` | | ask nothing: every question takes its default |
|
|
126
111
|
|
|
127
|
-
**
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
answered run can be repeated in a script.
|
|
112
|
+
**Nothing is asked.** Every option has a default, so the same command line makes the same project
|
|
113
|
+
at a terminal, in a script or in CI. More packages are added afterwards, from inside the project,
|
|
114
|
+
with `uv run xtr-recipes recipes:add <package>`.
|
|
131
115
|
|
|
132
116
|
The directory must be missing, or hold nothing but `.git` and editor metadata: nothing is ever
|
|
133
117
|
written over. A name that would not import — a keyword, a module of the standard library — is
|
|
@@ -136,8 +120,8 @@ refused before anything is written.
|
|
|
136
120
|
### What it runs
|
|
137
121
|
|
|
138
122
|
1. Writes the template's files.
|
|
139
|
-
2. `uv add` the template's packages
|
|
140
|
-
|
|
123
|
+
2. `uv add` the template's packages; every unversioned xtr requirement is pinned to this tool's
|
|
124
|
+
major version, so the files and the packages are from the same release.
|
|
141
125
|
3. `uv add --dev` the tools: `xtr-recipes`, `pytest`, `ruff`, `basedpyright`, `ty`.
|
|
142
126
|
4. `uv run xtr-recipes recipes:sync` — each package's recipe writes `<app>/config/bundles.py`, its
|
|
143
127
|
configuration files, its `.env` and `.gitignore` blocks, and `xtr.lock`; the steps it cannot
|
|
@@ -186,76 +170,24 @@ application's shutdown hooks run.
|
|
|
186
170
|
|
|
187
171
|
## Templates
|
|
188
172
|
|
|
189
|
-
| Template | Packages | Scripts |
|
|
190
|
-
|
|
191
|
-
| `cli` | `xtr-dependency-injection`, `xtr-console`, `xtr-dotenv
|
|
192
|
-
| `webapp` | the same, `xtr-http-kernel`, `fastapi`, `uvicorn` | `<name>`, `<name>-web` |
|
|
173
|
+
| Template | Made by | Packages | Scripts |
|
|
174
|
+
|---|---|---|---|
|
|
175
|
+
| `cli` | `xtr new <directory>` | `xtr-dependency-injection`, `xtr-console`, `xtr-dotenv` | `<name>` |
|
|
176
|
+
| `webapp` | `xtr new <directory> --webapp` | the same, and `xtr-logging`, `xtr-http-kernel`, `xtr-orm[postgres]`, `xtr-messenger[amqp]`, `xtr-http-client`, `xtr-notifier`, `xtr-security`, `fastapi`, `uvicorn` | `<name>`, `<name>-web` |
|
|
193
177
|
|
|
194
178
|
Both write `pyproject.toml` (with ruff, basedpyright, ty and pytest configured), `.python-version`,
|
|
195
179
|
`.gitignore`, `README.md`, `src/<app>/kernel.py`, `src/<app>/__main__.py`, an example
|
|
196
180
|
`app:hello` command and a test that builds the kernel. `webapp` adds `src/<app>/web/` — the
|
|
197
|
-
FastAPI application served with `setup(app, kernel)`, its routes and a uvicorn entry point —
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
### A template of your own
|
|
201
|
-
|
|
202
|
-
`--template=<directory>` takes any directory holding a `template.toml`:
|
|
203
|
-
|
|
204
|
-
```toml
|
|
205
|
-
summary = "What it makes, in one line."
|
|
206
|
-
|
|
207
|
-
[dependencies]
|
|
208
|
-
packages = ["xtr-console"] # uv add
|
|
209
|
-
dev = ["xtr-recipes", "pytest"] # uv add --dev; with xtr-recipes, the recipes are applied
|
|
210
|
-
|
|
211
|
-
[files]
|
|
212
|
-
# destination in the project = file under the template directory
|
|
213
|
-
"pyproject.toml" = "files/pyproject.toml.tmpl"
|
|
214
|
-
"src/${app}/__main__.py" = "files/main.py.tmpl"
|
|
181
|
+
FastAPI application served with `setup(app, kernel)`, its routes and a uvicorn entry point — a
|
|
182
|
+
kernel built with `concurrent_scoped_access=True` for the security bundle, and a test that
|
|
183
|
+
requests the home page.
|
|
215
184
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
placeholder, table or key is an `InvalidTemplateError` before anything is written. Write only
|
|
223
|
-
what no recipe writes: the bundle list, `.env` and the ignore lines are the recipes'.
|
|
224
|
-
|
|
225
|
-
## Add-ons
|
|
226
|
-
|
|
227
|
-
Choosing an add-on only adds its requirement; the recipe the package ships configures it, the
|
|
228
|
-
way it would for a package added later with `xtr-recipes recipes:add`.
|
|
229
|
-
|
|
230
|
-
| Add-on | Installs | Variants |
|
|
231
|
-
|---|---|---|
|
|
232
|
-
| `orm` | `xtr-orm` | `postgres` (default), `sqlite`, `mysql` |
|
|
233
|
-
| `messenger` | `xtr-messenger` | `amqp` (default), `pydantic` |
|
|
234
|
-
| `scheduler` | `xtr-scheduler[cron]` | |
|
|
235
|
-
| `event-dispatcher` | `xtr-event-dispatcher` | |
|
|
236
|
-
| `cache` | `xtr-cache` | `redis` |
|
|
237
|
-
| `lock` | `xtr-lock` | `redis` |
|
|
238
|
-
| `rate-limiter` | `xtr-rate-limiter` | |
|
|
239
|
-
| `storage` | `xtr-storage` | `s3`, `gcs` |
|
|
240
|
-
| `http-client` | `xtr-http-client` | `http2` |
|
|
241
|
-
| `security` | `xtr-security` — `webapp` only | |
|
|
242
|
-
| `jwt` | `xtr-security-jwt` — `webapp` only; brings `security` | |
|
|
243
|
-
| `oauth2` | `xtr-security-oauth2-server` — `webapp` only; brings `security` and `jwt` | |
|
|
244
|
-
| `webhook` | `xtr-webhook` — `webapp` only; brings `messenger` | |
|
|
245
|
-
|
|
246
|
-
Variants only pick third-party backends. Integrations between xtr packages turn on when both are
|
|
247
|
-
installed: choose `orm` beside `oauth2` to keep its clients in a database, `http-client` beside
|
|
248
|
-
`webhook` to send webhooks, `rate-limiter` beside `http-client` to pace requests.
|
|
249
|
-
|
|
250
|
-
`jwt` and `oauth2` each have their recipe mint a development signing key under `secrets/`,
|
|
251
|
-
encrypted with a pass phrase it generates in `.env` — and, for `oauth2`, the encryption key too.
|
|
252
|
-
The keys are ignored: another checkout runs `uv run xtr-recipes recipes:install <package>` to
|
|
253
|
-
mint its own, and production mounts the deployment's own key and sets its own secrets. `oauth2`
|
|
254
|
-
still issues nothing until its issuer is configured.
|
|
255
|
-
|
|
256
|
-
Variants follow the name after `:`, joined with `+`: `orm:sqlite+postgres`. A name alone takes
|
|
257
|
-
the defaults; `messenger:` picks none. Asked interactively, an add-on with variants is followed by
|
|
258
|
-
a question for them.
|
|
185
|
+
Each package's recipe configures it: the `webapp` project starts with a `compose.yaml` running
|
|
186
|
+
PostgreSQL — published on `5432` by `compose.override.yaml` — that `DATABASE_URL` points at,
|
|
187
|
+
`MESSENGER_DSN` pointing at a RabbitMQ on this machine, and both notifier DSNs at `null://null`.
|
|
188
|
+
Nothing connects until it is used, so the project boots and passes its tests with neither
|
|
189
|
+
running. Every other package — `xtr-cache`, `xtr-scheduler`, `xtr-security-jwt` — is a
|
|
190
|
+
`recipes:add` away.
|
|
259
191
|
|
|
260
192
|
## Errors
|
|
261
193
|
|
|
@@ -267,11 +199,10 @@ attributes. A command reports it and exits `1`.
|
|
|
267
199
|
| `DirectoryNotEmptyError` | the directory is a file, or holds anything but version-control and editor metadata |
|
|
268
200
|
| `AmbiguousWebEntryPointError` | `serve` found several web entry points, and no question may be asked |
|
|
269
201
|
| `InvalidProjectNameError` | the name normalises to nothing, or to a keyword, a non-identifier or a standard-library module |
|
|
270
|
-
| `InvalidTemplateError` | a `template.toml` does not parse, holds an unknown key, names a missing file, or a placeholder is unknown |
|
|
202
|
+
| `InvalidTemplateError` | a shipped `template.toml` does not parse, holds an unknown key, names a missing file, or a placeholder is unknown |
|
|
271
203
|
| `ProjectNotFoundError` | `serve` was run outside a project |
|
|
272
204
|
| `StepFailedError` | `uv` or `git` exited with a failure; the later steps were not run |
|
|
273
|
-
| `
|
|
274
|
-
| `UnknownTemplateError` | `--template` is neither a shipped template nor a directory holding one |
|
|
205
|
+
| `UnknownTemplateError` | `TemplateCatalog.load` is given a name no template ships under |
|
|
275
206
|
| `UnsupportedPythonError` | `--python` is not `3.<minor>` with a minor of 11 or more |
|
|
276
207
|
| `WebEntryPointNotFoundError` | the project has nothing to serve, or `--entrypoint` names nothing it has |
|
|
277
208
|
|
|
@@ -280,14 +211,11 @@ attributes. A command reports it and exits `1`.
|
|
|
280
211
|
```
|
|
281
212
|
xtr_cli/
|
|
282
213
|
├── command/ project:new, project:init and server:start
|
|
283
|
-
├── command_support.py what both commands share:
|
|
284
|
-
├── project_questions.py what the command line left out, asked
|
|
214
|
+
├── command_support.py what both commands share: settings, plan, apply, report
|
|
285
215
|
├── project_name.py the distribution and import names
|
|
286
|
-
├── project_settings.py everything decided
|
|
216
|
+
├── project_settings.py everything decided about the project
|
|
287
217
|
├── template.py a template.toml and its files
|
|
288
|
-
├── template_catalog.py the shipped templates
|
|
289
|
-
├── add_on.py one add-on: a name standing for a requirement
|
|
290
|
-
├── add_on_catalog.py the add-ons, and how --with names them
|
|
218
|
+
├── template_catalog.py the shipped templates
|
|
291
219
|
├── scaffolder.py settings into a plan, a plan into a project
|
|
292
220
|
├── scaffold_plan.py the files and commands, before anything is made
|
|
293
221
|
├── web_entry_point.py what serves an application, and the command starting it
|
|
@@ -46,27 +46,14 @@ Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/) on the `PATH`, and `g
|
|
|
46
46
|
## Quick start
|
|
47
47
|
|
|
48
48
|
```console
|
|
49
|
-
$ uvx xtr-cli new shop
|
|
50
|
-
• cli — A console application: a kernel, its commands, the layered .env files and logging.
|
|
51
|
-
• webapp — A web application: FastAPI served from the kernel, with the console beside it.
|
|
52
|
-
Template [cli/webapp] (cli): webapp
|
|
53
|
-
• orm — Database engines, sessions per unit of work and versioned migrations.
|
|
54
|
-
• messenger — A message bus: handlers, middleware, transports and consumers.
|
|
55
|
-
…
|
|
56
|
-
Add-ons, comma-separated (empty for none): orm, messenger
|
|
57
|
-
Database drivers for orm — sqlite, postgres, mysql, joined with + (sqlite): sqlite+postgres
|
|
58
|
-
Transports and serializers for messenger — amqp, pydantic, joined with +:
|
|
49
|
+
$ uvx xtr-cli new shop --webapp
|
|
59
50
|
|
|
60
51
|
shop — the webapp template
|
|
61
52
|
--------------------------
|
|
62
53
|
|
|
63
54
|
• directory: /home/ada/shop
|
|
64
|
-
• add-ons: orm (sqlite, postgres), messenger
|
|
65
55
|
• python: 3.13
|
|
66
|
-
The same, with no question asked:
|
|
67
|
-
uvx xtr-cli new shop --template=webapp --with=orm:sqlite+postgres,messenger --python=3.13 -n
|
|
68
56
|
|
|
69
|
-
Create it? [y/n] (y):
|
|
70
57
|
write pyproject.toml
|
|
71
58
|
…
|
|
72
59
|
run uv add -- 'xtr-dependency-injection>=5.0,<6' …
|
|
@@ -87,26 +74,23 @@ uv run pytest
|
|
|
87
74
|
## Commands
|
|
88
75
|
|
|
89
76
|
```sh
|
|
90
|
-
xtr project:new
|
|
91
|
-
xtr project:init [options]
|
|
92
|
-
xtr server:start [options]
|
|
77
|
+
xtr project:new <directory> [options] # alias: new — into a new or empty directory
|
|
78
|
+
xtr project:init [options] # alias: init — into the current directory
|
|
79
|
+
xtr server:start [options] # alias: serve — run the project's web application
|
|
93
80
|
```
|
|
94
81
|
|
|
95
82
|
| Option | Default | |
|
|
96
83
|
|---|---|---|
|
|
97
|
-
| `--
|
|
84
|
+
| `--webapp` | | a web application rather than a console application — see [Templates](#templates) |
|
|
98
85
|
| `--name` | the directory's name | the distribution and console script; dashes become underscores for the import package |
|
|
99
|
-
| `--with` | asked; none with `-n` | add-ons, comma-separated: `orm:sqlite+postgres,messenger` |
|
|
100
86
|
| `--python` | the Python running this tool | the `3.<minor>` the project is pinned to; 3.11 or later |
|
|
101
87
|
| `--no-git` | | do not run `git init`; `init` never re-initialises an existing repository |
|
|
102
88
|
| `--dry-run` | | print every file and command, and write nothing |
|
|
103
89
|
| `--local-packages` | | install the xtr packages, editable, from a directory of their sources |
|
|
104
|
-
| `-n` | | ask nothing: every question takes its default |
|
|
105
90
|
|
|
106
|
-
**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
answered run can be repeated in a script.
|
|
91
|
+
**Nothing is asked.** Every option has a default, so the same command line makes the same project
|
|
92
|
+
at a terminal, in a script or in CI. More packages are added afterwards, from inside the project,
|
|
93
|
+
with `uv run xtr-recipes recipes:add <package>`.
|
|
110
94
|
|
|
111
95
|
The directory must be missing, or hold nothing but `.git` and editor metadata: nothing is ever
|
|
112
96
|
written over. A name that would not import — a keyword, a module of the standard library — is
|
|
@@ -115,8 +99,8 @@ refused before anything is written.
|
|
|
115
99
|
### What it runs
|
|
116
100
|
|
|
117
101
|
1. Writes the template's files.
|
|
118
|
-
2. `uv add` the template's packages
|
|
119
|
-
|
|
102
|
+
2. `uv add` the template's packages; every unversioned xtr requirement is pinned to this tool's
|
|
103
|
+
major version, so the files and the packages are from the same release.
|
|
120
104
|
3. `uv add --dev` the tools: `xtr-recipes`, `pytest`, `ruff`, `basedpyright`, `ty`.
|
|
121
105
|
4. `uv run xtr-recipes recipes:sync` — each package's recipe writes `<app>/config/bundles.py`, its
|
|
122
106
|
configuration files, its `.env` and `.gitignore` blocks, and `xtr.lock`; the steps it cannot
|
|
@@ -165,76 +149,24 @@ application's shutdown hooks run.
|
|
|
165
149
|
|
|
166
150
|
## Templates
|
|
167
151
|
|
|
168
|
-
| Template | Packages | Scripts |
|
|
169
|
-
|
|
170
|
-
| `cli` | `xtr-dependency-injection`, `xtr-console`, `xtr-dotenv
|
|
171
|
-
| `webapp` | the same, `xtr-http-kernel`, `fastapi`, `uvicorn` | `<name>`, `<name>-web` |
|
|
152
|
+
| Template | Made by | Packages | Scripts |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| `cli` | `xtr new <directory>` | `xtr-dependency-injection`, `xtr-console`, `xtr-dotenv` | `<name>` |
|
|
155
|
+
| `webapp` | `xtr new <directory> --webapp` | the same, and `xtr-logging`, `xtr-http-kernel`, `xtr-orm[postgres]`, `xtr-messenger[amqp]`, `xtr-http-client`, `xtr-notifier`, `xtr-security`, `fastapi`, `uvicorn` | `<name>`, `<name>-web` |
|
|
172
156
|
|
|
173
157
|
Both write `pyproject.toml` (with ruff, basedpyright, ty and pytest configured), `.python-version`,
|
|
174
158
|
`.gitignore`, `README.md`, `src/<app>/kernel.py`, `src/<app>/__main__.py`, an example
|
|
175
159
|
`app:hello` command and a test that builds the kernel. `webapp` adds `src/<app>/web/` — the
|
|
176
|
-
FastAPI application served with `setup(app, kernel)`, its routes and a uvicorn entry point —
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
### A template of your own
|
|
180
|
-
|
|
181
|
-
`--template=<directory>` takes any directory holding a `template.toml`:
|
|
182
|
-
|
|
183
|
-
```toml
|
|
184
|
-
summary = "What it makes, in one line."
|
|
185
|
-
|
|
186
|
-
[dependencies]
|
|
187
|
-
packages = ["xtr-console"] # uv add
|
|
188
|
-
dev = ["xtr-recipes", "pytest"] # uv add --dev; with xtr-recipes, the recipes are applied
|
|
189
|
-
|
|
190
|
-
[files]
|
|
191
|
-
# destination in the project = file under the template directory
|
|
192
|
-
"pyproject.toml" = "files/pyproject.toml.tmpl"
|
|
193
|
-
"src/${app}/__main__.py" = "files/main.py.tmpl"
|
|
160
|
+
FastAPI application served with `setup(app, kernel)`, its routes and a uvicorn entry point — a
|
|
161
|
+
kernel built with `concurrent_scoped_access=True` for the security bundle, and a test that
|
|
162
|
+
requests the home page.
|
|
194
163
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
placeholder, table or key is an `InvalidTemplateError` before anything is written. Write only
|
|
202
|
-
what no recipe writes: the bundle list, `.env` and the ignore lines are the recipes'.
|
|
203
|
-
|
|
204
|
-
## Add-ons
|
|
205
|
-
|
|
206
|
-
Choosing an add-on only adds its requirement; the recipe the package ships configures it, the
|
|
207
|
-
way it would for a package added later with `xtr-recipes recipes:add`.
|
|
208
|
-
|
|
209
|
-
| Add-on | Installs | Variants |
|
|
210
|
-
|---|---|---|
|
|
211
|
-
| `orm` | `xtr-orm` | `postgres` (default), `sqlite`, `mysql` |
|
|
212
|
-
| `messenger` | `xtr-messenger` | `amqp` (default), `pydantic` |
|
|
213
|
-
| `scheduler` | `xtr-scheduler[cron]` | |
|
|
214
|
-
| `event-dispatcher` | `xtr-event-dispatcher` | |
|
|
215
|
-
| `cache` | `xtr-cache` | `redis` |
|
|
216
|
-
| `lock` | `xtr-lock` | `redis` |
|
|
217
|
-
| `rate-limiter` | `xtr-rate-limiter` | |
|
|
218
|
-
| `storage` | `xtr-storage` | `s3`, `gcs` |
|
|
219
|
-
| `http-client` | `xtr-http-client` | `http2` |
|
|
220
|
-
| `security` | `xtr-security` — `webapp` only | |
|
|
221
|
-
| `jwt` | `xtr-security-jwt` — `webapp` only; brings `security` | |
|
|
222
|
-
| `oauth2` | `xtr-security-oauth2-server` — `webapp` only; brings `security` and `jwt` | |
|
|
223
|
-
| `webhook` | `xtr-webhook` — `webapp` only; brings `messenger` | |
|
|
224
|
-
|
|
225
|
-
Variants only pick third-party backends. Integrations between xtr packages turn on when both are
|
|
226
|
-
installed: choose `orm` beside `oauth2` to keep its clients in a database, `http-client` beside
|
|
227
|
-
`webhook` to send webhooks, `rate-limiter` beside `http-client` to pace requests.
|
|
228
|
-
|
|
229
|
-
`jwt` and `oauth2` each have their recipe mint a development signing key under `secrets/`,
|
|
230
|
-
encrypted with a pass phrase it generates in `.env` — and, for `oauth2`, the encryption key too.
|
|
231
|
-
The keys are ignored: another checkout runs `uv run xtr-recipes recipes:install <package>` to
|
|
232
|
-
mint its own, and production mounts the deployment's own key and sets its own secrets. `oauth2`
|
|
233
|
-
still issues nothing until its issuer is configured.
|
|
234
|
-
|
|
235
|
-
Variants follow the name after `:`, joined with `+`: `orm:sqlite+postgres`. A name alone takes
|
|
236
|
-
the defaults; `messenger:` picks none. Asked interactively, an add-on with variants is followed by
|
|
237
|
-
a question for them.
|
|
164
|
+
Each package's recipe configures it: the `webapp` project starts with a `compose.yaml` running
|
|
165
|
+
PostgreSQL — published on `5432` by `compose.override.yaml` — that `DATABASE_URL` points at,
|
|
166
|
+
`MESSENGER_DSN` pointing at a RabbitMQ on this machine, and both notifier DSNs at `null://null`.
|
|
167
|
+
Nothing connects until it is used, so the project boots and passes its tests with neither
|
|
168
|
+
running. Every other package — `xtr-cache`, `xtr-scheduler`, `xtr-security-jwt` — is a
|
|
169
|
+
`recipes:add` away.
|
|
238
170
|
|
|
239
171
|
## Errors
|
|
240
172
|
|
|
@@ -246,11 +178,10 @@ attributes. A command reports it and exits `1`.
|
|
|
246
178
|
| `DirectoryNotEmptyError` | the directory is a file, or holds anything but version-control and editor metadata |
|
|
247
179
|
| `AmbiguousWebEntryPointError` | `serve` found several web entry points, and no question may be asked |
|
|
248
180
|
| `InvalidProjectNameError` | the name normalises to nothing, or to a keyword, a non-identifier or a standard-library module |
|
|
249
|
-
| `InvalidTemplateError` | a `template.toml` does not parse, holds an unknown key, names a missing file, or a placeholder is unknown |
|
|
181
|
+
| `InvalidTemplateError` | a shipped `template.toml` does not parse, holds an unknown key, names a missing file, or a placeholder is unknown |
|
|
250
182
|
| `ProjectNotFoundError` | `serve` was run outside a project |
|
|
251
183
|
| `StepFailedError` | `uv` or `git` exited with a failure; the later steps were not run |
|
|
252
|
-
| `
|
|
253
|
-
| `UnknownTemplateError` | `--template` is neither a shipped template nor a directory holding one |
|
|
184
|
+
| `UnknownTemplateError` | `TemplateCatalog.load` is given a name no template ships under |
|
|
254
185
|
| `UnsupportedPythonError` | `--python` is not `3.<minor>` with a minor of 11 or more |
|
|
255
186
|
| `WebEntryPointNotFoundError` | the project has nothing to serve, or `--entrypoint` names nothing it has |
|
|
256
187
|
|
|
@@ -259,14 +190,11 @@ attributes. A command reports it and exits `1`.
|
|
|
259
190
|
```
|
|
260
191
|
xtr_cli/
|
|
261
192
|
├── command/ project:new, project:init and server:start
|
|
262
|
-
├── command_support.py what both commands share:
|
|
263
|
-
├── project_questions.py what the command line left out, asked
|
|
193
|
+
├── command_support.py what both commands share: settings, plan, apply, report
|
|
264
194
|
├── project_name.py the distribution and import names
|
|
265
|
-
├── project_settings.py everything decided
|
|
195
|
+
├── project_settings.py everything decided about the project
|
|
266
196
|
├── template.py a template.toml and its files
|
|
267
|
-
├── template_catalog.py the shipped templates
|
|
268
|
-
├── add_on.py one add-on: a name standing for a requirement
|
|
269
|
-
├── add_on_catalog.py the add-ons, and how --with names them
|
|
197
|
+
├── template_catalog.py the shipped templates
|
|
270
198
|
├── scaffolder.py settings into a plan, a plan into a project
|
|
271
199
|
├── scaffold_plan.py the files and commands, before anything is made
|
|
272
200
|
├── web_entry_point.py what serves an application, and the command starting it
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Creates a new application on the xtr packages, ready to run, in one command.
|
|
2
2
|
|
|
3
|
-
``xtr new <directory>`` asks
|
|
4
|
-
|
|
5
|
-
the kernel and the entry points), installs the packages
|
|
6
|
-
each package's recipe configure itself with
|
|
7
|
-
project it leaves boots, and is already in
|
|
3
|
+
``xtr new <directory>`` asks nothing: it makes a console application, or a web
|
|
4
|
+
application with ``--webapp``. It writes the few files no package's recipe can
|
|
5
|
+
(the project manifest, the kernel and the entry points), installs the packages
|
|
6
|
+
with ``uv``, and lets each package's recipe configure itself with
|
|
7
|
+
``xtr-recipes recipes:sync``. The project it leaves boots, and is already in
|
|
8
|
+
the state a recipe check expects.
|
|
8
9
|
|
|
9
10
|
This module exposes the pieces that plan and make a project; the commands that
|
|
10
11
|
drive them live in :mod:`xtr_cli.command`, behind the ``xtr`` console script.
|
|
@@ -14,9 +15,6 @@ from __future__ import annotations
|
|
|
14
15
|
|
|
15
16
|
from importlib.metadata import PackageNotFoundError, version
|
|
16
17
|
|
|
17
|
-
from .add_on import AddOn
|
|
18
|
-
from .add_on_catalog import AddOnCatalog
|
|
19
|
-
from .add_on_choice import AddOnChoice
|
|
20
18
|
from .exception import (
|
|
21
19
|
AmbiguousWebEntryPointError,
|
|
22
20
|
CliError,
|
|
@@ -25,14 +23,12 @@ from .exception import (
|
|
|
25
23
|
InvalidTemplateError,
|
|
26
24
|
ProjectNotFoundError,
|
|
27
25
|
StepFailedError,
|
|
28
|
-
UnknownAddOnError,
|
|
29
26
|
UnknownTemplateError,
|
|
30
27
|
UnsupportedPythonError,
|
|
31
28
|
WebEntryPointNotFoundError,
|
|
32
29
|
)
|
|
33
30
|
from .process_runner_interface import ProcessRunnerInterface
|
|
34
31
|
from .project_name import ProjectName
|
|
35
|
-
from .project_questions import ProjectQuestions
|
|
36
32
|
from .project_settings import ProjectSettings
|
|
37
33
|
from .scaffold_plan import ScaffoldPlan
|
|
38
34
|
from .scaffolder import Scaffolder
|
|
@@ -50,9 +46,6 @@ except PackageNotFoundError: # pragma: no cover
|
|
|
50
46
|
__version__ = "0+unknown"
|
|
51
47
|
|
|
52
48
|
__all__ = [
|
|
53
|
-
"AddOn",
|
|
54
|
-
"AddOnCatalog",
|
|
55
|
-
"AddOnChoice",
|
|
56
49
|
"AmbiguousWebEntryPointError",
|
|
57
50
|
"CliError",
|
|
58
51
|
"DirectoryNotEmptyError",
|
|
@@ -61,7 +54,6 @@ __all__ = [
|
|
|
61
54
|
"ProcessRunnerInterface",
|
|
62
55
|
"ProjectName",
|
|
63
56
|
"ProjectNotFoundError",
|
|
64
|
-
"ProjectQuestions",
|
|
65
57
|
"ProjectSettings",
|
|
66
58
|
"ScaffoldPlan",
|
|
67
59
|
"Scaffolder",
|
|
@@ -69,7 +61,6 @@ __all__ = [
|
|
|
69
61
|
"SubprocessRunner",
|
|
70
62
|
"Template",
|
|
71
63
|
"TemplateCatalog",
|
|
72
|
-
"UnknownAddOnError",
|
|
73
64
|
"UnknownTemplateError",
|
|
74
65
|
"UnsupportedPythonError",
|
|
75
66
|
"WebEntryPoint",
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import final
|
|
7
7
|
|
|
8
|
-
from xtr_console import ConsoleStyle,
|
|
8
|
+
from xtr_console import ConsoleStyle, as_command
|
|
9
9
|
|
|
10
10
|
from xtr_cli import command_support
|
|
11
11
|
|
|
@@ -15,37 +15,30 @@ __all__ = ["InitCommand"]
|
|
|
15
15
|
@as_command("project:init", aliases="init")
|
|
16
16
|
@final
|
|
17
17
|
class InitCommand:
|
|
18
|
-
"""Create a new application
|
|
18
|
+
"""Create a new application in the current directory.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
Nothing is asked: a console application by default, a web application with
|
|
21
|
+
``--webapp``. The directory must hold nothing but version-control or editor
|
|
22
|
+
metadata; an existing git repository is kept rather than initialised again.
|
|
22
23
|
"""
|
|
23
24
|
|
|
24
25
|
async def __call__( # noqa: PLR0913 — one parameter per option.
|
|
25
26
|
self,
|
|
26
27
|
io: ConsoleStyle,
|
|
27
28
|
*,
|
|
28
|
-
|
|
29
|
+
webapp: bool = False,
|
|
29
30
|
name: str | None = None,
|
|
30
|
-
add_ons: Annotated[
|
|
31
|
-
str | None,
|
|
32
|
-
Option(
|
|
33
|
-
name="--with",
|
|
34
|
-
help="More packages to start with, comma-separated: orm:sqlite+postgres,messenger.",
|
|
35
|
-
),
|
|
36
|
-
] = None,
|
|
37
31
|
python: str | None = None,
|
|
38
32
|
no_git: bool = False,
|
|
39
33
|
dry_run: bool = False,
|
|
40
34
|
local_packages: Path | None = None,
|
|
41
35
|
) -> int:
|
|
42
|
-
"""Create a new application
|
|
36
|
+
"""Create a new application in the current directory.
|
|
43
37
|
|
|
44
38
|
Args:
|
|
45
|
-
io: Where the
|
|
46
|
-
|
|
39
|
+
io: Where the steps are reported.
|
|
40
|
+
webapp: Make a web application, with a database, a message bus and security.
|
|
47
41
|
name: The project's name; the directory's name by default.
|
|
48
|
-
add_ons: The ``--with`` add-ons.
|
|
49
42
|
python: The 3.<minor> version to pin; the one running this tool by default.
|
|
50
43
|
no_git: Do not initialise a git repository.
|
|
51
44
|
dry_run: Print the files and commands, and write nothing.
|
|
@@ -59,9 +52,8 @@ class InitCommand:
|
|
|
59
52
|
io,
|
|
60
53
|
command="init",
|
|
61
54
|
directory=Path.cwd(),
|
|
62
|
-
|
|
55
|
+
webapp=webapp,
|
|
63
56
|
name=name,
|
|
64
|
-
add_ons=add_ons,
|
|
65
57
|
python=python,
|
|
66
58
|
git=not no_git,
|
|
67
59
|
dry_run=dry_run,
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from pathlib import Path # noqa: TC003 — the console reads annotations at runtime.
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import final
|
|
7
7
|
|
|
8
|
-
from xtr_console import ConsoleStyle,
|
|
8
|
+
from xtr_console import ConsoleStyle, as_command
|
|
9
9
|
|
|
10
10
|
from xtr_cli import command_support
|
|
11
11
|
|
|
@@ -15,40 +15,32 @@ __all__ = ["NewCommand"]
|
|
|
15
15
|
@as_command("project:new", aliases="new")
|
|
16
16
|
@final
|
|
17
17
|
class NewCommand:
|
|
18
|
-
"""Create a new application
|
|
18
|
+
"""Create a new application in a new directory.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
Nothing is asked: a console application by default, a web application with
|
|
21
|
+
``--webapp``. The packages are installed with ``uv``, and each one's recipe
|
|
22
|
+
is applied, before the command ends.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
async def __call__( # noqa: PLR0913 — one parameter per option.
|
|
26
26
|
self,
|
|
27
27
|
io: ConsoleStyle,
|
|
28
|
-
directory: Path
|
|
28
|
+
directory: Path,
|
|
29
29
|
*,
|
|
30
|
-
|
|
30
|
+
webapp: bool = False,
|
|
31
31
|
name: str | None = None,
|
|
32
|
-
add_ons: Annotated[
|
|
33
|
-
str | None,
|
|
34
|
-
Option(
|
|
35
|
-
name="--with",
|
|
36
|
-
help="More packages to start with, comma-separated: orm:sqlite+postgres,messenger.",
|
|
37
|
-
),
|
|
38
|
-
] = None,
|
|
39
32
|
python: str | None = None,
|
|
40
33
|
no_git: bool = False,
|
|
41
34
|
dry_run: bool = False,
|
|
42
35
|
local_packages: Path | None = None,
|
|
43
36
|
) -> int:
|
|
44
|
-
"""Create a new application
|
|
37
|
+
"""Create a new application in a new directory.
|
|
45
38
|
|
|
46
39
|
Args:
|
|
47
|
-
io: Where the
|
|
40
|
+
io: Where the steps are reported.
|
|
48
41
|
directory: Where to create it; it must be missing or empty.
|
|
49
|
-
|
|
42
|
+
webapp: Make a web application, with a database, a message bus and security.
|
|
50
43
|
name: The project's name; the directory's name by default.
|
|
51
|
-
add_ons: The ``--with`` add-ons.
|
|
52
44
|
python: The 3.<minor> version to pin; the one running this tool by default.
|
|
53
45
|
no_git: Do not initialise a git repository.
|
|
54
46
|
dry_run: Print the files and commands, and write nothing.
|
|
@@ -62,9 +54,8 @@ class NewCommand:
|
|
|
62
54
|
io,
|
|
63
55
|
command="new",
|
|
64
56
|
directory=directory,
|
|
65
|
-
|
|
57
|
+
webapp=webapp,
|
|
66
58
|
name=name,
|
|
67
|
-
add_ons=add_ons,
|
|
68
59
|
python=python,
|
|
69
60
|
git=not no_git,
|
|
70
61
|
dry_run=dry_run,
|