freepod 0.1.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.
@@ -0,0 +1,53 @@
1
+ # Python
2
+ __pycache__/**
3
+ *.py[cod]
4
+ *.pyc
5
+ *.pyo
6
+ *.pyd
7
+ *.so
8
+ *.egg-info/
9
+ build/
10
+ dist/
11
+ .eggs/
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .mypy_cache/
15
+ .coverage
16
+ htmlcov/
17
+ coverage.xml
18
+ pytest.xml
19
+
20
+ # Virtualenv
21
+ .venv/
22
+ venv/
23
+
24
+ # Local env overrides (may contain secrets)
25
+ .env
26
+ .env.local
27
+
28
+ # SQLite
29
+ *.db
30
+ *.sqlite3
31
+
32
+
33
+ # IDE
34
+ .idea/
35
+ .vscode/
36
+
37
+ # OS
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ var/
42
+
43
+ # Playwright MCP artifacts
44
+ .playwright-mcp/
45
+
46
+ .claude/worktrees
47
+
48
+ # OpenCode tooling artifacts
49
+ .opencode/node_modules/
50
+ .opencode/package-lock.json
51
+ .opencode/bun.lock
52
+
53
+ api/static/
freepod-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright 2026 Erik van Zijst <erik.van.zijst@gmail.com>
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.
freepod-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.5
2
+ Name: freepod
3
+ Version: 0.1.1
4
+ Summary: Command-line client for Freepod — take a local project directory to a running deployment on freepod.eu.
5
+ Project-URL: Homepage, https://freepod.eu
6
+ Project-URL: Repository, https://github.com/erikvanzijst/freepod
7
+ Project-URL: Issues, https://github.com/erikvanzijst/freepod/issues
8
+ Author-email: Freepod <erik@freepod.eu>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,deployment,freepod,paas
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Build Tools
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: click>=8.0
25
+ Requires-Dist: httpx>=0.24
26
+ Requires-Dist: pathspec>=1.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # freepod
30
+
31
+ Take a local project directory to a running deployment on
32
+ [freepod.eu](https://freepod.eu).
33
+
34
+ ```bash
35
+ pip install freepod
36
+ ```
37
+
38
+ ```bash
39
+ cd myapp
40
+ freepod login # sign in through the browser
41
+ freepod init # choose a hostname for this project
42
+ freepod deploy # https://myapp.freepod.eu
43
+ ```
44
+
45
+ `deploy` packs the working tree, uploads it, builds a container image on the
46
+ platform and releases it. By the time the command returns, the app is live on
47
+ its own hostname over HTTPS. Ship a new version by running it again.
48
+
49
+ There is no Dockerfile to write and nothing to configure:
50
+ [Railpack](https://railpack.com) detects the stack — Node, Python, Go, Java,
51
+ PHP, Ruby, Rust and more — and builds an image from your source as it is.
52
+
53
+ ## What you need
54
+
55
+ - **A Freepod account.** `freepod login` opens the sign-in page, which is also
56
+ where you can register.
57
+ - **Python 3.9 or newer**, for the CLI itself. The project you deploy can be in
58
+ any language Railpack supports.
59
+ - **An app that listens on `$PORT`.** The platform assigns the port and passes
60
+ it in the environment, so bind `0.0.0.0:$PORT` (`process.env.PORT`,
61
+ `os.environ["PORT"]`, …) rather than a fixed number. An app that picks its own
62
+ port receives no traffic.
63
+
64
+ Deployments have no persistent disk. Whatever the app writes to its own
65
+ filesystem is gone when it restarts, and at every release.
66
+
67
+ ## Commands
68
+
69
+ | Command | What it does |
70
+ |------------------|------------------------------------------------------------------------------------------|
71
+ | `freepod login` | Sign in, and remember the credential. |
72
+ | `freepod init` | Set the current directory up as a project: choose a hostname, write `.freepod.json`. |
73
+ | `freepod deploy` | Pack, build and release the current project. |
74
+ | `freepod whoami` | Report who you are signed in as. |
75
+ | `freepod logout` | Forget the stored credential. |
76
+
77
+ `freepod --help` and `freepod <command> --help` cover the flags.
78
+
79
+ ## Hostnames
80
+
81
+ `init` asks for one. A bare name becomes a subdomain of the platform: `myapp` is
82
+ served at `myapp.freepod.eu`. To use a domain of your own, point a CNAME at
83
+ `freepod.eu` first, then give `init` the full name. Certificates are issued and
84
+ renewed for you either way.
85
+
86
+ ## .freepod.json
87
+
88
+ `init` writes it, and it is meant to be committed. It records the hostname you
89
+ chose and which deployment this directory belongs to, so the next `deploy` —
90
+ yours, a coworker's, or CI's — updates that same app instead of creating a
91
+ second one. To change a value, edit the file; the next deploy applies it.
92
+
93
+ ## What gets uploaded
94
+
95
+ The working tree, minus `.git/`, anything your `.gitignore` excludes, and the
96
+ usual build output (`node_modules/`, `.venv/`, `__pycache__/`, `dist/`, …). A
97
+ `.freepodignore` file — same syntax as `.gitignore`, and applied last — keeps
98
+ out anything else the build does not need.
99
+
100
+ Re-including a file follows git's rules, with one wrinkle worth knowing: the
101
+ negation has to name the path (`!node_modules/patched.js`; a bare
102
+ `!patched.js` does nothing), and nothing beneath a directory you excluded
103
+ yourself can come back. Exclude `build/*` rather than `build/` if you need an
104
+ exception.
105
+
106
+ ## Output
107
+
108
+ The address is the only thing `deploy` writes to stdout. The build log, the
109
+ progress bar and every status line go to stderr:
110
+
111
+ ```bash
112
+ URL=$(freepod deploy)
113
+ ```
114
+
115
+ ---
116
+
117
+ Deployments are listed and managed at [freepod.eu](https://freepod.eu). Source
118
+ and issues: [github.com/erikvanzijst/freepod](https://github.com/erikvanzijst/freepod).
@@ -0,0 +1,90 @@
1
+ # freepod
2
+
3
+ Take a local project directory to a running deployment on
4
+ [freepod.eu](https://freepod.eu).
5
+
6
+ ```bash
7
+ pip install freepod
8
+ ```
9
+
10
+ ```bash
11
+ cd myapp
12
+ freepod login # sign in through the browser
13
+ freepod init # choose a hostname for this project
14
+ freepod deploy # https://myapp.freepod.eu
15
+ ```
16
+
17
+ `deploy` packs the working tree, uploads it, builds a container image on the
18
+ platform and releases it. By the time the command returns, the app is live on
19
+ its own hostname over HTTPS. Ship a new version by running it again.
20
+
21
+ There is no Dockerfile to write and nothing to configure:
22
+ [Railpack](https://railpack.com) detects the stack — Node, Python, Go, Java,
23
+ PHP, Ruby, Rust and more — and builds an image from your source as it is.
24
+
25
+ ## What you need
26
+
27
+ - **A Freepod account.** `freepod login` opens the sign-in page, which is also
28
+ where you can register.
29
+ - **Python 3.9 or newer**, for the CLI itself. The project you deploy can be in
30
+ any language Railpack supports.
31
+ - **An app that listens on `$PORT`.** The platform assigns the port and passes
32
+ it in the environment, so bind `0.0.0.0:$PORT` (`process.env.PORT`,
33
+ `os.environ["PORT"]`, …) rather than a fixed number. An app that picks its own
34
+ port receives no traffic.
35
+
36
+ Deployments have no persistent disk. Whatever the app writes to its own
37
+ filesystem is gone when it restarts, and at every release.
38
+
39
+ ## Commands
40
+
41
+ | Command | What it does |
42
+ |------------------|------------------------------------------------------------------------------------------|
43
+ | `freepod login` | Sign in, and remember the credential. |
44
+ | `freepod init` | Set the current directory up as a project: choose a hostname, write `.freepod.json`. |
45
+ | `freepod deploy` | Pack, build and release the current project. |
46
+ | `freepod whoami` | Report who you are signed in as. |
47
+ | `freepod logout` | Forget the stored credential. |
48
+
49
+ `freepod --help` and `freepod <command> --help` cover the flags.
50
+
51
+ ## Hostnames
52
+
53
+ `init` asks for one. A bare name becomes a subdomain of the platform: `myapp` is
54
+ served at `myapp.freepod.eu`. To use a domain of your own, point a CNAME at
55
+ `freepod.eu` first, then give `init` the full name. Certificates are issued and
56
+ renewed for you either way.
57
+
58
+ ## .freepod.json
59
+
60
+ `init` writes it, and it is meant to be committed. It records the hostname you
61
+ chose and which deployment this directory belongs to, so the next `deploy` —
62
+ yours, a coworker's, or CI's — updates that same app instead of creating a
63
+ second one. To change a value, edit the file; the next deploy applies it.
64
+
65
+ ## What gets uploaded
66
+
67
+ The working tree, minus `.git/`, anything your `.gitignore` excludes, and the
68
+ usual build output (`node_modules/`, `.venv/`, `__pycache__/`, `dist/`, …). A
69
+ `.freepodignore` file — same syntax as `.gitignore`, and applied last — keeps
70
+ out anything else the build does not need.
71
+
72
+ Re-including a file follows git's rules, with one wrinkle worth knowing: the
73
+ negation has to name the path (`!node_modules/patched.js`; a bare
74
+ `!patched.js` does nothing), and nothing beneath a directory you excluded
75
+ yourself can come back. Exclude `build/*` rather than `build/` if you need an
76
+ exception.
77
+
78
+ ## Output
79
+
80
+ The address is the only thing `deploy` writes to stdout. The build log, the
81
+ progress bar and every status line go to stderr:
82
+
83
+ ```bash
84
+ URL=$(freepod deploy)
85
+ ```
86
+
87
+ ---
88
+
89
+ Deployments are listed and managed at [freepod.eu](https://freepod.eu). Source
90
+ and issues: [github.com/erikvanzijst/freepod](https://github.com/erikvanzijst/freepod).
@@ -0,0 +1,64 @@
1
+ [build-system]
2
+ # A floor rather than an open range: 1.27 is the first release that understands
3
+ # PEP 639, which the `license`/`license-files` fields below depend on.
4
+ requires = ["hatchling>=1.27"]
5
+ build-backend = "hatchling.build"
6
+
7
+ [project]
8
+ name = "freepod"
9
+ dynamic = ["version"]
10
+ description = "Command-line client for Freepod — take a local project directory to a running deployment on freepod.eu."
11
+ readme = "README.md"
12
+ requires-python = ">=3.9"
13
+ license = "MIT"
14
+ license-files = ["LICENSE"]
15
+ authors = [{ name = "Freepod", email = "erik@freepod.eu" }]
16
+ keywords = ["freepod", "deployment", "paas", "cli"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Software Development :: Build Tools",
29
+ ]
30
+
31
+ dependencies = [
32
+ "click>=8.0",
33
+ "httpx>=0.24",
34
+ "pathspec>=1.0",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://freepod.eu"
39
+ Repository = "https://github.com/erikvanzijst/freepod"
40
+ Issues = "https://github.com/erikvanzijst/freepod/issues"
41
+
42
+ [project.scripts]
43
+ freepod = "freepod.cli:main"
44
+
45
+ [tool.hatch.version]
46
+ path = "src/freepod/__init__.py"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/freepod"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ include = ["src/freepod", "tests", "README.md", "LICENSE", "pyproject.toml"]
53
+
54
+ [tool.ruff]
55
+ line-length = 100
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
59
+ addopts = "-q"
60
+
61
+ [dependency-groups]
62
+ dev = [
63
+ "pytest>=8.0",
64
+ ]
@@ -0,0 +1,86 @@
1
+ """freepod — the Freepod command-line client.
2
+
3
+ The package is a pure client of the public Freepod REST API. It shares no code
4
+ with the API server and imports nothing from it.
5
+
6
+ This module holds only the error hierarchy, because every other module raises
7
+ from it and a leaf module keeps the imports acyclic. Each error carries the
8
+ exit code its class maps onto; see `cli-distribution` § *Exit codes
9
+ distinguish failure classes*.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ __all__ = [
15
+ "FreepodError",
16
+ "UsageError",
17
+ "AuthenticationError",
18
+ "PermissionError_",
19
+ "BuildFailed",
20
+ "RolloutFailed",
21
+ "EXIT_OK",
22
+ "EXIT_ERROR",
23
+ "EXIT_USAGE",
24
+ "EXIT_NOT_AUTHENTICATED",
25
+ "EXIT_BUILD_FAILED",
26
+ "EXIT_ROLLOUT_FAILED",
27
+ ]
28
+
29
+ __version__ = "0.1.1"
30
+
31
+ EXIT_OK = 0
32
+ EXIT_ERROR = 1
33
+ EXIT_USAGE = 2
34
+ EXIT_NOT_AUTHENTICATED = 3
35
+ EXIT_BUILD_FAILED = 4
36
+ EXIT_ROLLOUT_FAILED = 5
37
+
38
+
39
+ class FreepodError(Exception):
40
+ """Anything that should end the run with a readable message.
41
+
42
+ Subclasses override `exit_code` to select their row of the exit-code
43
+ table. The base class is the "unexpected error" row.
44
+ """
45
+
46
+ exit_code = EXIT_ERROR
47
+
48
+
49
+ class UsageError(FreepodError):
50
+ """The command was invoked wrongly. Nothing was attempted."""
51
+
52
+ exit_code = EXIT_USAGE
53
+
54
+
55
+ class AuthenticationError(FreepodError):
56
+ """No usable credential, or one the platform will not accept.
57
+
58
+ Raised for the 401 row of the status-code contract, where re-authenticating
59
+ would succeed and change nothing, as well as for the ordinary "you are not
60
+ logged in" case.
61
+ """
62
+
63
+ exit_code = EXIT_NOT_AUTHENTICATED
64
+
65
+
66
+ class PermissionError_(FreepodError):
67
+ """Authenticated, but not permitted to act on the resource.
68
+
69
+ Named with a trailing underscore so it cannot shadow the builtin. This is
70
+ the API's own 403 — a credential problem no refresh can fix — and it is
71
+ deliberately *not* an `AuthenticationError`, so it never triggers a login.
72
+ """
73
+
74
+ exit_code = EXIT_ERROR
75
+
76
+
77
+ class BuildFailed(FreepodError):
78
+ """The build reached a non-successful terminal status."""
79
+
80
+ exit_code = EXIT_BUILD_FAILED
81
+
82
+
83
+ class RolloutFailed(FreepodError):
84
+ """The rollout failed, or waiting for it timed out."""
85
+
86
+ exit_code = EXIT_ROLLOUT_FAILED
@@ -0,0 +1,10 @@
1
+ """Support `python -m freepod` alongside the installed `freepod` script."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from .cli import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())