Skip to content

Sync shared package via repository_dispatch and stop Dependabot duplicates#695

Open
edvilme wants to merge 13 commits into
mainfrom
shared-package-sync
Open

Sync shared package via repository_dispatch and stop Dependabot duplicates#695
edvilme wants to merge 13 commits into
mainfrom
shared-package-sync

Conversation

@edvilme

@edvilme edvilme commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Two related changes so the shared package (@vscode/common-python-lsp npm / vscode-common-python-lsp pip) is updated via a repository_dispatch event from vscode-common-python-lsp instead of by Dependabot.

1. New .github/workflows/shared-package-release.yml

Handles repository_dispatch (shared-package-release): branches off main, bumps the npm dependency, rewrites the pip pin in requirements.in and recompiles requirements.txt with uv, then pushes the branch and opens a tracking issue with a manual compare/PR link (org settings prevent the workflow from opening PRs automatically).

2. .github/dependabot.yml

Moves the shared package from the group exclude-patterns (which still produced a standalone Dependabot PR) into the npm and pip ignore lists, so Dependabot no longer opens duplicate PRs for it.

…icates

Adds .github/workflows/shared-package-release.yml to handle
repository_dispatch (shared-package-release) events from
vscode-common-python-lsp: it bumps the npm dep, recompiles the pip pin via
uv, pushes a branch, and opens a tracking issue with a manual-PR link
(org settings block auto-created PRs).

Removes @vscode/common-python-lsp (npm) and vscode-common-python-lsp (pip)
from Dependabot via the ignore lists so Dependabot no longer opens
duplicate update PRs for the shared package.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@edvilme edvilme added the debt Code quality issues label Jun 30, 2026
Replace the inline Python rewrite of requirements.in with a sed one-liner,
keeping uv pip compile --generate-hashes --upgrade to regenerate the
hash-locked requirements.txt (matching the command documented in
requirements.in).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@edvilme edvilme enabled auto-merge (squash) June 30, 2026 21:44
Comment thread .github/workflows/shared-package-submodule-sync.yml Outdated
Comment thread .github/workflows/shared-package-submodule-sync.yml Outdated
Comment thread .github/workflows/shared-package-release.yml Outdated
Comment thread .github/workflows/shared-package-submodule-sync.yml Outdated
Comment thread .github/workflows/shared-package-submodule-sync.yml Outdated
@rchiodo

rchiodo commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Main theme: the dispatch workflow is functional but has a correctness gap in pip recompilation (missing --python-version) plus a few robustness gaps (re-dispatch idempotency, empty-tag validation, blanket --upgrade, silent sed no-op). Addressing the pip-version issue is required before merge.

@edvilme edvilme disabled auto-merge June 30, 2026 22:33
- Pin uv pip compile to the Python version documented in requirements.in
  (--python-version) so markers like exceptiongroup are not dropped
- Scope the recompile with --upgrade-package so unrelated deps don't move
- Assert the pip pin actually changed after sed; fail fast otherwise
- Validate release_tag is present and version-shaped before any work
- Add concurrency group, --force-with-lease, tracking-issue dedup, and
  tolerate Issues being disabled (surface compare URL via job summary)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@edvilme

edvilme commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @rchiodo — addressed the feedback in the latest commit:

  • Python version / reproducibility: the workflow now pins PYTHON_VERSION to the version documented in this repo's requirements.in and passes --python-version to uv pip compile, so environment markers (e.g. exceptiongroup) aren't dropped when the lockfile is regenerated.
  • Blanket upgrade: replaced --upgrade with --upgrade-package "${PIP_DEP}" so only the shared package (and its forced transitives) move.
  • Silent sed no-op: the pin rewrite is now case-insensitive and is followed by a grep assertion that fails the job if requirements.in wasn't actually re-pinned.
  • Payload validation: a new step fails fast if release_tag is empty or not version/ref-shaped (^v?[0-9A-Za-z][0-9A-Za-z.+-]*$), preventing degenerate branches and npm install pkg@ resolving to latest.
  • Idempotency / re-runs: added a concurrency group keyed on release_tag, switched the push to --force-with-lease (fetching any existing remote branch first), de-duplicated the tracking issue (reuse/comment on an existing open one), and made issue creation tolerant of Issues being disabled by also writing the compare URL to the job summary.

