ci: publish each distribution from its own PyPI environment - #63

Merged
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments
Aug 28, 2026
Merged

ci: publish each distribution from its own PyPI environment#63
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

Why

Registering the PyPI pending trusted publishers failed for real. nl2sql-engine
registered fine; nl2sql-api was rejected with:

A pending trusted publisher matching this configuration has already been
registered for a different project name.

PyPI keys a pending publisher on the tuple (owner, repository, workflow,
environment)
and requires it to be unique. All three of our distributions
publish from the same owner, the same repository and the same publish_pypi.yaml
with no environment — so all three tuples were identical and only the first
registration could succeed.

The environment is the only field left that can distinguish them.

What changed

publish_pypi.yaml's single pypi job becomes a three-leg matrix, one leg per
package, each in its own GitHub Environment:

PackageEnvironment
nl2sql-enginepypi-nl2sql-engine
nl2sql-apipypi-nl2sql-api
nl2sql-adapter-sdkpypi-nl2sql-adapter-sdk

A matrix rather than three copy-pasted jobs: GitHub evaluates expressions in
environment:, so environment: pypi-${{ matrix.package }} expresses the whole
difference between the legs in one line, and the publish steps stay identical.

pypa/gh-action-pypi-publish uploads everything in packages-dir, so each leg
downloads the shared dist artifact and copies only its own sdist + wheel
into upload/ before pointing packages-dir at it. The trailing - in the
glob is load-bearing — nl2sql_a* would match both nl2sql_api and
nl2sql_adapter_sdk. Verified against a locally built dist/ holding all six
files; each glob selects exactly two:

nl2sql-engine dist/nl2sql_engine-*.tar.gz dist/nl2sql_engine-*.whl
-> nl2sql_engine-0.1.0-py3-none-any.whl
nl2sql_engine-0.1.0.tar.gz
nl2sql-api dist/nl2sql_api-*.tar.gz dist/nl2sql_api-*.whl
-> nl2sql_api-0.1.0-py3-none-any.whl
nl2sql_api-0.1.0.tar.gz
nl2sql-adapter-sdk dist/nl2sql_adapter_sdk-*.tar.gz dist/nl2sql_adapter_sdk-*.whl
-> nl2sql_adapter_sdk-0.1.0-py3-none-any.whl
nl2sql_adapter_sdk-0.1.0.tar.gz

id-token: write stays on the publishing job, since OIDC is the entire
credential. build and its smoke gate are untouched: if the wheels do not
install and import, nothing publishes.

The job name stays pypi, so ghcr and docs keep needs: pypi unchanged —
a dependency on a matrix job waits for every leg, so both still run only
after all three uploads succeed.

build needs=- environment=-
pypi needs=build environment=pypi-${{ matrix.package }} id-token=write
leg nl2sql-engine -> pypi-nl2sql-engine
leg nl2sql-api -> pypi-nl2sql-api
leg nl2sql-adapter-sdk -> pypi-nl2sql-adapter-sdk
ghcr needs=pypi
docs needs=pypi

Action required before the first release

The pending publishers must be registered with these environment names. At
pypi.org/manage/account/publishing,
for each of the three project names, use owner nadeem4, repository nl2sql,
workflow publish_pypi.yaml, and the environment from the table above.
Leaving Environment blank is what caused the rejection — nl2sql-engine's
existing entry was registered with no environment and needs to be recreated with
pypi-nl2sql-engine.

The GitHub Environments themselves need no setup: referencing them in the
workflow is enough for GitHub to create them on the first run, after which they
appear under Settings → Environments (where reviewers or branch rules can be
added if the release should be gated further). None were created by this PR.

docs/development/releasing.md is updated to match: the pending-publisher table
now carries the Environment column, states the uniqueness constraint that forces
it, and no longer says to leave Environment empty.

Verification

  • yaml.safe_load parses publish_pypi.yaml; job graph as above.
  • Per-package glob expansion proven against a locally built dist/ (above).
  • Unit suite: 231 passed, 1 skipped, 47 deselected — run twice, unchanged.
  • Key-free integration: 28 passed — run twice, unchanged.
  • mkdocs build --strict clean (throwaway venv from requirements-docs.txt).
  • No version numbers or CHANGELOG.md touched; release-please owns both.

@nadeem4
nadeem4 merged commit 14da34c into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the ci/per-package-pypi-environments branch August 28, 2026 14:21
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

ci: publish each distribution from its own PyPI environment - #63

Merged
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments
Aug 28, 2026
Merged

ci: publish each distribution from its own PyPI environment#63
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

Why

Registering the PyPI pending trusted publishers failed for real. nl2sql-engine
registered fine; nl2sql-api was rejected with:

A pending trusted publisher matching this configuration has already been
registered for a different project name.

PyPI keys a pending publisher on the tuple (owner, repository, workflow,
environment)
and requires it to be unique. All three of our distributions
publish from the same owner, the same repository and the same publish_pypi.yaml
with no environment — so all three tuples were identical and only the first
registration could succeed.

The environment is the only field left that can distinguish them.

What changed

publish_pypi.yaml's single pypi job becomes a three-leg matrix, one leg per
package, each in its own GitHub Environment:

PackageEnvironment
nl2sql-enginepypi-nl2sql-engine
nl2sql-apipypi-nl2sql-api
nl2sql-adapter-sdkpypi-nl2sql-adapter-sdk

A matrix rather than three copy-pasted jobs: GitHub evaluates expressions in
environment:, so environment: pypi-${{ matrix.package }} expresses the whole
difference between the legs in one line, and the publish steps stay identical.

pypa/gh-action-pypi-publish uploads everything in packages-dir, so each leg
downloads the shared dist artifact and copies only its own sdist + wheel
into upload/ before pointing packages-dir at it. The trailing - in the
glob is load-bearing — nl2sql_a* would match both nl2sql_api and
nl2sql_adapter_sdk. Verified against a locally built dist/ holding all six
files; each glob selects exactly two:

nl2sql-engine dist/nl2sql_engine-*.tar.gz dist/nl2sql_engine-*.whl
-> nl2sql_engine-0.1.0-py3-none-any.whl
nl2sql_engine-0.1.0.tar.gz
nl2sql-api dist/nl2sql_api-*.tar.gz dist/nl2sql_api-*.whl
-> nl2sql_api-0.1.0-py3-none-any.whl
nl2sql_api-0.1.0.tar.gz
nl2sql-adapter-sdk dist/nl2sql_adapter_sdk-*.tar.gz dist/nl2sql_adapter_sdk-*.whl
-> nl2sql_adapter_sdk-0.1.0-py3-none-any.whl
nl2sql_adapter_sdk-0.1.0.tar.gz

id-token: write stays on the publishing job, since OIDC is the entire
credential. build and its smoke gate are untouched: if the wheels do not
install and import, nothing publishes.

The job name stays pypi, so ghcr and docs keep needs: pypi unchanged —
a dependency on a matrix job waits for every leg, so both still run only
after all three uploads succeed.

build needs=- environment=-
pypi needs=build environment=pypi-${{ matrix.package }} id-token=write
leg nl2sql-engine -> pypi-nl2sql-engine
leg nl2sql-api -> pypi-nl2sql-api
leg nl2sql-adapter-sdk -> pypi-nl2sql-adapter-sdk
ghcr needs=pypi
docs needs=pypi

Action required before the first release

The pending publishers must be registered with these environment names. At
pypi.org/manage/account/publishing,
for each of the three project names, use owner nadeem4, repository nl2sql,
workflow publish_pypi.yaml, and the environment from the table above.
Leaving Environment blank is what caused the rejection — nl2sql-engine's
existing entry was registered with no environment and needs to be recreated with
pypi-nl2sql-engine.

The GitHub Environments themselves need no setup: referencing them in the
workflow is enough for GitHub to create them on the first run, after which they
appear under Settings → Environments (where reviewers or branch rules can be
added if the release should be gated further). None were created by this PR.

docs/development/releasing.md is updated to match: the pending-publisher table
now carries the Environment column, states the uniqueness constraint that forces
it, and no longer says to leave Environment empty.

Verification

  • yaml.safe_load parses publish_pypi.yaml; job graph as above.
  • Per-package glob expansion proven against a locally built dist/ (above).
  • Unit suite: 231 passed, 1 skipped, 47 deselected — run twice, unchanged.
  • Key-free integration: 28 passed — run twice, unchanged.
  • mkdocs build --strict clean (throwaway venv from requirements-docs.txt).
  • No version numbers or CHANGELOG.md touched; release-please owns both.

@nadeem4
nadeem4 merged commit 14da34c into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the ci/per-package-pypi-environments branch August 28, 2026 14:21
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci: publish each distribution from its own PyPI environment - #63

Merged
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments
Aug 28, 2026
Merged

ci: publish each distribution from its own PyPI environment#63
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

Why

Registering the PyPI pending trusted publishers failed for real. nl2sql-engine
registered fine; nl2sql-api was rejected with:

A pending trusted publisher matching this configuration has already been
registered for a different project name.

PyPI keys a pending publisher on the tuple (owner, repository, workflow,
environment)
and requires it to be unique. All three of our distributions
publish from the same owner, the same repository and the same publish_pypi.yaml
with no environment — so all three tuples were identical and only the first
registration could succeed.

The environment is the only field left that can distinguish them.

What changed

publish_pypi.yaml's single pypi job becomes a three-leg matrix, one leg per
package, each in its own GitHub Environment:

PackageEnvironment
nl2sql-enginepypi-nl2sql-engine
nl2sql-apipypi-nl2sql-api
nl2sql-adapter-sdkpypi-nl2sql-adapter-sdk

A matrix rather than three copy-pasted jobs: GitHub evaluates expressions in
environment:, so environment: pypi-${{ matrix.package }} expresses the whole
difference between the legs in one line, and the publish steps stay identical.

pypa/gh-action-pypi-publish uploads everything in packages-dir, so each leg
downloads the shared dist artifact and copies only its own sdist + wheel
into upload/ before pointing packages-dir at it. The trailing - in the
glob is load-bearing — nl2sql_a* would match both nl2sql_api and
nl2sql_adapter_sdk. Verified against a locally built dist/ holding all six
files; each glob selects exactly two:

nl2sql-engine dist/nl2sql_engine-*.tar.gz dist/nl2sql_engine-*.whl
-> nl2sql_engine-0.1.0-py3-none-any.whl
nl2sql_engine-0.1.0.tar.gz
nl2sql-api dist/nl2sql_api-*.tar.gz dist/nl2sql_api-*.whl
-> nl2sql_api-0.1.0-py3-none-any.whl
nl2sql_api-0.1.0.tar.gz
nl2sql-adapter-sdk dist/nl2sql_adapter_sdk-*.tar.gz dist/nl2sql_adapter_sdk-*.whl
-> nl2sql_adapter_sdk-0.1.0-py3-none-any.whl
nl2sql_adapter_sdk-0.1.0.tar.gz

id-token: write stays on the publishing job, since OIDC is the entire
credential. build and its smoke gate are untouched: if the wheels do not
install and import, nothing publishes.

The job name stays pypi, so ghcr and docs keep needs: pypi unchanged —
a dependency on a matrix job waits for every leg, so both still run only
after all three uploads succeed.

build needs=- environment=-
pypi needs=build environment=pypi-${{ matrix.package }} id-token=write
leg nl2sql-engine -> pypi-nl2sql-engine
leg nl2sql-api -> pypi-nl2sql-api
leg nl2sql-adapter-sdk -> pypi-nl2sql-adapter-sdk
ghcr needs=pypi
docs needs=pypi

Action required before the first release

The pending publishers must be registered with these environment names. At
pypi.org/manage/account/publishing,
for each of the three project names, use owner nadeem4, repository nl2sql,
workflow publish_pypi.yaml, and the environment from the table above.
Leaving Environment blank is what caused the rejection — nl2sql-engine's
existing entry was registered with no environment and needs to be recreated with
pypi-nl2sql-engine.

The GitHub Environments themselves need no setup: referencing them in the
workflow is enough for GitHub to create them on the first run, after which they
appear under Settings → Environments (where reviewers or branch rules can be
added if the release should be gated further). None were created by this PR.

docs/development/releasing.md is updated to match: the pending-publisher table
now carries the Environment column, states the uniqueness constraint that forces
it, and no longer says to leave Environment empty.

Verification

  • yaml.safe_load parses publish_pypi.yaml; job graph as above.
  • Per-package glob expansion proven against a locally built dist/ (above).
  • Unit suite: 231 passed, 1 skipped, 47 deselected — run twice, unchanged.
  • Key-free integration: 28 passed — run twice, unchanged.
  • mkdocs build --strict clean (throwaway venv from requirements-docs.txt).
  • No version numbers or CHANGELOG.md touched; release-please owns both.

@nadeem4
nadeem4 merged commit 14da34c into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the ci/per-package-pypi-environments branch August 28, 2026 14:21
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci: publish each distribution from its own PyPI environment - #63

