Merge pull request #30091 from StateFarmIns/address_yarn_add
Address yarn add with backstage yarn plugin
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'yarn-plugin-backstage': patch
|
||||
---
|
||||
|
||||
added functionality so that adding or updating a backstage dependency to a package would maintain the "backstage:^" placeholder for the version.
|
||||
@@ -82,3 +82,12 @@ specific Backstage repository. As such, when publishing packages, all
|
||||
`backstage:^` versions should be removed from the package.json and replaced with
|
||||
the appropriate npm version ranges. This is handled by the
|
||||
`beforeWorkspacePacking` hook.
|
||||
|
||||
### `afterWorkspaceDependencyAddition` hook
|
||||
|
||||
_Replaces npm version ranges with `backstage:^` ranges for `@backstage/*` dependencies added after
|
||||
the plugin has converted existing dependencies to `backstage:^` range_
|
||||
|
||||
### `afterWorkspaceDependencyReplacement` hook
|
||||
|
||||
_warns user with console message when running `yarn add` for a `@backstage/*` scoped dependency that is already a dependency in the target package. Doing so will remove the `backstage:^` scope and replace it with the actual npm version range, which may not be desired._
|
||||
|
||||
@@ -32,8 +32,9 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "workspace:^",
|
||||
"@backstage/release-manifests": "workspace:^",
|
||||
"@yarnpkg/core": "^4.4.0",
|
||||
"@yarnpkg/core": "^4.4.1",
|
||||
"@yarnpkg/fslib": "^3.1.2",
|
||||
"@yarnpkg/plugin-essentials": "^4.4.0",
|
||||
"@yarnpkg/plugin-npm": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch",
|
||||
"@yarnpkg/plugin-pack": "^4.0.1",
|
||||
"semver": "^7.6.0"
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
```ts
|
||||
import { Hooks } from '@yarnpkg/core';
|
||||
import { Hooks as Hooks_2 } from '@yarnpkg/plugin-pack';
|
||||
import { Hooks as Hooks_2 } from '@yarnpkg/plugin-essentials';
|
||||
import { Hooks as Hooks_3 } from '@yarnpkg/plugin-pack';
|
||||
import { Plugin as Plugin_2 } from '@yarnpkg/core';
|
||||
|
||||
// @public (undocumented)
|
||||
const plugin: Plugin_2<Hooks & Hooks_2>;
|
||||
const plugin: Plugin_2<Hooks & Hooks_2 & Hooks_3>;
|
||||
export default plugin;
|
||||
```
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
Descriptor,
|
||||
DescriptorHash,
|
||||
IdentHash,
|
||||
Workspace,
|
||||
} from '@yarnpkg/core';
|
||||
import { suggestUtils } from '@yarnpkg/plugin-essentials';
|
||||
import { getPackageVersion } from '../util';
|
||||
import { afterWorkspaceDependencyAddition } from './afterWorkspaceDependencyAddition';
|
||||
|
||||
jest.mock('../util', () => ({
|
||||
getPackageVersion: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('afterWorkspaceDependencyAddition', () => {
|
||||
const workspace = {
|
||||
project: {
|
||||
configuration: {},
|
||||
},
|
||||
} as Workspace;
|
||||
const target = {} as suggestUtils.Target;
|
||||
const strategies: Array<suggestUtils.Strategy> = [];
|
||||
const mockGetPackageVersion = getPackageVersion as jest.MockedFunction<
|
||||
typeof getPackageVersion
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetPackageVersion.mockReset();
|
||||
});
|
||||
|
||||
it('should replace the range for a backstage scoped dependency', async () => {
|
||||
const input: Descriptor = {
|
||||
scope: 'backstage',
|
||||
name: 'test-package',
|
||||
range: '^1.0.0',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
|
||||
mockGetPackageVersion.mockImplementation(() => Promise.resolve('success'));
|
||||
|
||||
await afterWorkspaceDependencyAddition(
|
||||
workspace,
|
||||
target,
|
||||
input,
|
||||
strategies,
|
||||
);
|
||||
|
||||
expect(input.range).toBe('backstage:^');
|
||||
expect(mockGetPackageVersion).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetPackageVersion).toHaveBeenCalledWith(
|
||||
input,
|
||||
workspace.project.configuration,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not replace the range for a backstage scoped dependency where it cant find a version from remote', async () => {
|
||||
const input: Descriptor = {
|
||||
scope: 'backstage',
|
||||
name: 'test-package',
|
||||
range: '^1.0.0',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
|
||||
mockGetPackageVersion.mockImplementation(() =>
|
||||
Promise.reject(new Error('test error')),
|
||||
);
|
||||
|
||||
await afterWorkspaceDependencyAddition(
|
||||
workspace,
|
||||
target,
|
||||
input,
|
||||
strategies,
|
||||
);
|
||||
|
||||
expect(input.range).toBe('^1.0.0');
|
||||
expect(mockGetPackageVersion).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetPackageVersion).toHaveBeenCalledWith(
|
||||
input,
|
||||
workspace.project.configuration,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not replace the range for a non-backstage scoped dependency', async () => {
|
||||
const input: Descriptor = {
|
||||
scope: 'backstage-community',
|
||||
name: 'test-package',
|
||||
range: '^1.0.0',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
|
||||
await afterWorkspaceDependencyAddition(
|
||||
workspace,
|
||||
target,
|
||||
input,
|
||||
strategies,
|
||||
);
|
||||
|
||||
expect(input.range).toBe('^1.0.0');
|
||||
expect(mockGetPackageVersion).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Descriptor, structUtils, Workspace } from '@yarnpkg/core';
|
||||
import { suggestUtils } from '@yarnpkg/plugin-essentials';
|
||||
import { getPackageVersion } from '../util';
|
||||
import { PROTOCOL } from '../constants';
|
||||
|
||||
export const afterWorkspaceDependencyAddition = async (
|
||||
workspace: Workspace,
|
||||
_target: suggestUtils.Target,
|
||||
descriptor: Descriptor,
|
||||
_strategies: Array<suggestUtils.Strategy>,
|
||||
) => {
|
||||
const descriptorRange = structUtils.parseRange(descriptor.range);
|
||||
|
||||
if (
|
||||
descriptor.scope === 'backstage' &&
|
||||
descriptorRange.protocol !== PROTOCOL
|
||||
) {
|
||||
try {
|
||||
await getPackageVersion(descriptor, workspace.project.configuration);
|
||||
console.info(
|
||||
`Setting ${descriptor.scope}/${descriptor.name} to ${PROTOCOL}^`,
|
||||
);
|
||||
descriptor.range = `${PROTOCOL}^`;
|
||||
} catch (_error: any) {
|
||||
// if there's no found version then this is likely a deprecated package
|
||||
// or otherwise the plugin won't be able to resolve the real version
|
||||
// and we should leave the desired range as is
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
Descriptor,
|
||||
DescriptorHash,
|
||||
IdentHash,
|
||||
Workspace,
|
||||
} from '@yarnpkg/core';
|
||||
import { suggestUtils } from '@yarnpkg/plugin-essentials';
|
||||
import { getPackageVersion } from '../util';
|
||||
import { afterWorkspaceDependencyReplacement } from './afterWorkspaceDependencyReplacement';
|
||||
|
||||
jest.mock('../util', () => ({
|
||||
getPackageVersion: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('afterWorkspaceDependencyReplacement.test', () => {
|
||||
const workspace = {
|
||||
project: {
|
||||
configuration: {},
|
||||
},
|
||||
} as Workspace;
|
||||
const target = {} as suggestUtils.Target;
|
||||
const mockGetPackageVersion = getPackageVersion as jest.MockedFunction<
|
||||
typeof getPackageVersion
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetPackageVersion.mockReset();
|
||||
});
|
||||
|
||||
it('should warn that the range is being changed for a backstage scoped dependency', async () => {
|
||||
const fromDescriptor: Descriptor = {
|
||||
scope: 'backstage',
|
||||
name: 'test-package',
|
||||
range: 'backstage:^',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
const toDescriptor: Descriptor = {
|
||||
scope: 'backstage',
|
||||
name: 'test-package',
|
||||
range: '^1.0.0',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
|
||||
mockGetPackageVersion.mockImplementation(() => Promise.resolve('success'));
|
||||
|
||||
await afterWorkspaceDependencyReplacement(
|
||||
workspace,
|
||||
target,
|
||||
fromDescriptor,
|
||||
toDescriptor,
|
||||
);
|
||||
|
||||
expect(toDescriptor.range).toBe('^1.0.0');
|
||||
expect(mockGetPackageVersion).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetPackageVersion).toHaveBeenCalledWith(
|
||||
toDescriptor,
|
||||
workspace.project.configuration,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn that the range is being changed for a backstage scoped dependency where it cant find a version from remote', async () => {
|
||||
const fromDescriptor: Descriptor = {
|
||||
scope: 'backstage',
|
||||
name: 'test-package',
|
||||
range: 'backstage:^',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
const toDescriptor: Descriptor = {
|
||||
scope: 'backstage',
|
||||
name: 'test-package',
|
||||
range: '^1.0.0',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
|
||||
mockGetPackageVersion.mockImplementation(() =>
|
||||
Promise.reject(new Error('test error')),
|
||||
);
|
||||
|
||||
await afterWorkspaceDependencyReplacement(
|
||||
workspace,
|
||||
target,
|
||||
fromDescriptor,
|
||||
toDescriptor,
|
||||
);
|
||||
|
||||
expect(toDescriptor.range).toBe('^1.0.0');
|
||||
expect(mockGetPackageVersion).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetPackageVersion).toHaveBeenCalledWith(
|
||||
toDescriptor,
|
||||
workspace.project.configuration,
|
||||
);
|
||||
});
|
||||
|
||||
it('should ignore that the range is being changed for a non-backstage scoped dependency', async () => {
|
||||
const fromDescriptor: Descriptor = {
|
||||
scope: 'backstage-community',
|
||||
name: 'test-package',
|
||||
range: 'backstage:^',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
const toDescriptor: Descriptor = {
|
||||
scope: 'backstage-community',
|
||||
name: 'test-package',
|
||||
range: '^1.0.0',
|
||||
descriptorHash: {} as DescriptorHash,
|
||||
identHash: {} as IdentHash,
|
||||
};
|
||||
await afterWorkspaceDependencyReplacement(
|
||||
workspace,
|
||||
target,
|
||||
fromDescriptor,
|
||||
toDescriptor,
|
||||
);
|
||||
|
||||
expect(toDescriptor.range).toBe('^1.0.0');
|
||||
expect(mockGetPackageVersion).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Descriptor, structUtils, Workspace } from '@yarnpkg/core';
|
||||
import { suggestUtils } from '@yarnpkg/plugin-essentials';
|
||||
import { getPackageVersion } from '../util';
|
||||
import { PROTOCOL } from '../constants';
|
||||
|
||||
export const afterWorkspaceDependencyReplacement = async (
|
||||
workspace: Workspace,
|
||||
_target: suggestUtils.Target,
|
||||
_fromDescriptor: Descriptor,
|
||||
toDescriptor: Descriptor,
|
||||
) => {
|
||||
const toDescriptorRange = structUtils.parseRange(toDescriptor.range);
|
||||
|
||||
if (
|
||||
toDescriptor.scope === 'backstage' &&
|
||||
toDescriptorRange.protocol !== PROTOCOL
|
||||
) {
|
||||
try {
|
||||
await getPackageVersion(toDescriptor, workspace.project.configuration);
|
||||
console.warn(
|
||||
`${toDescriptor.name} should be set to "${PROTOCOL}^" instead of "${toDescriptor.range}". Make sure this change is intentional and not a mistake.`,
|
||||
);
|
||||
} catch (_error: any) {
|
||||
// if there's no found version then this is likely a deprecated package
|
||||
// or otherwise the plugin won't be able to resolve the real version
|
||||
// and we should not warn them.
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -16,3 +16,5 @@
|
||||
|
||||
export { beforeWorkspacePacking } from './beforeWorkspacePacking';
|
||||
export { reduceDependency } from './reduceDependency';
|
||||
export { afterWorkspaceDependencyAddition } from './afterWorkspaceDependencyAddition';
|
||||
export { afterWorkspaceDependencyReplacement } from './afterWorkspaceDependencyReplacement';
|
||||
|
||||
@@ -23,7 +23,13 @@
|
||||
|
||||
import { Plugin, Hooks, semverUtils, YarnVersion } from '@yarnpkg/core';
|
||||
import { Hooks as PackHooks } from '@yarnpkg/plugin-pack';
|
||||
import { beforeWorkspacePacking, reduceDependency } from './handlers';
|
||||
import { Hooks as EssentialHooks } from '@yarnpkg/plugin-essentials';
|
||||
import {
|
||||
afterWorkspaceDependencyAddition,
|
||||
afterWorkspaceDependencyReplacement,
|
||||
beforeWorkspacePacking,
|
||||
reduceDependency,
|
||||
} from './handlers';
|
||||
import { BackstageNpmResolver } from './resolvers';
|
||||
|
||||
// All dependencies of the yarn plugin are bundled during the build. Chalk
|
||||
@@ -44,8 +50,10 @@ if (!semverUtils.satisfiesWithPrereleases(YarnVersion, '^4.1.1')) {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const plugin: Plugin<Hooks & PackHooks> = {
|
||||
const plugin: Plugin<Hooks & EssentialHooks & PackHooks> = {
|
||||
hooks: {
|
||||
afterWorkspaceDependencyAddition,
|
||||
afterWorkspaceDependencyReplacement,
|
||||
reduceDependency,
|
||||
beforeWorkspacePacking,
|
||||
},
|
||||
|
||||
@@ -24036,7 +24036,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@yarnpkg/core@npm:^4.2.1, @yarnpkg/core@npm:^4.4.0":
|
||||
"@yarnpkg/core@npm:^4.2.1, @yarnpkg/core@npm:^4.4.0, @yarnpkg/core@npm:^4.4.1":
|
||||
version: 4.4.1
|
||||
resolution: "@yarnpkg/core@npm:4.4.1"
|
||||
dependencies:
|
||||
@@ -50645,8 +50645,9 @@ __metadata:
|
||||
"@backstage/cli-common": "workspace:^"
|
||||
"@backstage/release-manifests": "workspace:^"
|
||||
"@yarnpkg/builder": "npm:^4.2.1"
|
||||
"@yarnpkg/core": "npm:^4.4.0"
|
||||
"@yarnpkg/core": "npm:^4.4.1"
|
||||
"@yarnpkg/fslib": "npm:^3.1.2"
|
||||
"@yarnpkg/plugin-essentials": "npm:^4.4.0"
|
||||
"@yarnpkg/plugin-npm": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch"
|
||||
"@yarnpkg/plugin-pack": "npm:^4.0.1"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
|
||||
Reference in New Issue
Block a user