create-forge 0.2.0__tar.gz → 0.2.1__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.
- {create_forge-0.2.0 → create_forge-0.2.1}/CHANGELOG.md +14 -1
- {create_forge-0.2.0 → create_forge-0.2.1}/PKG-INFO +12 -2
- {create_forge-0.2.0 → create_forge-0.2.1}/README.md +10 -1
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/adr/README.md +3 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/README.md +8 -8
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/07-forge-cli-integration/README.md +8 -2
- create_forge-0.2.1/docs/roadmap-v1/roadmap/08-reference-archetype-validation/README.md +81 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/pyproject.toml +3 -2
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/cli.py +2 -1
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/pipeline.py +47 -22
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/staging.py +40 -3
- create_forge-0.2.1/tests/conftest.py +70 -0
- create_forge-0.2.1/tests/test_archetype_parity.py +161 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_cli.py +68 -1
- create_forge-0.2.1/tests/test_e2e_engine_generation.py +333 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_e2e_generation.py +24 -56
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_engine_contract.py +8 -3
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_pipeline.py +157 -12
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_spec.py +3 -3
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_staging.py +63 -1
- create_forge-0.2.0/docs/roadmap-v1/roadmap/08-reference-archetype-validation/README.md +0 -58
- create_forge-0.2.0/tests/conftest.py +0 -17
- {create_forge-0.2.0 → create_forge-0.2.1}/.gitignore +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/LICENSE +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/00-governance-and-principles/README.md +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/01-python-core/README.md +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/02-developer-experience/README.md +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/03-quality-and-ci/README.md +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/04-runtime-and-configuration/README.md +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/05-security-and-supply-chain/README.md +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/06-extension-and-composition-contract/README.md +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/09-blueprint-compatibility/README.md +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/__init__.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/compat.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/config.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/engine.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/models.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/prompts.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/registry.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/runner.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/spec.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/src/create_forge/templates.toml +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/__init__.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_adr.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_config.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_drift.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_engine_adapter.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_engine_cross_repository.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_labels.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_models.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_prompts.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_registry.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_runner.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_update.py +0 -0
- {create_forge-0.2.0 → create_forge-0.2.1}/tests/test_update_network.py +0 -0
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
Generated by git-cliff from Conventional Commits.
|
|
4
|
+
## [0.2.1] - 2026-08-31
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Generate engine project lockfiles
|
|
9
|
+
|
|
10
|
+
### Refactor
|
|
11
|
+
|
|
12
|
+
- Derive component options from discovery, not an archetype list (#92)
|
|
13
|
+
|
|
14
|
+
### Testing
|
|
15
|
+
|
|
16
|
+
- Extend end-to-end generation to the public engine (#93)
|
|
4
17
|
## [0.2.0] - 2026-08-30
|
|
5
18
|
|
|
6
19
|
### Documentation
|
|
@@ -36,7 +49,7 @@ Generated by git-cliff from Conventional Commits.
|
|
|
36
49
|
- Implement the shared create pipeline behind an opt-in engine flag (#83)
|
|
37
50
|
- Stage and finalise generation without leaving partials (#84)
|
|
38
51
|
- Expose the CLI Application archetype through the engine path (#89)
|
|
39
|
-
- Publish to PyPI and assign the first bounded engine range
|
|
52
|
+
- Publish to PyPI and assign the first bounded engine range (#90)
|
|
40
53
|
|
|
41
54
|
### Testing
|
|
42
55
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: create-forge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Scaffold modern Python projects from maintained templates.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Sandsy09/create-forge
|
|
6
6
|
Project-URL: Repository, https://github.com/Sandsy09/create-forge
|
|
@@ -27,6 +27,7 @@ Requires-Dist: rich>=13.9
|
|
|
27
27
|
Requires-Dist: typer>=0.15
|
|
28
28
|
Provides-Extra: engine
|
|
29
29
|
Requires-Dist: forge-template<0.4,>=0.3.1; extra == 'engine'
|
|
30
|
+
Requires-Dist: uv<0.13,>=0.12; extra == 'engine'
|
|
30
31
|
Description-Content-Type: text/markdown
|
|
31
32
|
|
|
32
33
|
# create-forge
|
|
@@ -158,13 +159,22 @@ it in the same `0.3.0` release, and
|
|
|
158
159
|
archetypes behind the hidden `new --engine-preview` flag's `--archetype`
|
|
159
160
|
option. Neither change alters this CLI's default `new` answers, registry, or
|
|
160
161
|
released dependency surface.
|
|
162
|
+
The Stage 08
|
|
163
|
+
[composition architecture review](https://github.com/Sandsy09/forge-template/blob/main/docs/composition-architecture-review.md)
|
|
164
|
+
is released in `forge-template 0.3.2`. On the engine-preview path,
|
|
165
|
+
`create-forge 0.2.1` now finalises the validated in-memory render with
|
|
166
|
+
`uv lock --directory <staging-directory>` before the atomic rename. The
|
|
167
|
+
result contains `uv.lock` and uses `uv run --locked poe check`, while the
|
|
168
|
+
default Copier path remains unchanged; see
|
|
169
|
+
[ADR 0021](docs/adr/0021-client-finalises-engine-lockfiles.md).
|
|
161
170
|
The engine now also defines in-memory
|
|
162
171
|
[generated-project validation](https://github.com/Sandsy09/forge-template/blob/main/docs/generated-project-validation.md)
|
|
163
172
|
([ADR 0030](https://github.com/Sandsy09/forge-template/blob/main/docs/adr/0030-generated-project-validation.md))
|
|
164
173
|
before rendered output is returned; `render_project` already calls it before
|
|
165
174
|
`--engine-preview` receives a result.
|
|
166
175
|
This repository now depends on a real, released `forge-template` range —
|
|
167
|
-
`>=0.3.1,<0.4`, published to PyPI as the optional
|
|
176
|
+
`>=0.3.1,<0.4`, published to PyPI with `uv>=0.12,<0.13` as the optional
|
|
177
|
+
`engine` extra
|
|
168
178
|
(`pip install 'create-forge[engine]'`; [#9](https://github.com/Sandsy09/create-forge/issues/9),
|
|
169
179
|
[ADR 0018](docs/adr/0018-pypi-distribution-and-the-first-engine-range.md)) —
|
|
170
180
|
rather than a development-only pin. That range is reachable only behind
|
|
@@ -127,13 +127,22 @@ it in the same `0.3.0` release, and
|
|
|
127
127
|
archetypes behind the hidden `new --engine-preview` flag's `--archetype`
|
|
128
128
|
option. Neither change alters this CLI's default `new` answers, registry, or
|
|
129
129
|
released dependency surface.
|
|
130
|
+
The Stage 08
|
|
131
|
+
[composition architecture review](https://github.com/Sandsy09/forge-template/blob/main/docs/composition-architecture-review.md)
|
|
132
|
+
is released in `forge-template 0.3.2`. On the engine-preview path,
|
|
133
|
+
`create-forge 0.2.1` now finalises the validated in-memory render with
|
|
134
|
+
`uv lock --directory <staging-directory>` before the atomic rename. The
|
|
135
|
+
result contains `uv.lock` and uses `uv run --locked poe check`, while the
|
|
136
|
+
default Copier path remains unchanged; see
|
|
137
|
+
[ADR 0021](docs/adr/0021-client-finalises-engine-lockfiles.md).
|
|
130
138
|
The engine now also defines in-memory
|
|
131
139
|
[generated-project validation](https://github.com/Sandsy09/forge-template/blob/main/docs/generated-project-validation.md)
|
|
132
140
|
([ADR 0030](https://github.com/Sandsy09/forge-template/blob/main/docs/adr/0030-generated-project-validation.md))
|
|
133
141
|
before rendered output is returned; `render_project` already calls it before
|
|
134
142
|
`--engine-preview` receives a result.
|
|
135
143
|
This repository now depends on a real, released `forge-template` range —
|
|
136
|
-
`>=0.3.1,<0.4`, published to PyPI as the optional
|
|
144
|
+
`>=0.3.1,<0.4`, published to PyPI with `uv>=0.12,<0.13` as the optional
|
|
145
|
+
`engine` extra
|
|
137
146
|
(`pip install 'create-forge[engine]'`; [#9](https://github.com/Sandsy09/create-forge/issues/9),
|
|
138
147
|
[ADR 0018](docs/adr/0018-pypi-distribution-and-the-first-engine-range.md)) —
|
|
139
148
|
rather than a development-only pin. That range is reachable only behind
|
|
@@ -21,6 +21,9 @@ format](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
|
|
|
21
21
|
- [0016 — Test the reference client end to end against the released template](0016-end-to-end-reference-client-tests.md)
|
|
22
22
|
- [0017 — Expose the CLI Application archetype through discovery-driven selection](0017-cli-application-archetype-exposure.md)
|
|
23
23
|
- [0018 — Publish to PyPI and assign the first bounded engine range](0018-pypi-distribution-and-the-first-engine-range.md)
|
|
24
|
+
- [0019 — CLI archetype-parity review](0019-cli-archetype-parity-review.md)
|
|
25
|
+
- [0020 — Test the public engine path end to end](0020-engine-path-end-to-end-tests.md)
|
|
26
|
+
- [0021 — Finalise engine-generated lockfiles in the client](0021-client-finalises-engine-lockfiles.md)
|
|
24
27
|
|
|
25
28
|
Add a new record by copying the most recent one and incrementing the number.
|
|
26
29
|
Records are immutable: supersede them rather than editing.
|
|
@@ -13,13 +13,17 @@ define the mandatory generated-project outcomes, while the
|
|
|
13
13
|
defines which concerns may belong in that baseline. The
|
|
14
14
|
[Library archetype contract](https://github.com/Sandsy09/forge-template/blob/main/docs/library-archetype.md)
|
|
15
15
|
defines the first production archetype, implemented on
|
|
16
|
-
`forge-template/main
|
|
16
|
+
`forge-template/main`, released at `0.3.0`, and included in the completed
|
|
17
|
+
two-archetype [composition review](https://github.com/Sandsy09/forge-template/blob/main/docs/composition-architecture-review.md)
|
|
18
|
+
released at `0.3.2`. The
|
|
17
19
|
[CLI Application archetype contract](https://github.com/Sandsy09/forge-template/blob/main/docs/cli-application-archetype.md)
|
|
18
20
|
selects the engine-owned, optionless `cli` archetype and derives its command
|
|
19
21
|
from `ProjectSpec.project.repository_name`; FT-08.04 implemented it, and
|
|
20
22
|
CF-08.02 exposes both archetypes behind `create-forge`'s hidden
|
|
21
23
|
`--engine-preview` flag. Neither contract changes this repository's default
|
|
22
|
-
`new` path
|
|
24
|
+
`new` path. `create-forge 0.2.1` keeps the released engine range unchanged and
|
|
25
|
+
adds client-owned lock finalisation under
|
|
26
|
+
[ADR 0021](../adr/0021-client-finalises-engine-lockfiles.md). The
|
|
23
27
|
[Python support policy](https://github.com/Sandsy09/forge-template/blob/main/docs/python-support.md)
|
|
24
28
|
defines the generated-project CPython window, defaults, and release lifecycle.
|
|
25
29
|
The [editor integration strategy](https://github.com/Sandsy09/forge-template/blob/main/docs/editor-integration.md)
|
|
@@ -60,12 +64,8 @@ The canonical
|
|
|
60
64
|
[generated-project validation contract](https://github.com/Sandsy09/forge-template/blob/main/docs/generated-project-validation.md)
|
|
61
65
|
and [ADR 0030](https://github.com/Sandsy09/forge-template/blob/main/docs/adr/0030-generated-project-validation.md)
|
|
62
66
|
complete the `forge-template` side of Stage 07 by validating rendered output
|
|
63
|
-
in memory.
|
|
64
|
-
|
|
65
|
-
`forge-template/main` now contains the Library production catalogue, while
|
|
66
|
-
this repository's exact development pin still sees the earlier empty
|
|
67
|
-
catalogue. CLI Application implementation and coordinated CLI discovery remain
|
|
68
|
-
later work.
|
|
67
|
+
in memory. Stage 07 and Stage 08 are now complete across both repositories;
|
|
68
|
+
the default Copier-to-engine cutover remains separate future work.
|
|
69
69
|
This roadmap links to the canonical sources rather than maintaining second
|
|
70
70
|
definitions.
|
|
71
71
|
|
{create_forge-0.2.0 → create_forge-0.2.1}/docs/roadmap-v1/roadmap/07-forge-cli-integration/README.md
RENAMED
|
@@ -60,9 +60,15 @@ the *public engine* against a *released* engine-and-assets unit -- because
|
|
|
60
60
|
neither exists yet: `forge-template` has no `0.2.x` release and its
|
|
61
61
|
production catalogue stays empty until
|
|
62
62
|
[FT-08.02 / forge-template#41](https://github.com/Sandsy09/forge-template/issues/41).
|
|
63
|
-
Those two criteria
|
|
63
|
+
Those two criteria carried forward verbatim to **CF-08.04**, filed under
|
|
64
64
|
[CF-EPIC-08](https://github.com/Sandsy09/create-forge/issues/39) and blocked
|
|
65
|
-
on the same two things
|
|
65
|
+
on the same two things at the time. Both blockers have since resolved --
|
|
66
|
+
[FT-08.02 / forge-template#41](https://github.com/Sandsy09/forge-template/issues/41)
|
|
67
|
+
shipped the production catalogue, and
|
|
68
|
+
[#9](https://github.com/Sandsy09/create-forge/issues/9) published a released
|
|
69
|
+
engine range -- and CF-08.04 is complete
|
|
70
|
+
([ADR 0020](https://github.com/Sandsy09/create-forge/blob/main/docs/adr/0020-engine-path-end-to-end-tests.md)).
|
|
71
|
+
See the canonical
|
|
66
72
|
[end-to-end tests contract](https://github.com/Sandsy09/create-forge/blob/main/docs/end-to-end-tests.md).
|
|
67
73
|
The atomic cutover that replaces both the v0.1.x registry seam and
|
|
68
74
|
`--engine-preview` stays gated on
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Stage 08 — Reference Archetype Validation
|
|
2
|
+
|
|
3
|
+
## Repository ownership
|
|
4
|
+
|
|
5
|
+
### forge-template
|
|
6
|
+
|
|
7
|
+
- ~~**FT-08.01 — Define Library archetype contract**~~ — complete via the
|
|
8
|
+
[canonical contract](https://github.com/Sandsy09/forge-template/blob/main/docs/library-archetype.md),
|
|
9
|
+
[ADR 0031](https://github.com/Sandsy09/forge-template/blob/main/docs/adr/0031-library-archetype-contract.md),
|
|
10
|
+
and [forge-template PR #79](https://github.com/Sandsy09/forge-template/pull/79).
|
|
11
|
+
- ~~**FT-08.02 — Migrate the Library archetype to the composition contract**~~
|
|
12
|
+
— complete via [forge-template PR #81](https://github.com/Sandsy09/forge-template/pull/81).
|
|
13
|
+
- ~~**FT-08.03 — Select and define the second reference archetype contract**~~
|
|
14
|
+
— CLI Application selected via the
|
|
15
|
+
[canonical contract](https://github.com/Sandsy09/forge-template/blob/main/docs/cli-application-archetype.md),
|
|
16
|
+
[ADR 0034](https://github.com/Sandsy09/forge-template/blob/main/docs/adr/0034-select-cli-application-reference-archetype.md),
|
|
17
|
+
and [forge-template PR #82](https://github.com/Sandsy09/forge-template/pull/82).
|
|
18
|
+
- ~~**FT-08.04 — Implement the CLI Application reference archetype**~~ —
|
|
19
|
+
complete via
|
|
20
|
+
[forge-template PR #84](https://github.com/Sandsy09/forge-template/pull/84),
|
|
21
|
+
released at `forge-template` `0.3.0` alongside Library.
|
|
22
|
+
- ~~**FT-08.05 — Run composition architecture review**~~ — complete via the
|
|
23
|
+
[canonical review](https://github.com/Sandsy09/forge-template/blob/main/docs/composition-architecture-review.md),
|
|
24
|
+
[ADR 0037](https://github.com/Sandsy09/forge-template/blob/main/docs/adr/0037-two-archetype-composition-review.md),
|
|
25
|
+
[forge-template PR #87](https://github.com/Sandsy09/forge-template/pull/87),
|
|
26
|
+
and `forge-template 0.3.2`. Its client-finalisation finding is implemented
|
|
27
|
+
by create-forge [ADR 0021](../../../adr/0021-client-finalises-engine-lockfiles.md)
|
|
28
|
+
for the `0.2.1` release.
|
|
29
|
+
|
|
30
|
+
### create-forge
|
|
31
|
+
|
|
32
|
+
- Epic: [CF-EPIC-08 / #39](https://github.com/Sandsy09/create-forge/issues/39)
|
|
33
|
+
- ~~**CF-08.01 — Expose Library archetype through create-forge**~~ — completed before roadmap filing.
|
|
34
|
+
- ~~**CF-08.02 — Expose CLI Application through create-forge**~~ — complete via
|
|
35
|
+
[ADR 0017](https://github.com/Sandsy09/create-forge/blob/main/docs/adr/0017-cli-application-archetype-exposure.md),
|
|
36
|
+
which moves this repository's exact development pin to `forge-template==0.3.0`
|
|
37
|
+
and adds a discovery-driven `--archetype` option and prompt behind
|
|
38
|
+
`--engine-preview`.
|
|
39
|
+
- ~~**CF-08.03 — Run CLI archetype-parity review**~~ — complete via
|
|
40
|
+
[ADR 0019](https://github.com/Sandsy09/create-forge/blob/main/docs/adr/0019-cli-archetype-parity-review.md)
|
|
41
|
+
and [create-forge#52](https://github.com/Sandsy09/create-forge/issues/52).
|
|
42
|
+
The shared ProjectSpec/pipeline path and engine-owned discovery were
|
|
43
|
+
confirmed generic; the one archetype-specific branch found
|
|
44
|
+
(`pipeline._resolved_component_options`) is now gated by a discovered
|
|
45
|
+
descriptor rather than a hardcoded archetype id. The engine path's
|
|
46
|
+
Library-shaped prompt set was recorded, not fixed, and tracked by
|
|
47
|
+
[create-forge#91](https://github.com/Sandsy09/create-forge/issues/91).
|
|
48
|
+
- ~~**CF-08.04 — Extend end-to-end generation to the public engine**~~ —
|
|
49
|
+
complete via
|
|
50
|
+
[ADR 0020](https://github.com/Sandsy09/create-forge/blob/main/docs/adr/0020-engine-path-end-to-end-tests.md)
|
|
51
|
+
and [create-forge#85](https://github.com/Sandsy09/create-forge/issues/85),
|
|
52
|
+
successor to [CF-07.06 / #51](https://github.com/Sandsy09/create-forge/issues/51).
|
|
53
|
+
`tests/test_e2e_engine_generation.py` generates both archetypes through
|
|
54
|
+
`--engine-preview` against the real installed engine, runs each generated
|
|
55
|
+
project's own checks, and proves the released-install compatibility
|
|
56
|
+
boundary (an out-of-range engine installed from a git tag, and no `engine`
|
|
57
|
+
extra at all) writes nothing. This closes
|
|
58
|
+
[CF-EPIC-08 / #39](https://github.com/Sandsy09/create-forge/issues/39).
|
|
59
|
+
|
|
60
|
+
The Library decision records the legacy answer mapping into
|
|
61
|
+
`component_options.library.packaging_mode`; CF-08.02 wires it on the engine
|
|
62
|
+
path via `spec.legacy_library_answers` and
|
|
63
|
+
`engine.map_legacy_library_options`. CLI Application is selected as
|
|
64
|
+
engine-owned ID `cli`, has no component options, and derives its console
|
|
65
|
+
command from `ProjectSpec.project.repository_name`; FT-08.04 implemented it
|
|
66
|
+
and create-forge #10 exposes it, both landing in this stage.
|
|
67
|
+
|
|
68
|
+
All repository-local children are complete. The two-archetype review removed
|
|
69
|
+
archetype assumptions from Foundation without adding inheritance or a shared
|
|
70
|
+
runtime layer, while create-forge now creates the dynamic lockfile in adjacent
|
|
71
|
+
staging before atomic placement. Stage 08 is complete across both
|
|
72
|
+
repositories.
|
|
73
|
+
|
|
74
|
+
## Stage completion rule
|
|
75
|
+
|
|
76
|
+
- [x] Repo-local issues are complete or explicitly deferred.
|
|
77
|
+
- [x] Cross-repository blockers are resolved. -- #9/ADR 0018 was the last one.
|
|
78
|
+
- [x] Public contracts changed by this stage are documented/versioned --
|
|
79
|
+
ADR 0017, ADR 0019, ADR 0020, ADR 0021, and forge-template ADR 0037.
|
|
80
|
+
- [x] No implementation concern is duplicated across repositories -- CF-08.03
|
|
81
|
+
(ADR 0019) confirmed this directly.
|
|
@@ -7,7 +7,7 @@ build-backend = "hatchling.build"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "create-forge"
|
|
10
|
-
version = "0.2.
|
|
10
|
+
version = "0.2.1"
|
|
11
11
|
description = "Scaffold modern Python projects from maintained templates."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -44,7 +44,8 @@ dependencies = [
|
|
|
44
44
|
# downloads an engine it doesn't call. src/create_forge/engine.py is the
|
|
45
45
|
# only module that imports it; src/create_forge/compat.py holds the range
|
|
46
46
|
# itself so cli.py's `doctor` can report it without importing the engine.
|
|
47
|
-
engine
|
|
47
|
+
# ADR 0021 adds bounded uv to finalise the engine render's dynamic lockfile.
|
|
48
|
+
engine = ["forge-template>=0.3.1,<0.4", "uv>=0.12,<0.13"]
|
|
48
49
|
|
|
49
50
|
[project.scripts]
|
|
50
51
|
create-forge = "create_forge.cli:app"
|
|
@@ -350,6 +350,7 @@ def _run_engine_preview(
|
|
|
350
350
|
|
|
351
351
|
def _report_created(project_name: object, dst: Path, *, updatable: bool = True) -> None:
|
|
352
352
|
"""Print the success panel once a project has actually been written."""
|
|
353
|
+
check_command = "uv run poe check" if updatable else "uv run --locked poe check"
|
|
353
354
|
update_line = (
|
|
354
355
|
"[dim]Pull later template changes with: uvx create-forge update[/dim]"
|
|
355
356
|
if updatable
|
|
@@ -360,7 +361,7 @@ def _report_created(project_name: object, dst: Path, *, updatable: bool = True)
|
|
|
360
361
|
Panel(
|
|
361
362
|
f"[bold]{project_name}[/bold] created at [dim]{dst}[/dim]\n\n"
|
|
362
363
|
f" cd {dst.name}\n"
|
|
363
|
-
"
|
|
364
|
+
f" {check_command}\n\n"
|
|
364
365
|
f"{update_line}",
|
|
365
366
|
border_style="green",
|
|
366
367
|
)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This is the one internal generation path CF-07.01 introduces (ADR 0014).
|
|
4
4
|
It depends on `create_forge.engine` -- and therefore, transitively, on the
|
|
5
|
-
|
|
5
|
+
optional `forge-template` engine extra (ADR 0018) -- but its own source never
|
|
6
6
|
imports `forge_template` directly: type annotations that need engine-owned
|
|
7
7
|
types import them only under `TYPE_CHECKING`, so this module's runtime
|
|
8
8
|
behaviour never requires the engine to be *type-checkable*, only to be
|
|
@@ -12,8 +12,8 @@ the only module whose source touches `forge_template` at runtime, per ADR
|
|
|
12
12
|
|
|
13
13
|
`create_forge.cli` imports this module lazily, inside `--engine-preview`'s
|
|
14
14
|
branch only, guarded by `try/except ImportError` -- see ADR 0014 for why:
|
|
15
|
-
`forge-template` is not
|
|
16
|
-
module reachable at `cli.py`'s own import time may depend on it.
|
|
15
|
+
`forge-template` is not installed by a plain `pip install create-forge`, so
|
|
16
|
+
no module reachable at `cli.py`'s own import time may depend on it.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
@@ -57,24 +57,44 @@ def _resolved_component_options(
|
|
|
57
57
|
answers: Mapping[str, object],
|
|
58
58
|
archetype: str,
|
|
59
59
|
component_options: Mapping[str, Mapping[str, object]] | None,
|
|
60
|
+
descriptors: Sequence[ComponentDescriptor],
|
|
60
61
|
) -> Mapping[str, Mapping[str, object]] | None:
|
|
61
62
|
"""Derive `component_options` when the caller supplied none.
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
`library` predates the engine, so its legacy `build_backend`/`versioning`
|
|
65
|
+
answers need translating into the production `packaging_mode` option or a
|
|
66
|
+
user's choice silently reverts to the engine's own default. CF-08.03's
|
|
67
|
+
archetype-parity review (ADR 0019) found this derivation keyed on a
|
|
68
|
+
hardcoded `archetype != "library"` check -- the one archetype-specific
|
|
69
|
+
branch in this codebase -- and generalised it: `engine.map_legacy_library_options`
|
|
70
|
+
already names the option it produces (`packaging_mode`), and the
|
|
71
|
+
selected archetype's own discovered `ComponentDescriptor.options` already
|
|
72
|
+
declares whether it accepts that name. Applying the mapping only when the
|
|
73
|
+
descriptor declares it needs no archetype id anywhere in this function,
|
|
74
|
+
so a future archetype that also wants the legacy mapping (or `library`
|
|
75
|
+
itself being renamed) needs no change here -- only in the engine's own
|
|
76
|
+
manifest.
|
|
77
|
+
|
|
78
|
+
The descriptor lookup runs before the mapping call, not after: an
|
|
79
|
+
archetype that declares no options at all (`cli`) never needs
|
|
80
|
+
`engine.map_legacy_library_options` invoked on its behalf, matching the
|
|
81
|
+
old code's behaviour of never calling it for a non-`library` archetype.
|
|
71
82
|
"""
|
|
72
|
-
if component_options is not None
|
|
83
|
+
if component_options is not None:
|
|
73
84
|
return component_options
|
|
85
|
+
descriptor = next((d for d in descriptors if d.id == archetype), None)
|
|
86
|
+
if descriptor is None or not descriptor.options:
|
|
87
|
+
return None
|
|
74
88
|
legacy = legacy_library_answers(answers)
|
|
75
89
|
if legacy is None:
|
|
76
90
|
return None
|
|
77
|
-
|
|
91
|
+
mapped = engine.map_legacy_library_options(legacy)
|
|
92
|
+
if not mapped:
|
|
93
|
+
return None
|
|
94
|
+
declared = {option.name for option in descriptor.options}
|
|
95
|
+
if not set(mapped) <= declared:
|
|
96
|
+
return None
|
|
97
|
+
return {archetype: mapped}
|
|
78
98
|
|
|
79
99
|
|
|
80
100
|
def build_generation_request(
|
|
@@ -95,19 +115,20 @@ def build_generation_request(
|
|
|
95
115
|
this pipeline mints no component identifiers of its own. An explicit
|
|
96
116
|
`component_options` is likewise passed through unchanged; when the
|
|
97
117
|
caller supplies none, `_resolved_component_options` derives the one
|
|
98
|
-
legacy mapping this repository still owns
|
|
99
|
-
|
|
100
|
-
`
|
|
101
|
-
`
|
|
118
|
+
legacy mapping this repository still owns, gated on the selected
|
|
119
|
+
archetype's own discovered descriptor rather than a hardcoded id
|
|
120
|
+
(CF-08.03, ADR 0019). `discover()`'s result is reused for exactly that
|
|
121
|
+
gate; `discover_archetypes()` is the separate, `kind`-filtered view that
|
|
122
|
+
actually drives selection, from `cli.py`.
|
|
102
123
|
|
|
103
124
|
Every downstream call (`build_project_spec`, `validate`, `render`)
|
|
104
125
|
independently re-checks package/protocol compatibility before doing its
|
|
105
126
|
own work, so there is no side effect -- in-memory or otherwise -- before
|
|
106
127
|
every check has passed.
|
|
107
128
|
"""
|
|
108
|
-
engine.discover()
|
|
129
|
+
descriptors = engine.discover()
|
|
109
130
|
resolved_options = _resolved_component_options(
|
|
110
|
-
answers, archetype, component_options
|
|
131
|
+
answers, archetype, component_options, descriptors
|
|
111
132
|
)
|
|
112
133
|
payload = build_spec_payload(
|
|
113
134
|
answers,
|
|
@@ -123,19 +144,23 @@ def build_generation_request(
|
|
|
123
144
|
|
|
124
145
|
|
|
125
146
|
def finalise_generation_request(request: GenerationRequest, destination: Path) -> None:
|
|
126
|
-
"""Stage and finalise `request`'s rendered files (ADR
|
|
147
|
+
"""Stage, lock, and finalise `request`'s rendered files (ADR 0021).
|
|
127
148
|
|
|
128
149
|
Renders them into a directory adjacent to `destination`, then moves that
|
|
129
|
-
directory into place atomically.
|
|
150
|
+
directory into place atomically. ``uv.lock`` is created after the reviewed
|
|
151
|
+
render is written and before the rename, so lock resolution cannot leave a
|
|
152
|
+
partial destination.
|
|
130
153
|
|
|
131
154
|
`create-forge` does not call `forge_template.validate_rendered_project`
|
|
132
155
|
itself -- `engine.render()` already did, as the last step inside
|
|
133
156
|
`build_generation_request`. Reaching this function at all means that
|
|
134
157
|
validation already passed; this function's only job is the filesystem
|
|
135
|
-
half create-forge owns: staging, target-safety, and an
|
|
158
|
+
half create-forge owns: staging, target-safety, lock finalisation, and an
|
|
159
|
+
atomic rename.
|
|
136
160
|
"""
|
|
137
161
|
with staging.staged(destination) as staging_dir:
|
|
138
162
|
staging.write_files(
|
|
139
163
|
staging_dir,
|
|
140
164
|
((file.target, file.content) for file in request.rendered.files),
|
|
141
165
|
)
|
|
166
|
+
staging.create_uv_lock(staging_dir)
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Deliberately engine-free: nothing here imports `forge_template`, not even
|
|
4
4
|
under `TYPE_CHECKING`. That keeps this module in the wheel and in the fast
|
|
5
|
-
test suite with no `engine`
|
|
6
|
-
both `runner.scaffold()` (Copier writes straight to the destination; this
|
|
5
|
+
test suite with no optional `engine` extra (ADR 0018) installed, and lets it
|
|
6
|
+
serve both `runner.scaffold()` (Copier writes straight to the destination; this
|
|
7
7
|
module only cleans up after a failure) and `pipeline.finalise_generation_request()`
|
|
8
8
|
(the engine path; this module stages and atomically finalises). See
|
|
9
|
-
[ADR 0015](../../docs/adr/0015-staged-filesystem-generation.md)
|
|
9
|
+
[ADR 0015](../../docs/adr/0015-staged-filesystem-generation.md),
|
|
10
|
+
[ADR 0021](../../docs/adr/0021-client-finalises-engine-lockfiles.md), and the
|
|
10
11
|
canonical [filesystem generation contract](../../docs/filesystem-generation.md)
|
|
11
12
|
for why the two paths differ and what each guarantees.
|
|
12
13
|
"""
|
|
@@ -15,6 +16,7 @@ from __future__ import annotations
|
|
|
15
16
|
|
|
16
17
|
import contextlib
|
|
17
18
|
import shutil
|
|
19
|
+
import subprocess
|
|
18
20
|
import tempfile
|
|
19
21
|
import warnings
|
|
20
22
|
from pathlib import Path, PurePosixPath
|
|
@@ -90,6 +92,41 @@ def write_files(root: Path, files: Iterable[tuple[str, bytes]]) -> None:
|
|
|
90
92
|
raise StagingError(msg) from exc
|
|
91
93
|
|
|
92
94
|
|
|
95
|
+
def create_uv_lock(root: Path) -> None:
|
|
96
|
+
"""Resolve the staged project's committed lockfile before finalisation.
|
|
97
|
+
|
|
98
|
+
The engine render is deliberately side-effect free, so ``uv.lock`` is a
|
|
99
|
+
client-owned finalisation artefact. Run uv without a shell and translate
|
|
100
|
+
launch or resolution failures into the staging error surface; the
|
|
101
|
+
surrounding :func:`staged` context then removes the incomplete tree and
|
|
102
|
+
leaves the destination untouched.
|
|
103
|
+
"""
|
|
104
|
+
command = ["uv", "lock", "--directory", str(root)]
|
|
105
|
+
try:
|
|
106
|
+
result = subprocess.run( # noqa: S603 - reviewed fixed executable
|
|
107
|
+
command,
|
|
108
|
+
capture_output=True,
|
|
109
|
+
text=True,
|
|
110
|
+
check=False,
|
|
111
|
+
)
|
|
112
|
+
except FileNotFoundError as exc:
|
|
113
|
+
msg = (
|
|
114
|
+
"could not create uv.lock because the uv executable is unavailable; "
|
|
115
|
+
"install uv>=0.12,<0.13 and retry"
|
|
116
|
+
)
|
|
117
|
+
raise StagingError(msg) from exc
|
|
118
|
+
except OSError as exc:
|
|
119
|
+
msg = f"could not launch uv to create uv.lock: {exc}"
|
|
120
|
+
raise StagingError(msg) from exc
|
|
121
|
+
|
|
122
|
+
if result.returncode != 0:
|
|
123
|
+
msg = (
|
|
124
|
+
f"uv lock failed with exit status {result.returncode}; check the "
|
|
125
|
+
"project's declared dependencies and package-index access, then retry"
|
|
126
|
+
)
|
|
127
|
+
raise StagingError(msg)
|
|
128
|
+
|
|
129
|
+
|
|
93
130
|
def _on_rm_error(func: object, path: str, exc_info: object) -> None:
|
|
94
131
|
"""`shutil.rmtree` error handler: clear read-only and retry once.
|
|
95
132
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Shared pytest configuration for contributor-only test options and fixtures
|
|
2
|
+
shared across the `e2e`-marked suites.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def pytest_addoption(parser: pytest.Parser) -> None:
|
|
14
|
+
"""Register the local forge-template checkout used by drift tests."""
|
|
15
|
+
parser.addoption(
|
|
16
|
+
"--forge-template-root",
|
|
17
|
+
metavar="PATH",
|
|
18
|
+
help=(
|
|
19
|
+
"Read copier.yml from a local forge-template checkout instead of "
|
|
20
|
+
"cloning its latest release tag."
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope="session")
|
|
26
|
+
def create_forge_command() -> str:
|
|
27
|
+
"""Resolve the real console script -- not `python -m create_forge`, and
|
|
28
|
+
not Typer's `CliRunner`. Both would skip the `[project.scripts]` entry
|
|
29
|
+
point users actually invoke. Shared by every `e2e`-marked module that
|
|
30
|
+
drives the real binary: `tests/test_e2e_generation.py` (Copier path) and
|
|
31
|
+
`tests/test_e2e_engine_generation.py` (engine path).
|
|
32
|
+
"""
|
|
33
|
+
found = shutil.which("create-forge")
|
|
34
|
+
if found is None: # pragma: no cover - packaging bug, not a test failure mode
|
|
35
|
+
pytest.fail(
|
|
36
|
+
"the 'create-forge' console script is not on PATH -- "
|
|
37
|
+
"run this suite via `uv run pytest`, which installs it"
|
|
38
|
+
)
|
|
39
|
+
return found
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture(scope="session")
|
|
43
|
+
def e2e_child_env() -> dict[str, str]:
|
|
44
|
+
"""The subprocess environment for a real `create-forge` invocation.
|
|
45
|
+
|
|
46
|
+
Both e2e paths spawn a child `uv` of their own -- the Copier path's
|
|
47
|
+
`copier.yml` `_tasks` run `uv sync --all-groups` and `git commit` inside
|
|
48
|
+
the generated project; the engine path's tests run the generated
|
|
49
|
+
project's own `uv run poe check` directly. Two things must hold for that
|
|
50
|
+
child `uv` to target the generated project rather than this repository's
|
|
51
|
+
own environment: `VIRTUAL_ENV`, `UV_PROJECT_ENVIRONMENT`, `PYTHONHOME`,
|
|
52
|
+
and `PYTHONPATH` -- all set by `uv run` for *this* pytest process -- must
|
|
53
|
+
not leak into the child. And `git commit` (the Copier path only) needs an
|
|
54
|
+
identity CI does not have, which a plain `env=` argument supplies for the
|
|
55
|
+
whole subprocess tree with no plumbum-snapshot caveat (contrast
|
|
56
|
+
`tests/test_update_network.py`'s `_git_identity_for_template_tasks`
|
|
57
|
+
fixture, needed only because that test calls `scaffold()` in-process).
|
|
58
|
+
"""
|
|
59
|
+
env = dict(os.environ)
|
|
60
|
+
for leak in ("VIRTUAL_ENV", "UV_PROJECT_ENVIRONMENT", "PYTHONHOME", "PYTHONPATH"):
|
|
61
|
+
env.pop(leak, None)
|
|
62
|
+
env.update(
|
|
63
|
+
{
|
|
64
|
+
"GIT_AUTHOR_NAME": "create-forge e2e",
|
|
65
|
+
"GIT_AUTHOR_EMAIL": "create-forge-e2e@example.invalid",
|
|
66
|
+
"GIT_COMMITTER_NAME": "create-forge e2e",
|
|
67
|
+
"GIT_COMMITTER_EMAIL": "create-forge-e2e@example.invalid",
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
return env
|