Address second round of PR review feedback

- Replace getConfig<T> with getMetadata/setMetadata on CliAuth, removing
  the unsafe type parameter in favor of returning unknown
- Move updateInstanceConfig from cli-module-auth public API to
  CliAuth.setMetadata, removing the cross-package dependency
- Rename 'config' to 'metadata' in StoredInstance and storage schemas
- Add zod validation at consumer sites (cli-module-actions) for
  type-safe metadata access
- Fix zod imports to use zod/v3 for compatibility with zod v4
- Add proper-lockfile to cli-node for metadata write locking
- Refactor cli-node storage from fs-extra to node:fs
- Remove @backstage/cli-module-auth dependency from cli-module-actions

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-17 19:24:26 +01:00
parent 2b90358730
commit 4f6e7de133
18 changed files with 168 additions and 86 deletions
-7
View File
@@ -9,12 +9,5 @@ import { CliModule } from '@backstage/cli-node';
const _default: CliModule;
export default _default;
// @public (undocumented)
export function updateInstanceConfig(
instanceName: string,
key: string,
value: unknown,
): Promise<void>;
// (No @packageDocumentation comment for this package)
```
-2
View File
@@ -52,5 +52,3 @@ export default createCliModule({
});
},
});
export { updateInstanceConfig } from './lib/storage';
@@ -22,8 +22,8 @@ import {
getAllInstances,
getSelectedInstance,
getInstanceByName,
getInstanceConfig,
updateInstanceConfig,
getInstanceMetadata,
updateInstanceMetadata,
upsertInstance,
removeInstance,
setSelectedInstance,
@@ -359,65 +359,65 @@ describe('storage', () => {
});
});
describe('getInstanceConfig', () => {
it('should return undefined when no config set', async () => {
describe('getInstanceMetadata', () => {
it('should return undefined when no metadata set', async () => {
await upsertInstance(mockInstance1);
const result = await getInstanceConfig('production', 'someKey');
const result = await getInstanceMetadata('production', 'someKey');
expect(result).toBeUndefined();
});
it('should return config value for a key', async () => {
it('should return metadata value for a key', async () => {
await upsertInstance(mockInstance1);
await updateInstanceConfig('production', 'myKey', 'myValue');
await updateInstanceMetadata('production', 'myKey', 'myValue');
const result = await getInstanceConfig('production', 'myKey');
const result = await getInstanceMetadata('production', 'myKey');
expect(result).toBe('myValue');
});
it('should throw NotFoundError for unknown instance', async () => {
await expect(getInstanceConfig('nonexistent', 'key')).rejects.toThrow(
await expect(getInstanceMetadata('nonexistent', 'key')).rejects.toThrow(
NotFoundError,
);
});
});
describe('updateInstanceConfig', () => {
it('should set a config value', async () => {
describe('updateInstanceMetadata', () => {
it('should set a metadata value', async () => {
await upsertInstance(mockInstance1);
await updateInstanceConfig('production', 'key1', 'value1');
await updateInstanceMetadata('production', 'key1', 'value1');
const result = await getInstanceConfig('production', 'key1');
const result = await getInstanceMetadata('production', 'key1');
expect(result).toBe('value1');
});
it('should preserve existing config keys', async () => {
it('should preserve existing metadata keys', async () => {
await upsertInstance(mockInstance1);
await updateInstanceConfig('production', 'key1', 'value1');
await updateInstanceConfig('production', 'key2', 'value2');
await updateInstanceMetadata('production', 'key1', 'value1');
await updateInstanceMetadata('production', 'key2', 'value2');
const result1 = await getInstanceConfig('production', 'key1');
const result2 = await getInstanceConfig('production', 'key2');
const result1 = await getInstanceMetadata('production', 'key1');
const result2 = await getInstanceMetadata('production', 'key2');
expect(result1).toBe('value1');
expect(result2).toBe('value2');
});
it('should throw NotFoundError for unknown instance', async () => {
await expect(
updateInstanceConfig('nonexistent', 'key', 'value'),
updateInstanceMetadata('nonexistent', 'key', 'value'),
).rejects.toThrow(NotFoundError);
});
it('should remove instance along with its config', async () => {
it('should remove instance along with its metadata', async () => {
await upsertInstance(mockInstance1);
await updateInstanceConfig('production', 'key1', 'value1');
await updateInstanceMetadata('production', 'key1', 'value1');
await removeInstance('production');
const { instances } = await getAllInstances();
expect(instances.find(i => i.name === 'production')).toBeUndefined();
await upsertInstance(mockInstance1);
const result = await getInstanceConfig('production', 'key1');
const result = await getInstanceMetadata('production', 'key1');
expect(result).toBeUndefined();
});
});
+7 -8
View File
@@ -29,7 +29,7 @@ export type StoredInstance = {
issuedAt: number;
accessTokenExpiresAt: number;
selected?: boolean;
config?: Record<string, unknown>;
metadata?: Record<string, unknown>;
};
const METADATA_FILE = 'auth-instances.yaml';
@@ -46,7 +46,7 @@ const storedInstanceSchema = z.object({
issuedAt: z.number().int().nonnegative(),
accessTokenExpiresAt: z.number().int().nonnegative(),
selected: z.boolean().optional(),
config: z.record(z.string(), z.unknown()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
const authYamlSchema = z.object({
@@ -168,16 +168,15 @@ export async function setSelectedInstance(name: string): Promise<void> {
});
}
export async function getInstanceConfig<T = unknown>(
export async function getInstanceMetadata(
instanceName: string,
key: string,
): Promise<T | undefined> {
): Promise<unknown> {
const instance = await getInstanceByName(instanceName);
return instance.config?.[key] as T | undefined;
return instance.metadata?.[key];
}
/** @public */
export async function updateInstanceConfig(
export async function updateInstanceMetadata(
instanceName: string,
key: string,
value: unknown,
@@ -190,7 +189,7 @@ export async function updateInstanceConfig(
}
data.instances[idx] = {
...data.instances[idx],
config: { ...data.instances[idx].config, [key]: value },
metadata: { ...data.instances[idx].metadata, [key]: value },
};
await writeAll(data);
});