Merged
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments
Aug 28, 2026
Merged

ci: publish each distribution from its own PyPI environment#63
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

Why

Registering the PyPI pending trusted publishers failed for real. nl2sql-engine
registered fine; nl2sql-api was rejected with:

A pending trusted publisher matching this configuration has already been
registered for a different project name.

PyPI keys a pending publisher on the tuple (owner, repository, workflow,
environment)
and requires it to be unique. All three of our distributions
publish from the same owner, the same repository and the same publish_pypi.yaml
with no environment — so all three tuples were identical and only the first
registration could succeed.

The environment is the only field left that can distinguish them.

What changed

publish_pypi.yaml's single pypi job becomes a three-leg matrix, one leg per
package, each in its own GitHub Environment:

PackageEnvironment
nl2sql-enginepypi-nl2sql-engine
nl2sql-apipypi-nl2sql-api
nl2sql-adapter-sdkpypi-nl2sql-adapter-sdk

A matrix rather than three copy-pasted jobs: GitHub evaluates expressions in
environment:, so environment: pypi-${{ matrix.package }} expresses the whole
difference between the legs in one line, and the publish steps stay identical.

pypa/gh-action-pypi-publish uploads everything in packages-dir, so each leg
downloads the shared dist artifact and copies only its own sdist + wheel
into upload/ before pointing packages-dir at it. The trailing - in the
glob is load-bearing — nl2sql_a* would match both nl2sql_api and
nl2sql_adapter_sdk. Verified against a locally built dist/ holding all six
files; each glob selects exactly two:

nl2sql-engine dist/nl2sql_engine-*.tar.gz dist/nl2sql_engine-*.whl
-> nl2sql_engine-0.1.0-py3-none-any.whl
nl2sql_engine-0.1.0.tar.gz
nl2sql-api dist/nl2sql_api-*.tar.gz dist/nl2sql_api-*.whl
-> nl2sql_api-0.1.0-py3-none-any.whl
nl2sql_api-0.1.0.tar.gz
nl2sql-adapter-sdk dist/nl2sql_adapter_sdk-*.tar.gz dist/nl2sql_adapter_sdk-*.whl
-> nl2sql_adapter_sdk-0.1.0-py3-none-any.whl
nl2sql_adapter_sdk-0.1.0.tar.gz

id-token: write stays on the publishing job, since OIDC is the entire
credential. build and its smoke gate are untouched: if the wheels do not
install and import, nothing publishes.

The job name stays pypi, so ghcr and docs keep needs: pypi unchanged —
a dependency on a matrix job waits for every leg, so both still run only
after all three uploads succeed.

build needs=- environment=-
pypi needs=build environment=pypi-${{ matrix.package }} id-token=write
leg nl2sql-engine -> pypi-nl2sql-engine
leg nl2sql-api -> pypi-nl2sql-api
leg nl2sql-adapter-sdk -> pypi-nl2sql-adapter-sdk
ghcr needs=pypi
docs needs=pypi

Action required before the first release

The pending publishers must be registered with these environment names. At
pypi.org/manage/account/publishing,
for each of the three project names, use owner nadeem4, repository nl2sql,
workflow publish_pypi.yaml, and the environment from the table above.
Leaving Environment blank is what caused the rejection — nl2sql-engine's
existing entry was registered with no environment and needs to be recreated with
pypi-nl2sql-engine.

The GitHub Environments themselves need no setup: referencing them in the
workflow is enough for GitHub to create them on the first run, after which they
appear under Settings → Environments (where reviewers or branch rules can be
added if the release should be gated further). None were created by this PR.

docs/development/releasing.md is updated to match: the pending-publisher table
now carries the Environment column, states the uniqueness constraint that forces
it, and no longer says to leave Environment empty.

Verification

  • yaml.safe_load parses publish_pypi.yaml; job graph as above.
  • Per-package glob expansion proven against a locally built dist/ (above).
  • Unit suite: 231 passed, 1 skipped, 47 deselected — run twice, unchanged.
  • Key-free integration: 28 passed — run twice, unchanged.
  • mkdocs build --strict clean (throwaway venv from requirements-docs.txt).
  • No version numbers or CHANGELOG.md touched; release-please owns both.

@nadeem4
nadeem4 merged commit 14da34c into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the ci/per-package-pypi-environments branch August 28, 2026 14:21
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

ci: publish each distribution from its own PyPI environment - #63

