chore: refactor to use opaque type helper instead
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -26,7 +26,7 @@ export const mockDecorator = createScaffolderFormDecorator({
|
||||
deps: {
|
||||
githubApi: githubAuthApiRef,
|
||||
},
|
||||
fn: async ({ setSecrets }, { githubApi }) => {
|
||||
decorator: async ({ setSecrets }, { githubApi }) => {
|
||||
const token = await githubApi.getAccessToken();
|
||||
setSecrets(state => ({ ...state, GITHUB_TOKEN: token }));
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-react": "workspace:^",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 { OpaqueType } from '@internal/opaque';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ScaffolderFormDecorator,
|
||||
ScaffolderFormDecoratorContext,
|
||||
} from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { AnyApiRef } from '@backstage/frontend-plugin-api';
|
||||
|
||||
/** @alpha */
|
||||
export const OpaqueFormDecorator = OpaqueType.create<{
|
||||
public: ScaffolderFormDecorator;
|
||||
versions: {
|
||||
readonly version: 'v1';
|
||||
readonly id: string;
|
||||
readonly schema?: {
|
||||
input?: {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
};
|
||||
};
|
||||
readonly deps?: { [key in string]: AnyApiRef };
|
||||
readonly decorator: (
|
||||
ctx: ScaffolderFormDecoratorContext,
|
||||
deps: { [depName in string]: AnyApiRef['T'] },
|
||||
) => Promise<void>;
|
||||
};
|
||||
}>({ type: '@backstage/scaffolder/FormDecorator', versions: ['v1'] });
|
||||
@@ -14,3 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { OpaqueFormField, type FormField } from './InternalFormField';
|
||||
export { OpaqueFormDecorator } from './InternalFormDecorator';
|
||||
|
||||
@@ -106,7 +106,7 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
|
||||
return;
|
||||
}
|
||||
|
||||
await formDecorator.fn({
|
||||
await formDecorator.decorator({
|
||||
setSecrets,
|
||||
setFormState: (
|
||||
handler: (
|
||||
@@ -116,7 +116,7 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
|
||||
formState = { ...handler(formState) };
|
||||
},
|
||||
formState,
|
||||
input: decorator.input,
|
||||
input: decorator.input ?? {},
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('createScaffolderFormDecorator', () => {
|
||||
const decorator = createScaffolderFormDecorator({
|
||||
id: 'test',
|
||||
deps: {},
|
||||
fn: async () => {},
|
||||
decorator: async () => {},
|
||||
});
|
||||
|
||||
expect(decorator).toMatchInlineSnapshot(`
|
||||
@@ -43,7 +43,7 @@ describe('createScaffolderFormDecorator', () => {
|
||||
age: z => z.number(),
|
||||
},
|
||||
},
|
||||
fn: async ctx => {
|
||||
decorator: async ctx => {
|
||||
const name: string = ctx.input.name;
|
||||
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { AnyApiRef } from '@backstage/core-plugin-api';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { OpaqueFormDecorator } from '@internal/scaffolder';
|
||||
import { z } from 'zod';
|
||||
|
||||
/** @alpha */
|
||||
export type ScaffolderFormDecoratorContext<TInput> = {
|
||||
export type ScaffolderFormDecoratorContext<
|
||||
TInput extends JsonObject = JsonObject,
|
||||
> = {
|
||||
input: TInput;
|
||||
formState: Record<string, JsonValue>;
|
||||
|
||||
@@ -31,53 +34,51 @@ export type ScaffolderFormDecoratorContext<TInput> = {
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type ScaffolderFormDecorator<
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
TDeps extends { [key in string]: AnyApiRef },
|
||||
> = {
|
||||
version: 'v1';
|
||||
id: string;
|
||||
schema?: {
|
||||
input?: TInputSchema;
|
||||
};
|
||||
deps?: TDeps;
|
||||
fn: (
|
||||
ctx: ScaffolderFormDecoratorContext<{
|
||||
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
|
||||
}>,
|
||||
deps: TDeps extends { [key in string]: AnyApiRef }
|
||||
? { [key in keyof TDeps]: TDeps[key]['T'] }
|
||||
: never,
|
||||
) => Promise<void>;
|
||||
export type ScaffolderFormDecorator<TInput extends JsonObject = JsonObject> = {
|
||||
readonly $$type: '@backstage/scaffolder/FormDecorator';
|
||||
readonly id: string;
|
||||
readonly TInput: TInput;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type AnyScaffolderFormDecorator = ScaffolderFormDecorator<any, any>;
|
||||
/**
|
||||
* Method for creating decorators which can be used to collect
|
||||
* secrets from the user before submitting to the backend.
|
||||
* @alpha
|
||||
*/
|
||||
export function createScaffolderFormDecorator<
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
TDeps extends { [key in string]: AnyApiRef },
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
TDeps extends { [key in string]: AnyApiRef } = { [key in string]: AnyApiRef },
|
||||
TInput extends JsonObject = {
|
||||
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
|
||||
},
|
||||
>(options: {
|
||||
id: string;
|
||||
schema?: {
|
||||
input?: TInputSchema;
|
||||
};
|
||||
deps?: TDeps;
|
||||
fn: (
|
||||
ctx: ScaffolderFormDecoratorContext<{
|
||||
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
|
||||
}>,
|
||||
decorator: (
|
||||
ctx: ScaffolderFormDecoratorContext<TInput>,
|
||||
deps: TDeps extends { [key in string]: AnyApiRef }
|
||||
? { [key in keyof TDeps]: TDeps[key]['T'] }
|
||||
: never,
|
||||
) => Promise<void>;
|
||||
}): ScaffolderFormDecorator<TInputSchema, TDeps> {
|
||||
return {
|
||||
}): ScaffolderFormDecorator<TInput> {
|
||||
return OpaqueFormDecorator.createInstance('v1', {
|
||||
...options,
|
||||
version: 'v1',
|
||||
};
|
||||
TInput: null as unknown as TInput,
|
||||
} as {
|
||||
id: string;
|
||||
schema?: {
|
||||
input?: TInputSchema;
|
||||
};
|
||||
TInput: TInput;
|
||||
deps?: TDeps;
|
||||
decorator: (
|
||||
ctx: ScaffolderFormDecoratorContext,
|
||||
deps: { [key in string]: AnyApiRef['T'] },
|
||||
) => Promise<void>;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ScaffolderFormDecoratorsApi } from './types';
|
||||
import { AnyScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { ScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
/** @alpha */
|
||||
export class DefaultScaffolderFormDecoratorsApi
|
||||
@@ -23,17 +23,17 @@ export class DefaultScaffolderFormDecoratorsApi
|
||||
{
|
||||
private constructor(
|
||||
private readonly options: {
|
||||
decorators: Array<AnyScaffolderFormDecorator>;
|
||||
decorators: Array<ScaffolderFormDecorator>;
|
||||
},
|
||||
) {}
|
||||
|
||||
static create(options?: { decorators: AnyScaffolderFormDecorator[] }) {
|
||||
static create(options?: { decorators: ScaffolderFormDecorator[] }) {
|
||||
return new DefaultScaffolderFormDecoratorsApi(
|
||||
options ?? { decorators: [] },
|
||||
);
|
||||
}
|
||||
|
||||
async getFormDecorators(): Promise<AnyScaffolderFormDecorator[]> {
|
||||
async getFormDecorators(): Promise<ScaffolderFormDecorator[]> {
|
||||
return this.options.decorators;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import {
|
||||
FormFieldExtensionData,
|
||||
AnyScaffolderFormDecorator,
|
||||
ScaffolderFormDecorator,
|
||||
} from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
export interface ScaffolderFormFieldsApi {
|
||||
@@ -24,5 +24,5 @@ export interface ScaffolderFormFieldsApi {
|
||||
}
|
||||
|
||||
export interface ScaffolderFormDecoratorsApi {
|
||||
getFormDecorators(): Promise<AnyScaffolderFormDecorator[]>;
|
||||
getFormDecorators(): Promise<ScaffolderFormDecorator[]>;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('useFormDecorators', () => {
|
||||
test: z => z.string(),
|
||||
},
|
||||
},
|
||||
async fn({ input: { test } }, { mockApiRef: mock }) {
|
||||
async decorator({ input: { test } }, { mockApiRef: mock }) {
|
||||
mock.test(test);
|
||||
},
|
||||
});
|
||||
@@ -70,7 +70,7 @@ describe('useFormDecorators', () => {
|
||||
const testDecorator = result.get('test')!;
|
||||
expect(testDecorator).toBeDefined();
|
||||
|
||||
await testDecorator.fn({
|
||||
await testDecorator.decorator({
|
||||
formState: {},
|
||||
setFormState: () => {},
|
||||
setSecrets: () => {},
|
||||
|
||||
@@ -13,19 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { AnyApiRef, useApi, useApiHolder } from '@backstage/core-plugin-api';
|
||||
import { errorApiRef, useApi, useApiHolder } from '@backstage/core-plugin-api';
|
||||
import { formDecoratorsApiRef } from '../api/ref';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { useMemo } from 'react';
|
||||
import { ScaffolderFormDecoratorContext } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { OpaqueFormDecorator } from '@internal/scaffolder';
|
||||
|
||||
/** @internal */
|
||||
type BoundFieldDecorator = {
|
||||
fn: (ctx: ScaffolderFormDecoratorContext<any>) => Promise<void>;
|
||||
decorator: (ctx: ScaffolderFormDecoratorContext) => Promise<void>;
|
||||
};
|
||||
|
||||
export const useFormDecorators = () => {
|
||||
const formDecoratorsApi = useApi(formDecoratorsApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const { value: decorators } = useAsync(
|
||||
() => formDecoratorsApi.getFormDecorators(),
|
||||
[],
|
||||
@@ -37,27 +39,27 @@ export const useFormDecorators = () => {
|
||||
|
||||
for (const decorator of decorators ?? []) {
|
||||
try {
|
||||
const resolvedDeps = Object.entries(decorator.deps ?? {}).map(
|
||||
([key, value]) => {
|
||||
const api = apiHolder.get(value);
|
||||
if (!api) {
|
||||
throw new Error(
|
||||
`Failed to resolve apiRef ${value.id} for form decorator ${decorator.id} it will be disabled`,
|
||||
);
|
||||
}
|
||||
return [key, api];
|
||||
},
|
||||
);
|
||||
const { decorator: decoratorFn, deps } =
|
||||
OpaqueFormDecorator.toInternal(decorator);
|
||||
|
||||
const resolvedDeps = Object.entries(deps ?? {}).map(([key, value]) => {
|
||||
const api = apiHolder.get(value);
|
||||
if (!api) {
|
||||
throw new Error(
|
||||
`Failed to resolve apiRef ${value.id} for form decorator ${decorator.id} it will be disabled`,
|
||||
);
|
||||
}
|
||||
return [key, api];
|
||||
});
|
||||
|
||||
decoratorsMap.set(decorator.id, {
|
||||
fn: ctx => decorator.fn(ctx, Object.fromEntries(resolvedDeps)),
|
||||
decorator: ctx => decoratorFn(ctx, Object.fromEntries(resolvedDeps)),
|
||||
});
|
||||
} catch (ex) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(ex);
|
||||
errorApi.post(ex);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return decoratorsMap;
|
||||
}, [apiHolder, decorators]);
|
||||
}, [apiHolder, decorators, errorApi]);
|
||||
};
|
||||
|
||||
@@ -10339,6 +10339,7 @@ __metadata:
|
||||
resolution: "@internal/scaffolder@workspace:packages/scaffolder-internal"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/plugin-scaffolder-react": "workspace:^"
|
||||
zod: ^3.22.4
|
||||
languageName: unknown
|
||||
|
||||
Reference in New Issue
Block a user