yarn-plugin: memoize loading of backstage.json
Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
@@ -14,8 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { join as joinPath } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { Configuration, Project, structUtils, httpUtils } from '@yarnpkg/core';
|
||||
import { npath, ppath } from '@yarnpkg/fslib';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
@@ -146,48 +144,6 @@ describe('reduceDependency', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if backstage.json is missing', async () => {
|
||||
await fs.remove(joinPath(mockDir.path, 'backstage.json'));
|
||||
|
||||
await expect(
|
||||
reduceDependency(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'other'),
|
||||
'backstage:^',
|
||||
),
|
||||
project,
|
||||
),
|
||||
).rejects.toThrow(
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining(
|
||||
'Valid version string not found in backstage.json',
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it(`throws if backstage.json doesn't contain a valid version`, async () => {
|
||||
mockDir.addContent({
|
||||
'backstage.json': JSON.stringify({}),
|
||||
});
|
||||
|
||||
await expect(
|
||||
reduceDependency(
|
||||
structUtils.makeDescriptor(
|
||||
structUtils.makeIdent('backstage', 'other'),
|
||||
'backstage:^',
|
||||
),
|
||||
project,
|
||||
),
|
||||
).rejects.toThrow(
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining(
|
||||
'Valid version string not found in backstage.json',
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it(`throws for packages that don't appear in the manifest`, async () => {
|
||||
await expect(
|
||||
reduceDependency(
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 { npath, ppath, xfs } from '@yarnpkg/fslib';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { memoize } from './memoize';
|
||||
import { getCurrentBackstageVersion } from './getCurrentBackstageVersion';
|
||||
|
||||
/**
|
||||
* Disable memoization to allow testing behavior under a variety of
|
||||
* circumstances.
|
||||
*/
|
||||
jest.mock('./memoize', () => {
|
||||
const memoizeMock: jest.MockedFn<typeof memoize> & {
|
||||
memoizationEnabled?: boolean;
|
||||
} = jest.fn(fn => {
|
||||
const memoized = jest.requireActual('./memoize').memoize(fn);
|
||||
|
||||
return () => {
|
||||
if (memoizeMock.memoizationEnabled) {
|
||||
return memoized();
|
||||
}
|
||||
|
||||
return fn();
|
||||
};
|
||||
});
|
||||
|
||||
return { memoize: memoizeMock };
|
||||
});
|
||||
|
||||
const memoizeMock = memoize as jest.MockedFunction<typeof memoize> & {
|
||||
memoizationEnabled?: boolean;
|
||||
};
|
||||
|
||||
describe('getCurrentBackstageVersion', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(process, 'cwd')
|
||||
.mockReturnValue(npath.toPortablePath(mockDir.path));
|
||||
|
||||
jest
|
||||
.spyOn(ppath, 'cwd')
|
||||
.mockReturnValue(npath.toPortablePath(mockDir.path));
|
||||
|
||||
mockDir.setContent({
|
||||
'package.json': JSON.stringify({}),
|
||||
});
|
||||
});
|
||||
|
||||
it('retrieves the version of Backstage from backstage.json', () => {
|
||||
mockDir.addContent({
|
||||
'backstage.json': JSON.stringify({
|
||||
version: '1.23.45',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(getCurrentBackstageVersion()).toEqual('1.23.45');
|
||||
});
|
||||
|
||||
it.each`
|
||||
description | content
|
||||
${'is missing'} | ${{}}
|
||||
${'is invalid'} | ${{ 'backstage.json': '}{' }}
|
||||
${'has missing version'} | ${{ 'backstage.json': '{"a":"b"}' }}
|
||||
${'has invalid version'} | ${{ 'backstage.json': '{"version":"foobar"}' }}
|
||||
`('throws if backstage.json $description', ({ content }) => {
|
||||
mockDir.addContent(content);
|
||||
|
||||
expect(() => getCurrentBackstageVersion()).toThrow(
|
||||
/valid version string not found/i,
|
||||
);
|
||||
});
|
||||
|
||||
it('caches repeated calls', () => {
|
||||
mockDir.addContent({
|
||||
'backstage.json': JSON.stringify({
|
||||
version: '1.23.45',
|
||||
}),
|
||||
});
|
||||
|
||||
memoizeMock.memoizationEnabled = true;
|
||||
|
||||
const readJsonSyncSpy = jest.spyOn(xfs, 'readJsonSync');
|
||||
|
||||
expect(readJsonSyncSpy).toHaveBeenCalledTimes(0);
|
||||
|
||||
getCurrentBackstageVersion();
|
||||
getCurrentBackstageVersion();
|
||||
|
||||
expect(readJsonSyncSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 assert from 'assert';
|
||||
import { valid as semverValid } from 'semver';
|
||||
import { npath, ppath, xfs } from '@yarnpkg/fslib';
|
||||
import { BACKSTAGE_JSON, findPaths } from '@backstage/cli-common';
|
||||
import { memoize } from './memoize';
|
||||
|
||||
export const getCurrentBackstageVersion = memoize(() => {
|
||||
const workspaceRoot = npath.toPortablePath(
|
||||
findPaths(npath.fromPortablePath(ppath.cwd())).targetRoot,
|
||||
);
|
||||
const backstageJsonPath = ppath.join(workspaceRoot, BACKSTAGE_JSON);
|
||||
|
||||
let backstageVersion: string | null = null;
|
||||
try {
|
||||
backstageVersion = semverValid(xfs.readJsonSync(backstageJsonPath).version);
|
||||
|
||||
assert(backstageVersion !== null);
|
||||
} catch {
|
||||
throw new Error('Valid version string not found in backstage.json');
|
||||
}
|
||||
|
||||
return backstageVersion;
|
||||
});
|
||||
+2
-27
@@ -20,35 +20,10 @@ import {
|
||||
httpUtils,
|
||||
structUtils,
|
||||
} from '@yarnpkg/core';
|
||||
import { ppath, npath, xfs } from '@yarnpkg/fslib';
|
||||
import { valid as semverValid } from 'semver';
|
||||
import { BACKSTAGE_JSON, findPaths } from '@backstage/cli-common';
|
||||
import { getManifestByVersion } from '@backstage/release-manifests';
|
||||
|
||||
import { PROTOCOL } from './constants';
|
||||
|
||||
export const getCurrentBackstageVersion = () => {
|
||||
const workspaceRoot = npath.toPortablePath(
|
||||
findPaths(npath.fromPortablePath(ppath.cwd())).targetRoot,
|
||||
);
|
||||
const backstageJsonPath = ppath.join(workspaceRoot, BACKSTAGE_JSON);
|
||||
|
||||
if (!xfs.existsSync(backstageJsonPath)) {
|
||||
throw new Error('Valid version string not found in backstage.json');
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
import { PROTOCOL } from '../constants';
|
||||
import { getCurrentBackstageVersion } from './getCurrentBackstageVersion';
|
||||
|
||||
export const getPackageVersion = async (
|
||||
descriptor: Descriptor,
|
||||
@@ -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 { getPackageVersion } from './getPackageVersion';
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 { memoize } from './memoize';
|
||||
|
||||
describe('memoize', () => {
|
||||
it('memoizes', () => {
|
||||
const mockFunc = jest.fn().mockReturnValue('abc');
|
||||
|
||||
const memoized = memoize(mockFunc);
|
||||
|
||||
expect(memoized()).toEqual('abc');
|
||||
expect(memoized()).toEqual('abc');
|
||||
expect(memoized()).toEqual('abc');
|
||||
|
||||
expect(mockFunc).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 memoize = <T>(fn: () => T): typeof fn => {
|
||||
let invoked = false;
|
||||
let result: ReturnType<typeof fn>;
|
||||
|
||||
return () => {
|
||||
if (!invoked) {
|
||||
result = fn();
|
||||
invoked = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user