Merged
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments
Aug 28, 2026
Merged

ci: publish each distribution from its own PyPI environment#63
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

Why

Registering the PyPI pending trusted publishers failed for real. nl2sql-engine
registered fine; nl2sql-api was rejected with:

A pending trusted publisher matching this configuration has already been
registered for a different project name.

PyPI keys a pending publisher on the tuple (owner, repository, workflow,
environment)
and requires it to be unique. All three of our distributions
publish from the same owner, the same repository and the same publish_pypi.yaml
with no environment — so all three tuples were identical and only the first
registration could succeed.

The environment is the only field left that can distinguish them.

What changed

publish_pypi.yaml's single pypi job becomes a three-leg matrix, one leg per
package, each in its own GitHub Environment:

PackageEnvironment
nl2sql-enginepypi-nl2sql-engine
nl2sql-apipypi-nl2sql-api
nl2sql-adapter-sdkpypi-nl2sql-adapter-sdk

A matrix rather than three copy-pasted jobs: GitHub evaluates expressions in
environment:, so environment: pypi-${{ matrix.package }} expresses the whole
difference between the legs in one line, and the publish steps stay identical.

pypa/gh-action-pypi-publish uploads everything in packages-dir, so each leg
downloads the shared dist artifact and copies only its own sdist + wheel
into upload/ before pointing packages-dir at it. The trailing - in the
glob is load-bearing — nl2sql_a* would match both nl2sql_api and
nl2sql_adapter_sdk. Verified against a locally built dist/ holding all six
files; each glob selects exactly two:

nl2sql-engine dist/nl2sql_engine-*.tar.gz dist/nl2sql_engine-*.whl
-> nl2sql_engine-0.1.0-py3-none-any.whl
nl2sql_engine-0.1.0.tar.gz
nl2sql-api dist/nl2sql_api-*.tar.gz dist/nl2sql_api-*.whl
-> nl2sql_api-0.1.0-py3-none-any.whl
nl2sql_api-0.1.0.tar.gz
nl2sql-adapter-sdk dist/nl2sql_adapter_sdk-*.tar.gz dist/nl2sql_adapter_sdk-*.whl
-> nl2sql_adapter_sdk-0.1.0-py3-none-any.whl
nl2sql_adapter_sdk-0.1.0.tar.gz

id-token: write stays on the publishing job, since OIDC is the entire
credential. build and its smoke gate are untouched: if the wheels do not
install and import, nothing publishes.

The job name stays pypi, so ghcr and docs keep needs: pypi unchanged —
a dependency on a matrix job waits for every leg, so both still run only
after all three uploads succeed.

build needs=- environment=-
pypi needs=build environment=pypi-${{ matrix.package }} id-token=write
leg nl2sql-engine -> pypi-nl2sql-engine
leg nl2sql-api -> pypi-nl2sql-api
leg nl2sql-adapter-sdk -> pypi-nl2sql-adapter-sdk
ghcr needs=pypi
docs needs=pypi

Action required before the first release

The pending publishers must be registered with these environment names. At
pypi.org/manage/account/publishing,
for each of the three project names, use owner nadeem4, repository nl2sql,
workflow publish_pypi.yaml, and the environment from the table above.
Leaving Environment blank is what caused the rejection — nl2sql-engine's
existing entry was registered with no environment and needs to be recreated with
pypi-nl2sql-engine.

The GitHub Environments themselves need no setup: referencing them in the
workflow is enough for GitHub to create them on the first run, after which they
appear under Settings → Environments (where reviewers or branch rules can be
added if the release should be gated further). None were created by this PR.

docs/development/releasing.md is updated to match: the pending-publisher table
now carries the Environment column, states the uniqueness constraint that forces
it, and no longer says to leave Environment empty.

Verification

  • yaml.safe_load parses publish_pypi.yaml; job graph as above.
  • Per-package glob expansion proven against a locally built dist/ (above).
  • Unit suite: 231 passed, 1 skipped, 47 deselected — run twice, unchanged.
  • Key-free integration: 28 passed — run twice, unchanged.
  • mkdocs build --strict clean (throwaway venv from requirements-docs.txt).
  • No version numbers or CHANGELOG.md touched; release-please owns both.

@nadeem4
nadeem4 merged commit 14da34c into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the ci/per-package-pypi-environments branch August 28, 2026 14:21
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci: publish each distribution from its own PyPI environment - #63

