Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,8 @@ For all project commands the \<`path`\> option is optional:
- If not valid json, return error.
- Validate local package json file structure, fields and values.
- If not validate structure, fields or values, return error.
- Check whether the dependency has already been added.
- If already added, then return error.
- Check whether the dependency has already been added at the requested version.
- If already added, the requested end state already holds - return the local package file as-is without error (idempotent success), rather than re-installing or re-adding it.
- Install dependency using same logic as package install
- Add dependency to the local package file and save.

Expand Down Expand Up @@ -586,7 +586,7 @@ Install existing dependencies listed inside a package json file.
- Validate local package json file structure, fields and values.
- If not validate structure, fields or values, return error.
- Check whether the dependency exists in the local package json file.
- If not a dependency, then return error.
- If not a dependency, the requested end state already holds - return the local package file as-is without error (idempotent success, mirroring [Install and add dependency logic](#install-and-add-dependency-logic)), rather than treating it as a failure.
- Uninstall dependency using same logic as package uninstall
- Remove dependency from the local package file and save.

Expand Down
10 changes: 8 additions & 2 deletions src/classes/ManagerLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,14 @@ export class ManagerLocal extends Manager {
async uninstallDependency(slug: string, version?: string, filePath?: string, type = RegistryType.Plugins) {
// Get local package file.
const pkgFile = packageLoadFile(filePath) as any;
if (!pkgFile[type]) throw new Error(`Package ${type} is missing`);
if (!pkgFile[type][slug]) throw new Error(`Package ${type} ${slug} is not a dependency`);
if (!pkgFile[type] || !pkgFile[type][slug]) {
// Mirrors installDependency()'s "already a dependency" no-op: the requested end state
// (this dependency is gone) is already true, so this is idempotent success rather than an
// error - matches how most package managers treat "already removed"/"already installed".
this.log(`Package ${type} ${slug} is not a dependency`);
pkgFile.installed = true;
return pkgFile;
}

// Uninstall dependency.
const manager = new ManagerLocal(type, this.config.config);
Expand Down
10 changes: 10 additions & 0 deletions tests/classes/ManagerLocal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,18 @@ test('Project sync, install project, add new dependency, remove new dependency',
const pkgDeps = await manager.installDependency(PLUGIN_PACKAGE.slug, '1.3.4', PROJECT_PATH);
expect(omitDownloads(pkgDeps)).toEqual(omitDownloads(PROJECT_DEPS));

// Adding the same dependency again is idempotent success, not an error - the requested end
// state (dependency present at this version) already holds.
const pkgDepsAgain = await manager.installDependency(PLUGIN_PACKAGE.slug, '1.3.4', PROJECT_PATH);
expect(omitDownloads(pkgDepsAgain)).toEqual(omitDownloads(PROJECT_DEPS));

const pkgNoDeps = await manager.uninstallDependency(PLUGIN_PACKAGE.slug, '1.3.4', PROJECT_PATH);
expect(omitDownloads(pkgNoDeps)).toEqual(omitDownloads(PROJECT_NO_DEPS));

// Removing a dependency that's already gone is likewise idempotent success, mirroring
// installDependency() above, rather than throwing "not a dependency".
const pkgNoDepsAgain = await manager.uninstallDependency(PLUGIN_PACKAGE.slug, '1.3.4', PROJECT_PATH);
expect(omitDownloads(pkgNoDepsAgain)).toEqual(omitDownloads(PROJECT_NO_DEPS));
});

test('Create save persists an incomplete package without throwing', () => {
Expand Down
Loading