Comment thread .github/workflows/shared-package-submodule-sync.yml Outdated
rchiodo
rchiodo previously approved these changes Jun 30, 2026

@rchiodo rchiodo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

* Integrate shared package as a git submodule

Replaces the external @vscode/common-python-lsp / vscode-common-python-lsp
dependency with a git submodule at external/vscode-common-python-lsp (pinned
to v0.8.0). npm consumes it via a file: reference, the Python lib is bundled
from the submodule, and the release pipeline now bumps the submodule commit
instead of a version pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix CI for git submodule integration

- Build the shared package TypeScript dist via a postinstall hook so the
  bundler and type-checker can resolve the file: dependency.
- Regenerate package-lock.json to record the file: link and the submodule
  dependency tree.
- Exclude external/ from the extension tsconfig so the submodule sources are
  not type-checked against the extension rootDir.
- Check out submodules (recursive) in PR and push CI so the submodule and its
  Python library are present for npm and nox.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix VSIX packaging and type-checking for submodule integration

- Exclude external/ from the VSIX (.vscodeignore) since webpack already
  bundles the shared package; avoids case-insensitive path collisions from
  the submodule node_modules.
- Add skipLibCheck to tsconfig where missing so hoisted submodule devDep
  type definitions do not fail the extension type-check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Checkout submodules in Azure DevOps pipelines

The 1ES/DevDiv checkout template does not initialize submodules, so add an
explicit git submodule update --init --recursive step before npm ci and nox
in the validation, stable, and pre-release pipelines.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Init submodules manually in isort tests job

The tests job checks out into a working directory with spaces and unicode
(the special-working-directory path), which breaks actions/checkout submodule
initialization. Check out without submodules there and run git submodule
update --init --recursive as a separate step instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
edvilme and others added 2 commits July 1, 2026 15:08
Consolidate the sync steps into a single script, add a retry wrapper for
network operations, fail when the released tag is missing instead of
silently falling back to main, and surface failures as workflow annotations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Pin an explicit --force-with-lease baseline so concurrent branch
  updates are not silently overwritten
- Regenerate the lockfile in the sync workflow so produced branches
  stay npm ci-mergeable
- Require a real semver release_tag in the dispatch payload
- Guard the postinstall build so a clone without the submodule warns
  instead of failing npm install

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread noxfile.py
Comment thread .github/dependabot.yml Outdated
@rchiodo

rchiodo commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Overall this looks good and safe to merge. One thing worth confirming before merge: the shared Python lib is now installed from the submodule with --no-deps, so its runtime dependencies must be guaranteed present in requirements.txt. The other candidate findings referencing shared-package-release.yml couldn't be grounded in this PR's diff (that file isn't among the changed files here), so I've dropped them.

rchiodo
rchiodo previously approved these changes Jul 1, 2026

@rchiodo rchiodo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

- Split sync workflow into prepare (validate/normalize tag) and sync jobs;
  create-once branch semantics (no force-push), normalized concurrency key,
  pin actions/checkout@v4
- Fix dependabot.yml comments to reference shared-package-submodule-sync
- Drop dangling '# via vscode-common-python-lsp' annotations from requirements.txt
- Document the shared-package runtime-dep contract in requirements.in

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread build/postinstall.js
);
process.exit(0);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 build/postinstall.js:18
The guard handles a missing submodule (warn + exit 0) but not a submodule that is present but fails npm run build. execSync throws on any nested tsc/toolchain error → non-zero exit → npm ci/npm install fails for everyone, including unrelated dev work. Previously the published npm package shipped prebuilt JS, so installs never compiled shared code. At minimum emit a clear actionable error on build failure; consider a SKIP_SHARED_BUILD opt-out for install flows that don't need the TS build.

@rchiodo

rchiodo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Overall this is a solid, well-guarded change. The main non-blocking concern is that sourcing the shared package from the submodule now couples both the npm postinstall build and the --no-deps Python install to invariants that aren't enforced automatically. Worth hardening those two spots before this lands widely.

rchiodo
rchiodo previously approved these changes Jul 2, 2026

@rchiodo rchiodo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

