Merge pull request #24136 from backstage/yarn-plugin
Add `yarn-plugin-backstage`
This commit is contained in:
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,2 @@
|
||||
# @yarnpkg/builder build output
|
||||
bundles/
|
||||
@@ -0,0 +1,13 @@
|
||||
# yarn-plugin-backstage
|
||||
|
||||
This yarn plugin adds a `backstage:` version protocol to yarn, which replaces
|
||||
specific version ranges for `@backstage/` packages. The only version range
|
||||
supported by this plugin is `backstage:^`, which has similar semantics to the
|
||||
corresponding [`workspace` range](https://yarnpkg.com/features/workspaces#cross-references) when using
|
||||
workspace dependencies; locally, the package will always resolve to the exact
|
||||
version specified in the manifest for the Backstage release listed in
|
||||
backstage.json. If the dependent package is published, this version will be
|
||||
prefixed by `^`.
|
||||
|
||||
**This plugin is still under active development, and requires some further
|
||||
testing before we recommend it for general use.**
|
||||
@@ -0,0 +1,11 @@
|
||||
## API Report File for "yarn-plugin-backstage"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Plugin as Plugin_2 } from '@yarnpkg/core';
|
||||
|
||||
// @public (undocumented)
|
||||
const plugin: Plugin_2;
|
||||
export default plugin;
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: yarn-plugin-backstage
|
||||
title: yarn-plugin-backstage
|
||||
description: Yarn plugin for working with Backstage monorepos
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-node-library
|
||||
owner: maintainers
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "yarn-plugin-backstage",
|
||||
"version": "0.0.0",
|
||||
"description": "Yarn plugin for working with Backstage monorepos",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
},
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/yarn-plugin"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "builder build plugin",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"start": "nodemon --",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"exec": "builder build plugin",
|
||||
"ext": "ts",
|
||||
"watch": "./src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "workspace:^",
|
||||
"@backstage/release-manifests": "workspace:^",
|
||||
"@yarnpkg/core": "^4.0.3",
|
||||
"@yarnpkg/fslib": "^3.0.2",
|
||||
"semver": "^7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@yarnpkg/builder": "^4.0.0",
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
export const PROTOCOL = 'backstage:';
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2024 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 { Manifest, Workspace } from '@yarnpkg/core';
|
||||
import { npath, ppath } from '@yarnpkg/fslib';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
import { beforeWorkspacePacking } from './beforeWorkspacePacking';
|
||||
|
||||
jest.mock('@backstage/release-manifests', () => ({
|
||||
getManifestByVersion: jest.fn().mockResolvedValue({
|
||||
releaseVersion: '1.23.45',
|
||||
packages: [
|
||||
{
|
||||
name: '@backstage/core',
|
||||
version: '3.2.1',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
const makeWorkspace = (manifest: object) => {
|
||||
return {
|
||||
manifest: Manifest.fromText(JSON.stringify(manifest)),
|
||||
} as Workspace;
|
||||
};
|
||||
|
||||
describe('beforeWorkspacePacking', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(ppath, 'cwd')
|
||||
.mockReturnValue(npath.toPortablePath(mockDir.path));
|
||||
|
||||
jest
|
||||
.spyOn(process, 'cwd')
|
||||
.mockReturnValue(npath.toPortablePath(mockDir.path));
|
||||
|
||||
mockDir.setContent({
|
||||
'backstage.json': JSON.stringify({
|
||||
version: '1.23.45',
|
||||
}),
|
||||
'package.json': JSON.stringify({
|
||||
workspaces: {
|
||||
packages: ['packages/*'],
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe.each`
|
||||
dependencyType
|
||||
${'dependencies'}
|
||||
${'devDependencies'}
|
||||
${'optionalDependencies'}
|
||||
`('$dependencyType', ({ dependencyType }) => {
|
||||
it(`ignores ${dependencyType} that don't use the backstage: protocol`, () => {
|
||||
const result = {
|
||||
name: 'test-package',
|
||||
[dependencyType]: {
|
||||
foo: '^1.1.1',
|
||||
},
|
||||
};
|
||||
|
||||
beforeWorkspacePacking(makeWorkspace(result), result);
|
||||
|
||||
expect(result).toEqual({
|
||||
name: 'test-package',
|
||||
[dependencyType]: {
|
||||
foo: '^1.1.1',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`throws an error for any backstage: versions with a selector other than ^`, async () => {
|
||||
const result = {
|
||||
name: 'test-package',
|
||||
[dependencyType]: {
|
||||
'@backstage/core': 'backstage:^1.1.1',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(() =>
|
||||
beforeWorkspacePacking(makeWorkspace(result), result),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('converts backstage:^ versions to the corresponding package version prefixed by ^', async () => {
|
||||
const result = {
|
||||
name: 'test-package',
|
||||
[dependencyType]: {
|
||||
'@backstage/core': 'backstage:^',
|
||||
},
|
||||
};
|
||||
|
||||
await beforeWorkspacePacking(makeWorkspace(result), result);
|
||||
|
||||
expect(result).toEqual({
|
||||
name: 'test-package',
|
||||
[dependencyType]: {
|
||||
'@backstage/core': '^3.2.1',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2024 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, Workspace, structUtils } from '@yarnpkg/core';
|
||||
import { getCurrentBackstageVersion, getPackageVersion } from '../util';
|
||||
import { PROTOCOL } from '../constants';
|
||||
|
||||
const getFinalDependencyType = (
|
||||
dependencyType: string,
|
||||
descriptor: Descriptor,
|
||||
workspace: Workspace,
|
||||
) => {
|
||||
if (dependencyType !== 'dependencies') {
|
||||
return dependencyType;
|
||||
}
|
||||
|
||||
return workspace.manifest.ensureDependencyMeta(
|
||||
structUtils.makeDescriptor(descriptor, 'unknown'),
|
||||
).optional
|
||||
? 'optionalDependencies'
|
||||
: dependencyType;
|
||||
};
|
||||
|
||||
export const beforeWorkspacePacking = async (
|
||||
workspace: Workspace,
|
||||
rawManifest: any,
|
||||
) => {
|
||||
const backstageVersion = getCurrentBackstageVersion();
|
||||
|
||||
for (const dependencyType of ['dependencies', 'devDependencies'] as const) {
|
||||
const entries = Array.from(
|
||||
workspace.manifest.getForScope(dependencyType).values(),
|
||||
).filter(descriptor => descriptor.range.startsWith(PROTOCOL));
|
||||
|
||||
for (const descriptor of entries) {
|
||||
const ident = structUtils.stringifyIdent(descriptor);
|
||||
const range = structUtils.parseRange(descriptor.range);
|
||||
|
||||
if (range.selector !== '^') {
|
||||
throw new Error(
|
||||
`Unexpected version range "${descriptor.range}" for dependency on "${ident}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const finalDependencyType = getFinalDependencyType(
|
||||
dependencyType,
|
||||
descriptor,
|
||||
workspace,
|
||||
);
|
||||
|
||||
rawManifest[finalDependencyType][ident] = `^${await getPackageVersion(
|
||||
structUtils.makeDescriptor(
|
||||
descriptor,
|
||||
`${PROTOCOL}${backstageVersion}`,
|
||||
),
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Yarn plugin for resolving package versions based on
|
||||
* a Backstage version manifest.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
import { Plugin } from '@yarnpkg/core';
|
||||
import { beforeWorkspacePacking } from './handlers/beforeWorkspacePacking';
|
||||
import { BackstageResolver } from './resolver/BackstageResolver';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const plugin: Plugin = {
|
||||
hooks: {
|
||||
beforeWorkspacePacking,
|
||||
},
|
||||
resolvers: [BackstageResolver],
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2024 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 { structUtils } from '@yarnpkg/core';
|
||||
import { npath, ppath } from '@yarnpkg/fslib';
|
||||
import { BackstageResolver } from './BackstageResolver';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
jest.mock('@backstage/release-manifests', () => ({
|
||||
getManifestByVersion: jest.fn().mockResolvedValue({
|
||||
releaseVersion: '1.23.45',
|
||||
packages: [
|
||||
{
|
||||
name: '@backstage/core',
|
||||
version: '6.7.8',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('BackstageResolver', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
let backstageResolver: BackstageResolver;
|
||||
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(ppath, 'cwd')
|
||||
.mockReturnValue(npath.toPortablePath(mockDir.path));
|
||||
|
||||
jest
|
||||
.spyOn(process, 'cwd')
|
||||
.mockReturnValue(npath.toPortablePath(mockDir.path));
|
||||
|
||||
mockDir.setContent({
|
||||
'backstage.json': JSON.stringify({
|
||||
version: '1.23.45',
|
||||
}),
|
||||
'package.json': JSON.stringify({
|
||||
workspaces: {
|
||||
packages: ['packages/*'],
|
||||
},
|
||||
}),
|
||||
packages: {
|
||||
a: {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'a',
|
||||
dependencies: {
|
||||
'@backstage/core': 'backstage:^',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
backstageResolver = new BackstageResolver();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('supportsDescriptor', () => {
|
||||
it.each([
|
||||
['backstage:^'],
|
||||
['backstage:1.26.0-next.3'],
|
||||
['backstage:anything'],
|
||||
])('returns true for range "%s"', range => {
|
||||
expect(
|
||||
backstageResolver.supportsDescriptor(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
range,
|
||||
),
|
||||
),
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bindDescriptor', () => {
|
||||
describe('with range "backstage:^"', () => {
|
||||
it('returns a descriptor basedwith a version range for the current Backstage version', () => {
|
||||
expect(
|
||||
backstageResolver.bindDescriptor(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
'backstage:^',
|
||||
),
|
||||
),
|
||||
).toEqual(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
'backstage:1.23.45',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with range "backstage:1.23.45"', () => {
|
||||
it('throws an error', () => {
|
||||
expect(() =>
|
||||
backstageResolver.bindDescriptor(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
'backstage:1.23.45',
|
||||
),
|
||||
),
|
||||
).toThrow(/unsupported version range/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCandidates', () => {
|
||||
it('returns an npm: descriptor based on the manifest for the appropriate backstage version', async () => {
|
||||
const descriptor = structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
'backstage:1.23.45',
|
||||
);
|
||||
|
||||
await expect(
|
||||
backstageResolver.getCandidates(descriptor),
|
||||
).resolves.toEqual([structUtils.makeLocator(descriptor, 'npm:6.7.8')]);
|
||||
});
|
||||
|
||||
it('rejects descriptors not using the backstage: protocol', async () => {
|
||||
await expect(
|
||||
backstageResolver.getCandidates(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
'npm:1.2.3',
|
||||
),
|
||||
),
|
||||
).rejects.toThrow(/unsupported version protocol/i);
|
||||
});
|
||||
|
||||
it('rejects backstage: ranges with a ^ shorthand version', async () => {
|
||||
await expect(
|
||||
backstageResolver.getCandidates(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
'backstage:^',
|
||||
),
|
||||
),
|
||||
).rejects.toThrow(/invalid backstage version/i);
|
||||
});
|
||||
|
||||
it('rejects backstage: ranges with a * shorthand version', async () => {
|
||||
await expect(
|
||||
backstageResolver.getCandidates(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
'backstage:*',
|
||||
),
|
||||
),
|
||||
).rejects.toThrow(/invalid backstage version/i);
|
||||
});
|
||||
|
||||
it('rejects backstage: ranges with an invalid version specified', async () => {
|
||||
await expect(
|
||||
backstageResolver.getCandidates(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'core'),
|
||||
'backstage:latest',
|
||||
),
|
||||
),
|
||||
).rejects.toThrow(/invalid backstage version/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
structUtils,
|
||||
Descriptor,
|
||||
Locator,
|
||||
Package,
|
||||
Resolver,
|
||||
} from '@yarnpkg/core';
|
||||
import semver from 'semver';
|
||||
import { PROTOCOL } from '../constants';
|
||||
import { getCurrentBackstageVersion, getPackageVersion } from '../util';
|
||||
|
||||
export class BackstageResolver implements Resolver {
|
||||
static protocol = PROTOCOL;
|
||||
|
||||
supportsDescriptor = (descriptor: Descriptor) =>
|
||||
descriptor.range.startsWith(BackstageResolver.protocol);
|
||||
|
||||
shouldPersistResolution = () => true;
|
||||
|
||||
/**
|
||||
* Called for each dependency present in the dependency list of a package
|
||||
* definition. If it returns a new descriptor, this new descriptor will be
|
||||
* used.
|
||||
*
|
||||
* In this plugin, we convert the specific range "backstage:^" to
|
||||
* "backstage:<version from backstage.json>". This new range will be the one
|
||||
* stored in lockfile entries, ensuring that we re-resolve the package when
|
||||
* the version in backstage.json changes.
|
||||
*/
|
||||
bindDescriptor(descriptor: Descriptor): Descriptor {
|
||||
if (descriptor.range !== 'backstage:^') {
|
||||
throw new Error(
|
||||
`Unsupported version range "${
|
||||
descriptor.range
|
||||
}" for package ${structUtils.stringifyIdent(
|
||||
descriptor,
|
||||
)}. The backstage protocol only supports the range "backstage:^".`,
|
||||
);
|
||||
}
|
||||
|
||||
return structUtils.makeDescriptor(
|
||||
descriptor,
|
||||
`${PROTOCOL}${getCurrentBackstageVersion()}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a descriptor, return the list of locators that potentially satisfy
|
||||
* it. The implementation in this plugin converts a `backstage:` range with a
|
||||
* concrete version into the appropriate concrete npm version for that
|
||||
* backstage release.
|
||||
*/
|
||||
async getCandidates(descriptor: Descriptor): Promise<Locator[]> {
|
||||
const range = structUtils.parseRange(descriptor.range);
|
||||
if (range.protocol !== BackstageResolver.protocol) {
|
||||
throw new Error(
|
||||
`Unsupported version protocol in version range "${
|
||||
descriptor.range
|
||||
}" for package ${structUtils.stringifyIdent(descriptor)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!semver.valid(range.selector)) {
|
||||
throw new Error(
|
||||
`Invalid Backstage version string when resolving version for ${structUtils.stringifyIdent(
|
||||
descriptor,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
structUtils.makeLocator(
|
||||
descriptor,
|
||||
`npm:${await getPackageVersion(descriptor)}`,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This plugin does not need to support any locators itself, since the
|
||||
* `getCandidates` method will always convert `backstage:` versions into
|
||||
* `npm:` versions which can be handled as usual.
|
||||
*/
|
||||
supportsLocator = () => false;
|
||||
|
||||
/**
|
||||
* This resolver never transforms the packages that are actually depended on,
|
||||
* only replaces versions. As such there's never a need to add additional
|
||||
* dependencies.
|
||||
*/
|
||||
getResolutionDependencies = () => ({});
|
||||
|
||||
/**
|
||||
* Candidate versions produced by this resolver always use the `npm:`
|
||||
* protocol, so this function will never be called.
|
||||
*/
|
||||
async getSatisfying(): Promise<{ locators: Locator[]; sorted: boolean }> {
|
||||
throw new Error('Unreachable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Once transformed into locators (through getCandidates), the versions are
|
||||
* resolved by the NpmSemverResolver
|
||||
*/
|
||||
async resolve(): Promise<Package> {
|
||||
throw new Error(`Unreachable`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2024 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 { ppath, xfs } from '@yarnpkg/fslib';
|
||||
import { valid as semverValid } from 'semver';
|
||||
import { getManifestByVersion } from '@backstage/release-manifests';
|
||||
import { BACKSTAGE_JSON, findPaths } from '@backstage/cli-common';
|
||||
import { Descriptor, structUtils } from '@yarnpkg/core';
|
||||
import { PROTOCOL } from './constants';
|
||||
|
||||
export const getCurrentBackstageVersion = () => {
|
||||
const workspaceRoot = ppath.resolve(findPaths(ppath.cwd()).targetRoot);
|
||||
|
||||
const backstageJson = xfs.readJsonSync(
|
||||
ppath.join(workspaceRoot, BACKSTAGE_JSON),
|
||||
);
|
||||
|
||||
const backstageVersion = semverValid(backstageJson.version);
|
||||
|
||||
if (backstageVersion === null) {
|
||||
throw new Error('Valid version string not found in backstage.json');
|
||||
}
|
||||
|
||||
return backstageVersion;
|
||||
};
|
||||
|
||||
export const getPackageVersion = async (descriptor: Descriptor) => {
|
||||
const ident = structUtils.stringifyIdent(descriptor);
|
||||
const range = structUtils.parseRange(descriptor.range);
|
||||
|
||||
if (range.protocol !== PROTOCOL) {
|
||||
throw new Error(`Unexpected ${range.protocol} range when packing`);
|
||||
}
|
||||
|
||||
if (!semverValid(range.selector)) {
|
||||
throw new Error(`Missing backstage version in range ${descriptor.range}`);
|
||||
}
|
||||
|
||||
const manifest = await getManifestByVersion({
|
||||
version: range.selector,
|
||||
});
|
||||
|
||||
const manifestEntry = manifest.packages.find(
|
||||
candidate => candidate.name === ident,
|
||||
);
|
||||
|
||||
if (!manifestEntry) {
|
||||
throw new Error(
|
||||
`Package ${ident} not found in manifest for Backstage v${range.selector}. ` +
|
||||
`This means the specified package is not included in this Backstage ` +
|
||||
`release. This may imply the package has been replaced with an alternative - ` +
|
||||
`please review the documentation for the package. If you need to continue ` +
|
||||
`using this package, it will be necessary to switch to manually managing its ` +
|
||||
`version.`,
|
||||
);
|
||||
}
|
||||
|
||||
return manifestEntry.version;
|
||||
};
|
||||
Reference in New Issue
Block a user