first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+205
View File
@@ -0,0 +1,205 @@
package pkgmgr
import (
"os"
"path/filepath"
"reflect"
"testing"
)
// writePkg writes a package.json (and optional extra files) into a fresh temp
// dir and returns the dir. extraFiles maps a relative path to its contents; a
// path ending in "/" is created as a directory.
func writePkg(t *testing.T, packageJSON string, extraFiles map[string]string) string {
t.Helper()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "package.json"), []byte(packageJSON), 0o644); err != nil {
t.Fatal(err)
}
for name, body := range extraFiles {
p := filepath.Join(dir, name)
if body == "" && name[len(name)-1] == '/' {
if err := os.MkdirAll(p, 0o755); err != nil {
t.Fatal(err)
}
continue
}
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
return dir
}
// TestClassifyExplicitType verifies a single explicit pi.type is recognized.
func TestClassifyExplicitType(t *testing.T) {
dir := writePkg(t, `{"name":"pi-web","version":"1.0.0","pi":{"type":"skill"}}`, nil)
name, version, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
if name != "pi-web" || version != "1.0.0" {
t.Errorf("name/version = %q/%q, want pi-web/1.0.0", name, version)
}
if !reflect.DeepEqual(types, []PackageType{TypeSkill}) {
t.Errorf("types = %v, want [skill]", types)
}
}
// TestClassifyMultiType verifies a package declaring several types via pi.types
// returns them all, sorted.
func TestClassifyMultiType(t *testing.T) {
dir := writePkg(t, `{"name":"combo","version":"2.0.0","pi":{"types":["extension","skill"]}}`, nil)
_, _, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
want := []PackageType{TypeExtension, TypeSkill}
if !reflect.DeepEqual(types, want) {
t.Errorf("types = %v, want %v", types, want)
}
}
// TestClassifyPerCapabilityKeys verifies pi.extension + pi.theme blocks both
// register their types.
func TestClassifyPerCapabilityKeys(t *testing.T) {
dir := writePkg(t, `{"name":"x","version":"0.1.0","pi":{"extension":{"cmd":"x"},"theme":{}}}`, nil)
_, _, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
want := []PackageType{TypeExtension, TypeTheme}
if !reflect.DeepEqual(types, want) {
t.Errorf("types = %v, want %v", types, want)
}
}
// TestClassifyPluralCapabilityKeys verifies the pi-ecosystem convention of
// plural path arrays (pi.extensions, pi.skills, ...) registers each type. This
// is the shape published packages actually use (pi-simplify, pi-ask-user, ...).
func TestClassifyPluralCapabilityKeys(t *testing.T) {
dir := writePkg(t, `{"name":"pi-ask-user","version":"1.0.0","pi":{"extensions":["./index.ts"],"skills":["./skills"]}}`, nil)
_, _, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
want := []PackageType{TypeExtension, TypeSkill}
if !reflect.DeepEqual(types, want) {
t.Errorf("types = %v, want %v", types, want)
}
}
// TestClassifyPluralExtensionsOnly verifies a pure extension declared via
// pi.extensions (no bin) classifies as an extension — the pi-simplify case.
func TestClassifyPluralExtensionsOnly(t *testing.T) {
dir := writePkg(t, `{"name":"pi-simplify","version":"0.2.3","pi":{"extensions":["dist/index.js"]}}`, nil)
_, _, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
if !reflect.DeepEqual(types, []PackageType{TypeExtension}) {
t.Errorf("types = %v, want [extension]", types)
}
}
// TestClassifyStructuralBin verifies a bare package with a bin entry is an
// extension even without pi metadata.
func TestClassifyStructuralBin(t *testing.T) {
dir := writePkg(t, `{"name":"pi-mcp-adapter","version":"1.0.0","bin":{"pi-mcp-adapter":"./index.js"}}`, nil)
_, _, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
if !reflect.DeepEqual(types, []PackageType{TypeExtension}) {
t.Errorf("types = %v, want [extension]", types)
}
}
// TestClassifyStructuralSkillMd verifies a SKILL.md file signals a skill.
func TestClassifyStructuralSkillMd(t *testing.T) {
dir := writePkg(t, `{"name":"pi-skill","version":"1.0.0"}`, map[string]string{
"SKILL.md": "# a skill",
})
_, _, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
if !reflect.DeepEqual(types, []PackageType{TypeSkill}) {
t.Errorf("types = %v, want [skill]", types)
}
}
// TestClassifyStructuralCommandsDir verifies a commands/ dir signals a prompt.
func TestClassifyStructuralCommandsDir(t *testing.T) {
dir := writePkg(t, `{"name":"pi-cmds","version":"1.0.0"}`, map[string]string{
"commands/hello.md": "hi",
})
_, _, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
if !reflect.DeepEqual(types, []PackageType{TypePrompt}) {
t.Errorf("types = %v, want [prompt]", types)
}
}
// TestClassifyExplicitAndStructural verifies explicit metadata and structural
// signals union together (skill via SKILL.md, extension via pi.type).
func TestClassifyExplicitAndStructural(t *testing.T) {
dir := writePkg(t, `{"name":"both","version":"1.0.0","pi":{"type":"extension"}}`, map[string]string{
"SKILL.md": "# skill",
})
_, _, types, err := Classify(dir)
if err != nil {
t.Fatalf("Classify: %v", err)
}
want := []PackageType{TypeExtension, TypeSkill}
if !reflect.DeepEqual(types, want) {
t.Errorf("types = %v, want %v", types, want)
}
}
// TestClassifyUnknown verifies a plain npm package with no pi signals errors.
func TestClassifyUnknown(t *testing.T) {
dir := writePkg(t, `{"name":"lodash","version":"4.17.21"}`, nil)
_, _, _, err := Classify(dir)
if err == nil {
t.Fatal("Classify of non-pi package = nil error, want error")
}
if !contains(err.Error(), "unrecognized pi package") {
t.Errorf("error = %q, want to mention 'unrecognized pi package'", err)
}
}
// TestClassifyMissingPackageJSON verifies a missing package.json errors.
func TestClassifyMissingPackageJSON(t *testing.T) {
if _, _, _, err := Classify(t.TempDir()); err == nil {
t.Fatal("Classify with no package.json = nil error, want error")
}
}
// TestClassifyCorruptPackageJSON verifies malformed JSON errors clearly.
func TestClassifyCorruptPackageJSON(t *testing.T) {
dir := writePkg(t, `{not valid json`, nil)
_, _, _, err := Classify(dir)
if err == nil {
t.Fatal("Classify with corrupt package.json = nil error, want error")
}
if !contains(err.Error(), "parse package.json") {
t.Errorf("error = %q, want to mention 'parse package.json'", err)
}
}
// TestClassifyUnknownTypeStringIgnored verifies an unrecognized pi.type string
// is ignored (not fatal) but leaves the package unclassified if nothing else
// matches.
func TestClassifyUnknownTypeStringIgnored(t *testing.T) {
dir := writePkg(t, `{"name":"x","version":"1.0.0","pi":{"type":"widget"}}`, nil)
_, _, _, err := Classify(dir)
if err == nil {
t.Fatal("Classify with only unknown pi.type = nil error, want error")
}
}