v0.8.5: Extension composability — slots, contributes, lib.require relaxation
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 5s
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-go-pg (pull_request) Successful in 2m42s
CI/CD / test-sqlite (pull_request) Successful in 2m58s
CI/CD / build-and-deploy (pull_request) Successful in 1m33s
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 5s
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-go-pg (pull_request) Successful in 2m42s
CI/CD / test-sqlite (pull_request) Successful in 2m58s
CI/CD / build-and-deploy (pull_request) Successful in 1m33s
Manifest declarations for slots (host surfaces declare injection points) and contributes (extensions declare UI contributions). lib.require() relaxed from library-only to any package with exports. Admin slots aggregation endpoint. SDK renderAll() helper. 7 new tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -212,10 +212,48 @@ func (h *PackageHandler) DeletePackage(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"id": id, "deleted": true})
|
||||
resp := gin.H{"id": id, "deleted": true}
|
||||
|
||||
// Warn about orphaned slot contributions
|
||||
if orphaned := findOrphanedContributors(c, h.stores, id); len(orphaned) > 0 {
|
||||
resp["warning"] = "slot contributions orphaned in: " + strings.Join(orphaned, ", ")
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
h.broadcastPackageChanged("deleted", id)
|
||||
}
|
||||
|
||||
// findOrphanedContributors returns package IDs that declare contributes
|
||||
// targeting slots owned by the given package (e.g. "notes:toolbar-actions"
|
||||
// targets the "notes" package). This is a warning, not a blocker.
|
||||
func findOrphanedContributors(c *gin.Context, stores store.Stores, deletedPkgID string) []string {
|
||||
allPkgs, err := stores.Packages.List(c.Request.Context())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
prefix := deletedPkgID + ":"
|
||||
seen := map[string]bool{}
|
||||
for _, p := range allPkgs {
|
||||
if p.ID == deletedPkgID || !p.Enabled {
|
||||
continue
|
||||
}
|
||||
contribs, ok := p.Manifest["contributes"].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for key := range contribs {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
seen[p.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
result := make([]string, 0, len(seen))
|
||||
for id := range seen {
|
||||
result = append(result, id)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// InstallPackage uploads and installs a .pkg/.surface archive.
|
||||
// POST /api/v1/admin/packages/install
|
||||
//
|
||||
@@ -466,6 +504,13 @@ func (h *PackageHandler) parseAndValidateArchive(c *gin.Context, tmpPath string)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Validate composability fields (slots / contributes)
|
||||
if errMsg := ValidateComposabilityFields(manifest); errMsg != "" {
|
||||
zr.Close()
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "manifest: " + errMsg})
|
||||
return nil, nil, nil, fmt.Errorf("invalid composability fields")
|
||||
}
|
||||
|
||||
// Signing hook (reserved)
|
||||
if os.Getenv("PACKAGE_VERIFY_SIGNATURES") == "true" {
|
||||
if mInfo.Signature != "" {
|
||||
|
||||
Reference in New Issue
Block a user