Add adds the `pkgs` to the config (i.e. devbox.json) and nix profile for this devbox project
(ctx context.Context, pkgsNames []string, opts devopt.AddOpts)
| 83 | // Add adds the `pkgs` to the config (i.e. devbox.json) and nix profile for this |
| 84 | // devbox project |
| 85 | func (d *Devbox) Add(ctx context.Context, pkgsNames []string, opts devopt.AddOpts) error { |
| 86 | ctx, task := trace.NewTask(ctx, "devboxAdd") |
| 87 | defer task.End() |
| 88 | |
| 89 | // Track which packages had no changes so we can report that to the user. |
| 90 | unchangedPackageNames := []string{} |
| 91 | |
| 92 | // Only add packages that are not already in config. If same canonical exists, |
| 93 | // replace it. |
| 94 | pkgs := devpkg.PackagesFromStringsWithOptions(lo.Uniq(pkgsNames), d.lockfile, opts) |
| 95 | |
| 96 | // addedPackageNames keeps track of the possibly transformed (versioned) |
| 97 | // names of added packages (even if they are already in config). We use this |
| 98 | // to know the exact name to mark as allowed insecure later on. |
| 99 | addedPackageNames := []string{} |
| 100 | existingPackageNames := lo.Map( |
| 101 | d.cfg.Root.TopLevelPackages(), func(p configfile.Package, _ int) string { |
| 102 | return p.VersionedName() |
| 103 | }) |
| 104 | for _, pkg := range pkgs { |
| 105 | // If exact versioned package is already in the config, we can skip the |
| 106 | // next loop that only deals with newPackages. |
| 107 | if slices.Contains(existingPackageNames, pkg.Versioned()) { |
| 108 | // But we still need to add to addedPackageNames. See its comment. |
| 109 | addedPackageNames = append(addedPackageNames, pkg.Versioned()) |
| 110 | unchangedPackageNames = append(unchangedPackageNames, pkg.Versioned()) |
| 111 | ux.Finfof(d.stderr, "Package %q already in devbox.json\n", pkg.Versioned()) |
| 112 | continue |
| 113 | } |
| 114 | |
| 115 | // On the other hand, if there's a package with same canonical name, replace |
| 116 | // it. Ignore error (which is either missing or more than one). We search by |
| 117 | // CanonicalName so any legacy or versioned packages will be removed if they |
| 118 | // match. |
| 119 | found, _ := d.findPackageByName(pkg.CanonicalName()) |
| 120 | if found != nil { |
| 121 | ux.Finfof(d.stderr, "Replacing package %q in devbox.json\n", found.Raw) |
| 122 | if err := d.Remove(ctx, found.Raw); err != nil { |
| 123 | return err |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | // validate that the versioned package exists in the search endpoint. |
| 128 | // if not, fallback to legacy vanilla nix. |
| 129 | versionedPkg := devpkg.PackageFromStringWithOptions(pkg.Versioned(), d.lockfile, opts) |
| 130 | |
| 131 | packageNameForConfig := pkg.Raw |
| 132 | ok, err := versionedPkg.ValidateExists(ctx) |
| 133 | if (err == nil && ok) || errors.Is(err, devpkg.ErrCannotBuildPackageOnSystem) { |
| 134 | // Only use versioned if it exists in search. We can disregard the error |
| 135 | // about not building on the current system, since user's can continue |
| 136 | // via --exclude-platform flag. |
| 137 | packageNameForConfig = pkg.Versioned() |
| 138 | } else if !versionedPkg.IsDevboxPackage { |
| 139 | // This means it didn't validate and we don't want to fallback to legacy |
| 140 | // Just propagate the error. |
| 141 | return err |
| 142 | } else { |
no test coverage detected