Skip to content

Support additional GitHub-hosted scopes via lpm config#47

Open
sclaiborne wants to merge 2 commits into
mainfrom
feature/multi-scope-registries
Open

Support additional GitHub-hosted scopes via lpm config#47
sclaiborne wants to merge 2 commits into
mainfrom
feature/multi-scope-registries

Conversation

@sclaiborne

Copy link
Copy Markdown
Member

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.

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.

Copilot AI 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.

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.py to 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, .npmrc generation in login(), per-scope npm logout, org-resolved git clone, multi-scope node_modules traversal, and a combined multi-org viewall (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 thread src/lpm_core.py
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 thread src/lpm_core.py Outdated
Comment on lines +135 to +139
try:
executeStandard(command)
except Exception:
# Don't let a failure on one scope abort the rest.
pass
Comment thread src/lpm_core.py
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 thread src/lpm_core.py Outdated
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 thread src/lpm_config.py Outdated
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
Comment thread src/LPM.py Outdated
# 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.
Sign up for free to 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.

2 participants