Address PR review feedback

- Convert CliAuth getters to methods (getInstanceName, getBaseUrl) so
  options can be added in the future
- Remove StoredInstance from cli-node public API, hiding instance details
- Move secretStore to cli-internal for re-use, refactoring from fs-extra
  to node:fs
- Add shared getAuthInstanceService helper in cli-internal for
  constructing secret-store service keys
- Define StoredInstance locally in cli-module-auth instead of importing
  from cli-node
- Update all consumers and tests for the new method-based API

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-17 17:10:01 +01:00
parent da8e6603a4
commit 2b90358730
23 changed files with 160 additions and 89 deletions
@@ -65,9 +65,9 @@ export default async ({ args, info }: CliCommandContext) => {
process.exit(1);
}
const { accessToken, instance } = await resolveAuth(instanceFlag);
const { accessToken, baseUrl } = await resolveAuth(instanceFlag);
const client = new ActionsClient(instance.baseUrl, accessToken);
const client = new ActionsClient(baseUrl, accessToken);
const actions = await client.listForPlugin(actionId);
const action = actions.find(a => a.id === actionId);
@@ -36,7 +36,7 @@ export default async ({ args, info }: CliCommandContext) => {
args,
);
const { accessToken, pluginSources, instance } = await resolveAuth(
const { accessToken, pluginSources, baseUrl } = await resolveAuth(
instanceFlag,
);
@@ -47,7 +47,7 @@ export default async ({ args, info }: CliCommandContext) => {
return;
}
const client = new ActionsClient(instance.baseUrl, accessToken);
const client = new ActionsClient(baseUrl, accessToken);
const actions = await client.list(pluginSources);
if (!actions.length) {
@@ -40,7 +40,7 @@ export default async ({ args, info }: CliCommandContext) => {
return;
}
await updateInstanceConfig(auth.instanceName, 'pluginSources', [
await updateInstanceConfig(auth.getInstanceName(), 'pluginSources', [
...existing,
pluginId,
]);
@@ -39,7 +39,7 @@ export default async ({ args, info }: CliCommandContext) => {
}
await updateInstanceConfig(
auth.instanceName,
auth.getInstanceName(),
'pluginSources',
existing.filter(s => s !== pluginId),
);
@@ -15,7 +15,7 @@
*/
import { resolveAuth } from './resolveAuth';
import { CliAuth, type StoredInstance } from '@backstage/cli-node';
import { CliAuth } from '@backstage/cli-node';
jest.mock('@backstage/cli-node', () => {
const actual = jest.requireActual('@backstage/cli-node');
@@ -28,23 +28,14 @@ jest.mock('@backstage/cli-node', () => {
const mockCreate = CliAuth.create as jest.MockedFunction<typeof CliAuth.create>;
describe('resolveAuth', () => {
const mockInstance: StoredInstance = {
name: 'production',
baseUrl: 'https://backstage.example.com',
clientId: 'my-client',
issuedAt: Date.now(),
accessTokenExpiresAt: Date.now() + 3600_000,
};
beforeEach(() => {
jest.clearAllMocks();
});
it('resolves auth with the selected instance and stored token', async () => {
mockCreate.mockResolvedValue({
instance: mockInstance,
instanceName: mockInstance.name,
baseUrl: mockInstance.baseUrl,
getInstanceName: jest.fn().mockReturnValue('production'),
getBaseUrl: jest.fn().mockReturnValue('https://backstage.example.com'),
getAccessToken: jest.fn().mockResolvedValue('test-access-token'),
getConfig: jest.fn().mockResolvedValue(['catalog', 'scaffolder']),
} as unknown as CliAuth);
@@ -53,7 +44,8 @@ describe('resolveAuth', () => {
expect(mockCreate).toHaveBeenCalledWith({ instanceName: undefined });
expect(result).toEqual({
instance: mockInstance,
baseUrl: 'https://backstage.example.com',
instanceName: 'production',
accessToken: 'test-access-token',
pluginSources: ['catalog', 'scaffolder'],
});
@@ -61,9 +53,8 @@ describe('resolveAuth', () => {
it('passes instance name flag to CliAuth.create', async () => {
mockCreate.mockResolvedValue({
instance: mockInstance,
instanceName: mockInstance.name,
baseUrl: mockInstance.baseUrl,
getInstanceName: jest.fn().mockReturnValue('staging'),
getBaseUrl: jest.fn().mockReturnValue('https://staging.example.com'),
getAccessToken: jest.fn().mockResolvedValue('test-access-token'),
getConfig: jest.fn().mockResolvedValue([]),
} as unknown as CliAuth);
@@ -75,9 +66,8 @@ describe('resolveAuth', () => {
it('throws when getAccessToken fails', async () => {
mockCreate.mockResolvedValue({
instance: mockInstance,
instanceName: mockInstance.name,
baseUrl: mockInstance.baseUrl,
getInstanceName: jest.fn().mockReturnValue('production'),
getBaseUrl: jest.fn().mockReturnValue('https://backstage.example.com'),
getAccessToken: jest
.fn()
.mockRejectedValue(
@@ -93,9 +83,8 @@ describe('resolveAuth', () => {
it('returns empty plugin sources when none are configured', async () => {
mockCreate.mockResolvedValue({
instance: mockInstance,
instanceName: mockInstance.name,
baseUrl: mockInstance.baseUrl,
getInstanceName: jest.fn().mockReturnValue('production'),
getBaseUrl: jest.fn().mockReturnValue('https://backstage.example.com'),
getAccessToken: jest.fn().mockResolvedValue('test-access-token'),
getConfig: jest.fn().mockResolvedValue(undefined),
} as unknown as CliAuth);
@@ -14,10 +14,11 @@
* limitations under the License.
*/
import { CliAuth, type StoredInstance } from '@backstage/cli-node';
import { CliAuth } from '@backstage/cli-node';
export async function resolveAuth(instanceFlag?: string): Promise<{
instance: StoredInstance;
baseUrl: string;
instanceName: string;
accessToken: string;
pluginSources: string[];
}> {
@@ -25,5 +26,10 @@ export async function resolveAuth(instanceFlag?: string): Promise<{
const accessToken = await auth.getAccessToken();
const pluginSources = (await auth.getConfig<string[]>('pluginSources')) ?? [];
return { instance: auth.instance, accessToken, pluginSources };
return {
baseUrl: auth.getBaseUrl(),
instanceName: auth.getInstanceName(),
accessToken,
pluginSources,
};
}