Merged
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments
Aug 28, 2026
Merged

ci: publish each distribution from its own PyPI environment#63
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

Why

Registering the PyPI pending trusted publishers failed for real. nl2sql-engine
registered fine; nl2sql-api was rejected with:

A pending trusted publisher matching this configuration has already been
registered for a different project name.

PyPI keys a pending publisher on the tuple (owner, repository, workflow,
environment)
and requires it to be unique. All three of our distributions
publish from the same owner, the same repository and the same publish_pypi.yaml
with no environment — so all three tuples were identical and only the first
registration could succeed.

The environment is the only field left that can distinguish them.

What changed

publish_pypi.yaml's single pypi job becomes a three-leg matrix, one leg per
package, each in its own GitHub Environment:

PackageEnvironment
nl2sql-enginepypi-nl2sql-engine
nl2sql-apipypi-nl2sql-api
nl2sql-adapter-sdkpypi-nl2sql-adapter-sdk

A matrix rather than three copy-pasted jobs: GitHub evaluates expressions in
environment:, so environment: pypi-${{ matrix.package }} expresses the whole
difference between the legs in one line, and the publish steps stay identical.

pypa/gh-action-pypi-publish uploads everything in packages-dir, so each leg
downloads the shared dist artifact and copies only its own sdist + wheel
into upload/ before pointing packages-dir at it. The trailing - in the
glob is load-bearing — nl2sql_a* would match both nl2sql_api and
nl2sql_adapter_sdk. Verified against a locally built dist/ holding all six
files; each glob selects exactly two:

nl2sql-engine dist/nl2sql_engine-*.tar.gz dist/nl2sql_engine-*.whl
-> nl2sql_engine-0.1.0-py3-none-any.whl
nl2sql_engine-0.1.0.tar.gz
nl2sql-api dist/nl2sql_api-*.tar.gz dist/nl2sql_api-*.whl
-> nl2sql_api-0.1.0-py3-none-any.whl
nl2sql_api-0.1.0.tar.gz
nl2sql-adapter-sdk dist/nl2sql_adapter_sdk-*.tar.gz dist/nl2sql_adapter_sdk-*.whl
-> nl2sql_adapter_sdk-0.1.0-py3-none-any.whl
nl2sql_adapter_sdk-0.1.0.tar.gz

id-token: write stays on the publishing job, since OIDC is the entire
credential. build and its smoke gate are untouched: if the wheels do not
install and import, nothing publishes.

The job name stays pypi, so ghcr and docs keep needs: pypi unchanged —
a dependency on a matrix job waits for every leg, so both still run only
after all three uploads succeed.

build needs=- environment=-
pypi needs=build environment=pypi-${{ matrix.package }} id-token=write
leg nl2sql-engine -> pypi-nl2sql-engine
leg nl2sql-api -> pypi-nl2sql-api
leg nl2sql-adapter-sdk -> pypi-nl2sql-adapter-sdk
ghcr needs=pypi
docs needs=pypi

Action required before the first release

The pending publishers must be registered with these environment names. At
pypi.org/manage/account/publishing,
for each of the three project names, use owner nadeem4, repository nl2sql,
workflow publish_pypi.yaml, and the environment from the table above.
Leaving Environment blank is what caused the rejection — nl2sql-engine's
existing entry was registered with no environment and needs to be recreated with
pypi-nl2sql-engine.

The GitHub Environments themselves need no setup: referencing them in the
workflow is enough for GitHub to create them on the first run, after which they
appear under Settings → Environments (where reviewers or branch rules can be
added if the release should be gated further). None were created by this PR.

docs/development/releasing.md is updated to match: the pending-publisher table
now carries the Environment column, states the uniqueness constraint that forces
it, and no longer says to leave Environment empty.

Verification

  • yaml.safe_load parses publish_pypi.yaml; job graph as above.
  • Per-package glob expansion proven against a locally built dist/ (above).
  • Unit suite: 231 passed, 1 skipped, 47 deselected — run twice, unchanged.
  • Key-free integration: 28 passed — run twice, unchanged.
  • mkdocs build --strict clean (throwaway venv from requirements-docs.txt).
  • No version numbers or CHANGELOG.md touched; release-please owns both.

@nadeem4
nadeem4 merged commit 14da34c into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the ci/per-package-pypi-environments branch August 28, 2026 14:21
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci: publish each distribution from its own PyPI environment - #63

