@oore/client 0.11.0 → 0.13.0
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/README.md +37 -0
- package/dist/cli/config.js +57 -0
- package/dist/cli/main.js +17 -0
- package/dist/cli/qr.js +327 -0
- package/dist/cli/run.js +438 -0
- package/dist/generated/types.gen.d.ts +34 -0
- package/dist/generated/valibot/v-create-notification-channel-body.d.ts +4 -0
- package/dist/generated/valibot/v-create-notification-channel-request.d.ts +4 -0
- package/dist/generated/valibot/v-create-notification-channel-request.js +2 -0
- package/dist/generated/valibot/v-create-notification-channel-response.d.ts +4 -0
- package/dist/generated/valibot/v-get-notification-channel-response.d.ts +4 -0
- package/dist/generated/valibot/v-get-pipeline-google-play-publish-response.d.ts +8 -0
- package/dist/generated/valibot/v-google-play-credentials-test-response.d.ts +1 -0
- package/dist/generated/valibot/v-google-play-credentials-test-response.js +2 -1
- package/dist/generated/valibot/v-list-notification-channels-response.d.ts +4 -0
- package/dist/generated/valibot/v-list-notification-channels-response2.d.ts +4 -0
- package/dist/generated/valibot/v-notification-channel-response.d.ts +4 -0
- package/dist/generated/valibot/v-notification-channel.d.ts +4 -0
- package/dist/generated/valibot/v-notification-channel.js +2 -0
- package/dist/generated/valibot/v-pipeline-google-play-publish-response.d.ts +8 -0
- package/dist/generated/valibot/v-pipeline-google-play-publish-response.js +2 -0
- package/dist/generated/valibot/v-slack-mention.d.ts +5 -0
- package/dist/generated/valibot/v-slack-mention.js +6 -0
- package/dist/generated/valibot/v-test-pipeline-google-play-credentials-response.d.ts +1 -0
- package/dist/generated/valibot/v-update-notification-channel-body.d.ts +4 -0
- package/dist/generated/valibot/v-update-notification-channel-request.d.ts +4 -0
- package/dist/generated/valibot/v-update-notification-channel-request.js +2 -0
- package/dist/generated/valibot/v-update-notification-channel-response.d.ts +4 -0
- package/dist/generated/valibot/v-update-pipeline-google-play-publish-response.d.ts +8 -0
- package/dist/msw-scenarios.d.ts +12 -1
- package/dist/msw-scenarios.js +345 -1
- package/dist/valibot.d.ts +1 -0
- package/dist/valibot.js +1 -0
- package/openapi/openapi.json +58 -0
- package/package.json +5 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by scripts/generate-valibot-modules.ts. Do not edit.
|
|
2
2
|
import * as v from 'valibot';
|
|
3
3
|
import { vNotificationChannelType } from './v-notification-channel-type.js';
|
|
4
|
+
import { vSlackMention } from './v-slack-mention.js';
|
|
4
5
|
import { vSmtpConfigSummary } from './v-smtp-config-summary.js';
|
|
5
6
|
export const vNotificationChannel = v.object({
|
|
6
7
|
channel_type: vNotificationChannelType,
|
|
@@ -18,6 +19,7 @@ export const vNotificationChannel = v.object({
|
|
|
18
19
|
id: v.string(),
|
|
19
20
|
name: v.string(),
|
|
20
21
|
project_id: v.nullish(v.string()),
|
|
22
|
+
slack_mentions: v.optional(v.array(vSlackMention)),
|
|
21
23
|
smtp_summary: v.nullish(vSmtpConfigSummary),
|
|
22
24
|
updated_at: v.pipe(v.union([
|
|
23
25
|
v.number(),
|
|
@@ -19,6 +19,14 @@ export declare const vPipelineGooglePlayPublishResponse: v.ObjectSchema<{
|
|
|
19
19
|
readonly track: v.StringSchema<undefined>;
|
|
20
20
|
readonly upload_mapping: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
21
21
|
}, undefined>, undefined>;
|
|
22
|
+
readonly last_credentials_check: v.NullishSchema<v.ObjectSchema<{
|
|
23
|
+
readonly checked_at: v.SchemaWithPipe<readonly [v.UnionSchema<[v.NumberSchema<undefined>, v.StringSchema<undefined>, v.BigintSchema<undefined>], undefined>, v.TransformAction<string | number | bigint, bigint>, v.MinValueAction<bigint, bigint, "Invalid value: Expected int64 to be >= -9223372036854775808">, v.MaxValueAction<bigint, bigint, "Invalid value: Expected int64 to be <= 9223372036854775807">]>;
|
|
24
|
+
readonly message: v.StringSchema<undefined>;
|
|
25
|
+
readonly ok: v.BooleanSchema<undefined>;
|
|
26
|
+
readonly package_name: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
27
|
+
readonly service_account_email: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
28
|
+
readonly service_account_secret: v.StringSchema<undefined>;
|
|
29
|
+
}, undefined>, undefined>;
|
|
22
30
|
readonly pipeline_id: v.StringSchema<undefined>;
|
|
23
31
|
readonly service_account_secret_present: v.BooleanSchema<undefined>;
|
|
24
32
|
readonly updated_at: v.NullishSchema<v.SchemaWithPipe<readonly [v.UnionSchema<[v.NumberSchema<undefined>, v.StringSchema<undefined>, v.BigintSchema<undefined>], undefined>, v.TransformAction<string | number | bigint, bigint>, v.MinValueAction<bigint, bigint, "Invalid value: Expected int64 to be >= -9223372036854775808">, v.MaxValueAction<bigint, bigint, "Invalid value: Expected int64 to be <= 9223372036854775807">]>, undefined>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// Generated by scripts/generate-valibot-modules.ts. Do not edit.
|
|
2
2
|
import * as v from 'valibot';
|
|
3
|
+
import { vGooglePlayCredentialsTestResponse } from './v-google-play-credentials-test-response.js';
|
|
3
4
|
import { vGooglePlayPublishConfig } from './v-google-play-publish-config.js';
|
|
4
5
|
export const vPipelineGooglePlayPublishResponse = v.object({
|
|
5
6
|
config: v.nullish(vGooglePlayPublishConfig),
|
|
7
|
+
last_credentials_check: v.nullish(vGooglePlayCredentialsTestResponse),
|
|
6
8
|
pipeline_id: v.string(),
|
|
7
9
|
service_account_secret_present: v.boolean(),
|
|
8
10
|
updated_at: v.nullish(v.pipe(v.union([
|
|
@@ -5,4 +5,5 @@ export declare const vTestPipelineGooglePlayCredentialsResponse: v.ObjectSchema<
|
|
|
5
5
|
readonly ok: v.BooleanSchema<undefined>;
|
|
6
6
|
readonly package_name: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
7
7
|
readonly service_account_email: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
8
|
+
readonly service_account_secret: v.StringSchema<undefined>;
|
|
8
9
|
}, undefined>;
|
|
@@ -5,6 +5,10 @@ export declare const vUpdateNotificationChannelBody: v.ObjectSchema<{
|
|
|
5
5
|
readonly name: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
6
6
|
readonly project_id: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
7
7
|
readonly secret: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
8
|
+
readonly slack_mentions: v.NullishSchema<v.ArraySchema<v.ObjectSchema<{
|
|
9
|
+
readonly identity: v.StringSchema<undefined>;
|
|
10
|
+
readonly member_id: v.StringSchema<undefined>;
|
|
11
|
+
}, undefined>, undefined>, undefined>;
|
|
8
12
|
readonly smtp_config: v.NullishSchema<v.ObjectSchema<{
|
|
9
13
|
readonly from_address: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
10
14
|
readonly host: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -5,6 +5,10 @@ export declare const vUpdateNotificationChannelRequest: v.ObjectSchema<{
|
|
|
5
5
|
readonly name: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
6
6
|
readonly project_id: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
7
7
|
readonly secret: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
8
|
+
readonly slack_mentions: v.NullishSchema<v.ArraySchema<v.ObjectSchema<{
|
|
9
|
+
readonly identity: v.StringSchema<undefined>;
|
|
10
|
+
readonly member_id: v.StringSchema<undefined>;
|
|
11
|
+
}, undefined>, undefined>, undefined>;
|
|
8
12
|
readonly smtp_config: v.NullishSchema<v.ObjectSchema<{
|
|
9
13
|
readonly from_address: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
10
14
|
readonly host: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/generate-valibot-modules.ts. Do not edit.
|
|
2
2
|
import * as v from 'valibot';
|
|
3
|
+
import { vSlackMention } from './v-slack-mention.js';
|
|
3
4
|
import { vUpdateSmtpConfig } from './v-update-smtp-config.js';
|
|
4
5
|
export const vUpdateNotificationChannelRequest = v.object({
|
|
5
6
|
enabled: v.nullish(v.boolean()),
|
|
@@ -7,6 +8,7 @@ export const vUpdateNotificationChannelRequest = v.object({
|
|
|
7
8
|
name: v.nullish(v.string()),
|
|
8
9
|
project_id: v.nullish(v.string()),
|
|
9
10
|
secret: v.nullish(v.string()),
|
|
11
|
+
slack_mentions: v.nullish(v.array(vSlackMention)),
|
|
10
12
|
smtp_config: v.nullish(vUpdateSmtpConfig),
|
|
11
13
|
url: v.nullish(v.string())
|
|
12
14
|
});
|
|
@@ -12,6 +12,10 @@ export declare const vUpdateNotificationChannelResponse: v.ObjectSchema<{
|
|
|
12
12
|
readonly id: v.StringSchema<undefined>;
|
|
13
13
|
readonly name: v.StringSchema<undefined>;
|
|
14
14
|
readonly project_id: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
15
|
+
readonly slack_mentions: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
|
|
16
|
+
readonly identity: v.StringSchema<undefined>;
|
|
17
|
+
readonly member_id: v.StringSchema<undefined>;
|
|
18
|
+
}, undefined>, undefined>, undefined>;
|
|
15
19
|
readonly smtp_summary: v.NullishSchema<v.ObjectSchema<{
|
|
16
20
|
readonly from_address: v.StringSchema<undefined>;
|
|
17
21
|
readonly host: v.StringSchema<undefined>;
|
|
@@ -19,6 +19,14 @@ export declare const vUpdatePipelineGooglePlayPublishResponse: v.ObjectSchema<{
|
|
|
19
19
|
readonly track: v.StringSchema<undefined>;
|
|
20
20
|
readonly upload_mapping: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
21
21
|
}, undefined>, undefined>;
|
|
22
|
+
readonly last_credentials_check: v.NullishSchema<v.ObjectSchema<{
|
|
23
|
+
readonly checked_at: v.SchemaWithPipe<readonly [v.UnionSchema<[v.NumberSchema<undefined>, v.StringSchema<undefined>, v.BigintSchema<undefined>], undefined>, v.TransformAction<string | number | bigint, bigint>, v.MinValueAction<bigint, bigint, "Invalid value: Expected int64 to be >= -9223372036854775808">, v.MaxValueAction<bigint, bigint, "Invalid value: Expected int64 to be <= 9223372036854775807">]>;
|
|
24
|
+
readonly message: v.StringSchema<undefined>;
|
|
25
|
+
readonly ok: v.BooleanSchema<undefined>;
|
|
26
|
+
readonly package_name: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
27
|
+
readonly service_account_email: v.NullishSchema<v.StringSchema<undefined>, undefined>;
|
|
28
|
+
readonly service_account_secret: v.StringSchema<undefined>;
|
|
29
|
+
}, undefined>, undefined>;
|
|
22
30
|
readonly pipeline_id: v.StringSchema<undefined>;
|
|
23
31
|
readonly service_account_secret_present: v.BooleanSchema<undefined>;
|
|
24
32
|
readonly updated_at: v.NullishSchema<v.SchemaWithPipe<readonly [v.UnionSchema<[v.NumberSchema<undefined>, v.StringSchema<undefined>, v.BigintSchema<undefined>], undefined>, v.TransformAction<string | number | bigint, bigint>, v.MinValueAction<bigint, bigint, "Invalid value: Expected int64 to be >= -9223372036854775808">, v.MaxValueAction<bigint, bigint, "Invalid value: Expected int64 to be <= 9223372036854775807">]>, undefined>;
|
package/dist/msw-scenarios.d.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { type HttpHandler } from 'msw';
|
|
2
2
|
import { type OoreMswHandlerOptions } from './msw.js';
|
|
3
|
-
import type { Project } from './generated/types.gen.js';
|
|
3
|
+
import type { Project, ReleaseDetailResponse } from './generated/types.gen.js';
|
|
4
4
|
export interface OoreMockScenario {
|
|
5
5
|
/** Fixed epoch seconds used for records created during the scenario. */
|
|
6
6
|
now: number;
|
|
7
7
|
projects: ReadonlyArray<Project>;
|
|
8
|
+
/**
|
|
9
|
+
* Releases with their history, newest history entry last. The handlers
|
|
10
|
+
* recompute `allowed_actions` for a maintainer from each release's state.
|
|
11
|
+
*/
|
|
12
|
+
releases?: ReadonlyArray<ReleaseDetailResponse>;
|
|
8
13
|
}
|
|
14
|
+
/** A Google Play production rollout at 20%, healthy and still moving. */
|
|
15
|
+
export declare const rollingReleaseScenario: ReleaseDetailResponse;
|
|
16
|
+
/** A Google Play rollout the daemon halted at 5% after a crash spike. */
|
|
17
|
+
export declare const haltedReleaseScenario: ReleaseDetailResponse;
|
|
18
|
+
/** An App Store release that finished its phased rollout. */
|
|
19
|
+
export declare const completeReleaseScenario: ReleaseDetailResponse;
|
|
9
20
|
export declare const defaultOoreScenario: OoreMockScenario;
|
|
10
21
|
/**
|
|
11
22
|
* Creates deterministic, stateful handlers for Oore's core demo scenario.
|
package/dist/msw-scenarios.js
CHANGED
|
@@ -1,5 +1,159 @@
|
|
|
1
1
|
import { HttpResponse } from 'msw';
|
|
2
|
-
import { createProjectMock, listProjectsMock, } from './msw.js';
|
|
2
|
+
import { approveReleaseMock, completeReleaseMock, createProjectMock, getReleaseMock, haltReleaseMock, listProjectsMock, listReleaseEventsMock, listReleasesMock, pauseReleaseMock, raiseReleaseMock, resumeReleaseMock, } from './msw.js';
|
|
3
|
+
const DEFAULT_RELEASE_POLICY = {
|
|
4
|
+
approval: 'none',
|
|
5
|
+
crash_rate_threshold: 0.02,
|
|
6
|
+
interval_hours: 24,
|
|
7
|
+
steps: [0.01, 0.05, 0.2, 0.5, 1],
|
|
8
|
+
};
|
|
9
|
+
/** A Google Play production rollout at 20%, healthy and still moving. */
|
|
10
|
+
export const rollingReleaseScenario = {
|
|
11
|
+
history: [
|
|
12
|
+
{
|
|
13
|
+
at: 1_767_052_800,
|
|
14
|
+
id: 'release-rolling-h1',
|
|
15
|
+
to_fraction: 0.01,
|
|
16
|
+
to_state: 'rolling',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
at: 1_767_139_200,
|
|
20
|
+
from_fraction: 0.01,
|
|
21
|
+
from_state: 'rolling',
|
|
22
|
+
id: 'release-rolling-h2',
|
|
23
|
+
to_fraction: 0.05,
|
|
24
|
+
to_state: 'rolling',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
actor_email: 'owner@acme.test',
|
|
28
|
+
actor_id: 'demo-owner',
|
|
29
|
+
at: 1_767_200_000,
|
|
30
|
+
from_fraction: 0.05,
|
|
31
|
+
from_state: 'rolling',
|
|
32
|
+
id: 'release-rolling-h3',
|
|
33
|
+
reason: 'Crash-free rate looks good',
|
|
34
|
+
to_fraction: 0.2,
|
|
35
|
+
to_state: 'rolling',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
release: {
|
|
39
|
+
allowed_actions: [],
|
|
40
|
+
artifact_id: 'artifact-rolling',
|
|
41
|
+
awaiting_approval: false,
|
|
42
|
+
build_id: 'build-142',
|
|
43
|
+
build_number: 142,
|
|
44
|
+
crash_rate: 0.004,
|
|
45
|
+
crash_rate_threshold: 0.02,
|
|
46
|
+
created_at: 1_767_052_800,
|
|
47
|
+
health: 'healthy',
|
|
48
|
+
id: 'release-rolling',
|
|
49
|
+
next_fraction: 0.5,
|
|
50
|
+
next_step_at: 1_767_286_400,
|
|
51
|
+
package_name: 'com.acme.fluttershop',
|
|
52
|
+
platform: 'android',
|
|
53
|
+
policy: DEFAULT_RELEASE_POLICY,
|
|
54
|
+
project_id: 'project-1',
|
|
55
|
+
project_name: 'FlutterShop',
|
|
56
|
+
rollout_fraction: 0.2,
|
|
57
|
+
state: 'rolling',
|
|
58
|
+
store: 'google_play',
|
|
59
|
+
track: 'production',
|
|
60
|
+
updated_at: 1_767_200_000,
|
|
61
|
+
version_code: 142,
|
|
62
|
+
version_name: '2.4.0',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
/** A Google Play rollout the daemon halted at 5% after a crash spike. */
|
|
66
|
+
export const haltedReleaseScenario = {
|
|
67
|
+
history: [
|
|
68
|
+
{
|
|
69
|
+
at: 1_766_880_000,
|
|
70
|
+
id: 'release-halted-h1',
|
|
71
|
+
to_fraction: 0.01,
|
|
72
|
+
to_state: 'rolling',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
at: 1_766_966_400,
|
|
76
|
+
from_fraction: 0.01,
|
|
77
|
+
from_state: 'rolling',
|
|
78
|
+
id: 'release-halted-h2',
|
|
79
|
+
to_fraction: 0.05,
|
|
80
|
+
to_state: 'rolling',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
at: 1_767_000_000,
|
|
84
|
+
from_fraction: 0.05,
|
|
85
|
+
from_state: 'rolling',
|
|
86
|
+
id: 'release-halted-h3',
|
|
87
|
+
reason: 'Crash rate 3.1% reached the 2% threshold',
|
|
88
|
+
to_state: 'halted',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
release: {
|
|
92
|
+
allowed_actions: [],
|
|
93
|
+
artifact_id: 'artifact-halted',
|
|
94
|
+
awaiting_approval: false,
|
|
95
|
+
build_id: 'build-139',
|
|
96
|
+
build_number: 139,
|
|
97
|
+
crash_rate: 0.031,
|
|
98
|
+
crash_rate_threshold: 0.02,
|
|
99
|
+
created_at: 1_766_880_000,
|
|
100
|
+
health: 'breached',
|
|
101
|
+
id: 'release-halted',
|
|
102
|
+
package_name: 'com.acme.fluttershop',
|
|
103
|
+
platform: 'android',
|
|
104
|
+
policy: DEFAULT_RELEASE_POLICY,
|
|
105
|
+
project_id: 'project-1',
|
|
106
|
+
project_name: 'FlutterShop',
|
|
107
|
+
rollout_fraction: 0.05,
|
|
108
|
+
state: 'halted',
|
|
109
|
+
store: 'google_play',
|
|
110
|
+
track: 'production',
|
|
111
|
+
updated_at: 1_767_000_000,
|
|
112
|
+
version_code: 139,
|
|
113
|
+
version_name: '2.3.1',
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
/** An App Store release that finished its phased rollout. */
|
|
117
|
+
export const completeReleaseScenario = {
|
|
118
|
+
history: [
|
|
119
|
+
{
|
|
120
|
+
at: 1_766_448_000,
|
|
121
|
+
id: 'release-complete-h1',
|
|
122
|
+
to_fraction: 0.01,
|
|
123
|
+
to_state: 'rolling',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
at: 1_767_052_800,
|
|
127
|
+
from_fraction: 0.5,
|
|
128
|
+
from_state: 'rolling',
|
|
129
|
+
id: 'release-complete-h2',
|
|
130
|
+
to_fraction: 1,
|
|
131
|
+
to_state: 'complete',
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
release: {
|
|
135
|
+
allowed_actions: [],
|
|
136
|
+
artifact_id: 'artifact-complete',
|
|
137
|
+
awaiting_approval: false,
|
|
138
|
+
build_id: 'build-137',
|
|
139
|
+
build_number: 137,
|
|
140
|
+
created_at: 1_766_448_000,
|
|
141
|
+
health: 'healthy',
|
|
142
|
+
id: 'release-complete',
|
|
143
|
+
package_name: 'com.acme.fluttershop',
|
|
144
|
+
platform: 'ios',
|
|
145
|
+
policy: DEFAULT_RELEASE_POLICY,
|
|
146
|
+
project_id: 'project-1',
|
|
147
|
+
project_name: 'FlutterShop',
|
|
148
|
+
rollout_fraction: 1,
|
|
149
|
+
state: 'complete',
|
|
150
|
+
store: 'app_store',
|
|
151
|
+
track: 'app_store',
|
|
152
|
+
updated_at: 1_767_052_800,
|
|
153
|
+
version_code: 137,
|
|
154
|
+
version_name: '2.3.0',
|
|
155
|
+
},
|
|
156
|
+
};
|
|
3
157
|
export const defaultOoreScenario = {
|
|
4
158
|
now: 1_767_225_600,
|
|
5
159
|
projects: [
|
|
@@ -32,7 +186,65 @@ export const defaultOoreScenario = {
|
|
|
32
186
|
updated_at: 1_767_139_200,
|
|
33
187
|
},
|
|
34
188
|
],
|
|
189
|
+
releases: [
|
|
190
|
+
rollingReleaseScenario,
|
|
191
|
+
haltedReleaseScenario,
|
|
192
|
+
completeReleaseScenario,
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
const isAwaitingApproval = (release) => {
|
|
196
|
+
if (release.approved_at != null)
|
|
197
|
+
return false;
|
|
198
|
+
if (release.state === 'complete' || release.state === 'failed')
|
|
199
|
+
return false;
|
|
200
|
+
switch (release.policy.approval ?? 'none') {
|
|
201
|
+
case 'before_upload':
|
|
202
|
+
return release.state === 'uploading';
|
|
203
|
+
case 'before_production':
|
|
204
|
+
return release.track === 'production';
|
|
205
|
+
default:
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
/** Mirrors the daemon's rules for a caller who may manage releases. */
|
|
210
|
+
const maintainerActions = (release) => {
|
|
211
|
+
const awaiting = isAwaitingApproval(release);
|
|
212
|
+
const { rollout_fraction: fraction, state } = release;
|
|
213
|
+
const appStore = release.store === 'app_store';
|
|
214
|
+
const playDraft = release.store === 'google_play' && state === 'paused' && fraction === 0;
|
|
215
|
+
const moving = state === 'rolling' || state === 'paused';
|
|
216
|
+
const actions = [];
|
|
217
|
+
if (!appStore && state === 'rolling' && !awaiting && fraction < 1)
|
|
218
|
+
actions.push('raise');
|
|
219
|
+
if (state === 'rolling')
|
|
220
|
+
actions.push('pause');
|
|
221
|
+
if ((state === 'paused' || state === 'halted') && !awaiting)
|
|
222
|
+
actions.push('resume');
|
|
223
|
+
if (!appStore && !playDraft && moving)
|
|
224
|
+
actions.push('halt');
|
|
225
|
+
if (moving && !awaiting)
|
|
226
|
+
actions.push('complete');
|
|
227
|
+
if (awaiting)
|
|
228
|
+
actions.push('approve');
|
|
229
|
+
return actions;
|
|
35
230
|
};
|
|
231
|
+
const withActions = (release) => ({
|
|
232
|
+
...release,
|
|
233
|
+
allowed_actions: maintainerActions(release),
|
|
234
|
+
awaiting_approval: isAwaitingApproval(release),
|
|
235
|
+
});
|
|
236
|
+
const apiError = (status, code, error) => HttpResponse.json({ code, error }, { status });
|
|
237
|
+
const cloneRelease = (detail) => ({
|
|
238
|
+
history: detail.history.map((entry) => ({ ...entry })),
|
|
239
|
+
release: {
|
|
240
|
+
...detail.release,
|
|
241
|
+
allowed_actions: [...detail.release.allowed_actions],
|
|
242
|
+
policy: {
|
|
243
|
+
...detail.release.policy,
|
|
244
|
+
steps: detail.release.policy.steps && [...detail.release.policy.steps],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
});
|
|
36
248
|
/**
|
|
37
249
|
* Creates deterministic, stateful handlers for Oore's core demo scenario.
|
|
38
250
|
* Each call owns an isolated copy of the supplied state.
|
|
@@ -43,6 +255,86 @@ export const createOoreMockHandlers = (scenario = defaultOoreScenario, options)
|
|
|
43
255
|
settings: { ...project.settings },
|
|
44
256
|
}));
|
|
45
257
|
let nextProjectNumber = projects.length + 1;
|
|
258
|
+
const releases = (scenario.releases ?? []).map(cloneRelease);
|
|
259
|
+
let clock = scenario.now;
|
|
260
|
+
let nextHistoryNumber = 1;
|
|
261
|
+
const releaseAction = (action) => async ({ params, request, }) => {
|
|
262
|
+
const detail = releases.find((item) => item.release.id === params.release_id);
|
|
263
|
+
if (!detail)
|
|
264
|
+
return apiError(404, 'not_found', 'Release not found');
|
|
265
|
+
const text = await request.text();
|
|
266
|
+
const body = text ? JSON.parse(text) : {};
|
|
267
|
+
const current = detail.release;
|
|
268
|
+
if (body.fraction != null && action !== 'raise')
|
|
269
|
+
return apiError(400, 'invalid_input', 'fraction is only accepted by raise');
|
|
270
|
+
if (!maintainerActions(current).includes(action))
|
|
271
|
+
return apiError(409, 'invalid_release_state', `Cannot ${action} a release that is ${current.state}`);
|
|
272
|
+
clock += 60;
|
|
273
|
+
if (action === 'approve') {
|
|
274
|
+
detail.release = withActions({
|
|
275
|
+
...current,
|
|
276
|
+
approved_at: clock,
|
|
277
|
+
approved_by_email: 'owner@acme.test',
|
|
278
|
+
updated_at: clock,
|
|
279
|
+
});
|
|
280
|
+
return HttpResponse.json(detail.release);
|
|
281
|
+
}
|
|
282
|
+
let toState;
|
|
283
|
+
let toFraction;
|
|
284
|
+
switch (action) {
|
|
285
|
+
case 'raise': {
|
|
286
|
+
const fraction = body.fraction ??
|
|
287
|
+
current.policy.steps?.find((step) => step > current.rollout_fraction) ??
|
|
288
|
+
1;
|
|
289
|
+
if (!Number.isFinite(fraction) ||
|
|
290
|
+
fraction <= current.rollout_fraction ||
|
|
291
|
+
fraction > 1)
|
|
292
|
+
return apiError(400, 'invalid_input', 'fraction must be above the current rollout and at most 1');
|
|
293
|
+
toFraction = fraction;
|
|
294
|
+
toState = fraction >= 1 ? 'complete' : 'rolling';
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case 'pause':
|
|
298
|
+
toState = 'paused';
|
|
299
|
+
break;
|
|
300
|
+
case 'halt':
|
|
301
|
+
toState = 'halted';
|
|
302
|
+
break;
|
|
303
|
+
case 'resume':
|
|
304
|
+
toState = 'rolling';
|
|
305
|
+
if (current.rollout_fraction === 0)
|
|
306
|
+
toFraction = current.policy.steps?.[0];
|
|
307
|
+
break;
|
|
308
|
+
default:
|
|
309
|
+
toState = 'complete';
|
|
310
|
+
toFraction = 1;
|
|
311
|
+
}
|
|
312
|
+
const entry = {
|
|
313
|
+
actor_email: 'owner@acme.test',
|
|
314
|
+
actor_id: 'demo-owner',
|
|
315
|
+
at: clock,
|
|
316
|
+
from_fraction: current.rollout_fraction,
|
|
317
|
+
from_state: current.state,
|
|
318
|
+
id: `release-history-${nextHistoryNumber++}`,
|
|
319
|
+
reason: body.reason ?? null,
|
|
320
|
+
to_fraction: toFraction ?? null,
|
|
321
|
+
to_state: toState,
|
|
322
|
+
};
|
|
323
|
+
detail.history.push(entry);
|
|
324
|
+
const rolloutFraction = toFraction ?? current.rollout_fraction;
|
|
325
|
+
const policySteps = current.policy.steps ?? [];
|
|
326
|
+
detail.release = withActions({
|
|
327
|
+
...current,
|
|
328
|
+
next_fraction: toState === 'rolling'
|
|
329
|
+
? (policySteps.find((step) => step > rolloutFraction) ?? null)
|
|
330
|
+
: null,
|
|
331
|
+
next_step_at: null,
|
|
332
|
+
rollout_fraction: rolloutFraction,
|
|
333
|
+
state: toState,
|
|
334
|
+
updated_at: clock,
|
|
335
|
+
});
|
|
336
|
+
return HttpResponse.json(detail.release);
|
|
337
|
+
};
|
|
46
338
|
return [
|
|
47
339
|
listProjectsMock(({ request }) => {
|
|
48
340
|
const url = new URL(request.url);
|
|
@@ -74,5 +366,57 @@ export const createOoreMockHandlers = (scenario = defaultOoreScenario, options)
|
|
|
74
366
|
projects.push(project);
|
|
75
367
|
return HttpResponse.json({ project }, { status: 201 });
|
|
76
368
|
}, options),
|
|
369
|
+
listReleasesMock(({ request }) => {
|
|
370
|
+
const url = new URL(request.url);
|
|
371
|
+
const projectId = url.searchParams.get('project_id');
|
|
372
|
+
const buildId = url.searchParams.get('build_id');
|
|
373
|
+
const states = url.searchParams.get('state')?.split(',');
|
|
374
|
+
const offset = Number(url.searchParams.get('offset') ?? 0);
|
|
375
|
+
const limit = Number(url.searchParams.get('limit') ?? 50);
|
|
376
|
+
const matching = releases
|
|
377
|
+
.map((detail) => detail.release)
|
|
378
|
+
.filter((release) => (!projectId || release.project_id === projectId) &&
|
|
379
|
+
(!buildId || release.build_id === buildId) &&
|
|
380
|
+
(!states || states.includes(release.state)))
|
|
381
|
+
.sort((a, b) => b.created_at - a.created_at);
|
|
382
|
+
return HttpResponse.json({
|
|
383
|
+
releases: matching.slice(offset, offset + limit).map(withActions),
|
|
384
|
+
total: matching.length,
|
|
385
|
+
});
|
|
386
|
+
}, options),
|
|
387
|
+
getReleaseMock(({ params }) => {
|
|
388
|
+
const detail = releases.find((item) => item.release.id === params.release_id);
|
|
389
|
+
if (!detail)
|
|
390
|
+
return apiError(404, 'not_found', 'Release not found');
|
|
391
|
+
return HttpResponse.json({
|
|
392
|
+
history: detail.history,
|
|
393
|
+
release: withActions(detail.release),
|
|
394
|
+
});
|
|
395
|
+
}, options),
|
|
396
|
+
listReleaseEventsMock(({ request }) => {
|
|
397
|
+
const url = new URL(request.url);
|
|
398
|
+
const projectId = url.searchParams.get('project_id');
|
|
399
|
+
const limit = Number(url.searchParams.get('limit') ?? 50);
|
|
400
|
+
const events = releases
|
|
401
|
+
.filter((detail) => !projectId || detail.release.project_id === projectId)
|
|
402
|
+
.flatMap(({ history, release }) => history.map((entry) => ({
|
|
403
|
+
...entry,
|
|
404
|
+
build_id: release.build_id,
|
|
405
|
+
project_id: release.project_id,
|
|
406
|
+
project_name: release.project_name,
|
|
407
|
+
release_id: release.id,
|
|
408
|
+
store: release.store,
|
|
409
|
+
track: release.track,
|
|
410
|
+
version_name: release.version_name,
|
|
411
|
+
})))
|
|
412
|
+
.sort((a, b) => b.at - a.at);
|
|
413
|
+
return HttpResponse.json({ events: events.slice(0, limit) });
|
|
414
|
+
}, options),
|
|
415
|
+
raiseReleaseMock(releaseAction('raise'), options),
|
|
416
|
+
pauseReleaseMock(releaseAction('pause'), options),
|
|
417
|
+
resumeReleaseMock(releaseAction('resume'), options),
|
|
418
|
+
haltReleaseMock(releaseAction('halt'), options),
|
|
419
|
+
completeReleaseMock(releaseAction('complete'), options),
|
|
420
|
+
approveReleaseMock(releaseAction('approve'), options),
|
|
77
421
|
];
|
|
78
422
|
};
|
package/dist/valibot.d.ts
CHANGED
|
@@ -245,6 +245,7 @@ export { vSetupSummaryResponse } from './generated/valibot/v-setup-summary-respo
|
|
|
245
245
|
export { vSetupTrustedProxyClaimOwnerResponse } from './generated/valibot/v-setup-trusted-proxy-claim-owner-response.js';
|
|
246
246
|
export { vSetupTrustedProxyConfigureRequest } from './generated/valibot/v-setup-trusted-proxy-configure-request.js';
|
|
247
247
|
export { vSetupTrustedProxyConfigureResponse } from './generated/valibot/v-setup-trusted-proxy-configure-response.js';
|
|
248
|
+
export { vSlackMention } from './generated/valibot/v-slack-mention.js';
|
|
248
249
|
export { vSmtpTlsMode } from './generated/valibot/v-smtp-tls-mode.js';
|
|
249
250
|
export { vSmtpConfig } from './generated/valibot/v-smtp-config.js';
|
|
250
251
|
export { vCreateNotificationChannelRequest } from './generated/valibot/v-create-notification-channel-request.js';
|
package/dist/valibot.js
CHANGED
|
@@ -246,6 +246,7 @@ export { vSetupSummaryResponse } from './generated/valibot/v-setup-summary-respo
|
|
|
246
246
|
export { vSetupTrustedProxyClaimOwnerResponse } from './generated/valibot/v-setup-trusted-proxy-claim-owner-response.js';
|
|
247
247
|
export { vSetupTrustedProxyConfigureRequest } from './generated/valibot/v-setup-trusted-proxy-configure-request.js';
|
|
248
248
|
export { vSetupTrustedProxyConfigureResponse } from './generated/valibot/v-setup-trusted-proxy-configure-response.js';
|
|
249
|
+
export { vSlackMention } from './generated/valibot/v-slack-mention.js';
|
|
249
250
|
export { vSmtpTlsMode } from './generated/valibot/v-smtp-tls-mode.js';
|
|
250
251
|
export { vSmtpConfig } from './generated/valibot/v-smtp-config.js';
|
|
251
252
|
export { vCreateNotificationChannelRequest } from './generated/valibot/v-create-notification-channel-request.js';
|
package/openapi/openapi.json
CHANGED
|
@@ -14046,6 +14046,13 @@
|
|
|
14046
14046
|
],
|
|
14047
14047
|
"description": "Webhook: HMAC secret for signing payloads. Slack: bot token with\n`users:read.email`, used to mention the triggering user. Optional."
|
|
14048
14048
|
},
|
|
14049
|
+
"slack_mentions": {
|
|
14050
|
+
"type": "array",
|
|
14051
|
+
"items": {
|
|
14052
|
+
"$ref": "#/components/schemas/SlackMention"
|
|
14053
|
+
},
|
|
14054
|
+
"description": "Slack member IDs to mention (Slack only). Works without a bot token."
|
|
14055
|
+
},
|
|
14049
14056
|
"smtp_config": {
|
|
14050
14057
|
"oneOf": [
|
|
14051
14058
|
{
|
|
@@ -14888,6 +14895,7 @@
|
|
|
14888
14895
|
"description": "Outcome of a Google Play credentials check. A failed check is a normal\nanswer, with Google's reason in `message`, not an HTTP error.",
|
|
14889
14896
|
"required": [
|
|
14890
14897
|
"ok",
|
|
14898
|
+
"service_account_secret",
|
|
14891
14899
|
"message",
|
|
14892
14900
|
"checked_at"
|
|
14893
14901
|
],
|
|
@@ -14916,6 +14924,10 @@
|
|
|
14916
14924
|
"null"
|
|
14917
14925
|
],
|
|
14918
14926
|
"description": "The key's `client_email`, when the secret is a service account key."
|
|
14927
|
+
},
|
|
14928
|
+
"service_account_secret": {
|
|
14929
|
+
"type": "string",
|
|
14930
|
+
"description": "The secret that was checked."
|
|
14919
14931
|
}
|
|
14920
14932
|
}
|
|
14921
14933
|
},
|
|
@@ -16038,6 +16050,13 @@
|
|
|
16038
16050
|
],
|
|
16039
16051
|
"description": "Project this channel is scoped to. Absent means the channel receives\nevents from every project, plus instance-wide events such as runner\noffline."
|
|
16040
16052
|
},
|
|
16053
|
+
"slack_mentions": {
|
|
16054
|
+
"type": "array",
|
|
16055
|
+
"items": {
|
|
16056
|
+
"$ref": "#/components/schemas/SlackMention"
|
|
16057
|
+
},
|
|
16058
|
+
"description": "Slack member IDs to mention, matched against the person behind each\nevent (Slack only). Checked before the bot token lookup. Absent from\ndaemons that predate it, which is the same as none."
|
|
16059
|
+
},
|
|
16041
16060
|
"smtp_summary": {
|
|
16042
16061
|
"oneOf": [
|
|
16043
16062
|
{
|
|
@@ -16579,6 +16598,17 @@
|
|
|
16579
16598
|
}
|
|
16580
16599
|
]
|
|
16581
16600
|
},
|
|
16601
|
+
"last_credentials_check": {
|
|
16602
|
+
"oneOf": [
|
|
16603
|
+
{
|
|
16604
|
+
"type": "null"
|
|
16605
|
+
},
|
|
16606
|
+
{
|
|
16607
|
+
"$ref": "#/components/schemas/GooglePlayCredentialsTestResponse",
|
|
16608
|
+
"description": "The latest credentials check, while the key it checked is still the\none its secret name resolves to."
|
|
16609
|
+
}
|
|
16610
|
+
]
|
|
16611
|
+
},
|
|
16582
16612
|
"pipeline_id": {
|
|
16583
16613
|
"type": "string"
|
|
16584
16614
|
},
|
|
@@ -19115,6 +19145,24 @@
|
|
|
19115
19145
|
}
|
|
19116
19146
|
}
|
|
19117
19147
|
},
|
|
19148
|
+
"SlackMention": {
|
|
19149
|
+
"type": "object",
|
|
19150
|
+
"description": "Maps a person to the Slack member mentioned in their notifications.",
|
|
19151
|
+
"required": [
|
|
19152
|
+
"identity",
|
|
19153
|
+
"member_id"
|
|
19154
|
+
],
|
|
19155
|
+
"properties": {
|
|
19156
|
+
"identity": {
|
|
19157
|
+
"type": "string",
|
|
19158
|
+
"description": "Oore sign-in email (manual and API builds, releases) or Git username\n(push and merge request builds). Matched without regard to case."
|
|
19159
|
+
},
|
|
19160
|
+
"member_id": {
|
|
19161
|
+
"type": "string",
|
|
19162
|
+
"description": "Slack member ID, such as `U0123ABCD`."
|
|
19163
|
+
}
|
|
19164
|
+
}
|
|
19165
|
+
},
|
|
19118
19166
|
"SmtpConfig": {
|
|
19119
19167
|
"type": "object",
|
|
19120
19168
|
"required": [
|
|
@@ -19934,6 +19982,16 @@
|
|
|
19934
19982
|
],
|
|
19935
19983
|
"description": "New HMAC secret (webhook) or bot token (slack). Omit to keep existing;\npass empty string to clear."
|
|
19936
19984
|
},
|
|
19985
|
+
"slack_mentions": {
|
|
19986
|
+
"type": [
|
|
19987
|
+
"array",
|
|
19988
|
+
"null"
|
|
19989
|
+
],
|
|
19990
|
+
"items": {
|
|
19991
|
+
"$ref": "#/components/schemas/SlackMention"
|
|
19992
|
+
},
|
|
19993
|
+
"description": "New Slack member mappings (Slack only). Omit to keep existing; pass an\nempty list to clear."
|
|
19994
|
+
},
|
|
19937
19995
|
"smtp_config": {
|
|
19938
19996
|
"oneOf": [
|
|
19939
19997
|
{
|