fix: close install gaps aligned with fullstack-cli

- latest version: re-check installed version after API resolves, skip
  download when already up to date
- actionPlugins sync: ensure package.json record is updated even when
  install is skipped (already_installed path)
- peerDependencies: warn about missing peer deps after extraction
  instead of silently ignoring them
This commit is contained in:
anguohui
2026-06-18 16:18:20 +08:00
parent e9f2da086f
commit 9dc032ca73
2 changed files with 65 additions and 1 deletions

View File

@@ -506,6 +506,47 @@ func pluginRemoveActionPlugin(pkg map[string]interface{}, key string) {
delete(m, key)
}
// pluginSyncActionPlugins ensures the actionPlugins record in package.json
// matches the actually installed version, even when install is skipped.
func pluginSyncActionPlugins(projectPath, key, version string) {
pkg, err := pluginReadPackageJSON(projectPath)
if err != nil {
return
}
ap := pluginGetActionPlugins(pkg)
if ap[key] == version {
return
}
pluginSetActionPlugin(pkg, key, version)
_ = pluginWritePackageJSON(projectPath, pkg)
}
// pluginCheckPeerDeps reads peerDependencies from the installed plugin's
// package.json and returns the names of any that are missing from node_modules.
func pluginCheckPeerDeps(projectPath, pluginKey string) []string {
pkgPath := filepath.Join(projectPath, "node_modules", pluginKey, "package.json")
data, err := os.ReadFile(pkgPath) //nolint:forbidigo // shortcuts cannot import internal/vfs; local package read.
if err != nil {
return nil
}
var pkg map[string]interface{}
if err := json.Unmarshal(data, &pkg); err != nil {
return nil
}
peerDeps, ok := pkg["peerDependencies"].(map[string]interface{})
if !ok || len(peerDeps) == 0 {
return nil
}
var missing []string
for dep := range peerDeps {
depDir := filepath.Join(projectPath, "node_modules", dep)
if !pluginDirExists(depDir) {
missing = append(missing, dep)
}
}
return missing
}
// pluginParseInstallTarget parses "key[@version]" where version is optional.
// For scoped packages like "@scope/name@1.0.0", the split is at the last "@".
func pluginParseInstallTarget(s string) (key string, version string) {

View File

@@ -77,9 +77,10 @@ func pluginInstallOne(ctx context.Context, rctx *common.RuntimeContext, projectP
return appsValidationParamError("--name", "invalid plugin name %q", name)
}
// Check if already installed with same version
// Check if already installed with same version (pre-API fast path)
if version != "" && version != "latest" {
if installed := pluginInstalledVersion(projectPath, key); installed == version {
pluginSyncActionPlugins(projectPath, key, version)
result := map[string]interface{}{
"key": key, "version": version, "status": "already_installed",
}
@@ -96,6 +97,18 @@ func pluginInstallOne(ctx context.Context, rctx *common.RuntimeContext, projectP
return err
}
// Post-API check: latest may resolve to the already-installed version
if installed := pluginInstalledVersion(projectPath, key); installed == resolvedVersion {
pluginSyncActionPlugins(projectPath, key, resolvedVersion)
result := map[string]interface{}{
"key": key, "version": resolvedVersion, "status": "already_installed",
}
rctx.OutFormat(result, nil, func(w io.Writer) {
fmt.Fprintf(w, "✓ %s@%s is already up to date\n", key, resolvedVersion)
})
return nil
}
// Download tgz
tgzData, err := pluginDownloadPackage(ctx, rctx, key, resolvedVersion, downloadURL, approach)
if err != nil {
@@ -114,6 +127,9 @@ func pluginInstallOne(ctx context.Context, rctx *common.RuntimeContext, projectP
return appsFileIOError(err, "cannot extract plugin package for %s", key)
}
// Check peer dependencies
missingPeers := pluginCheckPeerDeps(projectPath, key)
// Update package.json
pkg, err := pluginReadPackageJSON(projectPath)
if err != nil {
@@ -127,8 +143,15 @@ func pluginInstallOne(ctx context.Context, rctx *common.RuntimeContext, projectP
result := map[string]interface{}{
"key": key, "version": resolvedVersion, "status": "installed",
}
if len(missingPeers) > 0 {
result["missing_peer_dependencies"] = missingPeers
}
rctx.OutFormat(result, nil, func(w io.Writer) {
fmt.Fprintf(w, "✓ Installed %s@%s\n", key, resolvedVersion)
if len(missingPeers) > 0 {
fmt.Fprintf(w, "⚠ Missing peer dependencies: %s\n", strings.Join(missingPeers, ", "))
fmt.Fprintln(w, " Run 'npm install' in the project directory to install them.")
}
})
return nil
}