Merged
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments
Aug 28, 2026
Merged

ci: publish each distribution from its own PyPI environment#63
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

Why

Registering the PyPI pending trusted publishers failed for real. nl2sql-engine
registered fine; nl2sql-api was rejected with:

A pending trusted publisher matching this configuration has already been
registered for a different project name.

PyPI keys a pending publisher on the tuple (owner, repository, workflow,
environment)
and requires it to be unique. All three of our distributions
publish from the same owner, the same repository and the same publish_pypi.yaml
with no environment — so all three tuples were identical and only the first
registration could succeed.

The environment is the only field left that can distinguish them.

What changed

publish_pypi.yaml's single pypi job becomes a three-leg matrix, one leg per
package, each in its own GitHub Environment:

PackageEnvironment
nl2sql-enginepypi-nl2sql-engine
nl2sql-apipypi-nl2sql-api
nl2sql-adapter-sdkpypi-nl2sql-adapter-sdk

A matrix rather than three copy-pasted jobs: GitHub evaluates expressions in
environment:, so environment: pypi-${{ matrix.package }} expresses the whole
difference between the legs in one line, and the publish steps stay identical.

pypa/gh-action-pypi-publish uploads everything in packages-dir, so each leg
downloads the shared dist artifact and copies only its own sdist + wheel
into upload/ before pointing packages-dir at it. The trailing - in the
glob is load-bearing — nl2sql_a* would match both nl2sql_api and
nl2sql_adapter_sdk. Verified against a locally built dist/ holding all six
files; each glob selects exactly two:

nl2sql-engine dist/nl2sql_engine-*.tar.gz dist/nl2sql_engine-*.whl
-> nl2sql_engine-0.1.0-py3-none-any.whl
nl2sql_engine-0.1.0.tar.gz
nl2sql-api dist/nl2sql_api-*.tar.gz dist/nl2sql_api-*.whl
-> nl2sql_api-0.1.0-py3-none-any.whl
nl2sql_api-0.1.0.tar.gz
nl2sql-adapter-sdk dist/nl2sql_adapter_sdk-*.tar.gz dist/nl2sql_adapter_sdk-*.whl
-> nl2sql_adapter_sdk-0.1.0-py3-none-any.whl
nl2sql_adapter_sdk-0.1.0.tar.gz

id-token: write stays on the publishing job, since OIDC is the entire
credential. build and its smoke gate are untouched: if the wheels do not
install and import, nothing publishes.

The job name stays pypi, so ghcr and docs keep needs: pypi unchanged —
a dependency on a matrix job waits for every leg, so both still run only
after all three uploads succeed.

build needs=- environment=-
pypi needs=build environment=pypi-${{ matrix.package }} id-token=write
leg nl2sql-engine -> pypi-nl2sql-engine
leg nl2sql-api -> pypi-nl2sql-api
leg nl2sql-adapter-sdk -> pypi-nl2sql-adapter-sdk
ghcr needs=pypi
docs needs=pypi

Action required before the first release

The pending publishers must be registered with these environment names. At
pypi.org/manage/account/publishing,
for each of the three project names, use owner nadeem4, repository nl2sql,
workflow publish_pypi.yaml, and the environment from the table above.
Leaving Environment blank is what caused the rejection — nl2sql-engine's
existing entry was registered with no environment and needs to be recreated with
pypi-nl2sql-engine.

The GitHub Environments themselves need no setup: referencing them in the
workflow is enough for GitHub to create them on the first run, after which they
appear under Settings → Environments (where reviewers or branch rules can be
added if the release should be gated further). None were created by this PR.

docs/development/releasing.md is updated to match: the pending-publisher table
now carries the Environment column, states the uniqueness constraint that forces
it, and no longer says to leave Environment empty.

Verification

  • yaml.safe_load parses publish_pypi.yaml; job graph as above.
  • Per-package glob expansion proven against a locally built dist/ (above).
  • Unit suite: 231 passed, 1 skipped, 47 deselected — run twice, unchanged.
  • Key-free integration: 28 passed — run twice, unchanged.
  • mkdocs build --strict clean (throwaway venv from requirements-docs.txt).
  • No version numbers or CHANGELOG.md touched; release-please owns both.

@nadeem4
nadeem4 merged commit 14da34c into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the ci/per-package-pypi-environments branch August 28, 2026 14:21
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

ci: publish each distribution from its own PyPI environment - #63