bschnurr
bschnurr previously approved these changes Jul 2, 2026
edvilme and others added 4 commits July 2, 2026 10:47
Add a guard to the submodule-sync workflow that fails the run if the
extension's minimum Python (runtime.txt) is older than the shared
package's requires-python floor, preventing a shipped bundle that the
shared library can't import.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Break the single monolithic shell step into focused steps (guard, create
branch, move submodule, verify Python floor, detect change, commit/push,
tracking issue). Replace in-shell early-exits with step outputs and if:
conditions, and expose the retry helper to every step via BASH_ENV so
network operations stay clean one-liners. No behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a Development section explaining that the shared vscode-common-python-lsp
library is a git submodule and how to initialize it (clone with
--recurse-submodules, or git submodule update --init --recursive for an
existing clone) before running npm install.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a guard that fails the sync if the extension's pinned Node (.nvmrc) is
older than the shared package's pinned Node (submodule .nvmrc), since the
extension builds the shared package's TypeScript during install.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread build/postinstall.js
process.exit(0);
}

execSync(`npm --prefix ${pkgDir} run build`, { stdio: "inherit" });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 build/postinstall.js:19
The guard only handles a missing submodule (warn + exit 0); a submodule that is present but fails npm run build throws out of execSync → non-zero exit → npm install/npm ci hard-fails for everyone. The Skeptic escalated this: the submodule's tsc is a devDependency of the file:-linked package, so a --omit=dev/production install won't provision the build toolchain and this step fails with tsc: not found. This is a regression from the old prebuilt-npm-package behavior. Wrap execSync in try/catch with an actionable error (and consider a SKIP_SHARED_BUILD opt-out) so a bad pinned commit or production install doesn't hard-block unrelated installs.

[verified]

Comment thread noxfile.py
"--no-deps",
"--upgrade",
"./external/vscode-common-python-lsp/python",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 noxfile.py:99
The Advocate flagged that _update_npm_packages rewrites every non-pinned dependency to the latest ^-version from the npm registry, and @vscode/common-python-lsp is not in the pinned set — so running nox -s update_packages would overwrite the file:external/... link back to a published ^0.x version, silently undoing this PR's core mechanism. Add @vscode/common-python-lsp to the pinned set so the submodule wiring is protected.

[verified]

echo "### Shared package submodule update ready"
echo ''
echo "Branch \`${BRANCH}\` has been pushed, moving \`${SUBMODULE_PATH}\` to ${RELEASE_TAG}."
echo ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 .github/workflows/shared-package-submodule-sync.yml:150
The Skeptic notes the branch is pushed and a [Shared Package] Open PR… issue is filed claiming the update is "ready," but the lockfile refresh uses --ignore-scripts, so the shared TS package is never compiled and bundled/libs is never assembled in this job. A non-building submodule commit (or one adding an undeclared Python runtime dep) is reported as ready and only surfaces as broken when a human opens the PR. Consider running npm ci + build (and nox -s install_bundled_libs) on the branch before declaring it ready, or word the issue as "needs validation."

[verified]

@rchiodo

rchiodo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

The submodule migration is well thought out, but the postinstall build path is fragile (it only guards a missing submodule, not a failing build or a dev-toolchain-less install) and a couple of automation contracts around it are unenforced (nox can silently overwrite the file: link; --no-deps requires manual requirements.txt upkeep). Worth tightening before merge.

with:
path: ${{ env.special-working-directory-relative }}

- name: Checkout submodules

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 .github/workflows/pr-check.yml:60
The Python-floor and Node-floor compatibility gates live only in the dispatch workflow (shared-package-submodule-sync.yml). A maintainer who bumps external/vscode-common-python-lsp by hand in a PR bypasses both floors, and CI stays green even if the extension now claims support for an older Python than the shared lib requires. Mirror the floor checks as a shared step in pr-check.yml/push-check.yml.

[verified]

@rchiodo

rchiodo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

The submodule-based approach is well thought out, but a few robustness gaps around the postinstall build, the --no-deps runtime-dependency contract, and floor checks that only run on the dispatch path are worth addressing before merge.

Address reviewer feedback on the sync workflow:
- Retry the branch-existence ls-remote and only treat a definitive
  'no such ref' (exit 2) as absent, so a transient network error can no
  longer bypass the guard and clobber an existing branch.
- Run the no-op detection before the compatibility guards so a re-dispatch
  with nothing to sync exits cleanly instead of failing loudly.
- Compare the Python floor against the extension's declared minimum in
  src/common/constants.ts (not the bundling interpreter in runtime.txt),
  and the Node floor against the shared package's engines.node build floor
  (not the exact .nvmrc dev pin).
