Skip to content

Commit e64a442

Browse files
rdimitrovclaude
andcommitted
feat: Phase 1 - Add package reference parsers and dedicated package type structs
This is the foundation for refactoring package types to use dedicated structs per package type (NPM, PyPI, NuGet, OCI, MCPB) instead of a unified struct. Changes: - Created pkg/packageref/ package with reference parsers for all 5 package types - oci.go: Parse OCI container image references (registry/namespace/image:tag@digest) - npm.go: Parse NPM package references (@scope/name@version) - pypi.go: Parse PyPI package references (name==version) - nuget.go: Parse NuGet package references (ID/version) - mcpb.go: Parse and validate MCPB URLs with SHA256 hashes - Created pkg/model/package_types.go with dedicated structs: - NPMPackage: Uses 'name' and 'version' fields - PyPIPackage: Uses 'name' and 'version' fields - NuGetPackage: Uses 'id' and 'version' fields - OCIPackage: Uses single 'ref' field for canonical image reference - MCPBPackage: Uses 'url' and 'fileSha256' fields - Implemented discriminated union pattern with PackageUnion for JSON marshaling - Added comprehensive tests for parsers and package types - All tests passing, project compiles Benefits: - OCI packages now use canonical single-line references (ghcr.io/owner/repo:tag) - Each package type has exactly the fields it needs (no confusion) - Type-safe: impossible to mix up fields between package types - Matches ecosystem conventions (Docker, NPM, PyPI, etc.) Next phases will update validators, schemas, database, and documentation. See PACKAGE_TYPE_REFACTOR.md for complete implementation plan. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 568748e commit e64a442

File tree

10 files changed

+1756
-0
lines changed

10 files changed

+1756
-0
lines changed

PACKAGE_TYPE_REFACTOR.md

Lines changed: 406 additions & 0 deletions
Large diffs are not rendered by default.

pkg/model/package_types.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package model
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
// PackageType represents the type of package
9+
type PackageType string
10+
11+
const (
12+
PackageTypeNPM PackageType = "npm"
13+
PackageTypePyPI PackageType = "pypi"
14+
PackageTypeNuGet PackageType = "nuget"
15+
PackageTypeOCI PackageType = "oci"
16+
PackageTypeMCPB PackageType = "mcpb"
17+
)
18+
19+
// PackageInterface defines the common interface for all package types
20+
type PackageInterface interface {
21+
GetType() PackageType
22+
GetTransport() Transport
23+
GetRuntimeHint() string
24+
GetRuntimeArguments() []Argument
25+
GetPackageArguments() []Argument
26+
GetEnvironmentVariables() []KeyValueInput
27+
}
28+
29+
// NPMPackage represents an NPM package
30+
type NPMPackage struct {
31+
Type PackageType `json:"type"`
32+
Name string `json:"name"`
33+
Version string `json:"version"`
34+
Registry string `json:"registry,omitempty"`
35+
RuntimeHint string `json:"runtimeHint,omitempty"`
36+
Transport Transport `json:"transport"`
37+
RuntimeArguments []Argument `json:"runtimeArguments,omitempty"`
38+
PackageArguments []Argument `json:"packageArguments,omitempty"`
39+
EnvironmentVariables []KeyValueInput `json:"environmentVariables,omitempty"`
40+
}
41+
42+
func (p *NPMPackage) GetType() PackageType { return p.Type }
43+
func (p *NPMPackage) GetTransport() Transport { return p.Transport }
44+
func (p *NPMPackage) GetRuntimeHint() string { return p.RuntimeHint }
45+
func (p *NPMPackage) GetRuntimeArguments() []Argument { return p.RuntimeArguments }
46+
func (p *NPMPackage) GetPackageArguments() []Argument { return p.PackageArguments }
47+
func (p *NPMPackage) GetEnvironmentVariables() []KeyValueInput { return p.EnvironmentVariables }
48+
49+
// PyPIPackage represents a PyPI package
50+
type PyPIPackage struct {
51+
Type PackageType `json:"type"`
52+
Name string `json:"name"`
53+
Version string `json:"version"`
54+
Index string `json:"index,omitempty"`
55+
RuntimeHint string `json:"runtimeHint,omitempty"`
56+
Transport Transport `json:"transport"`
57+
RuntimeArguments []Argument `json:"runtimeArguments,omitempty"`
58+
PackageArguments []Argument `json:"packageArguments,omitempty"`
59+
EnvironmentVariables []KeyValueInput `json:"environmentVariables,omitempty"`
60+
}
61+
62+
func (p *PyPIPackage) GetType() PackageType { return p.Type }
63+
func (p *PyPIPackage) GetTransport() Transport { return p.Transport }
64+
func (p *PyPIPackage) GetRuntimeHint() string { return p.RuntimeHint }
65+
func (p *PyPIPackage) GetRuntimeArguments() []Argument { return p.RuntimeArguments }
66+
func (p *PyPIPackage) GetPackageArguments() []Argument { return p.PackageArguments }
67+
func (p *PyPIPackage) GetEnvironmentVariables() []KeyValueInput { return p.EnvironmentVariables }
68+
69+
// NuGetPackage represents a NuGet package
70+
type NuGetPackage struct {
71+
Type PackageType `json:"type"`
72+
ID string `json:"id"`
73+
Version string `json:"version"`
74+
Source string `json:"source,omitempty"`
75+
RuntimeHint string `json:"runtimeHint,omitempty"`
76+
Transport Transport `json:"transport"`
77+
RuntimeArguments []Argument `json:"runtimeArguments,omitempty"`
78+
PackageArguments []Argument `json:"packageArguments,omitempty"`
79+
EnvironmentVariables []KeyValueInput `json:"environmentVariables,omitempty"`
80+
}
81+
82+
func (p *NuGetPackage) GetType() PackageType { return p.Type }
83+
func (p *NuGetPackage) GetTransport() Transport { return p.Transport }
84+
func (p *NuGetPackage) GetRuntimeHint() string { return p.RuntimeHint }
85+
func (p *NuGetPackage) GetRuntimeArguments() []Argument { return p.RuntimeArguments }
86+
func (p *NuGetPackage) GetPackageArguments() []Argument { return p.PackageArguments }
87+
func (p *NuGetPackage) GetEnvironmentVariables() []KeyValueInput { return p.EnvironmentVariables }
88+
89+
// OCIPackage represents an OCI container image package
90+
type OCIPackage struct {
91+
Type PackageType `json:"type"`
92+
Ref string `json:"ref"` // Full OCI reference: registry/namespace/image:tag@digest
93+
RuntimeHint string `json:"runtimeHint,omitempty"`
94+
Transport Transport `json:"transport"`
95+
RuntimeArguments []Argument `json:"runtimeArguments,omitempty"`
96+
PackageArguments []Argument `json:"packageArguments,omitempty"`
97+
EnvironmentVariables []KeyValueInput `json:"environmentVariables,omitempty"`
98+
}
99+
100+
func (p *OCIPackage) GetType() PackageType { return p.Type }
101+
func (p *OCIPackage) GetTransport() Transport { return p.Transport }
102+
func (p *OCIPackage) GetRuntimeHint() string { return p.RuntimeHint }
103+
func (p *OCIPackage) GetRuntimeArguments() []Argument { return p.RuntimeArguments }
104+
func (p *OCIPackage) GetPackageArguments() []Argument { return p.PackageArguments }
105+
func (p *OCIPackage) GetEnvironmentVariables() []KeyValueInput { return p.EnvironmentVariables }
106+
107+
// MCPBPackage represents an MCPB binary package
108+
type MCPBPackage struct {
109+
Type PackageType `json:"type"`
110+
URL string `json:"url"`
111+
FileSHA256 string `json:"fileSha256"`
112+
RuntimeHint string `json:"runtimeHint,omitempty"`
113+
Transport Transport `json:"transport"`
114+
RuntimeArguments []Argument `json:"runtimeArguments,omitempty"`
115+
PackageArguments []Argument `json:"packageArguments,omitempty"`
116+
EnvironmentVariables []KeyValueInput `json:"environmentVariables,omitempty"`
117+
}
118+
119+
func (p *MCPBPackage) GetType() PackageType { return p.Type }
120+
func (p *MCPBPackage) GetTransport() Transport { return p.Transport }
121+
func (p *MCPBPackage) GetRuntimeHint() string { return p.RuntimeHint }
122+
func (p *MCPBPackage) GetRuntimeArguments() []Argument { return p.RuntimeArguments }
123+
func (p *MCPBPackage) GetPackageArguments() []Argument { return p.PackageArguments }
124+
func (p *MCPBPackage) GetEnvironmentVariables() []KeyValueInput { return p.EnvironmentVariables }
125+
126+
// PackageUnion represents a discriminated union of package types
127+
// This is used for JSON marshaling/unmarshaling
128+
type PackageUnion struct {
129+
PackageInterface
130+
}
131+
132+
// UnmarshalJSON implements custom JSON unmarshaling for the discriminated union
133+
func (p *PackageUnion) UnmarshalJSON(data []byte) error {
134+
// First, peek at the type field
135+
var peek struct {
136+
Type PackageType `json:"type"`
137+
}
138+
139+
if err := json.Unmarshal(data, &peek); err != nil {
140+
return fmt.Errorf("failed to determine package type: %w", err)
141+
}
142+
143+
// Unmarshal into the appropriate type based on the discriminator
144+
switch peek.Type {
145+
case PackageTypeNPM:
146+
var pkg NPMPackage
147+
if err := json.Unmarshal(data, &pkg); err != nil {
148+
return fmt.Errorf("failed to unmarshal NPM package: %w", err)
149+
}
150+
p.PackageInterface = &pkg
151+
return nil
152+
153+
case PackageTypePyPI:
154+
var pkg PyPIPackage
155+
if err := json.Unmarshal(data, &pkg); err != nil {
156+
return fmt.Errorf("failed to unmarshal PyPI package: %w", err)
157+
}
158+
p.PackageInterface = &pkg
159+
return nil
160+
161+
case PackageTypeNuGet:
162+
var pkg NuGetPackage
163+
if err := json.Unmarshal(data, &pkg); err != nil {
164+
return fmt.Errorf("failed to unmarshal NuGet package: %w", err)
165+
}
166+
p.PackageInterface = &pkg
167+
return nil
168+
169+
case PackageTypeOCI:
170+
var pkg OCIPackage
171+
if err := json.Unmarshal(data, &pkg); err != nil {
172+
return fmt.Errorf("failed to unmarshal OCI package: %w", err)
173+
}
174+
p.PackageInterface = &pkg
175+
return nil
176+
177+
case PackageTypeMCPB:
178+
var pkg MCPBPackage
179+
if err := json.Unmarshal(data, &pkg); err != nil {
180+
return fmt.Errorf("failed to unmarshal MCPB package: %w", err)
181+
}
182+
p.PackageInterface = &pkg
183+
return nil
184+
185+
default:
186+
return fmt.Errorf("unknown package type: %s", peek.Type)
187+
}
188+
}
189+
190+
// MarshalJSON implements custom JSON marshaling
191+
func (p *PackageUnion) MarshalJSON() ([]byte, error) {
192+
if p.PackageInterface == nil {
193+
return nil, fmt.Errorf("cannot marshal nil package")
194+
}
195+
return json.Marshal(p.PackageInterface)
196+
}

0 commit comments

Comments
 (0)