Merged
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments
Aug 28, 2026
Merged

ci: publish each distribution from its own PyPI environment#63
nadeem4 merged 1 commit into
mainfrom
ci/per-package-pypi-environments

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

Why

Registering the PyPI pending trusted publishers failed for real. nl2sql-engine
registered fine; nl2sql-api was rejected with:

A pending trusted publisher matching this configuration has already been
registered for a different project name.

PyPI keys a pending publisher on the tuple (owner, repository, workflow,
environment)
and requires it to be unique. All three of our distributions
publish from the same owner, the same repository and the same publish_pypi.yaml
with no environment — so all three tuples were identical and only the first
registration could succeed.

The environment is the only field left that can distinguish them.

What changed

publish_pypi.yaml's single pypi job becomes a three-leg matrix, one leg per
package, each in its own GitHub Environment:

PackageEnvironment
nl2sql-enginepypi-nl2sql-engine
nl2sql-apipypi-nl2sql-api
nl2sql-adapter-sdkpypi-nl2sql-adapter-sdk

A matrix rather than three copy-pasted jobs: GitHub evaluates expressions in
environment:, so environment: pypi-${{ matrix.package }} expresses the whole
difference between the legs in one line, and the publish steps stay identical.

pypa/gh-action-pypi-publish uploads everything in packages-dir, so each leg
downloads the shared dist artifact and copies only its own sdist + wheel
into upload/ before pointing packages-dir at it. The trailing - in the
glob is load-bearing — nl2sql_a* would match both nl2sql_api and
nl2sql_adapter_sdk. Verified against a locally built dist/ holding all six
files; each glob selects exactly two:

nl2sql-engine dist/nl2sql_engine-*.tar.gz dist/nl2sql_engine-*.whl
-> nl2sql_engine-0.1.0-py3-none-any.whl
nl2sql_engine-0.1.0.tar.gz
nl2sql-api dist/nl2sql_api-*.tar.gz dist/nl2sql_api-*.whl
-> nl2sql_api-0.1.0-py3-none-any.whl
nl2sql_api-0.1.0.tar.gz
nl2sql-adapter-sdk dist/nl2sql_adapter_sdk-*.tar.gz dist/nl2sql_adapter_sdk-*.whl
-> nl2sql_adapter_sdk-0.1.0-py3-none-any.whl
nl2sql_adapter_sdk-0.1.0.tar.gz

id-token: write stays on the publishing job, since OIDC is the entire
credential. build and its smoke gate are untouched: if the wheels do not
install and import, nothing publishes.

The job name stays pypi, so ghcr and docs keep needs: pypi unchanged —
a dependency on a matrix job waits for every leg, so both still run only
after all three uploads succeed.

build needs=- environment=-
pypi needs=build environment=pypi-${{ matrix.package }} id-token=write
leg nl2sql-engine -> pypi-nl2sql-engine
leg nl2sql-api -> pypi-nl2sql-api
leg nl2sql-adapter-sdk -> pypi-nl2sql-adapter-sdk
ghcr needs=pypi
docs needs=pypi

Action required before the first release

The pending publishers must be registered with these environment names. At
pypi.org/manage/account/publishing,
for each of the three project names, use owner nadeem4, repository nl2sql,
workflow publish_pypi.yaml, and the environment from the table above.
Leaving Environment blank is what caused the rejection — nl2sql-engine's
existing entry was registered with no environment and needs to be recreated with
pypi-nl2sql-engine.

The GitHub Environments themselves need no setup: referencing them in the
workflow is enough for GitHub to create them on the first run, after which they
appear under Settings → Environments (where reviewers or branch rules can be
added if the release should be gated further). None were created by this PR.

docs/development/releasing.md is updated to match: the pending-publisher table
now carries the Environment column, states the uniqueness constraint that forces
it, and no longer says to leave Environment empty.

Verification

  • yaml.safe_load parses publish_pypi.yaml; job graph as above.
  • Per-package glob expansion proven against a locally built dist/ (above).
  • Unit suite: 231 passed, 1 skipped, 47 deselected — run twice, unchanged.
  • Key-free integration: 28 passed — run twice, unchanged.
  • mkdocs build --strict clean (throwaway venv from requirements-docs.txt).
  • No version numbers or CHANGELOG.md touched; release-please owns both.

@nadeem4
nadeem4 merged commit 14da34c into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the ci/per-package-pypi-environments branch August 28, 2026 14:21
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4