@shipfox/client-runners 5.0.0 → 6.0.2
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 +49 -0
- package/dist/components/create-manual-registration-token-form.d.ts +3 -3
- package/dist/components/create-manual-registration-token-form.d.ts.map +1 -1
- package/dist/components/create-manual-registration-token-form.js +8 -23
- package/dist/components/create-manual-registration-token-form.js.map +1 -1
- package/dist/components/create-provisioner-token-form.d.ts +3 -3
- package/dist/components/create-provisioner-token-form.d.ts.map +1 -1
- package/dist/components/create-provisioner-token-form.js +8 -23
- package/dist/components/create-provisioner-token-form.js.map +1 -1
- package/dist/components/manual-registration-token-errors.d.ts.map +1 -1
- package/dist/components/manual-registration-token-errors.js +0 -1
- package/dist/components/manual-registration-token-errors.js.map +1 -1
- package/dist/components/manual-registration-token-format.d.ts +0 -2
- package/dist/components/manual-registration-token-format.d.ts.map +1 -1
- package/dist/components/manual-registration-token-format.js +0 -3
- package/dist/components/manual-registration-token-format.js.map +1 -1
- package/dist/components/manual-registration-token-list.d.ts +2 -2
- package/dist/components/manual-registration-token-list.d.ts.map +1 -1
- package/dist/components/manual-registration-token-list.js +12 -19
- package/dist/components/manual-registration-token-list.js.map +1 -1
- package/dist/components/manual-registration-tokens-settings-section.d.ts.map +1 -1
- package/dist/components/manual-registration-tokens-settings-section.js +1 -1
- package/dist/components/manual-registration-tokens-settings-section.js.map +1 -1
- package/dist/components/provisioner-token-errors.d.ts.map +1 -1
- package/dist/components/provisioner-token-errors.js +0 -1
- package/dist/components/provisioner-token-errors.js.map +1 -1
- package/dist/components/provisioner-token-format.d.ts +0 -18
- package/dist/components/provisioner-token-format.d.ts.map +1 -1
- package/dist/components/provisioner-token-format.js +0 -25
- package/dist/components/provisioner-token-format.js.map +1 -1
- package/dist/components/provisioner-token-list.d.ts +2 -2
- package/dist/components/provisioner-token-list.d.ts.map +1 -1
- package/dist/components/provisioner-token-list.js +9 -19
- package/dist/components/provisioner-token-list.js.map +1 -1
- package/dist/components/provisioner-tokens-settings-section.js +2 -2
- package/dist/components/provisioner-tokens-settings-section.js.map +1 -1
- package/dist/components/token-expiration-select.d.ts +7 -0
- package/dist/components/token-expiration-select.d.ts.map +1 -1
- package/dist/components/token-expiration-select.js +8 -0
- package/dist/components/token-expiration-select.js.map +1 -1
- package/dist/components/token-settings-sections.stories.d.ts.map +1 -1
- package/dist/components/token-settings-sections.stories.js +66 -23
- package/dist/components/token-settings-sections.stories.js.map +1 -1
- package/dist/core/token.d.ts +66 -0
- package/dist/core/token.d.ts.map +1 -0
- package/dist/core/token.js +37 -0
- package/dist/core/token.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/manual-registration-tokens.d.ts +13 -71
- package/dist/hooks/api/manual-registration-tokens.d.ts.map +1 -1
- package/dist/hooks/api/manual-registration-tokens.js +40 -19
- package/dist/hooks/api/manual-registration-tokens.js.map +1 -1
- package/dist/hooks/api/provisioner-tokens.d.ts +17 -115
- package/dist/hooks/api/provisioner-tokens.d.ts.map +1 -1
- package/dist/hooks/api/provisioner-tokens.js +59 -32
- package/dist/hooks/api/provisioner-tokens.js.map +1 -1
- package/dist/hooks/api/token-mapper.d.ts +12 -0
- package/dist/hooks/api/token-mapper.d.ts.map +1 -0
- package/dist/hooks/api/token-mapper.js +61 -0
- package/dist/hooks/api/token-mapper.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/components/create-manual-registration-token-form.tsx +11 -21
- package/src/components/create-provisioner-token-form.tsx +11 -21
- package/src/components/form-errors.test.ts +2 -2
- package/src/components/manual-registration-token-errors.ts +0 -1
- package/src/components/manual-registration-token-format.test.ts +4 -4
- package/src/components/manual-registration-token-format.ts +0 -7
- package/src/components/manual-registration-token-list.tsx +13 -22
- package/src/components/manual-registration-tokens-settings-section.tsx +3 -5
- package/src/components/provisioner-token-errors.ts +0 -1
- package/src/components/provisioner-token-form-errors.test.ts +2 -2
- package/src/components/provisioner-token-format.test.ts +15 -14
- package/src/components/provisioner-token-format.ts +0 -29
- package/src/components/provisioner-token-list.tsx +14 -23
- package/src/components/provisioner-tokens-settings-section.tsx +4 -4
- package/src/components/token-expiration-select.test.ts +15 -0
- package/src/components/token-expiration-select.tsx +6 -0
- package/src/components/token-settings-sections.stories.tsx +72 -23
- package/src/core/token.test.ts +30 -0
- package/src/core/token.ts +83 -0
- package/src/feature.ts +19 -17
- package/src/hooks/api/manual-registration-tokens.test.ts +8 -5
- package/src/hooks/api/manual-registration-tokens.ts +71 -24
- package/src/hooks/api/provisioner-tokens.test.ts +10 -7
- package/src/hooks/api/provisioner-tokens.ts +100 -36
- package/src/hooks/api/token-mapper.test.ts +161 -0
- package/src/hooks/api/token-mapper.ts +79 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vercel.json +5 -0
|
@@ -26,6 +26,12 @@ export const expirationOptions: Array<{value: TokenExpirationOption; label: stri
|
|
|
26
26
|
{value: 'never', label: 'Never'},
|
|
27
27
|
];
|
|
28
28
|
|
|
29
|
+
export function expirationCommand(expiration: TokenExpirationOption) {
|
|
30
|
+
return expiration === 'never'
|
|
31
|
+
? ({kind: 'never'} as const)
|
|
32
|
+
: ({kind: 'expires-after', seconds: Number(expiration)} as const);
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
export function expirationHint(expiration: TokenExpirationOption): string {
|
|
30
36
|
if (expiration === 'never') return 'Token will not expire.';
|
|
31
37
|
const expiresAt = new Date(Date.now() + Number(expiration) * 1000);
|
|
@@ -7,6 +7,11 @@ import type {Decorator, Meta, StoryObj} from '@storybook/react';
|
|
|
7
7
|
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
8
8
|
import type {ReactNode} from 'react';
|
|
9
9
|
import {within} from 'storybook/test';
|
|
10
|
+
import type {
|
|
11
|
+
CreatedProvisionerToken,
|
|
12
|
+
ManualRegistrationToken,
|
|
13
|
+
ProvisionerToken,
|
|
14
|
+
} from '#core/token.js';
|
|
10
15
|
import {CreatedManualRegistrationTokenPanel} from './create-manual-registration-token-form.js';
|
|
11
16
|
import {CreatedProvisionerTokenPanel} from './create-provisioner-token-form.js';
|
|
12
17
|
import {
|
|
@@ -115,20 +120,20 @@ export const Statuses: Story = {
|
|
|
115
120
|
<ProvisionerTokenList
|
|
116
121
|
workspaceId={WORKSPACE_ID}
|
|
117
122
|
tokens={[
|
|
118
|
-
|
|
123
|
+
provisionerTokenModel({
|
|
119
124
|
id: '33333333-3333-4333-8333-333333333333',
|
|
120
125
|
name: 'Docker autoscaler',
|
|
121
|
-
|
|
126
|
+
lastSeenAt: NOW,
|
|
122
127
|
}),
|
|
123
|
-
|
|
128
|
+
provisionerTokenModel({
|
|
124
129
|
id: '44444444-4444-4444-8444-444444444444',
|
|
125
130
|
name: 'Kubernetes spot pool',
|
|
126
|
-
|
|
131
|
+
lastSeenAt: EIGHT_MINUTES_AGO,
|
|
127
132
|
}),
|
|
128
|
-
|
|
133
|
+
provisionerTokenModel({
|
|
129
134
|
id: '55555555-5555-4555-8555-555555555555',
|
|
130
135
|
name: 'Buildkite migration pool',
|
|
131
|
-
|
|
136
|
+
lastSeenAt: null,
|
|
132
137
|
}),
|
|
133
138
|
]}
|
|
134
139
|
activeIds={new Set(['33333333-3333-4333-8333-333333333333'])}
|
|
@@ -151,18 +156,18 @@ export const Content: Story = {
|
|
|
151
156
|
<ManualRegistrationTokenList
|
|
152
157
|
workspaceId={WORKSPACE_ID}
|
|
153
158
|
tokens={[
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
manualRegistrationToken({name: 'Deploy runner', prefix: 'sf_mrt_deploy'}),
|
|
160
|
+
manualRegistrationToken({
|
|
156
161
|
id: '66666666-6666-4666-8666-666666666666',
|
|
157
162
|
name: 'macOS build host',
|
|
158
163
|
prefix: 'sf_mrt_macos',
|
|
159
|
-
|
|
164
|
+
expiresAt: null,
|
|
160
165
|
}),
|
|
161
166
|
]}
|
|
162
167
|
/>
|
|
163
168
|
<ProvisionerTokenList
|
|
164
169
|
workspaceId={WORKSPACE_ID}
|
|
165
|
-
tokens={
|
|
170
|
+
tokens={provisionerTokenModels()}
|
|
166
171
|
activeIds={new Set(['33333333-3333-4333-8333-333333333333'])}
|
|
167
172
|
/>
|
|
168
173
|
</div>
|
|
@@ -172,7 +177,7 @@ export const Content: Story = {
|
|
|
172
177
|
<ManualRegistrationTokenList
|
|
173
178
|
workspaceId={WORKSPACE_ID}
|
|
174
179
|
tokens={[
|
|
175
|
-
|
|
180
|
+
manualRegistrationToken({
|
|
176
181
|
name: 'self-hosted-runner-for-production-release-candidate-validation-on-metal',
|
|
177
182
|
prefix: 'sf_mrt_release_candidate_validation',
|
|
178
183
|
}),
|
|
@@ -181,10 +186,10 @@ export const Content: Story = {
|
|
|
181
186
|
<ProvisionerTokenList
|
|
182
187
|
workspaceId={WORKSPACE_ID}
|
|
183
188
|
tokens={[
|
|
184
|
-
|
|
189
|
+
provisionerTokenModel({
|
|
185
190
|
name: 'docker-provisioner-for-west-coast-gpu-burst-capacity-and-fallback',
|
|
186
191
|
prefix: 'sf_pt_west_coast_gpu_burst',
|
|
187
|
-
|
|
192
|
+
lastSeenAt: EIGHT_MINUTES_AGO,
|
|
188
193
|
}),
|
|
189
194
|
]}
|
|
190
195
|
activeIds={new Set()}
|
|
@@ -202,22 +207,17 @@ export const CreatedTokenPanels: Story = {
|
|
|
202
207
|
<CreatedManualRegistrationTokenPanel
|
|
203
208
|
token={{
|
|
204
209
|
id: '77777777-7777-4777-8777-777777777777',
|
|
205
|
-
|
|
210
|
+
token: 'sf_mrt_4nJvNrs23ExampleManualTokenValue',
|
|
206
211
|
prefix: 'sf_mrt_4nJv',
|
|
207
212
|
name: 'Deploy runner',
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
workspaceId: WORKSPACE_ID,
|
|
214
|
+
expiresAt: EXPIRES_AT,
|
|
215
|
+
createdAt: CREATED_AT,
|
|
211
216
|
}}
|
|
212
217
|
/>
|
|
213
218
|
</StateExample>
|
|
214
219
|
<StateExample label="Provisioner token reveal">
|
|
215
|
-
<CreatedProvisionerTokenPanel
|
|
216
|
-
token={{
|
|
217
|
-
...provisionerToken({name: 'Docker autoscaler'}),
|
|
218
|
-
raw_token: 'sf_pt_Rv8YwrExampleProvisionerTokenValue',
|
|
219
|
-
}}
|
|
220
|
-
/>
|
|
220
|
+
<CreatedProvisionerTokenPanel token={createdProvisionerToken()} />
|
|
221
221
|
</StateExample>
|
|
222
222
|
</StorySurface>
|
|
223
223
|
),
|
|
@@ -308,6 +308,55 @@ function provisionerTokens(): ProvisionerTokenDto[] {
|
|
|
308
308
|
];
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
function manualRegistrationToken(
|
|
312
|
+
overrides: Partial<ManualRegistrationToken> = {},
|
|
313
|
+
): ManualRegistrationToken {
|
|
314
|
+
return {
|
|
315
|
+
id: '22222222-2222-4222-8222-222222222222',
|
|
316
|
+
workspaceId: WORKSPACE_ID,
|
|
317
|
+
prefix: 'sf_mrt_deploy',
|
|
318
|
+
name: 'Deploy runner',
|
|
319
|
+
expiresAt: EXPIRES_AT,
|
|
320
|
+
revokedAt: null,
|
|
321
|
+
createdAt: CREATED_AT,
|
|
322
|
+
updatedAt: CREATED_AT,
|
|
323
|
+
...overrides,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function provisionerTokenModel(overrides: Partial<ProvisionerToken> = {}): ProvisionerToken {
|
|
328
|
+
return {
|
|
329
|
+
...manualRegistrationToken(),
|
|
330
|
+
createdByUserId: '99999999-9999-4999-8999-999999999999',
|
|
331
|
+
revokedByUserId: null,
|
|
332
|
+
lastSeenAt: NOW,
|
|
333
|
+
...overrides,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function provisionerTokenModels(): ProvisionerToken[] {
|
|
338
|
+
return [
|
|
339
|
+
provisionerTokenModel({id: '33333333-3333-4333-8333-333333333333', name: 'Docker autoscaler'}),
|
|
340
|
+
provisionerTokenModel({
|
|
341
|
+
id: '44444444-4444-4444-8444-444444444444',
|
|
342
|
+
name: 'Kubernetes spot pool',
|
|
343
|
+
lastSeenAt: EIGHT_MINUTES_AGO,
|
|
344
|
+
}),
|
|
345
|
+
provisionerTokenModel({
|
|
346
|
+
id: '55555555-5555-4555-8555-555555555555',
|
|
347
|
+
name: 'Buildkite migration pool',
|
|
348
|
+
lastSeenAt: null,
|
|
349
|
+
}),
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function createdProvisionerToken(): CreatedProvisionerToken {
|
|
354
|
+
return {
|
|
355
|
+
...provisionerTokenModel({name: 'Docker autoscaler'}),
|
|
356
|
+
token: 'sf_pt_Rv8YwrExampleProvisionerTokenValue',
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
311
360
|
function manualToken(
|
|
312
361
|
overrides: Partial<ManualRegistrationTokenDto> = {},
|
|
313
362
|
): ManualRegistrationTokenDto {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTokenCommand,
|
|
3
|
+
provisionerConnectionStatus,
|
|
4
|
+
provisionerTokenDisplayName,
|
|
5
|
+
} from './token.js';
|
|
6
|
+
|
|
7
|
+
describe('token domain policies', () => {
|
|
8
|
+
test('omits a blank token name while keeping its expiration policy', () => {
|
|
9
|
+
const result = createTokenCommand(' ', {kind: 'expires-after', seconds: 86_400});
|
|
10
|
+
|
|
11
|
+
expect(result).toEqual({expiration: {kind: 'expires-after', seconds: 86_400}});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('derives provisioner status from active and last-seen state', () => {
|
|
15
|
+
const token = {id: 'token-1', name: null, lastSeenAt: '2026-07-22T10:00:00.000Z'};
|
|
16
|
+
|
|
17
|
+
expect(provisionerConnectionStatus(token, new Set(['token-1']))).toEqual({
|
|
18
|
+
kind: 'connected',
|
|
19
|
+
dotVariant: 'success',
|
|
20
|
+
label: 'Connected',
|
|
21
|
+
});
|
|
22
|
+
expect(provisionerConnectionStatus(token, new Set())).toEqual({
|
|
23
|
+
kind: 'last-seen',
|
|
24
|
+
dotVariant: 'neutral',
|
|
25
|
+
label: 'Last seen',
|
|
26
|
+
lastSeenAt: token.lastSeenAt,
|
|
27
|
+
});
|
|
28
|
+
expect(provisionerTokenDisplayName(token)).toBe('Unnamed provisioner');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export interface ManualRegistrationToken {
|
|
2
|
+
id: string;
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
prefix: string;
|
|
5
|
+
name: string | null;
|
|
6
|
+
expiresAt: string | null;
|
|
7
|
+
revokedAt: string | null;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ProvisionerToken extends ManualRegistrationToken {
|
|
13
|
+
createdByUserId: string;
|
|
14
|
+
revokedByUserId: string | null;
|
|
15
|
+
lastSeenAt: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ActiveProvisioner {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string | null;
|
|
21
|
+
prefix: string;
|
|
22
|
+
lastSeenAt: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CreatedManualRegistrationToken {
|
|
26
|
+
token: string;
|
|
27
|
+
id: string;
|
|
28
|
+
prefix: string;
|
|
29
|
+
name: string | null;
|
|
30
|
+
workspaceId: string;
|
|
31
|
+
expiresAt: string | null;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CreatedProvisionerToken extends CreatedManualRegistrationToken {
|
|
36
|
+
createdByUserId: string;
|
|
37
|
+
revokedByUserId: string | null;
|
|
38
|
+
revokedAt: string | null;
|
|
39
|
+
lastSeenAt: string | null;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type TokenExpiration = {kind: 'never'} | {kind: 'expires-after'; seconds: number};
|
|
44
|
+
|
|
45
|
+
export interface CreateTokenCommand {
|
|
46
|
+
name?: string;
|
|
47
|
+
expiration: TokenExpiration;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ProvisionerConnectionStatus =
|
|
51
|
+
| {kind: 'connected'; dotVariant: 'success'; label: 'Connected'}
|
|
52
|
+
| {kind: 'last-seen'; dotVariant: 'neutral'; label: 'Last seen'; lastSeenAt: string}
|
|
53
|
+
| {kind: 'never-connected'; dotVariant: 'neutral'; label: 'Never connected'};
|
|
54
|
+
|
|
55
|
+
export function createTokenCommand(name: string, expiration: TokenExpiration): CreateTokenCommand {
|
|
56
|
+
const trimmedName = name.trim();
|
|
57
|
+
return {expiration, ...(trimmedName ? {name: trimmedName} : {})};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function tokenDisplayName(token: Pick<ManualRegistrationToken, 'name'>): string {
|
|
61
|
+
return token.name || 'Unnamed token';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function provisionerTokenDisplayName(token: Pick<ProvisionerToken, 'name'>): string {
|
|
65
|
+
return token.name || 'Unnamed provisioner';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function provisionerConnectionStatus(
|
|
69
|
+
token: Pick<ProvisionerToken, 'id' | 'lastSeenAt'>,
|
|
70
|
+
activeIds: ReadonlySet<string>,
|
|
71
|
+
): ProvisionerConnectionStatus {
|
|
72
|
+
if (activeIds.has(token.id))
|
|
73
|
+
return {kind: 'connected', dotVariant: 'success', label: 'Connected'};
|
|
74
|
+
if (token.lastSeenAt) {
|
|
75
|
+
return {
|
|
76
|
+
kind: 'last-seen',
|
|
77
|
+
dotVariant: 'neutral',
|
|
78
|
+
label: 'Last seen',
|
|
79
|
+
lastSeenAt: token.lastSeenAt,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {kind: 'never-connected', dotVariant: 'neutral', label: 'Never connected'};
|
|
83
|
+
}
|
package/src/feature.ts
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
import {defineClientFeature} from '@shipfox/client-shell';
|
|
1
|
+
import {defineClientFeature, type SettingsSectionEntry} from '@shipfox/client-shell';
|
|
2
|
+
|
|
3
|
+
export const runnersSettingsSections = [
|
|
4
|
+
{
|
|
5
|
+
id: 'settings.runners',
|
|
6
|
+
pathSegment: 'runners',
|
|
7
|
+
label: 'Runners',
|
|
8
|
+
icon: 'settings3Line',
|
|
9
|
+
order: 200,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'settings.provisioners',
|
|
13
|
+
pathSegment: 'provisioners',
|
|
14
|
+
label: 'Runner provisioners',
|
|
15
|
+
icon: 'serverLine',
|
|
16
|
+
order: 300,
|
|
17
|
+
},
|
|
18
|
+
] as const satisfies readonly SettingsSectionEntry[];
|
|
2
19
|
|
|
3
20
|
export const runnersFeature = defineClientFeature({
|
|
4
21
|
id: 'shipfox.runners',
|
|
@@ -14,20 +31,5 @@ export const runnersFeature = defineClientFeature({
|
|
|
14
31
|
impl: '@shipfox/client-runners/routes/provisioners-settings',
|
|
15
32
|
},
|
|
16
33
|
],
|
|
17
|
-
settingsSections:
|
|
18
|
-
{
|
|
19
|
-
id: 'settings.runners',
|
|
20
|
-
pathSegment: 'runners',
|
|
21
|
-
label: 'Runners',
|
|
22
|
-
icon: 'settings3Line',
|
|
23
|
-
order: 200,
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: 'settings.provisioners',
|
|
27
|
-
pathSegment: 'provisioners',
|
|
28
|
-
label: 'Runner provisioners',
|
|
29
|
-
icon: 'serverLine',
|
|
30
|
-
order: 300,
|
|
31
|
-
},
|
|
32
|
-
],
|
|
34
|
+
settingsSections: runnersSettingsSections,
|
|
33
35
|
});
|
|
@@ -35,20 +35,23 @@ describe('createManualRegistrationToken', () => {
|
|
|
35
35
|
);
|
|
36
36
|
});
|
|
37
37
|
configureApiClient({fetchImpl});
|
|
38
|
-
const
|
|
38
|
+
const command = {
|
|
39
|
+
name: 'Local runner',
|
|
40
|
+
expiration: {kind: 'expires-after' as const, seconds: 86_400},
|
|
41
|
+
};
|
|
39
42
|
|
|
40
43
|
const result = await createManualRegistrationToken({
|
|
41
44
|
workspaceId: '11111111-1111-4111-8111-111111111111',
|
|
42
|
-
|
|
45
|
+
command,
|
|
43
46
|
});
|
|
44
47
|
|
|
45
48
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
46
|
-
expect(result.
|
|
49
|
+
expect(result.token).toBe('sf_mrt_raw-created-token');
|
|
47
50
|
expect(request.url).toBe(
|
|
48
51
|
'https://api.example.test/workspaces/11111111-1111-4111-8111-111111111111/runners/manual-registration-tokens',
|
|
49
52
|
);
|
|
50
53
|
expect(request.method).toBe('POST');
|
|
51
|
-
expect(requestBody).toEqual(
|
|
54
|
+
expect(requestBody).toEqual({name: 'Local runner', ttl_seconds: 86_400});
|
|
52
55
|
});
|
|
53
56
|
});
|
|
54
57
|
|
|
@@ -78,7 +81,7 @@ describe('revokeManualRegistrationToken', () => {
|
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
81
|
-
expect(result.
|
|
84
|
+
expect(result.revokedAt).toBe('2026-05-08T01:00:00.000Z');
|
|
82
85
|
expect(request.url).toBe(
|
|
83
86
|
'https://api.example.test/workspaces/11111111-1111-4111-8111-111111111111/runners/manual-registration-tokens/33333333-3333-4333-8333-333333333333/revoke',
|
|
84
87
|
);
|
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
RevokeManualRegistrationTokenResponseDto,
|
|
1
|
+
import {
|
|
2
|
+
createManualRegistrationTokenResponseSchema,
|
|
3
|
+
listManualRegistrationTokensResponseSchema,
|
|
4
|
+
revokeManualRegistrationTokenResponseSchema,
|
|
6
5
|
} from '@shipfox/api-runners-dto';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import {checkedApiRequest} from '@shipfox/client-api';
|
|
7
|
+
import {
|
|
8
|
+
type FetchQueryOptions,
|
|
9
|
+
queryOptions,
|
|
10
|
+
useMutation,
|
|
11
|
+
useQuery,
|
|
12
|
+
useQueryClient,
|
|
13
|
+
} from '@tanstack/react-query';
|
|
14
|
+
import type {
|
|
15
|
+
CreatedManualRegistrationToken,
|
|
16
|
+
CreateTokenCommand,
|
|
17
|
+
ManualRegistrationToken,
|
|
18
|
+
} from '#core/token.js';
|
|
19
|
+
import {
|
|
20
|
+
toCreatedManualRegistrationToken,
|
|
21
|
+
toCreateTokenBody,
|
|
22
|
+
toManualRegistrationToken,
|
|
23
|
+
} from './token-mapper.js';
|
|
9
24
|
|
|
10
25
|
export const manualRegistrationTokenQueryKeys = {
|
|
11
26
|
all: ['manual-registration-tokens'] as const,
|
|
@@ -13,30 +28,41 @@ export const manualRegistrationTokenQueryKeys = {
|
|
|
13
28
|
[...manualRegistrationTokenQueryKeys.all, 'list', workspaceId] as const,
|
|
14
29
|
};
|
|
15
30
|
|
|
31
|
+
type ManualRegistrationTokensQueryOptions = FetchQueryOptions<
|
|
32
|
+
ManualRegistrationToken[],
|
|
33
|
+
Error,
|
|
34
|
+
ManualRegistrationToken[],
|
|
35
|
+
ReturnType<typeof manualRegistrationTokenQueryKeys.list>
|
|
36
|
+
>;
|
|
37
|
+
|
|
16
38
|
export async function listManualRegistrationTokens({
|
|
17
39
|
workspaceId,
|
|
18
40
|
signal,
|
|
19
41
|
}: {
|
|
20
42
|
workspaceId: string;
|
|
21
43
|
signal?: AbortSignal;
|
|
22
|
-
}) {
|
|
23
|
-
|
|
44
|
+
}): Promise<ManualRegistrationToken[]> {
|
|
45
|
+
const response = await checkedApiRequest(
|
|
46
|
+
listManualRegistrationTokensResponseSchema,
|
|
24
47
|
`/workspaces/${workspaceId}/runners/manual-registration-tokens`,
|
|
25
48
|
{signal},
|
|
26
49
|
);
|
|
50
|
+
return response.manual_registration_tokens.map(toManualRegistrationToken);
|
|
27
51
|
}
|
|
28
52
|
|
|
29
53
|
export async function createManualRegistrationToken({
|
|
30
54
|
workspaceId,
|
|
31
|
-
|
|
55
|
+
command,
|
|
32
56
|
}: {
|
|
33
57
|
workspaceId: string;
|
|
34
|
-
|
|
35
|
-
}) {
|
|
36
|
-
|
|
58
|
+
command: CreateTokenCommand;
|
|
59
|
+
}): Promise<CreatedManualRegistrationToken> {
|
|
60
|
+
const response = await checkedApiRequest(
|
|
61
|
+
createManualRegistrationTokenResponseSchema,
|
|
37
62
|
`/workspaces/${workspaceId}/runners/manual-registration-tokens`,
|
|
38
|
-
{method: 'POST', body},
|
|
63
|
+
{method: 'POST', body: toCreateTokenBody(command)},
|
|
39
64
|
);
|
|
65
|
+
return toCreatedManualRegistrationToken(response);
|
|
40
66
|
}
|
|
41
67
|
|
|
42
68
|
export async function revokeManualRegistrationToken({
|
|
@@ -45,27 +71,48 @@ export async function revokeManualRegistrationToken({
|
|
|
45
71
|
}: {
|
|
46
72
|
workspaceId: string;
|
|
47
73
|
tokenId: string;
|
|
48
|
-
}) {
|
|
49
|
-
|
|
74
|
+
}): Promise<ManualRegistrationToken> {
|
|
75
|
+
const response = await checkedApiRequest(
|
|
76
|
+
revokeManualRegistrationTokenResponseSchema,
|
|
50
77
|
`/workspaces/${workspaceId}/runners/manual-registration-tokens/${tokenId}/revoke`,
|
|
51
78
|
{method: 'POST'},
|
|
52
79
|
);
|
|
80
|
+
return toManualRegistrationToken(response);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function manualRegistrationTokensQueryOptions(
|
|
84
|
+
workspaceId: string,
|
|
85
|
+
): ManualRegistrationTokensQueryOptions {
|
|
86
|
+
return queryOptions({
|
|
87
|
+
queryKey: manualRegistrationTokenQueryKeys.list(workspaceId),
|
|
88
|
+
queryFn: ({signal}) => listManualRegistrationTokens({workspaceId, signal}),
|
|
89
|
+
});
|
|
53
90
|
}
|
|
54
91
|
|
|
55
92
|
export function useManualRegistrationTokensQuery(workspaceId: string | undefined) {
|
|
56
93
|
return useQuery({
|
|
57
|
-
|
|
58
|
-
? manualRegistrationTokenQueryKeys.list(workspaceId)
|
|
59
|
-
: [...manualRegistrationTokenQueryKeys.all, 'list'],
|
|
94
|
+
...manualRegistrationTokensQueryOptions(workspaceId ?? ''),
|
|
60
95
|
enabled: Boolean(workspaceId),
|
|
61
|
-
queryFn: ({signal}) => listManualRegistrationTokens({workspaceId: workspaceId ?? '', signal}),
|
|
62
96
|
});
|
|
63
97
|
}
|
|
64
98
|
|
|
65
|
-
export function useCreateManualRegistrationTokenMutation() {
|
|
66
|
-
|
|
99
|
+
export function useCreateManualRegistrationTokenMutation(workspaceId: string) {
|
|
100
|
+
const queryClient = useQueryClient();
|
|
101
|
+
return useMutation({
|
|
102
|
+
mutationFn: (command: CreateTokenCommand) =>
|
|
103
|
+
createManualRegistrationToken({workspaceId, command}),
|
|
104
|
+
onSuccess: async () => {
|
|
105
|
+
await queryClient.invalidateQueries(manualRegistrationTokensQueryOptions(workspaceId));
|
|
106
|
+
},
|
|
107
|
+
});
|
|
67
108
|
}
|
|
68
109
|
|
|
69
|
-
export function useRevokeManualRegistrationTokenMutation() {
|
|
70
|
-
|
|
110
|
+
export function useRevokeManualRegistrationTokenMutation(workspaceId: string) {
|
|
111
|
+
const queryClient = useQueryClient();
|
|
112
|
+
return useMutation({
|
|
113
|
+
mutationFn: (tokenId: string) => revokeManualRegistrationToken({workspaceId, tokenId}),
|
|
114
|
+
onSuccess: async () => {
|
|
115
|
+
await queryClient.invalidateQueries(manualRegistrationTokensQueryOptions(workspaceId));
|
|
116
|
+
},
|
|
117
|
+
});
|
|
71
118
|
}
|
|
@@ -29,7 +29,7 @@ describe('provisioner token transports', () => {
|
|
|
29
29
|
const result = await listProvisionerTokens({workspaceId});
|
|
30
30
|
|
|
31
31
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
32
|
-
expect(result
|
|
32
|
+
expect(result).toEqual([]);
|
|
33
33
|
expect(request.url).toBe(
|
|
34
34
|
`https://api.example.test/workspaces/${workspaceId}/provisioners/tokens`,
|
|
35
35
|
);
|
|
@@ -60,17 +60,20 @@ describe('provisioner token transports', () => {
|
|
|
60
60
|
);
|
|
61
61
|
});
|
|
62
62
|
configureApiClient({fetchImpl});
|
|
63
|
-
const
|
|
63
|
+
const command = {
|
|
64
|
+
name: 'Docker provisioner',
|
|
65
|
+
expiration: {kind: 'expires-after' as const, seconds: 86_400},
|
|
66
|
+
};
|
|
64
67
|
|
|
65
|
-
const result = await createProvisionerToken({workspaceId,
|
|
68
|
+
const result = await createProvisionerToken({workspaceId, command});
|
|
66
69
|
|
|
67
70
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
68
|
-
expect(result.
|
|
71
|
+
expect(result.token).toBe('sf_pt_raw-created-token');
|
|
69
72
|
expect(request.url).toBe(
|
|
70
73
|
`https://api.example.test/workspaces/${workspaceId}/provisioners/tokens`,
|
|
71
74
|
);
|
|
72
75
|
expect(request.method).toBe('POST');
|
|
73
|
-
expect(requestBody).toEqual(
|
|
76
|
+
expect(requestBody).toEqual({name: 'Docker provisioner', ttl_seconds: 86_400});
|
|
74
77
|
});
|
|
75
78
|
|
|
76
79
|
test('posts to the token revoke endpoint', async () => {
|
|
@@ -95,7 +98,7 @@ describe('provisioner token transports', () => {
|
|
|
95
98
|
const result = await revokeProvisionerToken({workspaceId, tokenId});
|
|
96
99
|
|
|
97
100
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
98
|
-
expect(result.
|
|
101
|
+
expect(result.revokedAt).toBe('2026-05-08T01:00:00.000Z');
|
|
99
102
|
expect(request.url).toBe(
|
|
100
103
|
`https://api.example.test/workspaces/${workspaceId}/provisioners/tokens/${tokenId}/revoke`,
|
|
101
104
|
);
|
|
@@ -120,7 +123,7 @@ describe('provisioner token transports', () => {
|
|
|
120
123
|
const result = await listActiveProvisioners({workspaceId});
|
|
121
124
|
|
|
122
125
|
const request = fetchImpl.mock.calls[0]?.[0] as Request;
|
|
123
|
-
expect(result
|
|
126
|
+
expect(result).toHaveLength(1);
|
|
124
127
|
expect(request.url).toBe(
|
|
125
128
|
`https://api.example.test/workspaces/${workspaceId}/provisioners/active`,
|
|
126
129
|
);
|