Support additional GitHub-hosted scopes via lpm config#47
Open
sclaiborne wants to merge 2 commits into
Open
Conversation
Adds src/lpm_config.py: a layered loader (~/.lpm/config.json + per-project lpmConfig in package.json) that exposes scope/org/registry helpers. Built-in fallback keeps @loupeteam behavior identical when no config is present. Refactors lpm_core.py and LPM.py to consult lpm_config for: scope-aware package normalization, .npmrc generation in login(), per-scope npm logout, git clone org resolution in installSource, multi-scope node_modules traversal in syncPackages, scope-aware library manifest creation, and a multi-org viewall (with new Scope column). lpm publish remains restricted to @loupeteam pending validation; TODO markers point at the single-line flip when ready. New tests in test/test_lpm_config.py cover load/merge precedence, default-scope override and fallback, scope_for_package/org_for_package, _normalize_packages, and login() registry-line generation per configured scope.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a multi-scope configuration layer so LPM can work with additional GitHub-hosted npm scopes/orgs/registries (while preserving @loupeteam behavior when no config is present), and refactors core flows to consult that config.
Changes:
- Introduces
src/lpm_config.pyto load/merge built-in + user-global + per-project scope configuration and expose scope/org/registry helpers. - Updates CLI/core logic for scope-aware package normalization,
.npmrcgeneration inlogin(), per-scopenpm logout, org-resolvedgit clone, multi-scopenode_modulestraversal, and a combined multi-orgviewall(with a Scope column). - Adds tests and docs covering configuration precedence and scope-aware behaviors.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/lpm_config.py |
New layered config loader + helpers for scope/org/registry resolution. |
src/lpm_core.py |
Refactors auth, cloning, dependency resolution, syncing, manifest generation, and viewall to be scope-aware. |
src/LPM.py |
Updates CLI package normalization to preserve explicit scopes; minor init/publish adjustments and comments. |
test/test_lpm_config.py |
New unit tests validating config merge/precedence and key scope-aware behaviors. |
README.md |
Documents configuring additional scopes/registries and resulting behavior changes. |
CHANGELOG.md |
Notes the new multi-scope configuration support in 1.1.0. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+115
to
+120
| for scope, info in lpm_config.get_scopes().items(): | ||
| host = info['registry'].split('://', 1)[-1].rstrip('/') | ||
| if host in auth_hosts_written: | ||
| continue | ||
| auth_hosts_written.add(host) | ||
| text.append(f'//{host}/:_authToken=' + token) |
Comment on lines
+135
to
+139
| try: | ||
| executeStandard(command) | ||
| except Exception: | ||
| # Don't let a failure on one scope abort the rest. | ||
| pass |
Comment on lines
274
to
280
| password = getLocalToken() | ||
| # Clone the repo into the target directory. | ||
| org = lpm_config.org_for_package(package) | ||
| command1 = [] | ||
| command1.append('git clone') | ||
| command1.append(f'https://{username}:{password}@github.com/loupeteam/{repoName}') | ||
| command1.append(f'https://{username}:{password}@github.com/{org}/{repoName}') | ||
| command1.append(repoPath) |
Comment on lines
806
to
812
| # Determine column widths. | ||
| name_col_width = max(len(package["name"]) for package in packages_sorted) + 2 | ||
| scope_col_width = max((len(package.get("_lpm_scope", "")) for package in packages_sorted), default=6) + 2 | ||
| scope_col_width = max(scope_col_width, len("SCOPE") + 2) | ||
| version_col_width = 12 | ||
| lastmod_col_width = 14 | ||
| description_col_width = max(len(description) for description in package_descriptions) |
Comment on lines
+1
to
+12
| ''' | ||
| * File: lpm_config.py | ||
| * Copyright (c) 2026 Loupe | ||
| * https://loupe.team | ||
| * | ||
| * This file is part of LPM, licensed under the MIT License. | ||
| ''' | ||
| ''' | ||
| LPM scope/registry configuration. | ||
|
|
||
| LPM was originally hardcoded to the @loupeteam npm scope (backed by the | ||
| `loupeteam` GitHub organization on npm.pkg.github.com). This module generalizes |
| # publish flow is verified for user-added scopes. The check would become: | ||
| # scope = lpm_config.scope_for_package(data['name']) | ||
| # if lpm_config.get_scope_info(scope) is None: ... | ||
| if data['name'].find('@loupeteam') != 0: |
- lpm_config.py: merge the two leading triple-quoted strings into a single
module docstring so __doc__ is correctly populated and doc tooling works.
- lpm_core.login(): parse the registry host with urllib.parse.urlparse so URLs
with paths or embedded credentials still produce a clean //host/:_authToken
line, and skip registries whose URL has no netloc rather than emitting a
broken auth entry.
- lpm_core.logout(): keep the per-scope continue-on-failure behavior but
collect each failure and surface a yellow warning per scope instead of
silently swallowing exceptions.
- lpm_core.installSource(): require the package's scope to be configured
before constructing the git clone URL. A typo or unknown scope now fails
fast with a clear error rather than silently cloning from the default org.
- lpm_core.printLoupePackageList(): handle the empty-result case explicitly
and use default= on the description-width max() so viewall does not crash
with ValueError when the registry returns zero packages.
- LPM.cmd_publish: replace data['name'].find('@loupeteam') != 0 with
startswith('@loupeteam/') so names like @loupeteamx/foo are no longer
accepted.
All 9 multi-scope tests still pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds src/lpm_config.py: a layered loader (~/.lpm/config.json + per-project lpmConfig in package.json) that exposes scope/org/registry helpers. Built-in fallback keeps @loupeteam behavior identical when no config is present.
Refactors lpm_core.py and LPM.py to consult lpm_config for: scope-aware package normalization, .npmrc generation in login(), per-scope npm logout, git clone org resolution in installSource, multi-scope node_modules traversal in syncPackages, scope-aware library manifest creation, and a multi-org viewall (with new Scope column).
lpm publish remains restricted to @loupeteam pending validation; TODO markers point at the single-line flip when ready.
New tests in test/test_lpm_config.py cover load/merge precedence, default-scope override and fallback, scope_for_package/org_for_package, _normalize_packages, and login() registry-line generation per configured scope.
What:
Describe what the changes are.
Why:
Describe the improvement(s) that these changes bring. If applicable, describe the reasoning behind key implementation details.