make-cloudflare 0.3.0__tar.gz → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: make-cloudflare
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Cloudflare Pages direct upload for mkrun -- deploy a built site with no Node and no wrangler.
5
5
  Project-URL: Homepage, https://make.optersoft.com
6
6
  Author-email: "Optersoft, S.L." <david@optersoft.com>
@@ -21,7 +21,7 @@ in the pipeline.
21
21
  # Makefile.py in a consuming repo
22
22
  # /// script
23
23
  # requires-python = ">=3.11"
24
- # dependencies = ["mkrun>=0.4", "make-cloudflare>=0.1"]
24
+ # dependencies = ["mkrun>=0.4.1", "make-cloudflare>=0.3"]
25
25
  # ///
26
26
  from make_cloudflare import cloudflare # importing is what registers the group
27
27
  ```
@@ -62,6 +62,13 @@ site's own `_routes.json` wins. Everything else is still the four calls below.
62
62
  Bindings (D1, KV) and secrets are settings on the Pages project, never part of
63
63
  the upload.
64
64
 
65
+ **Those settings are one call.** `pages.configure(project, d1={"DB": "<id>"},
66
+ vars={...}, compatibility_date="2026-10-01")` PATCHes the project's
67
+ `production` (or `preview`) configuration. Only what you pass is sent, and
68
+ Cloudflare merges it in. It takes effect on the **next** deployment. Secrets
69
+ are not set this way: a plain variable is shown in the dashboard, so a secret
70
+ goes through `wrangler pages secret put`.
71
+
65
72
  ## Why this exists
66
73
 
67
74
  `wrangler pages deploy` is four HTTPS calls and a hash. Reaching them through
@@ -8,7 +8,7 @@ in the pipeline.
8
8
  # Makefile.py in a consuming repo
9
9
  # /// script
10
10
  # requires-python = ">=3.11"
11
- # dependencies = ["mkrun>=0.4", "make-cloudflare>=0.1"]
11
+ # dependencies = ["mkrun>=0.4.1", "make-cloudflare>=0.3"]
12
12
  # ///
13
13
  from make_cloudflare import cloudflare # importing is what registers the group
14
14
  ```
@@ -49,6 +49,13 @@ site's own `_routes.json` wins. Everything else is still the four calls below.
49
49
  Bindings (D1, KV) and secrets are settings on the Pages project, never part of
50
50
  the upload.
51
51
 
52
+ **Those settings are one call.** `pages.configure(project, d1={"DB": "<id>"},
53
+ vars={...}, compatibility_date="2026-10-01")` PATCHes the project's
54
+ `production` (or `preview`) configuration. Only what you pass is sent, and
55
+ Cloudflare merges it in. It takes effect on the **next** deployment. Secrets
56
+ are not set this way: a plain variable is shown in the dashboard, so a secret
57
+ goes through `wrangler pages secret put`.
58
+
52
59
  ## Why this exists
53
60
 
54
61
  `wrangler pages deploy` is four HTTPS calls and a hash. Reaching them through
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "make-cloudflare"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Cloudflare Pages direct upload for mkrun -- deploy a built site with no Node and no wrangler."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -17,7 +17,7 @@ namespaces, and this package claims only the four names above.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = "0.3.0"
20
+ __version__ = "0.4.0"
21
21
 
22
22
  __all__ = ["cloudflare", "pages"]
23
23
 
@@ -61,6 +61,7 @@ from make.errors import ConfigError, MakeError
61
61
  __all__ = [
62
62
  "Asset",
63
63
  "build_functions",
64
+ "configure",
64
65
  "collect",
65
66
  "deploy",
66
67
  "deployments",
@@ -494,6 +495,53 @@ def create_project(
494
495
  return created if isinstance(created, dict) else {"name": project}
495
496
 
496
497
 
498
+ def configure(
499
+ project: str,
500
+ *,
501
+ d1: Mapping[str, str] | None = None,
502
+ vars: Mapping[str, str] | None = None,
503
+ compatibility_date: str | None = None,
504
+ compatibility_flags: Iterable[str] | None = None,
505
+ environment: str = "production",
506
+ account: str | None = None,
507
+ token: str | None = None,
508
+ ) -> dict:
509
+ """Set what a project's Functions run with; return the project as Cloudflare now has it.
510
+
511
+ `d1` maps a binding name to a D1 database id (`{"DB": "<uuid>"}`), `vars` sets plain
512
+ environment variables, and the compatibility date and flags pick the runtime.
513
+ `environment` is `production` or `preview`. Only what is passed is sent, and
514
+ Cloudflare merges it into what the project already has -- nothing unnamed is removed.
515
+
516
+ ⚠ Settings take effect on the **next deployment**, never on the live one. Secrets are
517
+ not set here: a value sent this way is stored in plain text and shown in the dashboard,
518
+ so a secret goes through `wrangler pages secret put`.
519
+ """
520
+ if environment not in ("production", "preview"):
521
+ raise MakeError(f"no Pages environment {environment!r}", hint="production or preview")
522
+ settings: dict[str, Any] = {}
523
+ if d1:
524
+ settings["d1_databases"] = {name: {"id": ident} for name, ident in d1.items()}
525
+ if vars:
526
+ settings["env_vars"] = {name: {"type": "plain_text", "value": value} for name, value in vars.items()}
527
+ if compatibility_date:
528
+ settings["compatibility_date"] = compatibility_date
529
+ if compatibility_flags is not None:
530
+ settings["compatibility_flags"] = list(compatibility_flags)
531
+ if not settings:
532
+ raise MakeError(f"nothing to set on {project}", hint="pass d1=, vars= or a compatibility date")
533
+
534
+ account, token = _credentials(account, token)
535
+ step(f"{project} ({environment}): " + ", ".join(sorted(settings)))
536
+ result = _json_call(
537
+ "PATCH",
538
+ f"{API}/accounts/{account}/pages/projects/{project}",
539
+ auth=token,
540
+ payload={"deployment_configs": {environment: settings}},
541
+ )
542
+ return result if isinstance(result, dict) else {"name": project}
543
+
544
+
497
545
  def deployments(project: str, *, account: str | None = None, token: str | None = None) -> list[dict]:
498
546
  """A project's deployments, newest first -- every page of them.
499
547
 
@@ -423,3 +423,37 @@ def test_a_missing_functions_tree_is_named(monkeypatch, tmp_path):
423
423
  site = build(tmp_path / "dist", {"index.html": "x"})
424
424
  with pytest.raises(MakeError, match="not a directory"):
425
425
  deploy(monkeypatch, site, FakeHTTP(), functions=tmp_path / "nope")
426
+
427
+
428
+ # -- project settings --------------------------------------------------------
429
+
430
+
431
+ def test_configure_patches_only_what_it_is_given(monkeypatch):
432
+ fake = FakeHTTP()
433
+ monkeypatch.setattr(pages.http, "request", fake)
434
+ pages.configure(
435
+ "demo",
436
+ d1={"DB": "uuid-1"},
437
+ vars={"ZONE": "example.dev"},
438
+ compatibility_date="2026-10-01",
439
+ account="acct",
440
+ token="tok",
441
+ )
442
+ method, url, call = fake.calls[0]
443
+ assert (method, url) == ("PATCH", f"{pages.API}/accounts/acct/pages/projects/demo")
444
+ assert json.loads(call["data"]) == {
445
+ "deployment_configs": {
446
+ "production": {
447
+ "d1_databases": {"DB": {"id": "uuid-1"}},
448
+ "env_vars": {"ZONE": {"type": "plain_text", "value": "example.dev"}},
449
+ "compatibility_date": "2026-10-01",
450
+ }
451
+ }
452
+ }
453
+
454
+
455
+ def test_configure_refuses_an_empty_or_unknown_request():
456
+ with pytest.raises(MakeError, match="nothing to set"):
457
+ pages.configure("demo", account="acct", token="tok")
458
+ with pytest.raises(MakeError, match="no Pages environment"):
459
+ pages.configure("demo", vars={"A": "1"}, environment="staging", account="acct", token="tok")