create-forge 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,180 @@
1
+ """Filesystem orchestration shared by the Copier and engine generation paths.
2
+
3
+ Deliberately engine-free: nothing here imports `forge_template`, not even
4
+ under `TYPE_CHECKING`. That keeps this module in the wheel and in the fast
5
+ test suite with no `engine` dependency group installed, and lets it serve
6
+ both `runner.scaffold()` (Copier writes straight to the destination; this
7
+ module only cleans up after a failure) and `pipeline.finalise_generation_request()`
8
+ (the engine path; this module stages and atomically finalises). See
9
+ [ADR 0015](../../docs/adr/0015-staged-filesystem-generation.md) and the
10
+ canonical [filesystem generation contract](../../docs/filesystem-generation.md)
11
+ for why the two paths differ and what each guarantees.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import contextlib
17
+ import shutil
18
+ import tempfile
19
+ import warnings
20
+ from pathlib import Path, PurePosixPath
21
+ from typing import TYPE_CHECKING
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Iterable, Iterator
25
+
26
+ _STAGING_PREFIX = ".create-forge-"
27
+
28
+
29
+ class StagingError(Exception):
30
+ """A filesystem operation failed in a way the user can act on."""
31
+
32
+
33
+ class DestinationConflictError(StagingError):
34
+ """The destination already exists and is not empty."""
35
+
36
+
37
+ def ensure_available(dst: Path) -> None:
38
+ """Reject a destination that already exists and has content.
39
+
40
+ Cheap and side-effect free -- callers run this before any compatibility
41
+ check or write, so an obvious conflict is reported before anything else
42
+ is attempted.
43
+ """
44
+ if dst.exists() and any(dst.iterdir()):
45
+ msg = f"{dst} already exists and is not empty"
46
+ raise DestinationConflictError(msg)
47
+
48
+
49
+ def _safe_relative_path(root: Path, target: str) -> Path:
50
+ """Resolve one project-relative target under `root`, refusing escapes.
51
+
52
+ Targets are engine-owned strings using forward slashes (`RenderedFile`
53
+ documents them as project-relative). `PurePosixPath` parses them
54
+ platform-independently before `Path` joins them, so a target containing
55
+ backslashes is treated as a literal filename component, never as a
56
+ Windows separator.
57
+ """
58
+ posix_target = PurePosixPath(target)
59
+ if posix_target.is_absolute() or posix_target.drive:
60
+ msg = f"refusing to write outside the staging directory: {target!r}"
61
+ raise StagingError(msg)
62
+ if ".." in posix_target.parts:
63
+ msg = f"refusing to write outside the staging directory: {target!r}"
64
+ raise StagingError(msg)
65
+
66
+ resolved_root = root.resolve()
67
+ destination = (root / Path(*posix_target.parts)).resolve()
68
+ if destination != resolved_root and resolved_root not in destination.parents:
69
+ msg = f"refusing to write outside the staging directory: {target!r}"
70
+ raise StagingError(msg)
71
+ return destination
72
+
73
+
74
+ def write_files(root: Path, files: Iterable[tuple[str, bytes]]) -> None:
75
+ """Write each (target, content) pair under `root`, creating parents.
76
+
77
+ Every target is validated before anything is written -- an absolute
78
+ path, a drive-qualified path, or a `..` segment anywhere in a single
79
+ target aborts the whole call with nothing written by it.
80
+ """
81
+ resolved: list[tuple[Path, bytes]] = [
82
+ (_safe_relative_path(root, target), content) for target, content in files
83
+ ]
84
+ for path, content in resolved:
85
+ path.parent.mkdir(parents=True, exist_ok=True)
86
+ try:
87
+ path.write_bytes(content)
88
+ except OSError as exc:
89
+ msg = f"could not write {path}: {exc}"
90
+ raise StagingError(msg) from exc
91
+
92
+
93
+ def _on_rm_error(func: object, path: str, exc_info: object) -> None:
94
+ """`shutil.rmtree` error handler: clear read-only and retry once.
95
+
96
+ Accepted as both 3.12+'s `onexc` and 3.11's `onerror` -- both call the
97
+ handler with three positional arguments; the third (`exc_info`) is
98
+ unused by either signature this implements.
99
+ """
100
+ del exc_info
101
+ target = Path(path)
102
+ target.chmod(0o700)
103
+ if callable(func):
104
+ func(path)
105
+
106
+
107
+ def _remove_tree(path: Path) -> None:
108
+ """Best-effort recursive removal that clears read-only files first.
109
+
110
+ Never raises: a failed cleanup must not mask the original error that
111
+ triggered it, so a residual directory is reported as a warning instead.
112
+ """
113
+ if not path.exists():
114
+ return
115
+ try:
116
+ try:
117
+ # `onexc` only exists from Python 3.12; mypy is pinned to the 3.11
118
+ # stub (pyproject.toml's python_version), so this call needs an
119
+ # explicit ignore regardless of the interpreter mypy itself runs
120
+ # under.
121
+ shutil.rmtree(path, onexc=_on_rm_error) # type: ignore[call-arg]
122
+ except TypeError:
123
+ shutil.rmtree(path, onerror=_on_rm_error)
124
+ except OSError as exc:
125
+ warnings.warn(f"could not remove {path}: {exc}", RuntimeWarning, stacklevel=2)
126
+
127
+
128
+ @contextlib.contextmanager
129
+ def staged(dst: Path) -> Iterator[Path]:
130
+ """Yield a staging directory adjacent to `dst`; finalise by atomic rename.
131
+
132
+ The staging directory is created next to `dst` with `tempfile.mkdtemp`,
133
+ not under the system temp directory -- same-volume placement is what
134
+ makes the finalising `Path.rename` an atomic directory rename on both
135
+ NTFS and POSIX rather than a copy. There is deliberately no cross-volume
136
+ copy fallback: that would silently trade the atomicity guarantee for
137
+ availability, so a cross-volume destination fails instead.
138
+
139
+ On success, an existing *empty* `dst` is removed first -- `os.rename`
140
+ will not replace a directory on Windows. On any exception, the staging
141
+ tree is removed and the original error propagates; `dst` is left exactly
142
+ as it was found.
143
+ """
144
+ ensure_available(dst)
145
+ dst.parent.mkdir(parents=True, exist_ok=True)
146
+ staging_dir = Path(tempfile.mkdtemp(prefix=_STAGING_PREFIX, dir=dst.parent))
147
+
148
+ try:
149
+ yield staging_dir
150
+ if dst.exists():
151
+ dst.rmdir()
152
+ try:
153
+ staging_dir.rename(dst)
154
+ except OSError as exc:
155
+ msg = f"could not move {staging_dir} into place at {dst}: {exc}"
156
+ raise StagingError(msg) from exc
157
+ except BaseException:
158
+ _remove_tree(staging_dir)
159
+ raise
160
+
161
+
162
+ @contextlib.contextmanager
163
+ def discard_on_failure(dst: Path) -> Iterator[None]:
164
+ """Remove `dst` on failure, but only if this call is what created it.
165
+
166
+ Used around the Copier path, which writes straight to `dst` and cannot
167
+ safely be staged: templates run `_tasks` (`uv sync`, `pre-commit
168
+ install`) that bake `dst`'s absolute path into `.venv/pyvenv.cfg`,
169
+ console-script shims, and `.git/hooks/pre-commit`. Renaming a completed
170
+ Copier output afterward would silently break all three, so this context
171
+ manager only ever cleans up a failure at the path Copier already used --
172
+ it never stages or moves anything.
173
+ """
174
+ pre_existing = dst.exists()
175
+ try:
176
+ yield
177
+ except BaseException:
178
+ if not pre_existing:
179
+ _remove_tree(dst)
180
+ raise
@@ -0,0 +1,121 @@
1
+ # Bundled template registry.
2
+ #
3
+ # Adding a template here requires a new CLI release. Validated at import time by
4
+ # the Pydantic models in models.py, and in CI by tests/test_registry.py.
5
+ #
6
+ # Every `key` below must match a question in forge-template's copier.yml
7
+ # (invariant 1 in CLAUDE.md — Copier silently drops unknown `data` keys, so a
8
+ # typo here produces a scaffold that looks fine and is subtly wrong). Audited
9
+ # against forge-template@v0.1.1. Questions deliberately left unasked and
10
+ # falling through to their copier.yml default via runner.py's `defaults=True`:
11
+ # package_name, repo_name, repo_url, author_name, author_email,
12
+ # codeowners_team, python_all, python_version, python_min_version,
13
+ # python_matrix, versioning_resolved, initial_version, coverage_fail_under,
14
+ # dependency_updates, changelog_tool. All of them have a default, so this is
15
+ # safe — a test guarding it is tracked as a follow-up issue.
16
+
17
+ default_template = "library"
18
+
19
+ [[templates]]
20
+ id = "library"
21
+ name = "Library"
22
+ description = "An installable Python package. src layout, no service scaffolding."
23
+ url = "https://github.com/Sandsy09/forge-template"
24
+ status = "stable"
25
+
26
+ # ---- identity -------------------------------------------------------------
27
+
28
+ [[templates.prompts]]
29
+ key = "project_name"
30
+ kind = "text"
31
+ message = "Project name"
32
+ help = "Human readable, e.g. 'Credit Risk Utils'"
33
+
34
+ [[templates.prompts]]
35
+ key = "project_description"
36
+ kind = "text"
37
+ message = "Short description"
38
+
39
+ [[templates.prompts]]
40
+ key = "github_org"
41
+ kind = "text"
42
+ message = "GitHub organisation"
43
+ help = "Pre-filled from your forge config if set"
44
+
45
+ [[templates.prompts]]
46
+ key = "license"
47
+ kind = "select"
48
+ message = "License"
49
+
50
+ [[templates.prompts.choices]]
51
+ value = "proprietary"
52
+ label = "Proprietary"
53
+ hint = "internal use only"
54
+
55
+ [[templates.prompts.choices]]
56
+ value = "mit"
57
+ label = "MIT"
58
+
59
+ [[templates.prompts.choices]]
60
+ value = "apache-2.0"
61
+ label = "Apache-2.0"
62
+
63
+ # ---- the one question that constrains another -----------------------------
64
+
65
+ [[templates.prompts]]
66
+ key = "build_backend"
67
+ kind = "select"
68
+ message = "Build backend"
69
+
70
+ [[templates.prompts.choices]]
71
+ value = "uv_build"
72
+ label = "uv_build"
73
+ hint = "simplest; static version only"
74
+
75
+ [[templates.prompts.choices]]
76
+ value = "hatchling"
77
+ label = "Hatchling"
78
+ hint = "required for git-tag versioning"
79
+
80
+ [[templates.prompts]]
81
+ key = "versioning"
82
+ kind = "select"
83
+ message = "Version source"
84
+ depends_on = { build_backend = "hatchling" }
85
+
86
+ [[templates.prompts.choices]]
87
+ value = "static"
88
+ label = "Static in pyproject.toml"
89
+ hint = "explicit; uv version works"
90
+
91
+ [[templates.prompts.choices]]
92
+ value = "vcs"
93
+ label = "Git tags (hatch-vcs)"
94
+ hint = "tag and version cannot disagree"
95
+
96
+ # ---- tooling --------------------------------------------------------------
97
+
98
+ [[templates.prompts]]
99
+ key = "type_checking"
100
+ kind = "select"
101
+ message = "Type checker"
102
+
103
+ [[templates.prompts.choices]]
104
+ value = "mypy"
105
+ label = "mypy"
106
+ hint = "recommended default"
107
+
108
+ [[templates.prompts.choices]]
109
+ value = "pyright"
110
+ label = "pyright"
111
+
112
+ [[templates.prompts.choices]]
113
+ value = "both"
114
+ label = "Both"
115
+ hint = "strictest; two ignore dialects to manage"
116
+
117
+ [[templates.prompts]]
118
+ key = "use_docs"
119
+ kind = "confirm"
120
+ message = "Include a MkDocs documentation site?"
121
+ default = false
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.5
2
+ Name: create-forge
3
+ Version: 0.2.0
4
+ Summary: Scaffold modern Python projects from maintained templates.
5
+ Project-URL: Homepage, https://github.com/Sandsy09/create-forge
6
+ Project-URL: Repository, https://github.com/Sandsy09/create-forge
7
+ Project-URL: Issues, https://github.com/Sandsy09/create-forge/issues
8
+ Project-URL: Changelog, https://github.com/Sandsy09/create-forge/blob/main/CHANGELOG.md
9
+ Author: Alex
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: cookiecutter,copier,project,scaffold,template
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development :: Code Generators
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: copier<10,>=9.4
24
+ Requires-Dist: pydantic>=2.10
25
+ Requires-Dist: questionary>=2.0
26
+ Requires-Dist: rich>=13.9
27
+ Requires-Dist: typer>=0.15
28
+ Provides-Extra: engine
29
+ Requires-Dist: forge-template<0.4,>=0.3.1; extra == 'engine'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # create-forge
33
+
34
+ [![CI](https://github.com/Sandsy09/create-forge/actions/workflows/ci.yml/badge.svg)](https://github.com/Sandsy09/create-forge/actions/workflows/ci.yml)
35
+
36
+ Scaffold modern Python projects from maintained templates — and pull template
37
+ improvements back into projects you generated months ago.
38
+
39
+ ```bash
40
+ uvx create-forge new
41
+ ```
42
+
43
+ No install step. Requires [uv](https://docs.astral.sh/uv/) and git.
44
+
45
+ ## Why
46
+
47
+ Most project generators are fire-and-forget: you scaffold once, and from that
48
+ moment your project drifts away from the template. Six months later the template
49
+ has better lint rules, a security fix in CI, and a newer toolchain — and no path
50
+ to get any of it into projects already in the wild.
51
+
52
+ create-forge is built on [Copier](https://copier.readthedocs.io/), which does a
53
+ three-way merge between the template version your project was generated from and
54
+ the latest one. Local edits survive; template changes arrive.
55
+
56
+ ```bash
57
+ uvx create-forge update
58
+ ```
59
+
60
+ ## What you get
61
+
62
+ Every generated project ships with:
63
+
64
+ - **[uv](https://docs.astral.sh/uv/)** for packaging and dependency management
65
+ - **[Ruff](https://docs.astral.sh/ruff/)** for linting and formatting
66
+ - **mypy** or **pyright** (or both) for type checking
67
+ - **pytest** with coverage
68
+ - **pre-commit** hooks, including Conventional Commits enforcement
69
+ - **GitHub Actions** CI, with a test matrix across your supported Python versions
70
+ - **Renovate** or **Dependabot** for dependency updates
71
+ - `README`, `CONTRIBUTING`, `SECURITY`, `CHANGELOG`, issue and PR templates
72
+ - Optionally: a MkDocs documentation site and ADR scaffolding
73
+
74
+ Choices you make at scaffold time — build backend, versioning strategy, license,
75
+ type checker — are remembered, so updates respect them.
76
+
77
+ ## Usage
78
+
79
+ ```bash
80
+ # Interactive
81
+ uvx create-forge new
82
+
83
+ # Named up front
84
+ uvx create-forge new "Credit Risk Utils"
85
+
86
+ # Non-interactive, for scripts and CI
87
+ uvx create-forge new "My Lib" --yes \
88
+ --data build_backend=hatchling \
89
+ --data versioning=vcs \
90
+ --data type_checking=both
91
+ ```
92
+
93
+ | Command | What it does |
94
+ | --- | --- |
95
+ | `new` | Create a project |
96
+ | `list` | Show available templates |
97
+ | `update` | Pull template changes into an existing project |
98
+ | `doctor` | Check your environment can scaffold and update |
99
+ | `config` | Inspect or initialise your saved configuration |
100
+
101
+ Useful flags on `new`: `--template/-t`, `--path/-p`, `--data/-d`, `--yes/-y`,
102
+ `--ref`, `--dry-run`.
103
+
104
+ ## Configuration
105
+
106
+ Optional. Saves retyping the same answers:
107
+
108
+ ```toml
109
+ # ~/.config/create-forge/config.toml
110
+ author_name = "Your Name"
111
+ author_email = "you@example.com"
112
+ github_org = "your-org"
113
+ default_template = "library"
114
+ ```
115
+
116
+ `create-forge config init` writes a commented starter file at that path
117
+ without overwriting one that already exists. `create-forge config show`
118
+ prints the resolved values and where each came from.
119
+
120
+ `github_org` pre-fills its prompt — you're still asked, just with the answer
121
+ already typed in. `author_name` and `author_email` aren't prompted for at all,
122
+ so a configured value is applied directly. `default_template` picks which
123
+ template `new` offers first, interactively or under `--yes`.
124
+
125
+ Every key can be overridden with an environment variable —
126
+ `FORGE_GITHUB_ORG` and so on — or a command line flag. Precedence is
127
+ config < environment < `--data` < an interactive answer.
128
+
129
+ ## Templates
130
+
131
+ Run `create-forge list` for what your installed version offers. The registry is
132
+ bundled with each release, so new templates arrive when you update the tool.
133
+
134
+ To use your own template:
135
+
136
+ ```bash
137
+ uvx create-forge new --template-url https://github.com/you/your-template
138
+ ```
139
+
140
+ This describes the released v0.1.x architecture. Forge has accepted a future
141
+ [public-engine integration contract](docs/integration-contract.md) in which a
142
+ versioned `forge-template` package owns discovery and rendering. Its strict
143
+ [ProjectSpec protocol v1](https://github.com/Sandsy09/forge-template/blob/main/docs/project-spec.md)
144
+ and [component manifest protocol v1](https://github.com/Sandsy09/forge-template/blob/main/docs/component-manifests.md)
145
+ are now implemented behind the canonical
146
+ [stable template-engine API](https://github.com/Sandsy09/forge-template/blob/main/docs/template-engine-api.md)
147
+ ([ADR 0029](https://github.com/Sandsy09/forge-template/blob/main/docs/adr/0029-stable-template-engine-api.md)).
148
+ The accepted
149
+ [Library archetype contract](https://github.com/Sandsy09/forge-template/blob/main/docs/library-archetype.md)
150
+ defines the first production component, implemented on `forge-template/main`
151
+ and released at `0.3.0`. The accepted
152
+ [CLI Application archetype contract](https://github.com/Sandsy09/forge-template/blob/main/docs/cli-application-archetype.md)
153
+ selects the optionless engine-owned `cli` archetype and derives its console
154
+ command from `ProjectSpec.project.repository_name`;
155
+ [FT-08.04](https://github.com/Sandsy09/forge-template/issues/4) implemented
156
+ it in the same `0.3.0` release, and
157
+ [CF-08.02](https://github.com/Sandsy09/create-forge/issues/10) exposes both
158
+ archetypes behind the hidden `new --engine-preview` flag's `--archetype`
159
+ option. Neither change alters this CLI's default `new` answers, registry, or
160
+ released dependency surface.
161
+ The engine now also defines in-memory
162
+ [generated-project validation](https://github.com/Sandsy09/forge-template/blob/main/docs/generated-project-validation.md)
163
+ ([ADR 0030](https://github.com/Sandsy09/forge-template/blob/main/docs/adr/0030-generated-project-validation.md))
164
+ before rendered output is returned; `render_project` already calls it before
165
+ `--engine-preview` receives a result.
166
+ This repository now depends on a real, released `forge-template` range —
167
+ `>=0.3.1,<0.4`, published to PyPI as the optional `engine` extra
168
+ (`pip install 'create-forge[engine]'`; [#9](https://github.com/Sandsy09/create-forge/issues/9),
169
+ [ADR 0018](docs/adr/0018-pypi-distribution-and-the-first-engine-range.md)) —
170
+ rather than a development-only pin. That range is reachable only behind
171
+ `--engine-preview`; the current registry and `--template-url` behaviour
172
+ remain unchanged until the complete, tested cutover is released — at which
173
+ point `--engine-source`/`--engine-ref` (see the
174
+ [engine resolution contract](docs/engine-resolution.md)) take over this role,
175
+ not `--template-url`.
176
+
177
+ ## Security
178
+
179
+ **create-forge executes code from the template it clones.** Copier templates can
180
+ declare post-generation tasks, and this tool runs them — that is how a generated
181
+ project arrives already git-initialised with hooks installed.
182
+
183
+ The template addresses are compiled into each release rather than fetched at
184
+ runtime or read from user configuration, so the only code trusted by default is
185
+ code published alongside the tool. `--template-url` bypasses that, and prompts
186
+ for confirmation before doing so. Point it only at repositories you trust.
187
+
188
+ Report vulnerabilities per [SECURITY.md](SECURITY.md) rather than in a public
189
+ issue.
190
+
191
+ ## Using this at work
192
+
193
+ In v0.1.x, organisations needing custom executable templates can fork this
194
+ repository, point the bundled registry at their own templates, and maintain it
195
+ internally. The accepted target makes a downstream client of the
196
+ `forge-template` public engine the preferred route for organisation defaults
197
+ and constraints. Forks remain appropriate for genuinely custom executable
198
+ template content; see the [integration contract](docs/integration-contract.md).
199
+
200
+ ## Contributing
201
+
202
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Issues and pull requests welcome.
203
+ Significant design decisions are recorded in [docs/adr/](docs/adr/).
204
+
205
+ ## License
206
+
207
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,18 @@
1
+ create_forge/__init__.py,sha256=2-hcPocqdDwT8xkrvZvaPhX8sCqs-lRWxW2KfRfTOjs,65
2
+ create_forge/cli.py,sha256=naFa_KDve7K0qF-bZ3RmiWbjw0uoWddoP8bJk0URE74,26882
3
+ create_forge/compat.py,sha256=CoeKBnouR4DeXSCGwL3hnf3BExnVLvc0OGtw0_xbnUY,2127
4
+ create_forge/config.py,sha256=D0C4hMBiUKN-AORhWkXM7U7ldwJcd4WWD06hdY4WV6w,4017
5
+ create_forge/engine.py,sha256=SX2bYqySTiiDLMzZSOyz6Qjh3EHDcXERTpNPZEi-bco,8953
6
+ create_forge/models.py,sha256=uBx5yimdIErNbX6mwn_0JSy-LS3b5_YBCfDjN22pPb4,4949
7
+ create_forge/pipeline.py,sha256=CfhsjvYicmUOHRIRrenionx6-vqDrEPP7XUeBAhntq0,5930
8
+ create_forge/prompts.py,sha256=PjPXNieMQssOFhshHXf78bG6S29lSF-wHLrAQrVK-Qo,6898
9
+ create_forge/registry.py,sha256=a3K0MWizORB0ghpjhBYlOCn_1kScglJYsJcKRQfWTlo,1144
10
+ create_forge/runner.py,sha256=KvvgxMVWcBOyXU4B0ZYKsMRJ_g5DbYjUhLds3vEz-kI,5572
11
+ create_forge/spec.py,sha256=lhAadGUMdp6mqdLh_ZeyWSqj8Hwla_OkYfRwPsPxP9Y,7620
12
+ create_forge/staging.py,sha256=7PDW_k8J6OY6O9Tpp2ujp2R8n7IJY1p1pgx1yYYAwj4,7014
13
+ create_forge/templates.toml,sha256=xjvgPGVGW9Q5td5GkbcI0Ake2oSetEA_S9r8BFwhdGY,3433
14
+ create_forge-0.2.0.dist-info/METADATA,sha256=6NTcfq__WoaJtxWZpXJ9hW63RiwY7cK4kGJD7nopcpY,8814
15
+ create_forge-0.2.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
16
+ create_forge-0.2.0.dist-info/entry_points.txt,sha256=KG4OcDHGWvh9JZKoeDJL3WcTlTN8Nj3q0Huq2WLrt7M,54
17
+ create_forge-0.2.0.dist-info/licenses/LICENSE,sha256=PuaS9iQNMxQolcG-Im2lGUD5wzytHvnOcmvhceUau-o,1067
18
+ create_forge-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ create-forge = create_forge.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Sands
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.