insightfactory-cli 1.1.1__tar.gz → 1.1.2.dev28__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.
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/CHANGELOG.md +15 -2
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/CLAUDE.md +10 -6
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/PKG-INFO +23 -11
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/README.md +22 -10
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/pyproject.toml +1 -1
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_release_tools.py +136 -8
- insightfactory_cli-1.1.2.dev28/tools/check_release.py +163 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/uv.lock +1 -1
- insightfactory_cli-1.1.1/tools/check_release.py +0 -79
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/.github/workflows/ci.yml +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/.github/workflows/claude.yml +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/.github/workflows/release.yml +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/.gitignore +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/.python-version +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/AGENTS.md +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/LICENSE +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/__init__.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/__main__.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/assets/__init__.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/assets/insightfactoryai-logo.svg +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/cache.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/callback_page.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/cli.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/colour.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/__init__.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/api.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/config.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/login.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/logout.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/mcp.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/profiles.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/set_token.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/token.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/config.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/constants.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/http.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/main.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/oauth.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/router/__init__.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/router/catalog.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/router/log.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/router/policy.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/router/server.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/router/upstream.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/runtime.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/__init__.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/cache_writer.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/conftest.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/helpers.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/servers.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_api.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_api_command.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_cache.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_cli.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_config.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_config_command.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_login.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_mcp_command.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_oauth.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_oauth_flow.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_oauth_force_refresh.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_profiles.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_programmatic_api.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_router_catalog.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_router_policy.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_router_server.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_router_upstream.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_runtime.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_set_token.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_token.py +0 -0
- {insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tools/release_notes.py +0 -0
|
@@ -7,8 +7,21 @@ project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
The release job reads the section whose heading matches the version in
|
|
9
9
|
`pyproject.toml` and makes it the body of the GitHub Release. CI holds both ends of
|
|
10
|
-
that:
|
|
11
|
-
|
|
10
|
+
that: every PR into `develop` must add an entry here, and a tag whose version has no
|
|
11
|
+
section fails rather than releasing an empty page. The version only moves when the
|
|
12
|
+
last one has been released, so a cycle's PRs share a section.
|
|
13
|
+
|
|
14
|
+
## 1.1.2
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- `release-guard` asks every PR into `develop` for a `CHANGELOG.md` entry, and asks
|
|
19
|
+
for a version bump only when `develop` is still on the version `main` is on. One
|
|
20
|
+
bump opens a release cycle and the PRs after it add to the section it opened,
|
|
21
|
+
rather than each minting a version that is never tagged.
|
|
22
|
+
- The guard now measures a version against `main` as well as the base branch. A bump
|
|
23
|
+
has to land above the released version, not merely above the base, and a PR into an
|
|
24
|
+
open cycle has to add a line rather than reword one.
|
|
12
25
|
|
|
13
26
|
## 1.1.1
|
|
14
27
|
|
|
@@ -34,9 +34,13 @@ uv run ty check
|
|
|
34
34
|
match `pyproject.toml`. Pushing `develop` publishes a `.dev{run_number}`
|
|
35
35
|
pre-release. Creating the release tag is a deliberate human act — do not create
|
|
36
36
|
or push one unless asked to cut a release.
|
|
37
|
-
- **Every PR into `develop`
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
- **Every PR into `develop` adds a `CHANGELOG.md` entry**, in the Added/Changed/Fixed
|
|
38
|
+
group that fits. For internal-only work (tests, CI, a refactor nothing observes) a
|
|
39
|
+
one-line entry saying so is enough. The section becomes the body of that version's
|
|
40
|
+
GitHub Release.
|
|
41
|
+
- **Raise `version` in `pyproject.toml` only when `develop` is on the same version as
|
|
42
|
+
`main`.** That version is released, so a new entry under it would describe a change
|
|
43
|
+
it does not contain: bump, and open a section for the new version. While `develop`
|
|
44
|
+
is ahead of `main` the cycle is open and PRs add to the section already there. A PR
|
|
45
|
+
into `main` always raises the version. The `release-guard` job runs
|
|
46
|
+
`tools/check_release.py` and fails the PR otherwise.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: insightfactory-cli
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2.dev28
|
|
4
4
|
Summary: Profile-based authentication CLI for the InsightFactory Interfaces API
|
|
5
5
|
Project-URL: Homepage, https://insightfactory.ai
|
|
6
6
|
Author-email: "insightfactory.ai Support" <support@insightfactory.ai>
|
|
@@ -354,20 +354,32 @@ artifact, so its notes belong on that Release. A checkout with no tags falls bac
|
|
|
354
354
|
to the tagged version's section alone.
|
|
355
355
|
|
|
356
356
|
`develop` therefore carries the version the next release will use, and the
|
|
357
|
-
`release-guard` job holds it there.
|
|
358
|
-
|
|
359
|
-
`CHANGELOG.md` section. `tools/check_release.py` runs both checks and is what
|
|
360
|
-
fails the PR:
|
|
357
|
+
`release-guard` job holds it there. `tools/check_release.py` runs the checks and
|
|
358
|
+
is what fails the PR:
|
|
361
359
|
|
|
362
360
|
```bash
|
|
363
|
-
BASE=origin/develop python3 tools/check_release.py
|
|
361
|
+
BASE=origin/develop RELEASED=origin/main python3 tools/check_release.py
|
|
364
362
|
```
|
|
365
363
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
364
|
+
**Every PR into `develop` adds a `CHANGELOG.md` entry.** That is the whole point
|
|
365
|
+
of the file, and a change with no entry reaches users with no line anywhere
|
|
366
|
+
saying it happened.
|
|
367
|
+
|
|
368
|
+
**The version moves once per release cycle, not once per PR.** The first PR after
|
|
369
|
+
a release raises `version` in `pyproject.toml` and opens that version's section;
|
|
370
|
+
the rest of the cycle adds to the section it opened. The guard asks for the bump
|
|
371
|
+
only while `develop` is still on the version `main` is on, because that version
|
|
372
|
+
is released and its notes describe something your change is not in.
|
|
373
|
+
|
|
374
|
+
The comparison is against `main` rather than against the tag, which matters in
|
|
375
|
+
the window where the release PR has merged but the tag is not pushed yet. What a
|
|
376
|
+
version contains is fixed when it reaches `main`, so a change landing after that
|
|
377
|
+
belongs to the next version, whatever the tags say.
|
|
378
|
+
|
|
379
|
+
A PR into `main` always needs the bump. It carries no new entries, only the
|
|
380
|
+
version they were written under, and `main` exists to be tagged: leaving it on
|
|
381
|
+
`main`'s own version makes it un-taggable, since that version is on PyPI and any
|
|
382
|
+
other tag matches no file.
|
|
371
383
|
|
|
372
384
|
Verify and build live in the reusable workflow
|
|
373
385
|
[`if_s_insightfactory_cli.yml`](https://github.com/insightfactory-ai/if_sre_github_actions/blob/main/.github/workflows/if_s_insightfactory_cli.yml)
|
|
@@ -330,20 +330,32 @@ artifact, so its notes belong on that Release. A checkout with no tags falls bac
|
|
|
330
330
|
to the tagged version's section alone.
|
|
331
331
|
|
|
332
332
|
`develop` therefore carries the version the next release will use, and the
|
|
333
|
-
`release-guard` job holds it there.
|
|
334
|
-
|
|
335
|
-
`CHANGELOG.md` section. `tools/check_release.py` runs both checks and is what
|
|
336
|
-
fails the PR:
|
|
333
|
+
`release-guard` job holds it there. `tools/check_release.py` runs the checks and
|
|
334
|
+
is what fails the PR:
|
|
337
335
|
|
|
338
336
|
```bash
|
|
339
|
-
BASE=origin/develop python3 tools/check_release.py
|
|
337
|
+
BASE=origin/develop RELEASED=origin/main python3 tools/check_release.py
|
|
340
338
|
```
|
|
341
339
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
340
|
+
**Every PR into `develop` adds a `CHANGELOG.md` entry.** That is the whole point
|
|
341
|
+
of the file, and a change with no entry reaches users with no line anywhere
|
|
342
|
+
saying it happened.
|
|
343
|
+
|
|
344
|
+
**The version moves once per release cycle, not once per PR.** The first PR after
|
|
345
|
+
a release raises `version` in `pyproject.toml` and opens that version's section;
|
|
346
|
+
the rest of the cycle adds to the section it opened. The guard asks for the bump
|
|
347
|
+
only while `develop` is still on the version `main` is on, because that version
|
|
348
|
+
is released and its notes describe something your change is not in.
|
|
349
|
+
|
|
350
|
+
The comparison is against `main` rather than against the tag, which matters in
|
|
351
|
+
the window where the release PR has merged but the tag is not pushed yet. What a
|
|
352
|
+
version contains is fixed when it reaches `main`, so a change landing after that
|
|
353
|
+
belongs to the next version, whatever the tags say.
|
|
354
|
+
|
|
355
|
+
A PR into `main` always needs the bump. It carries no new entries, only the
|
|
356
|
+
version they were written under, and `main` exists to be tagged: leaving it on
|
|
357
|
+
`main`'s own version makes it un-taggable, since that version is on PyPI and any
|
|
358
|
+
other tag matches no file.
|
|
347
359
|
|
|
348
360
|
Verify and build live in the reusable workflow
|
|
349
361
|
[`if_s_insightfactory_cli.yml`](https://github.com/insightfactory-ai/if_sre_github_actions/blob/main/.github/workflows/if_s_insightfactory_cli.yml)
|
|
@@ -296,31 +296,38 @@ def test_main_still_requires_the_changelog_on_the_opt_out(changelog, monkeypatch
|
|
|
296
296
|
check_release.main()
|
|
297
297
|
|
|
298
298
|
|
|
299
|
-
@pytest.mark.parametrize(
|
|
300
|
-
|
|
299
|
+
@pytest.mark.parametrize(
|
|
300
|
+
("base", "error"),
|
|
301
|
+
[("9.9.8", None), ("9.9.9", "has to raise the version"), ("10.0.0", "below origin/main's")],
|
|
302
|
+
)
|
|
303
|
+
def test_main_requires_a_bump_without_released(changelog, monkeypatch, base, error):
|
|
304
|
+
"""No RELEASED is a PR into the release branch: the version must always move."""
|
|
301
305
|
changelog("# Changelog\n\n## 9.9.9\n\n- a thing\n")
|
|
302
|
-
monkeypatch.setenv("BASE", "origin/
|
|
306
|
+
monkeypatch.setenv("BASE", "origin/main")
|
|
307
|
+
monkeypatch.delenv("RELEASED", raising=False)
|
|
303
308
|
monkeypatch.setattr(check_release, "base_version", lambda _: base)
|
|
304
|
-
if
|
|
309
|
+
if error is None:
|
|
305
310
|
assert check_release.main() == 0
|
|
306
311
|
else:
|
|
307
|
-
with pytest.raises(SystemExit, match=
|
|
312
|
+
with pytest.raises(SystemExit, match=error):
|
|
308
313
|
check_release.main()
|
|
309
314
|
|
|
310
315
|
|
|
311
316
|
def test_main_treats_a_short_base_version_as_padded(changelog, monkeypatch):
|
|
312
317
|
"""9.9 and 9.9.0 are the same release, so 9.9.9 is still a bump over 9.9."""
|
|
313
318
|
changelog("# Changelog\n\n## 9.9.9\n\n- a thing\n")
|
|
314
|
-
monkeypatch.setenv("BASE", "origin/
|
|
319
|
+
monkeypatch.setenv("BASE", "origin/main")
|
|
320
|
+
monkeypatch.delenv("RELEASED", raising=False)
|
|
315
321
|
monkeypatch.setattr(check_release, "base_version", lambda _: "9.9")
|
|
316
322
|
assert check_release.main() == 0
|
|
317
323
|
|
|
318
324
|
|
|
319
325
|
def test_main_refuses_an_equal_short_base_version(changelog, monkeypatch):
|
|
320
326
|
changelog("# Changelog\n\n## 9.9\n\n- a thing\n", version="9.9")
|
|
321
|
-
monkeypatch.setenv("BASE", "origin/
|
|
327
|
+
monkeypatch.setenv("BASE", "origin/main")
|
|
328
|
+
monkeypatch.delenv("RELEASED", raising=False)
|
|
322
329
|
monkeypatch.setattr(check_release, "base_version", lambda _: "9.9.0")
|
|
323
|
-
with pytest.raises(SystemExit, match="
|
|
330
|
+
with pytest.raises(SystemExit, match="has to raise the version"):
|
|
324
331
|
check_release.main()
|
|
325
332
|
|
|
326
333
|
|
|
@@ -488,3 +495,124 @@ def test_versions_in_changelog_also_refuses_an_unclosed_fence():
|
|
|
488
495
|
"""Strictness is a property of the module, not of which entry point you call."""
|
|
489
496
|
with pytest.raises(SystemExit, match="never closed"):
|
|
490
497
|
release_notes.versions_in_changelog("# Changelog\n\n## 2.0.0\n\n```bash\nx\n")
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
# ── a bump once a cycle, an entry every PR ────────────────────────────────────
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@pytest.fixture
|
|
504
|
+
def refs(monkeypatch):
|
|
505
|
+
"""Stand in for the two branches the guard reads, as a PR into develop."""
|
|
506
|
+
|
|
507
|
+
def setup(develop_version: str, main_version: str, develop_changelog: str) -> None:
|
|
508
|
+
monkeypatch.setenv("BASE", "origin/develop")
|
|
509
|
+
monkeypatch.setenv("RELEASED", "origin/main")
|
|
510
|
+
monkeypatch.setattr(
|
|
511
|
+
check_release,
|
|
512
|
+
"base_version",
|
|
513
|
+
lambda ref: develop_version if ref == "origin/develop" else main_version,
|
|
514
|
+
)
|
|
515
|
+
monkeypatch.setattr(
|
|
516
|
+
check_release,
|
|
517
|
+
"file_at",
|
|
518
|
+
lambda ref, path: develop_changelog,
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
return setup
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def test_open_cycle_accepts_a_new_entry_without_a_bump(changelog, refs):
|
|
525
|
+
"""develop is ahead of main, so 9.9.9's section is still open to add to."""
|
|
526
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- an earlier entry\n- what this PR did\n")
|
|
527
|
+
refs("9.9.9", "9.9.8", "# Changelog\n\n## 9.9.9\n\n- an earlier entry\n")
|
|
528
|
+
assert check_release.main() == 0
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def test_open_cycle_refuses_an_unchanged_section(changelog, refs):
|
|
532
|
+
"""The bump is not required, so the entry is what this PR owes instead."""
|
|
533
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- an earlier entry\n")
|
|
534
|
+
refs("9.9.9", "9.9.8", "# Changelog\n\n## 9.9.9\n\n- an earlier entry\n")
|
|
535
|
+
with pytest.raises(SystemExit, match="adds nothing to origin/develop's"):
|
|
536
|
+
check_release.main()
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def test_released_version_still_requires_a_bump(changelog, refs):
|
|
540
|
+
"""develop is on what main is on, so 9.9.9 is released and its notes are fixed."""
|
|
541
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- an earlier entry\n- what this PR did\n")
|
|
542
|
+
refs("9.9.9", "9.9.9", "# Changelog\n\n## 9.9.9\n\n- an earlier entry\n")
|
|
543
|
+
with pytest.raises(SystemExit, match="origin/main is on 9.9.9: that version is released"):
|
|
544
|
+
check_release.main()
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def test_a_bump_satisfies_the_guard_whatever_the_cycle(changelog, refs):
|
|
548
|
+
"""Deliberately jumping the version mid-cycle stays allowed."""
|
|
549
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- a thing\n")
|
|
550
|
+
refs("9.9.8", "9.9.7", "# Changelog\n\n## 9.9.8\n\n- older\n")
|
|
551
|
+
assert check_release.main() == 0
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def test_a_bump_off_the_released_version_is_the_cycle_opening(changelog, refs):
|
|
555
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- opens the cycle\n")
|
|
556
|
+
refs("9.9.8", "9.9.8", "# Changelog\n\n## 9.9.8\n\n- released\n")
|
|
557
|
+
assert check_release.main() == 0
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def test_a_lowered_version_is_refused(changelog, refs):
|
|
561
|
+
changelog("# Changelog\n\n## 9.9.7\n\n- a thing\n", version="9.9.7")
|
|
562
|
+
refs("9.9.9", "9.9.8", "# Changelog\n\n## 9.9.9\n\n- newer\n")
|
|
563
|
+
with pytest.raises(SystemExit, match="below origin/develop's"):
|
|
564
|
+
check_release.main()
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def test_a_version_at_or_below_the_released_one_is_refused(changelog, refs):
|
|
568
|
+
"""A bump has to clear main, not just develop: 9.9.9's notes are already published."""
|
|
569
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- a thing\n")
|
|
570
|
+
refs("9.9.8", "9.9.9", "# Changelog\n\n## 9.9.8\n\n- older\n")
|
|
571
|
+
with pytest.raises(SystemExit, match="not above origin/main's 9.9.9"):
|
|
572
|
+
check_release.main()
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def test_the_released_comparison_pads_to_the_wider_version(changelog, refs):
|
|
576
|
+
"""9.9 and 9.9.0 are the same release, so this is not a bump off it."""
|
|
577
|
+
changelog("# Changelog\n\n## 9.9\n\n- a thing\n", version="9.9")
|
|
578
|
+
refs("9.8", "9.9.0", "# Changelog\n\n## 9.8\n\n- older\n")
|
|
579
|
+
with pytest.raises(SystemExit, match="not above origin/main's 9.9.0"):
|
|
580
|
+
check_release.main()
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def test_a_reformatted_section_adds_nothing(changelog, refs):
|
|
584
|
+
"""Re-indenting an entry is not writing one."""
|
|
585
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- an earlier entry\n")
|
|
586
|
+
refs("9.9.9", "9.9.8", "# Changelog\n\n## 9.9.9\n\n- an earlier entry\n")
|
|
587
|
+
with pytest.raises(SystemExit, match="adds nothing to origin/develop's"):
|
|
588
|
+
check_release.main()
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def test_deleting_an_entry_is_not_adding_one(changelog, refs):
|
|
592
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- an earlier entry\n")
|
|
593
|
+
refs("9.9.9", "9.9.8", "# Changelog\n\n## 9.9.9\n\n- an earlier entry\n- a second entry\n")
|
|
594
|
+
with pytest.raises(SystemExit, match="adds nothing to origin/develop's"):
|
|
595
|
+
check_release.main()
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def test_section_at_reports_an_unreadable_ref(monkeypatch):
|
|
599
|
+
"""A git failure is not the same as a missing section, and must not be swallowed."""
|
|
600
|
+
|
|
601
|
+
def boom(ref, path):
|
|
602
|
+
raise SystemExit(f"error: cannot read {path} at {ref}")
|
|
603
|
+
|
|
604
|
+
monkeypatch.setattr(check_release, "file_at", boom)
|
|
605
|
+
with pytest.raises(SystemExit, match="cannot read CHANGELOG.md"):
|
|
606
|
+
check_release.section_at("origin/develop", "9.9.9")
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def test_section_at_returns_none_when_the_base_has_no_section(monkeypatch):
|
|
610
|
+
monkeypatch.setattr(check_release, "file_at", lambda ref, path: "# Changelog\n\n## 1.0.0\n\n- old\n")
|
|
611
|
+
assert check_release.section_at("origin/develop", "9.9.9") is None
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def test_a_first_entry_for_a_version_the_base_lacks_counts_as_changed(changelog, refs):
|
|
615
|
+
"""The section is new on this branch, so it cannot match the base's."""
|
|
616
|
+
changelog("# Changelog\n\n## 9.9.9\n\n- the first entry\n")
|
|
617
|
+
refs("9.9.9", "9.9.8", "# Changelog\n\n## 1.0.0\n\n- unrelated\n")
|
|
618
|
+
assert check_release.main() == 0
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Refuse a change that nobody would be able to find in the release notes.
|
|
3
|
+
|
|
4
|
+
Run by the `release-guard` CI job. Every PR must say what it did; only some have
|
|
5
|
+
to move the version to say it.
|
|
6
|
+
|
|
7
|
+
* The version in pyproject.toml always has a CHANGELOG.md section.
|
|
8
|
+
* A PR whose base is still on the released version has to raise the version and
|
|
9
|
+
open a section of its own. Any version it moves to is above the released one.
|
|
10
|
+
* Any other PR into an open cycle has to add a line to the section already there.
|
|
11
|
+
|
|
12
|
+
A cycle is open while the base branch is ahead of RELEASED, the branch releases are
|
|
13
|
+
tagged from. Anchoring on that branch rather than on the tag matters in the window
|
|
14
|
+
where a release PR has merged but the tag is not pushed yet: the contents of that
|
|
15
|
+
version are fixed at the merge, so a later change belongs to the next one, and
|
|
16
|
+
appending to it would ship a Release describing something it does not contain.
|
|
17
|
+
|
|
18
|
+
BASE names the ref to compare against (`origin/develop`). A push has no base to
|
|
19
|
+
diff, so it passes `BASE=none` and gets the CHANGELOG check alone. BASE unset is an
|
|
20
|
+
error rather than that same skip: these are the checks a human cannot replicate by
|
|
21
|
+
reading the diff, and a renamed or mis-indented `env:` key should fail the job
|
|
22
|
+
rather than quietly turn it into a no-op.
|
|
23
|
+
|
|
24
|
+
RELEASED names the release branch (`origin/main`), and is what makes the bump
|
|
25
|
+
conditional. Left unset, every PR must bump, which is what a PR into the release
|
|
26
|
+
branch itself wants: it carries no new entries, only the version they were written
|
|
27
|
+
under.
|
|
28
|
+
|
|
29
|
+
Stdlib only: this runs on the CI runner's system python3, outside the project venv.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import os
|
|
35
|
+
import subprocess
|
|
36
|
+
import sys
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
sys.path.append(str(Path(__file__).resolve().parent))
|
|
40
|
+
|
|
41
|
+
import release_notes # noqa: E402
|
|
42
|
+
from release_notes import changelog_section, current_version, parse_version, release_parts, version_key # noqa: E402
|
|
43
|
+
|
|
44
|
+
NO_BASE = "none"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def file_at(ref: str, path: str) -> str:
|
|
48
|
+
result = subprocess.run(
|
|
49
|
+
["git", "show", f"{ref}:{path}"],
|
|
50
|
+
cwd=release_notes.ROOT,
|
|
51
|
+
capture_output=True,
|
|
52
|
+
text=True,
|
|
53
|
+
)
|
|
54
|
+
if result.returncode != 0:
|
|
55
|
+
raise SystemExit(
|
|
56
|
+
f"error: cannot read {path} at {ref}: {result.stderr.strip()}\n"
|
|
57
|
+
f"If this is CI, the checkout needs fetch-depth: 0 for {ref} to exist locally."
|
|
58
|
+
)
|
|
59
|
+
return result.stdout
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def base_version(base: str) -> str:
|
|
63
|
+
return parse_version(file_at(base, "pyproject.toml"), where=f"pyproject.toml at {base}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def section_at(ref: str, version: str) -> str | None:
|
|
67
|
+
"""That version's CHANGELOG section on `ref`, or None if it has none yet."""
|
|
68
|
+
text = file_at(ref, "CHANGELOG.md")
|
|
69
|
+
try:
|
|
70
|
+
return release_notes.changelog_section(version, text)
|
|
71
|
+
except SystemExit:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def entry_lines(section: str | None) -> set[str]:
|
|
76
|
+
"""The section's entries, as comparable lines.
|
|
77
|
+
|
|
78
|
+
Whitespace is collapsed and headings dropped, so re-indenting or re-wrapping an
|
|
79
|
+
entry does not read as writing one.
|
|
80
|
+
"""
|
|
81
|
+
if section is None:
|
|
82
|
+
return set()
|
|
83
|
+
lines = (" ".join(line.split()) for line in section.splitlines())
|
|
84
|
+
return {line for line in lines if line and not line.startswith("#")}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main() -> int:
|
|
88
|
+
version = current_version()
|
|
89
|
+
|
|
90
|
+
# Raises if the section is missing or empty, with the message to act on.
|
|
91
|
+
section = changelog_section(version)
|
|
92
|
+
print(f"CHANGELOG.md documents {version}")
|
|
93
|
+
|
|
94
|
+
base = os.environ.get("BASE", "").strip()
|
|
95
|
+
if not base:
|
|
96
|
+
raise SystemExit(
|
|
97
|
+
f"error: BASE is unset. Pass the ref to compare against, "
|
|
98
|
+
f"or BASE={NO_BASE} for a run that genuinely has no base branch."
|
|
99
|
+
)
|
|
100
|
+
if base == NO_BASE:
|
|
101
|
+
print(f"BASE={NO_BASE}, skipping the version-bump check")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
previous = base_version(base)
|
|
105
|
+
released_ref = os.environ.get("RELEASED", "").strip()
|
|
106
|
+
released = base_version(released_ref) if released_ref else None
|
|
107
|
+
|
|
108
|
+
widths = [len(release_parts(version)), len(release_parts(previous))]
|
|
109
|
+
if released is not None:
|
|
110
|
+
widths.append(len(release_parts(released)))
|
|
111
|
+
width = max(widths)
|
|
112
|
+
|
|
113
|
+
def key(value: str) -> tuple[int, ...]:
|
|
114
|
+
return version_key(value, width)
|
|
115
|
+
|
|
116
|
+
if key(version) < key(previous):
|
|
117
|
+
raise SystemExit(
|
|
118
|
+
f"error: pyproject.toml is at {version}, below {base}'s {previous}. "
|
|
119
|
+
f"Rebase on {base} rather than lowering the version."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if released is not None and key(version) <= key(released):
|
|
123
|
+
# Every version this repo can still write notes for is above the released one,
|
|
124
|
+
# whichever way the base branch got to where it is.
|
|
125
|
+
if key(version) == key(previous):
|
|
126
|
+
raise SystemExit(
|
|
127
|
+
f"error: pyproject.toml is at {version}, the same as {base}'s, and {released_ref} is "
|
|
128
|
+
f"on {released}: that version is released. Raise the version and open a CHANGELOG.md "
|
|
129
|
+
f"section for it."
|
|
130
|
+
)
|
|
131
|
+
raise SystemExit(
|
|
132
|
+
f"error: pyproject.toml is at {version}, which is not above {released_ref}'s {released}. "
|
|
133
|
+
f"{released} is released, so its notes are fixed. Raise the version past it."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if key(version) > key(previous):
|
|
137
|
+
# A bump always satisfies the guard. The section check above already made
|
|
138
|
+
# sure the version it opens is documented and not empty.
|
|
139
|
+
print(f"version {version} is above {base}'s {previous}")
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
if released is None:
|
|
143
|
+
raise SystemExit(
|
|
144
|
+
f"error: pyproject.toml is at {version}, the same as {base}'s. "
|
|
145
|
+
f"A PR into {base} has to raise the version to the one its entries were written under."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# The cycle is open, so no bump. The entry is what this PR owes instead. The
|
|
149
|
+
# checkout is the merge ref, so the working tree section holds the base's entries
|
|
150
|
+
# as well as this PR's: anything not on the base tip is what this PR added.
|
|
151
|
+
if not entry_lines(section) - entry_lines(section_at(base, version)):
|
|
152
|
+
raise SystemExit(
|
|
153
|
+
f"error: the CHANGELOG.md section for {version} adds nothing to {base}'s. "
|
|
154
|
+
f"{version} is unreleased, so no version bump is needed, but this change still has to "
|
|
155
|
+
f"say what it did. Add an entry under `## {version}`."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
print(f"{version} is unreleased ({released_ref} is on {released}) and this PR adds to its section")
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
raise SystemExit(main())
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Refuse a change that would land under a version someone has already written up.
|
|
3
|
-
|
|
4
|
-
Two checks, run by the `release-guard` CI job:
|
|
5
|
-
|
|
6
|
-
* the version in pyproject.toml has a CHANGELOG.md section, and
|
|
7
|
-
* on a PR, that version is above the one on the base branch.
|
|
8
|
-
|
|
9
|
-
BASE names the git ref to compare against (`origin/develop`). A push has no base
|
|
10
|
-
to diff, so it passes `BASE=none` and gets the CHANGELOG check alone. BASE unset
|
|
11
|
-
is an error rather than that same skip: the bump check is the half a human cannot
|
|
12
|
-
replicate by reading the diff, and a renamed or mis-indented `env:` key should
|
|
13
|
-
fail the job rather than quietly turn it into a no-op.
|
|
14
|
-
|
|
15
|
-
Stdlib only: this runs on the CI runner's system python3, outside the project venv.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
import os
|
|
21
|
-
import subprocess
|
|
22
|
-
import sys
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
|
|
25
|
-
sys.path.append(str(Path(__file__).resolve().parent))
|
|
26
|
-
|
|
27
|
-
import release_notes # noqa: E402
|
|
28
|
-
from release_notes import changelog_section, current_version, parse_version, release_parts, version_key # noqa: E402
|
|
29
|
-
|
|
30
|
-
NO_BASE = "none"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def base_version(base: str) -> str:
|
|
34
|
-
result = subprocess.run(
|
|
35
|
-
["git", "show", f"{base}:pyproject.toml"],
|
|
36
|
-
cwd=release_notes.ROOT,
|
|
37
|
-
capture_output=True,
|
|
38
|
-
text=True,
|
|
39
|
-
)
|
|
40
|
-
if result.returncode != 0:
|
|
41
|
-
raise SystemExit(
|
|
42
|
-
f"error: cannot read pyproject.toml at {base}: {result.stderr.strip()}\n"
|
|
43
|
-
f"If this is CI, the checkout needs fetch-depth: 0 for {base} to exist locally."
|
|
44
|
-
)
|
|
45
|
-
return parse_version(result.stdout, where=f"pyproject.toml at {base}")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def main() -> int:
|
|
49
|
-
version = current_version()
|
|
50
|
-
|
|
51
|
-
# Raises if the section is missing or empty, with the message to act on.
|
|
52
|
-
changelog_section(version)
|
|
53
|
-
print(f"CHANGELOG.md documents {version}")
|
|
54
|
-
|
|
55
|
-
base = os.environ.get("BASE", "").strip()
|
|
56
|
-
if not base:
|
|
57
|
-
raise SystemExit(
|
|
58
|
-
f"error: BASE is unset. Pass the ref to compare against, "
|
|
59
|
-
f"or BASE={NO_BASE} for a run that genuinely has no base branch."
|
|
60
|
-
)
|
|
61
|
-
if base == NO_BASE:
|
|
62
|
-
print(f"BASE={NO_BASE}, skipping the version-bump check")
|
|
63
|
-
return 0
|
|
64
|
-
|
|
65
|
-
previous = base_version(base)
|
|
66
|
-
width = max(len(release_parts(version)), len(release_parts(previous)))
|
|
67
|
-
if version_key(version, width) <= version_key(previous, width):
|
|
68
|
-
raise SystemExit(
|
|
69
|
-
f"error: pyproject.toml is at {version}, and {base} is at {previous}. "
|
|
70
|
-
f"{base} already has a {previous} section in CHANGELOG.md, so this change would land "
|
|
71
|
-
f"under notes written for other work, and could be tagged before anyone lists it. "
|
|
72
|
-
f"Raise the version and open its own section."
|
|
73
|
-
)
|
|
74
|
-
print(f"version {version} is above {base}'s {previous}")
|
|
75
|
-
return 0
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if __name__ == "__main__":
|
|
79
|
-
raise SystemExit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/src/if_cli/commands/set_token.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{insightfactory_cli-1.1.1 → insightfactory_cli-1.1.2.dev28}/tests/test_oauth_force_refresh.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|