- Treat an unparseable package floor as a warning instead of hard-failing
  every future sync.
- Wrap 'npm install --package-lock-only' in the retry helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread build/postinstall.js
"Run `git submodule update --init --recursive` and reinstall to build it.",
);
process.exit(0);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 build/postinstall.js:11
The missing-submodule guard is largely dead code: npm resolves the file:external/vscode-common-python-lsp/typescript dependency (package.json:212) during install/link, before the root postinstall lifecycle runs. On a clone without --recurse-submodules npm aborts earlier with a cryptic ENOENT/link:true failure, so this friendly warn-and-exit(0) branch is rarely reached. Either drop/repurpose it, or bootstrap the submodule before the file: link resolves; at minimum don't advertise a graceful path npm preempts.

[verified]

Comment thread noxfile.py
"./bundled/libs",
"--no-cache-dir",
"--no-deps",
"--upgrade",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 noxfile.py:99
Partially-addressed prior High. session.install(..., '--no-deps', ...) makes requirements.txt responsible for the submodule's Python runtime deps, and while pygls/packaging/lsprotocol are now documented and pinned in requirements.in, nothing enforces the subset invariant. A future submodule bump that adds a Python runtime dep would ship a bundled/libs missing it and fail at import (fails closed, but latent). Add a sync-time assertion comparing the submodule's pyproject runtime deps against requirements.txt, or drop --no-deps.

[verified]

exit 1
else
echo "Node versions compatible: extension ${EXT_NODE} >= shared package build floor ${PKG_NODE}."
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 .github/workflows/shared-package-submodule-sync.yml:186
The commit step runs npm install --package-lock-only --ignore-scripts (no build, no nox install_bundled_libs), then pushes the branch and files a [Shared Package] Open PR… issue claiming the update is "ready." Because scripts are ignored, the shared TS package is never compiled in this job, so a non-building submodule commit is reported as ready and only surfaces as broken when a human opens the PR. Run npm ci + build (and nox -s install_bundled_libs) on the branch before declaring it ready, or word the issue as "needs validation."

[verified]

echo "branch=shared-package-v${VERSION}"
} >>"$GITHUB_OUTPUT"

sync:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 .github/workflows/shared-package-submodule-sync.yml:33
Residual input-validation bypass: grep -Eq matches per-line, so RELEASE_TAG=$'1.2.3\nmalicious' passes the semver gate and ${RELEASE_TAG#v} strips only a leading v, letting the embedded newline flow into version/branch. It fails closed and the payload comes from a trusted repository_dispatch sender, so this is low-surface, but it defeats the fail-fast intent. Reject any tag containing a newline outright (e.g. validate the raw value has no newline, or use grep -Ezqx).

[verified]

@rchiodo

rchiodo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

The submodule-based bundling approach is sound, but the postinstall build path is fragile: it must not hard-fail unrelated installs when tsc is unavailable (production/--omit=dev). A few workflow hardening notes (compat floors only enforced in the dispatch job, branch reported "ready" before it's built) are also worth addressing before merge.

@edvilme edvilme enabled auto-merge (squash) July 2, 2026 22:09
Point external/vscode-common-python-lsp at the v0.8.1 release and refresh
package-lock.json to keep the branch npm ci-mergeable. Python (>=3.10) and
Node (engines.node >=18.0.0) compatibility floors are unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread .vscodeignore
.vscode-test/**
out/**
node_modules/**
external/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 .vscodeignore:5
The Skeptic notes .vscodeignore external/** + the file: link is correct only because webpack bundles the built shared JS into dist/extension.js before packaging. This silently depends on postinstall having produced that build first; if postinstall is skipped (--ignore-scripts) before npm run package, webpack resolves an unbuilt main and the VSIX can ship broken with no error. Consider a guard/verification that the shared package built before packaging.

[verified]

@rchiodo

rchiodo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

The submodule approach is sound, but two things undermine its robustness: (1) the postinstall build path can hard-fail non-dev/CI installs and needs error handling + an opt-out, and (2) the pin wiring isn't self-defending against the repo's own nox -s update_packages. Addressing those two plus the CI-floor and validate-before-ready gaps would make this safe to merge.

@rchiodo

rchiodo commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🔒 Automated review in progress — @rchiodo is auto-reviewing this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

debt Code quality issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants