Skip to content

Add support for profiles from URL, including git and raw file#65

Merged
8bitAlex merged 3 commits intomainfrom
add-url
May 1, 2026
Merged

Add support for profiles from URL, including git and raw file#65
8bitAlex merged 3 commits intomainfrom
add-url

Conversation

@8bitAlex
Copy link
Copy Markdown
Owner

@8bitAlex 8bitAlex commented May 1, 2026

This pull request adds support for importing profiles from remote sources in addition to local files. Now, raid profile add can accept a git repository URL or a direct file URL, automatically detecting the type and handling the download or clone process. The documentation and tests are updated to reflect and validate this new functionality.

New profile import functionality:

  • src/cmd/profile/fetch.go: Added logic to detect if the argument is a URL, distinguish between git and raw file URLs, clone or download as appropriate, and register found profiles. Includes robust file detection, error handling, and ensures profiles are saved to the user's home directory.
  • src/cmd/profile/add.go: Updated to delegate to the new URL-handling logic when a URL is provided.

Documentation updates:

  • site/docs/whats-new.mdx, site/docs/features/profiles.mdx, site/docs/usage/profile.mdx, llms.txt: Updated to document the new ability to add profiles from git repositories and direct file URLs, including usage examples and behavioral details. [1] [2] [3] [4]

Internal utilities and testing:

  • src/internal/sys/system.go, src/internal/sys/system_test.go: Added and tested a new CopyFile utility to support copying profile files to their final destination. [1] [2]
  • src/cmd/profile/profile_test.go: Extended test harness to mock the new injectable functions for git and HTTP operations, ensuring testability of the new import logic. [1] [2] [3]

These changes make it much easier to share and reuse profile configurations across teams and environments.…and raw file URLs

Closes #23

Copilot AI review requested due to automatic review settings May 1, 2026 05:22
@codecov
Copy link
Copy Markdown

codecov Bot commented May 1, 2026

Codecov Report

