mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 10:05:14 +00:00
fix(coding-agent): tighten git source parsing and local path normalization (fixes #1426)
This commit is contained in:
parent
31f765ff1b
commit
0adce69b3b
8 changed files with 152 additions and 242 deletions
|
|
@ -5,6 +5,7 @@
|
|||
### Breaking Changes
|
||||
|
||||
- `ContextUsage.tokens` and `ContextUsage.percent` are now `number | null`. After compaction, context token count is unknown until the next LLM response, so these fields return `null`. Extensions that read `ContextUsage` must handle the `null` case. Removed `usageTokens`, `trailingTokens`, and `lastUsageIndex` fields from `ContextUsage` (implementation details that should not have been public) ([#1382](https://github.com/badlogic/pi-mono/pull/1382) by [@ferologics](https://github.com/ferologics))
|
||||
- Git source parsing is now strict without `git:` prefix: only protocol URLs are treated as git (`https://`, `http://`, `ssh://`, `git://`). Shorthand sources like `github.com/org/repo` and `git@github.com:org/repo` now require the `git:` prefix. ([#1426](https://github.com/badlogic/pi-mono/issues/1426))
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
|||
|
|
@ -341,7 +341,12 @@ pi install npm:@foo/pi-tools
|
|||
pi install npm:@foo/pi-tools@1.2.3 # pinned version
|
||||
pi install git:github.com/user/repo
|
||||
pi install git:github.com/user/repo@v1 # tag or commit
|
||||
pi install git:git@github.com:user/repo
|
||||
pi install git:git@github.com:user/repo@v1 # tag or commit
|
||||
pi install https://github.com/user/repo
|
||||
pi install https://github.com/user/repo@v1 # tag or commit
|
||||
pi install ssh://git@github.com/user/repo
|
||||
pi install ssh://git@github.com/user/repo@v1 # tag or commit
|
||||
pi remove npm:@foo/pi-tools
|
||||
pi list
|
||||
pi update # skips pinned packages
|
||||
|
|
|
|||
|
|
@ -59,32 +59,30 @@ npm:pkg
|
|||
|
||||
```
|
||||
git:github.com/user/repo@v1
|
||||
git:git@github.com:user/repo@v1
|
||||
https://github.com/user/repo@v1
|
||||
git@github.com:user/repo@v1
|
||||
ssh://git@github.com/user/repo@v1
|
||||
```
|
||||
|
||||
- Without `git:` prefix, only protocol URLs are accepted (`https://`, `http://`, `ssh://`, `git://`).
|
||||
- With `git:` prefix, shorthand formats are accepted, including `github.com/user/repo` and `git@github.com:user/repo`.
|
||||
- HTTPS and SSH URLs are both supported.
|
||||
- SSH URLs use your configured SSH keys automatically (respects `~/.ssh/config`).
|
||||
- For non-interactive runs (for example CI), you can set `GIT_TERMINAL_PROMPT=0` to disable credential prompts and set `GIT_SSH_COMMAND` (for example `ssh -o BatchMode=yes -o ConnectTimeout=5`) to fail fast.
|
||||
- Raw `https://` URLs work without the `git:` prefix.
|
||||
- Refs pin the package and skip `pi update`.
|
||||
- Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project).
|
||||
- Runs `npm install` after clone or pull if `package.json` exists.
|
||||
|
||||
**SSH examples:**
|
||||
```bash
|
||||
# Standard git@host:path format
|
||||
pi install git@github.com:user/repo
|
||||
|
||||
# With git: prefix
|
||||
# git@host:path shorthand (requires git: prefix)
|
||||
pi install git:git@github.com:user/repo
|
||||
|
||||
# ssh:// protocol format
|
||||
pi install ssh://git@github.com/user/repo
|
||||
|
||||
# With version ref
|
||||
pi install git@github.com:user/repo@v1.0.0
|
||||
pi install git:git@github.com:user/repo@v1.0.0
|
||||
```
|
||||
|
||||
### Local Paths
|
||||
|
|
|
|||
|
|
@ -92,8 +92,9 @@ Options:
|
|||
Examples:
|
||||
${APP_NAME} install npm:@foo/bar
|
||||
${APP_NAME} install git:github.com/user/repo
|
||||
${APP_NAME} install git:git@github.com:user/repo
|
||||
${APP_NAME} install https://github.com/user/repo
|
||||
${APP_NAME} install git@github.com:user/repo
|
||||
${APP_NAME} install ssh://git@github.com/user/repo
|
||||
${APP_NAME} install ./local/path
|
||||
`);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@ function parseGenericGitUrl(url: string): GitSource | null {
|
|||
} else if (
|
||||
repoWithoutRef.startsWith("https://") ||
|
||||
repoWithoutRef.startsWith("http://") ||
|
||||
repoWithoutRef.startsWith("ssh://")
|
||||
repoWithoutRef.startsWith("ssh://") ||
|
||||
repoWithoutRef.startsWith("git://")
|
||||
) {
|
||||
try {
|
||||
const parsed = new URL(repoWithoutRef);
|
||||
|
|
@ -124,10 +125,21 @@ function parseGenericGitUrl(url: string): GitSource | null {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse any git URL (SSH or HTTPS) into a GitSource.
|
||||
* Parse git source into a GitSource.
|
||||
*
|
||||
* Rules:
|
||||
* - With git: prefix, accept all historical shorthand forms.
|
||||
* - Without git: prefix, only accept explicit protocol URLs.
|
||||
*/
|
||||
export function parseGitUrl(source: string): GitSource | null {
|
||||
const url = source.startsWith("git:") ? source.slice(4).trim() : source;
|
||||
const trimmed = source.trim();
|
||||
const hasGitPrefix = trimmed.startsWith("git:");
|
||||
const url = hasGitPrefix ? trimmed.slice(4).trim() : trimmed;
|
||||
|
||||
if (!hasGitPrefix && !/^(https?|ssh|git):\/\//i.test(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const split = splitRef(url);
|
||||
|
||||
const hostedCandidates = [split.ref ? `${split.repo}#${split.ref}` : undefined, url].filter(
|
||||
|
|
@ -143,6 +155,7 @@ export function parseGitUrl(source: string): GitSource | null {
|
|||
!split.repo.startsWith("http://") &&
|
||||
!split.repo.startsWith("https://") &&
|
||||
!split.repo.startsWith("ssh://") &&
|
||||
!split.repo.startsWith("git://") &&
|
||||
!split.repo.startsWith("git@");
|
||||
return {
|
||||
type: "git",
|
||||
|
|
|
|||
|
|
@ -1,89 +1,8 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { parseGitUrl } from "../src/utils/git.js";
|
||||
|
||||
describe("SSH Git URL Parsing", () => {
|
||||
describe("ssh:// protocol", () => {
|
||||
it("should parse basic ssh:// URL", () => {
|
||||
const result = parseGitUrl("ssh://git@github.com/user/repo");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
repo: "ssh://git@github.com/user/repo",
|
||||
});
|
||||
expect(result?.ref).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should parse ssh:// URL with port", () => {
|
||||
const result = parseGitUrl("ssh://git@github.com:22/user/repo");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
repo: "ssh://git@github.com:22/user/repo",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse ssh:// URL with .git suffix", () => {
|
||||
const result = parseGitUrl("ssh://git@github.com/user/repo.git");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
repo: "ssh://git@github.com/user/repo.git",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse ssh:// URL with ref", () => {
|
||||
const result = parseGitUrl("ssh://git@github.com/user/repo@v1.0.0");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
ref: "v1.0.0",
|
||||
repo: "ssh://git@github.com/user/repo",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("git@host:path pattern", () => {
|
||||
it("should parse basic git@host:path", () => {
|
||||
const result = parseGitUrl("git@github.com:user/repo");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
repo: "git@github.com:user/repo",
|
||||
});
|
||||
expect(result?.ref).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should parse git@host:path with .git", () => {
|
||||
const result = parseGitUrl("git@github.com:user/repo.git");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
repo: "git@github.com:user/repo.git",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse git@host:path with ref", () => {
|
||||
const result = parseGitUrl("git@github.com:user/repo@v1.0.0");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
ref: "v1.0.0",
|
||||
repo: "git@github.com:user/repo",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse git@host:path with ref and .git", () => {
|
||||
const result = parseGitUrl("git@github.com:user/repo.git@main");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
ref: "main",
|
||||
repo: "git@github.com:user/repo.git",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTPS URLs", () => {
|
||||
describe("Git URL Parsing", () => {
|
||||
describe("protocol URLs (accepted without git: prefix)", () => {
|
||||
it("should parse HTTPS URL", () => {
|
||||
const result = parseGitUrl("https://github.com/user/repo");
|
||||
expect(result).toMatchObject({
|
||||
|
|
@ -93,25 +12,67 @@ describe("SSH Git URL Parsing", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should parse shorthand URL", () => {
|
||||
const result = parseGitUrl("github.com/user/repo");
|
||||
it("should parse ssh:// URL", () => {
|
||||
const result = parseGitUrl("ssh://git@github.com/user/repo");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
repo: "ssh://git@github.com/user/repo",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse protocol URL with ref", () => {
|
||||
const result = parseGitUrl("https://github.com/user/repo@v1.0.0");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
ref: "v1.0.0",
|
||||
repo: "https://github.com/user/repo",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("shorthand URLs (accepted only with git: prefix)", () => {
|
||||
it("should parse git@host:path with git: prefix", () => {
|
||||
const result = parseGitUrl("git:git@github.com:user/repo");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
repo: "git@github.com:user/repo",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse host/path shorthand with git: prefix", () => {
|
||||
const result = parseGitUrl("git:github.com/user/repo");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
repo: "https://github.com/user/repo",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse shorthand with ref and git: prefix", () => {
|
||||
const result = parseGitUrl("git:git@github.com:user/repo@v1.0.0");
|
||||
expect(result).toMatchObject({
|
||||
host: "github.com",
|
||||
path: "user/repo",
|
||||
ref: "v1.0.0",
|
||||
repo: "git@github.com:user/repo",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
it("should return null for invalid URLs", () => {
|
||||
expect(parseGitUrl("git@github.com")).toBeNull();
|
||||
expect(parseGitUrl("not-a-url")).toBeNull();
|
||||
describe("unsupported without git: prefix", () => {
|
||||
it("should reject git@host:path without git: prefix", () => {
|
||||
expect(parseGitUrl("git@github.com:user/repo")).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle different hosts", () => {
|
||||
expect(parseGitUrl("git@gitlab.com:user/repo")?.host).toBe("gitlab.com");
|
||||
expect(parseGitUrl("git@bitbucket.org:user/repo")?.host).toBe("bitbucket.org");
|
||||
it("should reject host/path shorthand without git: prefix", () => {
|
||||
expect(parseGitUrl("github.com/user/repo")).toBeNull();
|
||||
});
|
||||
|
||||
it("should reject user/repo shorthand", () => {
|
||||
expect(parseGitUrl("user/repo")).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|||
import { DefaultPackageManager } from "../src/core/package-manager.js";
|
||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||
|
||||
describe("Package Manager SSH URL Support", () => {
|
||||
describe("Package Manager git source parsing", () => {
|
||||
let tempDir: string;
|
||||
let agentDir: string;
|
||||
let settingsManager: SettingsManager;
|
||||
|
|
@ -29,9 +29,26 @@ describe("Package Manager SSH URL Support", () => {
|
|||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("parseSource with SSH URLs", () => {
|
||||
describe("protocol URLs without git: prefix", () => {
|
||||
it("should parse https:// URL", () => {
|
||||
const parsed = (packageManager as any).parseSource("https://github.com/user/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.com");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
});
|
||||
|
||||
it("should parse ssh:// URL", () => {
|
||||
const parsed = (packageManager as any).parseSource("ssh://git@github.com/user/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.com");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
expect(parsed.repo).toBe("ssh://git@github.com/user/repo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("shorthand URLs with git: prefix", () => {
|
||||
it("should parse git@host:path format", () => {
|
||||
const parsed = (packageManager as any).parseSource("git@github.com:user/repo");
|
||||
const parsed = (packageManager as any).parseSource("git:git@github.com:user/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.com");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
|
|
@ -39,137 +56,42 @@ describe("Package Manager SSH URL Support", () => {
|
|||
expect(parsed.pinned).toBe(false);
|
||||
});
|
||||
|
||||
it("should parse git@host:path with ref", () => {
|
||||
const parsed = (packageManager as any).parseSource("git@github.com:user/repo@v1.0.0");
|
||||
it("should parse host/path shorthand", () => {
|
||||
const parsed = (packageManager as any).parseSource("git:github.com/user/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.com");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
});
|
||||
|
||||
it("should parse shorthand with ref", () => {
|
||||
const parsed = (packageManager as any).parseSource("git:git@github.com:user/repo@v1.0.0");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.ref).toBe("v1.0.0");
|
||||
expect(parsed.repo).toBe("git@github.com:user/repo");
|
||||
expect(parsed.pinned).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse ssh:// protocol format", () => {
|
||||
const parsed = (packageManager as any).parseSource("ssh://git@github.com/user/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.com");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
expect(parsed.repo).toBe("ssh://git@github.com/user/repo");
|
||||
describe("unsupported without git: prefix", () => {
|
||||
it("should treat git@host:path as local without git: prefix", () => {
|
||||
const parsed = (packageManager as any).parseSource("git@github.com:user/repo");
|
||||
expect(parsed.type).toBe("local");
|
||||
});
|
||||
|
||||
it("should parse ssh:// with port", () => {
|
||||
const parsed = (packageManager as any).parseSource("ssh://git@github.com:22/user/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.com");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
expect(parsed.repo).toBe("ssh://git@github.com:22/user/repo");
|
||||
});
|
||||
|
||||
it("should parse git: prefix with SSH URL", () => {
|
||||
const parsed = (packageManager as any).parseSource("git:git@github.com:user/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.com");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
expect(parsed.repo).toBe("git@github.com:user/repo");
|
||||
});
|
||||
|
||||
it("should parse .git suffix", () => {
|
||||
const parsed = (packageManager as any).parseSource("git@github.com:user/repo.git");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
expect(parsed.repo).toBe("git@github.com:user/repo.git");
|
||||
it("should treat host/path shorthand as local without git: prefix", () => {
|
||||
const parsed = (packageManager as any).parseSource("github.com/user/repo");
|
||||
expect(parsed.type).toBe("local");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPackageIdentity with SSH URLs", () => {
|
||||
it("should normalize SSH URL to same identity as HTTPS", () => {
|
||||
const sshIdentity = (packageManager as any).getPackageIdentity("git@github.com:user/repo");
|
||||
const httpsIdentity = (packageManager as any).getPackageIdentity("https://github.com/user/repo");
|
||||
expect(sshIdentity).toBe(httpsIdentity);
|
||||
expect(sshIdentity).toBe("git:github.com/user/repo");
|
||||
});
|
||||
describe("identity normalization", () => {
|
||||
it("should normalize protocol and shorthand-prefixed URLs to same identity", () => {
|
||||
const prefixed = (packageManager as any).getPackageIdentity("git:git@github.com:user/repo");
|
||||
const https = (packageManager as any).getPackageIdentity("https://github.com/user/repo");
|
||||
const ssh = (packageManager as any).getPackageIdentity("ssh://git@github.com/user/repo");
|
||||
|
||||
it("should normalize ssh:// to same identity as git@", () => {
|
||||
const sshProtocol = (packageManager as any).getPackageIdentity("ssh://git@github.com/user/repo");
|
||||
const gitAt = (packageManager as any).getPackageIdentity("git@github.com:user/repo");
|
||||
expect(sshProtocol).toBe(gitAt);
|
||||
});
|
||||
|
||||
it("should ignore ref in identity", () => {
|
||||
const withRef = (packageManager as any).getPackageIdentity("git@github.com:user/repo@v1.0.0");
|
||||
const withoutRef = (packageManager as any).getPackageIdentity("git@github.com:user/repo");
|
||||
expect(withRef).toBe(withoutRef);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SSH URL install behavior", () => {
|
||||
it("should emit start event for SSH URL install", async () => {
|
||||
const events: any[] = [];
|
||||
packageManager.setProgressCallback((event) => events.push(event));
|
||||
|
||||
try {
|
||||
await packageManager.install("git@github.com:nonexistent/repo");
|
||||
} catch {
|
||||
// Expected to fail - repo doesn't exist or no SSH access
|
||||
}
|
||||
|
||||
expect(events.some((e) => e.type === "start" && e.action === "install")).toBe(true);
|
||||
});
|
||||
|
||||
it("should emit start event for ssh:// URL install", async () => {
|
||||
const events: any[] = [];
|
||||
packageManager.setProgressCallback((event) => events.push(event));
|
||||
|
||||
try {
|
||||
await packageManager.install("ssh://git@github.com/nonexistent/repo");
|
||||
} catch {
|
||||
// Expected to fail
|
||||
}
|
||||
|
||||
expect(events.some((e) => e.type === "start" && e.action === "install")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("different git hosts", () => {
|
||||
it("should parse GitLab SSH URL", () => {
|
||||
const parsed = (packageManager as any).parseSource("git@gitlab.com:group/project");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("gitlab.com");
|
||||
expect(parsed.path).toBe("group/project");
|
||||
expect(parsed.repo).toBe("git@gitlab.com:group/project");
|
||||
});
|
||||
|
||||
it("should parse Bitbucket SSH URL", () => {
|
||||
const parsed = (packageManager as any).parseSource("git@bitbucket.org:team/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("bitbucket.org");
|
||||
expect(parsed.path).toBe("team/repo");
|
||||
expect(parsed.repo).toBe("git@bitbucket.org:team/repo");
|
||||
});
|
||||
|
||||
it("should parse unknown enterprise host shorthand", () => {
|
||||
const parsed = (packageManager as any).parseSource("git:github.tools.sap/agent-dev/sap-pie");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.tools.sap");
|
||||
expect(parsed.path).toBe("agent-dev/sap-pie");
|
||||
expect(parsed.repo).toBe("https://github.tools.sap/agent-dev/sap-pie");
|
||||
});
|
||||
|
||||
it("should parse unknown enterprise host with ref", () => {
|
||||
const parsed = (packageManager as any).parseSource("git:github.tools.sap/agent-dev/sap-pie@v1");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.tools.sap");
|
||||
expect(parsed.path).toBe("agent-dev/sap-pie");
|
||||
expect(parsed.ref).toBe("v1");
|
||||
expect(parsed.repo).toBe("https://github.tools.sap/agent-dev/sap-pie");
|
||||
expect(parsed.pinned).toBe(true);
|
||||
});
|
||||
|
||||
it("should normalize unknown enterprise host identities", () => {
|
||||
const withPrefix = (packageManager as any).getPackageIdentity("git:github.tools.sap/agent-dev/sap-pie");
|
||||
const withHttps = (packageManager as any).getPackageIdentity("https://github.tools.sap/agent-dev/sap-pie");
|
||||
expect(withPrefix).toBe("git:github.tools.sap/agent-dev/sap-pie");
|
||||
expect(withPrefix).toBe(withHttps);
|
||||
expect(prefixed).toBe("git:github.com/user/repo");
|
||||
expect(prefixed).toBe(https);
|
||||
expect(prefixed).toBe(ssh);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ Content`,
|
|||
|
||||
expect((packageManager as any).parseSource("git:github.com/user/repo@v1").type).toBe("git");
|
||||
expect((packageManager as any).parseSource("https://github.com/user/repo@v1").type).toBe("git");
|
||||
expect((packageManager as any).parseSource("git@github.com:user/repo@v1").type).toBe("git");
|
||||
expect((packageManager as any).parseSource("git:git@github.com:user/repo@v1").type).toBe("git");
|
||||
expect((packageManager as any).parseSource("ssh://git@github.com/user/repo@v1").type).toBe("git");
|
||||
|
||||
expect((packageManager as any).parseSource("/absolute/path/to/package").type).toBe("local");
|
||||
|
|
@ -316,7 +316,9 @@ Content`,
|
|||
expect(added).toBe(true);
|
||||
|
||||
const settings = settingsManager.getGlobalSettings();
|
||||
expect(settings.packages?.[0]).toBe(relative(agentDir, pkgDir) || ".");
|
||||
const rel = relative(agentDir, pkgDir);
|
||||
const expected = rel.startsWith(".") ? rel : `./${rel}`;
|
||||
expect(settings.packages?.[0]).toBe(expected);
|
||||
});
|
||||
|
||||
it("should store project local packages relative to .pi settings base", () => {
|
||||
|
|
@ -328,7 +330,9 @@ Content`,
|
|||
expect(added).toBe(true);
|
||||
|
||||
const settings = settingsManager.getProjectSettings();
|
||||
expect(settings.packages?.[0]).toBe(relative(join(tempDir, ".pi"), projectPkgDir) || ".");
|
||||
const rel = relative(join(tempDir, ".pi"), projectPkgDir);
|
||||
const expected = rel.startsWith(".") ? rel : `./${rel}`;
|
||||
expect(settings.packages?.[0]).toBe(expected);
|
||||
});
|
||||
|
||||
it("should remove local package entries using equivalent path forms", () => {
|
||||
|
|
@ -368,13 +372,18 @@ Content`,
|
|||
expect(parsed.pinned).toBe(true);
|
||||
});
|
||||
|
||||
it("should parse HTTPS URLs without protocol", async () => {
|
||||
const parsed = (packageManager as any).parseSource("github.com/user/repo");
|
||||
it("should parse host/path shorthand only with git: prefix", async () => {
|
||||
const parsed = (packageManager as any).parseSource("git:github.com/user/repo");
|
||||
expect(parsed.type).toBe("git");
|
||||
expect(parsed.host).toBe("github.com");
|
||||
expect(parsed.path).toBe("user/repo");
|
||||
});
|
||||
|
||||
it("should treat host/path shorthand as local without git: prefix", async () => {
|
||||
const parsed = (packageManager as any).parseSource("github.com/user/repo");
|
||||
expect(parsed.type).toBe("local");
|
||||
});
|
||||
|
||||
it("should parse HTTPS URLs with .git suffix", async () => {
|
||||
const parsed = (packageManager as any).parseSource("https://github.com/user/repo.git");
|
||||
expect(parsed.type).toBe("git");
|
||||
|
|
@ -403,10 +412,10 @@ Content`,
|
|||
expect(parsed.path).toBe("user/repo");
|
||||
});
|
||||
|
||||
it("should generate correct package identity for HTTPS URLs", async () => {
|
||||
it("should generate correct package identity for protocol and git:-prefixed URLs", async () => {
|
||||
const identity1 = (packageManager as any).getPackageIdentity("https://github.com/user/repo");
|
||||
const identity2 = (packageManager as any).getPackageIdentity("https://github.com/user/repo@v1.0.0");
|
||||
const identity3 = (packageManager as any).getPackageIdentity("github.com/user/repo");
|
||||
const identity3 = (packageManager as any).getPackageIdentity("git:github.com/user/repo");
|
||||
const identity4 = (packageManager as any).getPackageIdentity("https://github.com/user/repo.git");
|
||||
|
||||
// All should have the same identity (normalized)
|
||||
|
|
@ -416,7 +425,7 @@ Content`,
|
|||
expect(identity4).toBe("git:github.com/user/repo");
|
||||
});
|
||||
|
||||
it("should deduplicate HTTPS URLs with different formats", async () => {
|
||||
it("should deduplicate git URLs with different supported formats", async () => {
|
||||
const pkgDir = join(tempDir, "https-dedup-pkg");
|
||||
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
|
||||
writeFileSync(join(pkgDir, "extensions", "test.ts"), "export default function() {}");
|
||||
|
|
@ -425,14 +434,14 @@ Content`,
|
|||
// In reality, these would all point to the same local dir after install
|
||||
settingsManager.setPackages([
|
||||
"https://github.com/user/repo",
|
||||
"github.com/user/repo",
|
||||
"git:github.com/user/repo",
|
||||
"https://github.com/user/repo.git",
|
||||
]);
|
||||
|
||||
// Since these URLs don't actually exist and we can't clone them,
|
||||
// we verify they produce the same identity
|
||||
const id1 = (packageManager as any).getPackageIdentity("https://github.com/user/repo");
|
||||
const id2 = (packageManager as any).getPackageIdentity("github.com/user/repo");
|
||||
const id2 = (packageManager as any).getPackageIdentity("git:github.com/user/repo");
|
||||
const id3 = (packageManager as any).getPackageIdentity("https://github.com/user/repo.git");
|
||||
|
||||
expect(id1).toBe(id2);
|
||||
|
|
@ -915,7 +924,7 @@ Content`,
|
|||
it("should dedupe SSH and HTTPS URLs for same repo", async () => {
|
||||
// Same repository, different URL formats
|
||||
const httpsUrl = "https://github.com/user/repo";
|
||||
const sshUrl = "git@github.com:user/repo";
|
||||
const sshUrl = "git:git@github.com:user/repo";
|
||||
|
||||
const httpsIdentity = (packageManager as any).getPackageIdentity(httpsUrl);
|
||||
const sshIdentity = (packageManager as any).getPackageIdentity(sshUrl);
|
||||
|
|
@ -928,7 +937,7 @@ Content`,
|
|||
|
||||
it("should dedupe SSH and HTTPS with refs", async () => {
|
||||
const httpsUrl = "https://github.com/user/repo@v1.0.0";
|
||||
const sshUrl = "git@github.com:user/repo@v1.0.0";
|
||||
const sshUrl = "git:git@github.com:user/repo@v1.0.0";
|
||||
|
||||
const httpsIdentity = (packageManager as any).getPackageIdentity(httpsUrl);
|
||||
const sshIdentity = (packageManager as any).getPackageIdentity(sshUrl);
|
||||
|
|
@ -941,7 +950,7 @@ Content`,
|
|||
|
||||
it("should dedupe SSH URL with ssh:// protocol and git@ format", async () => {
|
||||
const sshProtocol = "ssh://git@github.com/user/repo";
|
||||
const gitAt = "git@github.com:user/repo";
|
||||
const gitAt = "git:git@github.com:user/repo";
|
||||
|
||||
const sshProtocolIdentity = (packageManager as any).getPackageIdentity(sshProtocol);
|
||||
const gitAtIdentity = (packageManager as any).getPackageIdentity(gitAt);
|
||||
|
|
@ -952,15 +961,15 @@ Content`,
|
|||
expect(sshProtocolIdentity).toBe(gitAtIdentity);
|
||||
});
|
||||
|
||||
it("should dedupe all URL formats for same repo (HTTPS, SSH, git@)", async () => {
|
||||
it("should dedupe all supported URL formats for same repo", async () => {
|
||||
const urls = [
|
||||
"https://github.com/user/repo",
|
||||
"github.com/user/repo",
|
||||
"https://github.com/user/repo.git",
|
||||
"git@github.com:user/repo",
|
||||
"git@github.com:user/repo.git",
|
||||
"ssh://git@github.com/user/repo",
|
||||
"git:https://github.com/user/repo",
|
||||
"git:github.com/user/repo",
|
||||
"git:git@github.com:user/repo",
|
||||
"git:git@github.com:user/repo.git",
|
||||
];
|
||||
|
||||
const identities = urls.map((url) => (packageManager as any).getPackageIdentity(url));
|
||||
|
|
@ -973,7 +982,7 @@ Content`,
|
|||
|
||||
it("should keep different repos separate (HTTPS vs SSH)", async () => {
|
||||
const repo1Https = "https://github.com/user/repo1";
|
||||
const repo2Ssh = "git@github.com:user/repo2";
|
||||
const repo2Ssh = "git:git@github.com:user/repo2";
|
||||
|
||||
const id1 = (packageManager as any).getPackageIdentity(repo1Https);
|
||||
const id2 = (packageManager as any).getPackageIdentity(repo2Ssh);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue