@shipfox/client-secrets 5.0.0 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +38 -0
- package/dist/components/form-errors.d.ts.map +1 -1
- package/dist/components/form-errors.js +0 -6
- package/dist/components/form-errors.js.map +1 -1
- package/dist/components/secret-form.d.ts.map +1 -1
- package/dist/components/secret-form.js +6 -8
- package/dist/components/secret-form.js.map +1 -1
- package/dist/components/variable-form.d.ts.map +1 -1
- package/dist/components/variable-form.js +6 -8
- package/dist/components/variable-form.js.map +1 -1
- package/dist/components/workspace-secrets-section.d.ts.map +1 -1
- package/dist/components/workspace-secrets-section.js +4 -2
- package/dist/components/workspace-secrets-section.js.map +1 -1
- package/dist/components/workspace-secrets-section.stories.d.ts.map +1 -1
- package/dist/components/workspace-secrets-section.stories.js.map +1 -1
- package/dist/components/workspace-variables-section.d.ts.map +1 -1
- package/dist/components/workspace-variables-section.js +5 -3
- package/dist/components/workspace-variables-section.js.map +1 -1
- package/dist/components/workspace-variables-section.stories.d.ts.map +1 -1
- package/dist/components/workspace-variables-section.stories.js.map +1 -1
- package/dist/core/store.d.ts +54 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +44 -0
- package/dist/core/store.js.map +1 -0
- package/dist/feature.d.ts +13 -0
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +17 -16
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/create-store-api.d.ts +31 -49
- package/dist/hooks/api/create-store-api.d.ts.map +1 -1
- package/dist/hooks/api/create-store-api.js +44 -45
- package/dist/hooks/api/create-store-api.js.map +1 -1
- package/dist/hooks/api/secrets.d.ts +16 -55
- package/dist/hooks/api/secrets.d.ts.map +1 -1
- package/dist/hooks/api/secrets.js +10 -2
- package/dist/hooks/api/secrets.js.map +1 -1
- package/dist/hooks/api/store-mapper.d.ts +7 -0
- package/dist/hooks/api/store-mapper.d.ts.map +1 -0
- package/dist/hooks/api/store-mapper.js +36 -0
- package/dist/hooks/api/store-mapper.js.map +1 -0
- package/dist/hooks/api/variables.d.ts +25 -78
- package/dist/hooks/api/variables.d.ts.map +1 -1
- package/dist/hooks/api/variables.js +37 -28
- package/dist/hooks/api/variables.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -10
- package/src/components/form-errors.test.ts +2 -2
- package/src/components/form-errors.ts +0 -3
- package/src/components/secret-form.tsx +15 -5
- package/src/components/variable-form.tsx +15 -5
- package/src/components/workspace-secrets-section.stories.tsx +1 -2
- package/src/components/workspace-secrets-section.tsx +8 -4
- package/src/components/workspace-variables-section.stories.tsx +1 -2
- package/src/components/workspace-variables-section.tsx +10 -6
- package/src/core/store.test.ts +38 -0
- package/src/core/store.ts +91 -0
- package/src/feature.ts +13 -11
- package/src/hooks/api/create-store-api.ts +104 -75
- package/src/hooks/api/secrets.test.ts +16 -10
- package/src/hooks/api/secrets.ts +11 -14
- package/src/hooks/api/store-mapper.test.ts +39 -0
- package/src/hooks/api/store-mapper.ts +47 -0
- package/src/hooks/api/variables.ts +52 -36
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vercel.json +5 -0
- package/dist/components/store-key.d.ts +0 -20
- package/dist/components/store-key.d.ts.map +0 -1
- package/dist/components/store-key.js +0 -25
- package/dist/components/store-key.js.map +0 -1
- package/src/components/store-key.test.ts +0 -53
- package/src/components/store-key.ts +0 -36
|
@@ -1,102 +1,124 @@
|
|
|
1
|
-
import {SECRETS_MAX_LIST_LIMIT
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import {SECRETS_MAX_LIST_LIMIT} from '@shipfox/api-secrets-dto';
|
|
2
|
+
import {
|
|
3
|
+
checkedApiRequest,
|
|
4
|
+
emptyResponseSchema,
|
|
5
|
+
type StandardSchema,
|
|
6
|
+
type StandardSchemaOutput,
|
|
7
|
+
} from '@shipfox/client-api';
|
|
8
|
+
import {
|
|
9
|
+
type FetchQueryOptions,
|
|
10
|
+
queryOptions,
|
|
11
|
+
useMutation,
|
|
12
|
+
useQuery,
|
|
13
|
+
useQueryClient,
|
|
14
|
+
} from '@tanstack/react-query';
|
|
15
|
+
import {
|
|
16
|
+
type DeleteStoreCommand,
|
|
17
|
+
type PutStoreCommand,
|
|
18
|
+
projectIdFromScope,
|
|
19
|
+
type StoreScope,
|
|
20
|
+
type StoreWriteWarning,
|
|
21
|
+
workspaceStoreScope,
|
|
22
|
+
} from '#core/store.js';
|
|
4
23
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface PutResult<TItem> {
|
|
23
|
-
item: TItem;
|
|
24
|
-
warnings: SecretWriteWarningDto[];
|
|
25
|
-
}
|
|
24
|
+
type StoreApiConfig<
|
|
25
|
+
TListItem,
|
|
26
|
+
TPutItem,
|
|
27
|
+
TListSchema extends StandardSchema,
|
|
28
|
+
TPutSchema extends StandardSchema,
|
|
29
|
+
> = {
|
|
30
|
+
resource: 'secrets' | 'variables';
|
|
31
|
+
listResponseSchema: TListSchema;
|
|
32
|
+
putResponseSchema: TPutSchema;
|
|
33
|
+
toListItems: (response: StandardSchemaOutput<TListSchema>) => TListItem[];
|
|
34
|
+
toPutResult: (response: StandardSchemaOutput<TPutSchema>) => {
|
|
35
|
+
item: TPutItem;
|
|
36
|
+
warnings: StoreWriteWarning[];
|
|
37
|
+
};
|
|
38
|
+
};
|
|
26
39
|
|
|
27
40
|
export function createStoreApi<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
>(config:
|
|
33
|
-
const {resource
|
|
34
|
-
|
|
41
|
+
TListItem,
|
|
42
|
+
TPutItem,
|
|
43
|
+
TListSchema extends StandardSchema,
|
|
44
|
+
TPutSchema extends StandardSchema,
|
|
45
|
+
>(config: StoreApiConfig<TListItem, TPutItem, TListSchema, TPutSchema>) {
|
|
46
|
+
const {resource} = config;
|
|
35
47
|
const queryKeys = {
|
|
36
48
|
all: [resource] as const,
|
|
37
|
-
list: (workspaceId: string
|
|
49
|
+
list: (workspaceId: string, scope: StoreScope = workspaceStoreScope) =>
|
|
50
|
+
[...queryKeys.all, 'list', workspaceId, scope] as const,
|
|
38
51
|
};
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
type ListQueryOptions = FetchQueryOptions<
|
|
54
|
+
TListItem[],
|
|
55
|
+
Error,
|
|
56
|
+
TListItem[],
|
|
57
|
+
ReturnType<typeof queryKeys.list>
|
|
58
|
+
>;
|
|
59
|
+
|
|
60
|
+
function searchForScope(scope: StoreScope, includeLimit = false): string {
|
|
61
|
+
const search = new URLSearchParams();
|
|
62
|
+
if (includeLimit) search.set('limit', String(SECRETS_MAX_LIST_LIMIT));
|
|
63
|
+
const projectId = projectIdFromScope(scope);
|
|
64
|
+
if (projectId) search.set('project_id', projectId);
|
|
65
|
+
return search.toString();
|
|
66
|
+
}
|
|
67
|
+
|
|
43
68
|
async function listAll(params: {
|
|
44
69
|
workspaceId: string;
|
|
45
|
-
|
|
70
|
+
scope?: StoreScope;
|
|
46
71
|
signal?: AbortSignal | undefined;
|
|
47
|
-
}): Promise<
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`/workspaces/${params.workspaceId}/${resource}?${
|
|
72
|
+
}): Promise<TListItem[]> {
|
|
73
|
+
const scope = params.scope ?? workspaceStoreScope;
|
|
74
|
+
const response = await checkedApiRequest(
|
|
75
|
+
config.listResponseSchema,
|
|
76
|
+
`/workspaces/${params.workspaceId}/${resource}?${searchForScope(scope, true)}`,
|
|
52
77
|
{signal: params.signal},
|
|
53
78
|
);
|
|
54
|
-
return
|
|
79
|
+
return config.toListItems(response);
|
|
55
80
|
}
|
|
56
81
|
|
|
57
|
-
async function put(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
async function put(command: PutStoreCommand) {
|
|
83
|
+
const response = await checkedApiRequest(
|
|
84
|
+
config.putResponseSchema,
|
|
85
|
+
`/workspaces/${command.workspaceId}/${resource}/${encodeURIComponent(command.key)}`,
|
|
86
|
+
{
|
|
87
|
+
method: 'PUT',
|
|
88
|
+
body: {value: command.value, project_id: projectIdFromScope(command.scope)},
|
|
89
|
+
},
|
|
65
90
|
);
|
|
66
|
-
return
|
|
91
|
+
return config.toPutResult(response);
|
|
67
92
|
}
|
|
68
93
|
|
|
69
|
-
async function remove(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const search = params.projectId
|
|
75
|
-
? `?${new URLSearchParams({project_id: params.projectId}).toString()}`
|
|
76
|
-
: '';
|
|
77
|
-
await apiRequest<void>(
|
|
78
|
-
`/workspaces/${params.workspaceId}/${resource}/${encodeURIComponent(params.key)}${search}`,
|
|
94
|
+
async function remove(command: DeleteStoreCommand): Promise<void> {
|
|
95
|
+
const search = searchForScope(command.scope);
|
|
96
|
+
await checkedApiRequest(
|
|
97
|
+
emptyResponseSchema,
|
|
98
|
+
`/workspaces/${command.workspaceId}/${resource}/${encodeURIComponent(command.key)}${search ? `?${search}` : ''}`,
|
|
79
99
|
{method: 'DELETE'},
|
|
80
100
|
);
|
|
81
101
|
}
|
|
82
102
|
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
103
|
+
function listQueryOptions(
|
|
104
|
+
workspaceId: string,
|
|
105
|
+
scope: StoreScope = workspaceStoreScope,
|
|
106
|
+
): ListQueryOptions {
|
|
107
|
+
return queryOptions({
|
|
108
|
+
queryKey: queryKeys.list(workspaceId, scope),
|
|
109
|
+
queryFn: ({signal}) => listAll({workspaceId, scope, signal}),
|
|
88
110
|
});
|
|
89
111
|
}
|
|
90
112
|
|
|
113
|
+
function useListQuery(workspaceId: string | undefined, scope: StoreScope = workspaceStoreScope) {
|
|
114
|
+
return useQuery({...listQueryOptions(workspaceId ?? '', scope), enabled: Boolean(workspaceId)});
|
|
115
|
+
}
|
|
116
|
+
|
|
91
117
|
function usePutMutation() {
|
|
92
118
|
const queryClient = useQueryClient();
|
|
93
119
|
return useMutation({
|
|
94
120
|
mutationFn: put,
|
|
95
|
-
|
|
96
|
-
// (full variable values) is refreshed too, not just the list.
|
|
97
|
-
onSuccess: async () => {
|
|
98
|
-
await queryClient.invalidateQueries({queryKey: queryKeys.all});
|
|
99
|
-
},
|
|
121
|
+
onSuccess: async () => await queryClient.invalidateQueries({queryKey: queryKeys.all}),
|
|
100
122
|
});
|
|
101
123
|
}
|
|
102
124
|
|
|
@@ -104,11 +126,18 @@ export function createStoreApi<
|
|
|
104
126
|
const queryClient = useQueryClient();
|
|
105
127
|
return useMutation({
|
|
106
128
|
mutationFn: remove,
|
|
107
|
-
onSuccess: async () => {
|
|
108
|
-
await queryClient.invalidateQueries({queryKey: queryKeys.all});
|
|
109
|
-
},
|
|
129
|
+
onSuccess: async () => await queryClient.invalidateQueries({queryKey: queryKeys.all}),
|
|
110
130
|
});
|
|
111
131
|
}
|
|
112
132
|
|
|
113
|
-
return {
|
|
133
|
+
return {
|
|
134
|
+
queryKeys,
|
|
135
|
+
listAll,
|
|
136
|
+
put,
|
|
137
|
+
remove,
|
|
138
|
+
listQueryOptions,
|
|
139
|
+
useListQuery,
|
|
140
|
+
usePutMutation,
|
|
141
|
+
useDeleteMutation,
|
|
142
|
+
};
|
|
114
143
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {SECRETS_MAX_LIST_LIMIT} from '@shipfox/api-secrets-dto';
|
|
2
2
|
import {configureApiClient} from '@shipfox/client-api';
|
|
3
|
+
import {workspaceStoreScope} from '#core/store.js';
|
|
3
4
|
import {
|
|
4
5
|
SECRETS_TEST_WORKSPACE_ID,
|
|
5
6
|
secret,
|
|
6
7
|
secretsListResponse,
|
|
7
8
|
variable,
|
|
8
|
-
variableListItem,
|
|
9
9
|
variablesListResponse,
|
|
10
10
|
} from '#test/fixtures/secrets.js';
|
|
11
11
|
import {deleteSecret, listSecrets, putSecret} from './secrets.js';
|
|
@@ -32,7 +32,7 @@ describe('store transport', () => {
|
|
|
32
32
|
|
|
33
33
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
34
34
|
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
35
|
-
expect(result).
|
|
35
|
+
expect(result).toMatchObject([{key: 'MY_TOKEN', scope: {kind: 'workspace'}}]);
|
|
36
36
|
expect(request.method).toBe('GET');
|
|
37
37
|
expect(request.url).toBe(
|
|
38
38
|
`https://api.example.test/workspaces/${SECRETS_TEST_WORKSPACE_ID}/secrets?limit=${SECRETS_MAX_LIST_LIMIT}`,
|
|
@@ -64,7 +64,8 @@ describe('store transport', () => {
|
|
|
64
64
|
const result = await putSecret({
|
|
65
65
|
workspaceId: SECRETS_TEST_WORKSPACE_ID,
|
|
66
66
|
key: 'MY_TOKEN',
|
|
67
|
-
|
|
67
|
+
value: 'short',
|
|
68
|
+
scope: workspaceStoreScope,
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
@@ -74,14 +75,18 @@ describe('store transport', () => {
|
|
|
74
75
|
);
|
|
75
76
|
expect(requestBody).toEqual({value: 'short'});
|
|
76
77
|
expect(result.warnings).toEqual([{code: 'short-secret-value', key: 'MY_TOKEN'}]);
|
|
77
|
-
expect(result.item
|
|
78
|
+
expect(result.item).toMatchObject({key: 'MY_TOKEN', scope: {kind: 'workspace'}});
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
test('deletes a secret with a 204 and no project scope by default', async () => {
|
|
81
82
|
const fetchImpl = vi.fn().mockResolvedValue(new Response(null, {status: 204}));
|
|
82
83
|
configureApiClient({fetchImpl});
|
|
83
84
|
|
|
84
|
-
const result = await deleteSecret({
|
|
85
|
+
const result = await deleteSecret({
|
|
86
|
+
workspaceId: SECRETS_TEST_WORKSPACE_ID,
|
|
87
|
+
key: 'MY_TOKEN',
|
|
88
|
+
scope: workspaceStoreScope,
|
|
89
|
+
});
|
|
85
90
|
|
|
86
91
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
87
92
|
expect(result).toBeUndefined();
|
|
@@ -98,7 +103,7 @@ describe('store transport', () => {
|
|
|
98
103
|
const result = await listVariables({workspaceId: SECRETS_TEST_WORKSPACE_ID});
|
|
99
104
|
|
|
100
105
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
101
|
-
expect(result).
|
|
106
|
+
expect(result).toMatchObject([{key: 'LOG_LEVEL', value: 'debug', valueState: 'preview'}]);
|
|
102
107
|
expect(request.url).toBe(
|
|
103
108
|
`https://api.example.test/workspaces/${SECRETS_TEST_WORKSPACE_ID}/variables?limit=${SECRETS_MAX_LIST_LIMIT}`,
|
|
104
109
|
);
|
|
@@ -113,7 +118,7 @@ describe('store transport', () => {
|
|
|
113
118
|
const result = await getVariable({workspaceId: SECRETS_TEST_WORKSPACE_ID, key: 'LOG_LEVEL'});
|
|
114
119
|
|
|
115
120
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
116
|
-
expect(result
|
|
121
|
+
expect(result).toMatchObject({value: 'first line\nsecond line', valueState: 'full'});
|
|
117
122
|
expect(request.method).toBe('GET');
|
|
118
123
|
expect(request.url).toBe(
|
|
119
124
|
`https://api.example.test/workspaces/${SECRETS_TEST_WORKSPACE_ID}/variables/LOG_LEVEL`,
|
|
@@ -132,11 +137,12 @@ describe('store transport', () => {
|
|
|
132
137
|
const result = await putVariable({
|
|
133
138
|
workspaceId: SECRETS_TEST_WORKSPACE_ID,
|
|
134
139
|
key: 'MY_KEY',
|
|
135
|
-
|
|
140
|
+
value: 'v',
|
|
141
|
+
scope: workspaceStoreScope,
|
|
136
142
|
});
|
|
137
143
|
|
|
138
144
|
expect(result.warnings).toEqual([{code: 'sensitive-variable-name', key: 'MY_KEY'}]);
|
|
139
|
-
expect(result.item
|
|
145
|
+
expect(result.item).toMatchObject({value: 'v', valueState: 'full'});
|
|
140
146
|
});
|
|
141
147
|
|
|
142
148
|
test('deletes a variable within a project scope via project_id query', async () => {
|
|
@@ -146,7 +152,7 @@ describe('store transport', () => {
|
|
|
146
152
|
await deleteVariable({
|
|
147
153
|
workspaceId: SECRETS_TEST_WORKSPACE_ID,
|
|
148
154
|
key: 'LOG_LEVEL',
|
|
149
|
-
projectId: '33333333-3333-4333-8333-333333333333',
|
|
155
|
+
scope: {kind: 'project', projectId: '33333333-3333-4333-8333-333333333333'},
|
|
150
156
|
});
|
|
151
157
|
|
|
152
158
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
package/src/hooks/api/secrets.ts
CHANGED
|
@@ -1,26 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
ListSecretsResponseDto,
|
|
3
|
-
PutSecretBodyDto,
|
|
4
|
-
PutSecretResponseDto,
|
|
5
|
-
SecretDto,
|
|
6
|
-
} from '@shipfox/api-secrets-dto';
|
|
1
|
+
import {listSecretsResponseSchema, putSecretResponseSchema} from '@shipfox/api-secrets-dto';
|
|
7
2
|
import {createStoreApi} from './create-store-api.js';
|
|
3
|
+
import {toSecretMetadata, toStoreWriteWarnings} from './store-mapper.js';
|
|
8
4
|
|
|
9
|
-
const secretsApi = createStoreApi
|
|
10
|
-
SecretDto,
|
|
11
|
-
ListSecretsResponseDto,
|
|
12
|
-
PutSecretBodyDto,
|
|
13
|
-
PutSecretResponseDto
|
|
14
|
-
>({
|
|
5
|
+
const secretsApi = createStoreApi({
|
|
15
6
|
resource: 'secrets',
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
listResponseSchema: listSecretsResponseSchema,
|
|
8
|
+
putResponseSchema: putSecretResponseSchema,
|
|
9
|
+
toListItems: (response) => response.secrets.map(toSecretMetadata),
|
|
10
|
+
toPutResult: (response) => ({
|
|
11
|
+
item: toSecretMetadata(response.secret),
|
|
12
|
+
warnings: toStoreWriteWarnings(response.warnings),
|
|
13
|
+
}),
|
|
18
14
|
});
|
|
19
15
|
|
|
20
16
|
export const secretsQueryKeys = secretsApi.queryKeys;
|
|
21
17
|
export const listSecrets = secretsApi.listAll;
|
|
22
18
|
export const putSecret = secretsApi.put;
|
|
23
19
|
export const deleteSecret = secretsApi.remove;
|
|
20
|
+
export const listSecretsQueryOptions = secretsApi.listQueryOptions;
|
|
24
21
|
export const useSecretsQuery = secretsApi.useListQuery;
|
|
25
22
|
export const usePutSecretMutation = secretsApi.usePutMutation;
|
|
26
23
|
export const useDeleteSecretMutation = secretsApi.useDeleteMutation;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {secret, variable, variableListItem} from '#test/fixtures/secrets.js';
|
|
2
|
+
import {
|
|
3
|
+
toSecretMetadata,
|
|
4
|
+
toStoreWriteWarnings,
|
|
5
|
+
toVariable,
|
|
6
|
+
toVariablePreview,
|
|
7
|
+
} from './store-mapper.js';
|
|
8
|
+
|
|
9
|
+
describe('store transport mapping', () => {
|
|
10
|
+
test('maps secret metadata and workspace scope into a package-owned model', () => {
|
|
11
|
+
expect(toSecretMetadata(secret())).toEqual({
|
|
12
|
+
key: 'MY_TOKEN',
|
|
13
|
+
scope: {kind: 'workspace'},
|
|
14
|
+
createdAt: '2026-05-08T00:00:00.000Z',
|
|
15
|
+
updatedAt: '2026-05-08T00:00:00.000Z',
|
|
16
|
+
lastEditedBy: '22222222-2222-4222-8222-222222222222',
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('keeps truncated list values distinct from full detail values', () => {
|
|
21
|
+
expect(
|
|
22
|
+
toVariablePreview(variableListItem({value: 'preview', value_truncated: true})),
|
|
23
|
+
).toMatchObject({
|
|
24
|
+
value: 'preview',
|
|
25
|
+
valueState: 'preview',
|
|
26
|
+
valueTruncated: true,
|
|
27
|
+
});
|
|
28
|
+
expect(toVariable(variable({value: 'complete value'}))).toMatchObject({
|
|
29
|
+
value: 'complete value',
|
|
30
|
+
valueState: 'full',
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('maps server warning DTOs to the store warning model', () => {
|
|
35
|
+
expect(toStoreWriteWarnings([{code: 'short-secret-value', key: 'MY_TOKEN'}])).toEqual([
|
|
36
|
+
{code: 'short-secret-value', key: 'MY_TOKEN'},
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SecretDto,
|
|
3
|
+
SecretWriteWarningDto,
|
|
4
|
+
VariableDto,
|
|
5
|
+
VariableListItemDto,
|
|
6
|
+
} from '@shipfox/api-secrets-dto';
|
|
7
|
+
import type {
|
|
8
|
+
SecretMetadata,
|
|
9
|
+
StoreMetadata,
|
|
10
|
+
StoreWriteWarning,
|
|
11
|
+
Variable,
|
|
12
|
+
VariablePreview,
|
|
13
|
+
} from '#core/store.js';
|
|
14
|
+
import {scopeFromProjectId} from '#core/store.js';
|
|
15
|
+
|
|
16
|
+
function toStoreMetadata(item: SecretDto): StoreMetadata {
|
|
17
|
+
return {
|
|
18
|
+
key: item.key,
|
|
19
|
+
scope: scopeFromProjectId(item.project_id),
|
|
20
|
+
createdAt: item.created_at,
|
|
21
|
+
updatedAt: item.updated_at,
|
|
22
|
+
lastEditedBy: item.last_edited_by,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function toSecretMetadata(secret: SecretDto): SecretMetadata {
|
|
27
|
+
return toStoreMetadata(secret);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function toVariablePreview(variable: VariableListItemDto): VariablePreview {
|
|
31
|
+
return {
|
|
32
|
+
...toStoreMetadata(variable),
|
|
33
|
+
value: variable.value,
|
|
34
|
+
valueState: 'preview',
|
|
35
|
+
valueTruncated: variable.value_truncated,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function toVariable(variable: VariableDto): Variable {
|
|
40
|
+
return {...toStoreMetadata(variable), value: variable.value, valueState: 'full'};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function toStoreWriteWarnings(
|
|
44
|
+
warnings: readonly SecretWriteWarningDto[],
|
|
45
|
+
): StoreWriteWarning[] {
|
|
46
|
+
return warnings.map((warning) => ({code: warning.code, key: warning.key}));
|
|
47
|
+
}
|
|
@@ -1,61 +1,77 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
PutVariableResponseDto,
|
|
6
|
-
VariableDto,
|
|
7
|
-
VariableListItemDto,
|
|
1
|
+
import {
|
|
2
|
+
getVariableResponseSchema,
|
|
3
|
+
listVariablesResponseSchema,
|
|
4
|
+
putVariableResponseSchema,
|
|
8
5
|
} from '@shipfox/api-secrets-dto';
|
|
9
|
-
import {
|
|
10
|
-
import {useQuery} from '@tanstack/react-query';
|
|
6
|
+
import {checkedApiRequest} from '@shipfox/client-api';
|
|
7
|
+
import {type FetchQueryOptions, queryOptions, useQuery} from '@tanstack/react-query';
|
|
8
|
+
import {
|
|
9
|
+
projectIdFromScope,
|
|
10
|
+
type StoreScope,
|
|
11
|
+
type Variable,
|
|
12
|
+
workspaceStoreScope,
|
|
13
|
+
} from '#core/store.js';
|
|
11
14
|
import {createStoreApi} from './create-store-api.js';
|
|
15
|
+
import {toStoreWriteWarnings, toVariable, toVariablePreview} from './store-mapper.js';
|
|
12
16
|
|
|
13
|
-
const variablesApi = createStoreApi
|
|
14
|
-
VariableListItemDto,
|
|
15
|
-
ListVariablesResponseDto,
|
|
16
|
-
PutVariableBodyDto,
|
|
17
|
-
PutVariableResponseDto
|
|
18
|
-
>({
|
|
17
|
+
const variablesApi = createStoreApi({
|
|
19
18
|
resource: 'variables',
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
listResponseSchema: listVariablesResponseSchema,
|
|
20
|
+
putResponseSchema: putVariableResponseSchema,
|
|
21
|
+
toListItems: (response) => response.variables.map(toVariablePreview),
|
|
22
|
+
toPutResult: (response) => ({
|
|
23
|
+
item: toVariable(response.variable),
|
|
24
|
+
warnings: toStoreWriteWarnings(response.warnings),
|
|
25
|
+
}),
|
|
23
26
|
});
|
|
24
27
|
|
|
25
28
|
export const variablesQueryKeys = variablesApi.queryKeys;
|
|
26
29
|
export const listVariables = variablesApi.listAll;
|
|
27
30
|
export const putVariable = variablesApi.put;
|
|
28
31
|
export const deleteVariable = variablesApi.remove;
|
|
32
|
+
export const listVariablesQueryOptions = variablesApi.listQueryOptions;
|
|
29
33
|
export const useVariablesQuery = variablesApi.useListQuery;
|
|
30
34
|
export const usePutVariableMutation = variablesApi.usePutMutation;
|
|
31
35
|
export const useDeleteVariableMutation = variablesApi.useDeleteMutation;
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
export const variableQueryKey = (workspaceId: string, key: string, scope: StoreScope) =>
|
|
38
|
+
[...variablesQueryKeys.all, 'detail', workspaceId, key, scope] as const;
|
|
39
|
+
|
|
40
|
+
type VariableQueryOptions = FetchQueryOptions<
|
|
41
|
+
Variable,
|
|
42
|
+
Error,
|
|
43
|
+
Variable,
|
|
44
|
+
ReturnType<typeof variableQueryKey>
|
|
45
|
+
>;
|
|
46
|
+
|
|
37
47
|
export async function getVariable(params: {
|
|
38
48
|
workspaceId: string;
|
|
39
49
|
key: string;
|
|
40
|
-
|
|
50
|
+
scope?: StoreScope;
|
|
41
51
|
signal?: AbortSignal | undefined;
|
|
42
|
-
})
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const response = await
|
|
52
|
+
}) {
|
|
53
|
+
const scope = params.scope ?? workspaceStoreScope;
|
|
54
|
+
const projectId = projectIdFromScope(scope);
|
|
55
|
+
const search = projectId ? `?${new URLSearchParams({project_id: projectId}).toString()}` : '';
|
|
56
|
+
const response = await checkedApiRequest(
|
|
57
|
+
getVariableResponseSchema,
|
|
47
58
|
`/workspaces/${params.workspaceId}/variables/${encodeURIComponent(params.key)}${search}`,
|
|
48
59
|
{signal: params.signal},
|
|
49
60
|
);
|
|
50
|
-
return response.variable;
|
|
61
|
+
return toVariable(response.variable);
|
|
51
62
|
}
|
|
52
63
|
|
|
53
|
-
export function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
export function variableQueryOptions(
|
|
65
|
+
workspaceId: string,
|
|
66
|
+
key: string,
|
|
67
|
+
scope: StoreScope = workspaceStoreScope,
|
|
68
|
+
): VariableQueryOptions {
|
|
69
|
+
return queryOptions({
|
|
70
|
+
queryKey: variableQueryKey(workspaceId, key, scope),
|
|
71
|
+
queryFn: ({signal}) => getVariable({workspaceId, key, scope, signal}),
|
|
60
72
|
});
|
|
61
73
|
}
|
|
74
|
+
|
|
75
|
+
export function useVariableQuery(workspaceId: string, key: string | undefined) {
|
|
76
|
+
return useQuery({...variableQueryOptions(workspaceId, key ?? ''), enabled: Boolean(key)});
|
|
77
|
+
}
|