❌ Patch coverage is 75.42857% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.56%. Comparing base (fa61749) to head (1934c0d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/cmd/profile/fetch.go 75.47% 31 Missing and 8 partials ⚠️
src/internal/sys/system.go 71.42% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #65      +/-   ##
==========================================
- Coverage   92.60%   91.56%   -1.05%     
==========================================
  Files          32       33       +1     
  Lines        2503     2678     +175     
==========================================
+ Hits         2318     2452     +134     
- Misses        115      147      +32     
- Partials       70       79       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds remote import support to the raid profile add command so profiles can be registered from either local files, git repository URLs, or direct raw file URLs—improving profile sharing workflows across teams.

Changes:

  • Added URL detection + fetch/clone flow for raid profile add, including profile discovery in cloned repos and downloading raw profile files.
  • Introduced sys.CopyFile utility (with tests) to persist fetched profile files locally before registration.
  • Updated CLI docs and added extensive tests covering URL-based profile imports and failure modes.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/cmd/profile/fetch.go New URL import implementation (detect git vs raw file, clone/download, discover profiles, register them).
src/cmd/profile/add.go Routes URL arguments to the new fetch/import path.
src/cmd/profile/fetch_test.go New unit + subprocess tests for URL import paths and error handling.
src/cmd/profile/profile_test.go Extends mocking harness to include new injectable fetch/git/http funcs.
src/internal/sys/system.go Adds CopyFile helper used by URL import to persist files.
src/internal/sys/system_test.go Adds tests for CopyFile.
site/docs/whats-new.mdx Documents new URL-based profile add capability.
site/docs/features/profiles.mdx Adds feature documentation and examples for URL imports.
site/docs/usage/profile.mdx Updates usage docs with URL-based profile add examples and behavior.
llms.txt Updates command summary to mention URL-based profile add support.

Comment thread site/docs/whats-new.mdx Outdated

## 0.7.0 — upcoming

**Add profiles from a URL.** `raid profile add` now accepts a git repo URL or a direct file URL in addition to a local path. Pass a GitHub (or any git host) URL and raid shallow-clones the repo, finds `*.raid.yaml` files at the root, and saves them to `~/<name>.raid.yaml`. Pass a raw HTTPS URL ending in `.yaml`/`.yml`/`.json` and raid downloads and registers it directly. Ambiguous HTTPS URLs are probed with `git ls-remote` to determine the right strategy automatically.
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

Release notes mention importing *.raid.yaml files from a repo, but the code also supports *.raid.yml and profile.json. Consider updating this note to reflect the full supported set (or restricting the implementation).

Suggested change
**Add profiles from a URL.** `raid profile add` now accepts a git repo URL or a direct file URL in addition to a local path. Pass a GitHub (or any git host) URL and raid shallow-clones the repo, finds `*.raid.yaml` files at the root, and saves them to `~/<name>.raid.yaml`. Pass a raw HTTPS URL ending in `.yaml`/`.yml`/`.json` and raid downloads and registers it directly. Ambiguous HTTPS URLs are probed with `git ls-remote` to determine the right strategy automatically.
**Add profiles from a URL.** `raid profile add` now accepts a git repo URL or a direct file URL in addition to a local path. Pass a GitHub (or any git host) URL and raid shallow-clones the repo, finds `*.raid.yaml`, `*.raid.yml`, or `profile.json` files at the root, and saves them to `~/<name>.raid.yaml`. Pass a raw HTTPS URL ending in `.yaml`/`.yml`/`.json` and raid downloads and registers it directly. Ambiguous HTTPS URLs are probed with `git ls-remote` to determine the right strategy automatically.

Copilot uses AI. Check for mistakes.
Comment thread src/cmd/profile/fetch.go
Comment on lines +203 to +210
home := getHomeDir()
var toRegister []pro.Profile
var destPaths []string
for _, q := range queued {
destPath := filepath.Join(home, q.p.Name+".raid.yaml")
if err := sys.CopyFile(q.srcPath, destPath); err != nil {
fmt.Printf("Failed to save profile '%s': %v\n", q.p.Name, err)
continue
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

processProfileFiles uses q.p.Name (from remote profile content) to build destPath without validating/sanitizing it. A crafted name containing path separators could write outside the home directory (path traversal) or create unexpected paths. Validate the profile name with sys.ValidateFileName (and/or enforce a safe filename mapping) before using it in filepath.Join and skip invalid names with a clear message.

Copilot uses AI. Check for mistakes.
Comment thread src/cmd/profile/fetch.go
Comment on lines +184 to +190
for _, p := range profiles {
if proContains(p.Name) {
existingNames = append(existingNames, p.Name)
continue
}
queued = append(queued, pending{p: p, srcPath: srcPath})
}
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

queued can contain multiple entries with the same profile name (e.g., duplicates across files, or within multi-doc YAML). Later, destPath := <home>/<name>.raid.yaml will be the same and the copies will overwrite each other before registration. Deduplicate queued profiles by name (and/or detect conflicts and skip with an explicit message) before copying/writing.

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +72
// CopyFile copies the file at src to dest, creating parent directories as needed.
func CopyFile(src, dest string) error {
data, err := os.ReadFile(src)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return err
}
return os.WriteFile(dest, data, 0644)
}
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

CopyFile does not call ExpandPath on src/dest (unlike CreateFile and FileExists) and returns raw errors without context. Consider expanding both paths and wrapping errors with enough context to debug failures. Also, reading the whole file into memory can be avoided by streaming copy (open src/dest and io.Copy).

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +67
```bash
# Shallow-clone a repo and import any *.raid.yaml files found at the root
raid profile add https://github.com/my-org/raid-profiles

# Download a single profile file directly
raid profile add https://raw.githubusercontent.com/my-org/repo/main/team.raid.yaml

# Register a local file
raid profile add ./team.raid.yaml
```
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

This description says repo import finds *.raid.yaml files, but the implementation also supports *.raid.yml and profile.json. Please align documentation and behavior to avoid surprises.

Copilot uses AI. Check for mistakes.
Comment thread src/cmd/profile/fetch.go Outdated
Comment on lines +102 to +106
u, _ := url.Parse(rawURL)
ext := strings.ToLower(filepath.Ext(u.Path))
if ext == "" {
ext = ".yaml"
}
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

url.Parse(rawURL) errors are ignored here; if parsing fails and returns a nil URL, using u.Path will panic. Handle the parse error and return a user-facing "invalid URL" error/exit code instead of continuing.

Copilot uses AI. Check for mistakes.
Comment thread src/cmd/profile/fetch.go
Comment on lines +23 to +33
httpGetFunc = func(rawURL string) ([]byte, error) {
resp, err := http.Get(rawURL) //nolint:gosec
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP %d from %s", resp.StatusCode, rawURL)
}
return io.ReadAll(resp.Body)
}
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

httpGetFunc uses http.Get with no timeout and then io.ReadAll with no size limit. A slow or very large response can hang the command or exhaust memory. Use an http.Client with a reasonable timeout and read via an io.LimitReader (and surface a helpful error when the limit is exceeded).

Copilot uses AI. Check for mistakes.
Comment thread src/cmd/profile/fetch.go Outdated
Comment on lines +145 to +156
entries, _ := os.ReadDir(dir)
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
lower := strings.ToLower(name)
ext := filepath.Ext(lower)
stem := strings.TrimSuffix(lower, ext)
if (ext == ".yaml" || ext == ".yml") && strings.HasSuffix(stem, ".raid") {
add(name)
}
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

os.ReadDir(dir) errors are dropped, which can lead to silently missing profile files and returning confusing "No profile files found" results. Handle the error and either return it (so the caller can fail) or print a warning and proceed with only the explicitly-checked filenames.

Suggested change
entries, _ := os.ReadDir(dir)
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
lower := strings.ToLower(name)
ext := filepath.Ext(lower)
stem := strings.TrimSuffix(lower, ext)
if (ext == ".yaml" || ext == ".yml") && strings.HasSuffix(stem, ".raid") {
add(name)
}
entries, err := os.ReadDir(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: failed to read profile directory %q: %v\n", dir, err)
} else {
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
lower := strings.ToLower(name)
ext := filepath.Ext(lower)
stem := strings.TrimSuffix(lower, ext)
if (ext == ".yaml" || ext == ".yml") && strings.HasSuffix(stem, ".raid") {
add(name)
}
}

Copilot uses AI. Check for mistakes.
Comment thread site/docs/usage/profile.mdx Outdated
Comment on lines +31 to +36
Add a profile from a git repository (raid shallow-clones it and looks for `*.raid.yaml` files at the repo root):

```bash
raid profile add https://github.com/my-org/raid-profiles
raid profile add git@github.com:my-org/raid-profiles.git
```
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

Docs say the repo import looks for *.raid.yaml files only, but findProfileFilesInDir also imports *.raid.yml and profile.json. Either update the docs to match the actual detection behavior or narrow the implementation to what is documented.

Copilot uses AI. Check for mistakes.
@8bitAlex 8bitAlex enabled auto-merge (squash) May 1, 2026 05:36
@8bitAlex 8bitAlex merged commit 7b0dd1e into main May 1, 2026
11 of 13 checks passed
@8bitAlex 8bitAlex deleted the add-url branch May 1, 2026 05:37
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.

Add profile directly from a git repo link or URL

2 participants