@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.
Files changed (35) hide show
  1. package/README.md +37 -0
  2. package/dist/cli/config.js +57 -0
  3. package/dist/cli/main.js +17 -0
  4. package/dist/cli/qr.js +327 -0
  5. package/dist/cli/run.js +438 -0
  6. package/dist/generated/types.gen.d.ts +34 -0
  7. package/dist/generated/valibot/v-create-notification-channel-body.d.ts +4 -0
  8. package/dist/generated/valibot/v-create-notification-channel-request.d.ts +4 -0
  9. package/dist/generated/valibot/v-create-notification-channel-request.js +2 -0
  10. package/dist/generated/valibot/v-create-notification-channel-response.d.ts +4 -0
  11. package/dist/generated/valibot/v-get-notification-channel-response.d.ts +4 -0
  12. package/dist/generated/valibot/v-get-pipeline-google-play-publish-response.d.ts +8 -0
  13. package/dist/generated/valibot/v-google-play-credentials-test-response.d.ts +1 -0
  14. package/dist/generated/valibot/v-google-play-credentials-test-response.js +2 -1
  15. package/dist/generated/valibot/v-list-notification-channels-response.d.ts +4 -0
  16. package/dist/generated/valibot/v-list-notification-channels-response2.d.ts +4 -0
  17. package/dist/generated/valibot/v-notification-channel-response.d.ts +4 -0
  18. package/dist/generated/valibot/v-notification-channel.d.ts +4 -0
  19. package/dist/generated/valibot/v-notification-channel.js +2 -0
  20. package/dist/generated/valibot/v-pipeline-google-play-publish-response.d.ts +8 -0
  21. package/dist/generated/valibot/v-pipeline-google-play-publish-response.js +2 -0
  22. package/dist/generated/valibot/v-slack-mention.d.ts +5 -0
  23. package/dist/generated/valibot/v-slack-mention.js +6 -0
  24. package/dist/generated/valibot/v-test-pipeline-google-play-credentials-response.d.ts +1 -0
  25. package/dist/generated/valibot/v-update-notification-channel-body.d.ts +4 -0
  26. package/dist/generated/valibot/v-update-notification-channel-request.d.ts +4 -0
  27. package/dist/generated/valibot/v-update-notification-channel-request.js +2 -0
  28. package/dist/generated/valibot/v-update-notification-channel-response.d.ts +4 -0
  29. package/dist/generated/valibot/v-update-pipeline-google-play-publish-response.d.ts +8 -0
  30. package/dist/msw-scenarios.d.ts +12 -1
  31. package/dist/msw-scenarios.js +345 -1
  32. package/dist/valibot.d.ts +1 -0
  33. package/dist/valibot.js +1 -0
  34. package/openapi/openapi.json +58 -0
  35. package/package.json +5 -2
@@ -0,0 +1,438 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { createOoreClient, isOoreApiError } from '../client.js';
3
+ import { createArtifactInstallLink, createBuild, createStreamToken, getBuild, getBuildLogs, getMe, getPipeline, listBuildArtifacts, listBuilds, listPipelines, listProjects, } from '../generated/sdk.gen.js';
4
+ import { streamBuildLogEvents } from '../logs.js';
5
+ import { configPath, readConfig, resolveInstance, writeConfig, } from './config.js';
6
+ import { encodeQr, renderQr } from './qr.js';
7
+ export const CLI_NAME = 'oore-dev';
8
+ const USAGE = `Usage: ${CLI_NAME} <command> [options]
9
+
10
+ Commands:
11
+ run <pipeline> Queue a build (pipeline ID or name)
12
+ --branch <name> Branch to build (default: the project's default)
13
+ --platform <name> android, ios or macos; repeat or comma-separate
14
+ --project <id|name> Narrow pipeline lookup to one project
15
+ -f, --follow Follow the build's logs; exit non-zero if it fails
16
+ logs <build> Print a build's logs
17
+ -f, --follow Keep streaming until the build finishes
18
+ install <build> Print a device install link and its QR code
19
+ --platform <name> android or ios, when the build has both
20
+ --artifact <id> A specific artifact
21
+ --no-qr Print the link only
22
+ status Show the instance, your account and recent builds
23
+ --project <id|name> Only this project's builds
24
+ --limit <n> Number of builds (default 10)
25
+ login Save an instance to the config file
26
+ --url <url> Daemon URL (required)
27
+ --token <token> API token; else OORE_TOKEN or piped stdin
28
+ --name <name> Instance name (default: "default")
29
+ --project <id|name> Default project for this instance
30
+
31
+ Global options:
32
+ --instance <name> Instance from the config file (env: OORE_INSTANCE)
33
+ --url, --token Override the instance (env: OORE_URL, OORE_TOKEN)
34
+ -h, --help Show this help
35
+
36
+ Config file: $OORE_DEV_CONFIG, else $XDG_CONFIG_HOME/oore-dev/config.json,
37
+ else ~/.config/oore-dev/config.json. Create an API token in the web app
38
+ under Settings → API tokens.`;
39
+ /** A failure with a message meant for the terminal, not a stack trace. */
40
+ export class CliError extends Error {
41
+ exitCode;
42
+ constructor(message, exitCode = 1) {
43
+ super(message);
44
+ this.exitCode = exitCode;
45
+ this.name = 'CliError';
46
+ }
47
+ }
48
+ const TERMINAL_STATUSES = [
49
+ 'succeeded',
50
+ 'failed',
51
+ 'canceled',
52
+ 'timed_out',
53
+ 'expired',
54
+ ];
55
+ const isFinished = (status) => TERMINAL_STATUSES.includes(status);
56
+ const PLATFORMS = ['android', 'ios', 'macos'];
57
+ /** Runs the CLI and resolves to the process exit code. */
58
+ export async function runCli(argv, io) {
59
+ try {
60
+ return await dispatch(argv, io);
61
+ }
62
+ catch (error) {
63
+ if (error instanceof CliError) {
64
+ io.stderr(`${CLI_NAME}: ${error.message}\n`);
65
+ return error.exitCode;
66
+ }
67
+ if (isOoreApiError(error)) {
68
+ const hint = error.status === 401
69
+ ? ' (is the token valid? run `oore-dev login` again)'
70
+ : '';
71
+ io.stderr(`${CLI_NAME}: ${error.message} [HTTP ${error.status}]${hint}\n`);
72
+ return 1;
73
+ }
74
+ let message = error instanceof Error ? error.message : String(error);
75
+ // Node's fetch reports "fetch failed" and keeps the reason in `cause`.
76
+ const cause = error instanceof Error ? error.cause : undefined;
77
+ if (cause instanceof Error)
78
+ message += `: ${cause.message}`;
79
+ io.stderr(`${CLI_NAME}: ${message}\n`);
80
+ return 1;
81
+ }
82
+ }
83
+ async function dispatch(argv, io) {
84
+ const { positionals, values } = parseArgs({
85
+ allowPositionals: true,
86
+ args: [...argv],
87
+ options: {
88
+ artifact: { type: 'string' },
89
+ branch: { type: 'string' },
90
+ follow: { short: 'f', type: 'boolean' },
91
+ help: { short: 'h', type: 'boolean' },
92
+ instance: { type: 'string' },
93
+ limit: { type: 'string' },
94
+ name: { type: 'string' },
95
+ 'no-qr': { type: 'boolean' },
96
+ platform: { multiple: true, type: 'string' },
97
+ project: { type: 'string' },
98
+ token: { type: 'string' },
99
+ url: { type: 'string' },
100
+ },
101
+ strict: true,
102
+ });
103
+ const [command, target, ...extra] = positionals;
104
+ if (values.help || !command || command === 'help') {
105
+ io.stdout(`${USAGE}\n`);
106
+ return command || values.help ? 0 : 2;
107
+ }
108
+ if (extra.length > 0)
109
+ throw new CliError(`Unexpected argument: ${extra[0]}`, 2);
110
+ if (command === 'login') {
111
+ return login(io, {
112
+ name: values.name,
113
+ project: values.project,
114
+ token: values.token,
115
+ url: values.url,
116
+ });
117
+ }
118
+ const config = await readConfig(configPath(io.env));
119
+ const instance = resolveInstance(config, values, io.env);
120
+ const ctx = {
121
+ client: createOoreClient({
122
+ baseUrl: instance.url,
123
+ fetch: io.fetch,
124
+ token: instance.token,
125
+ }),
126
+ io,
127
+ project: values.project ?? instance.project,
128
+ url: instance.url,
129
+ };
130
+ switch (command) {
131
+ case 'run':
132
+ return run(ctx, requireTarget(target, 'run <pipeline>'), {
133
+ branch: values.branch,
134
+ follow: values.follow ?? false,
135
+ platforms: parsePlatforms(values.platform),
136
+ });
137
+ case 'logs':
138
+ return logs(ctx, requireTarget(target, 'logs <build>'), values.follow ?? false);
139
+ case 'install':
140
+ return install(ctx, requireTarget(target, 'install <build>'), {
141
+ artifact: values.artifact,
142
+ platform: values.platform?.[0],
143
+ qr: !values['no-qr'],
144
+ });
145
+ case 'status':
146
+ return status(ctx, parseLimit(values.limit));
147
+ default:
148
+ throw new CliError(`Unknown command "${command}". See \`${CLI_NAME} --help\`.`, 2);
149
+ }
150
+ }
151
+ const requireTarget = (value, usage) => {
152
+ if (!value)
153
+ throw new CliError(`Usage: ${CLI_NAME} ${usage}`, 2);
154
+ return value;
155
+ };
156
+ const parsePlatforms = (values) => {
157
+ if (!values)
158
+ return undefined;
159
+ const platforms = values.flatMap((value) => value.split(',')).map((p) => p.trim());
160
+ for (const platform of platforms) {
161
+ if (!PLATFORMS.includes(platform)) {
162
+ throw new CliError(`Unknown platform "${platform}". Use ${PLATFORMS.join(', ')}.`, 2);
163
+ }
164
+ }
165
+ return [...new Set(platforms)];
166
+ };
167
+ const parseLimit = (value) => {
168
+ if (value === undefined)
169
+ return 10;
170
+ const limit = Number(value);
171
+ if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
172
+ throw new CliError('--limit must be a whole number from 1 to 200', 2);
173
+ }
174
+ return limit;
175
+ };
176
+ // ── login ───────────────────────────────────────────────────────
177
+ async function login(io, options) {
178
+ if (!options.url)
179
+ throw new CliError('Usage: oore-dev login --url <daemon url> [--token <token>]', 2);
180
+ const url = options.url.replace(/\/+$/, '');
181
+ let token = options.token ?? io.env.OORE_TOKEN;
182
+ if (!token && io.readStdin)
183
+ token = (await io.readStdin()).trim();
184
+ if (!token) {
185
+ throw new CliError('Pass an API token with --token, OORE_TOKEN or stdin. Create one in the web app under Settings → API tokens.', 2);
186
+ }
187
+ const client = createOoreClient({ baseUrl: url, fetch: io.fetch, token });
188
+ const { user } = await getMe({ client });
189
+ const path = configPath(io.env);
190
+ const config = await readConfig(path);
191
+ const name = options.name ?? 'default';
192
+ config.instances[name] = {
193
+ ...(options.project ? { project: options.project } : {}),
194
+ token,
195
+ url,
196
+ };
197
+ config.default ??= name;
198
+ await writeConfig(path, config);
199
+ io.stdout(`Signed in to ${url} as ${user.email} (${user.role}). Saved "${name}" to ${path}\n`);
200
+ return 0;
201
+ }
202
+ // ── lookups ─────────────────────────────────────────────────────
203
+ const notFound = (error) => isOoreApiError(error) && (error.status === 404 || error.status === 400);
204
+ async function resolveProject(ctx, value) {
205
+ const { projects } = await listProjects({ client: ctx.client, query: { limit: 200 } });
206
+ const lower = value.toLowerCase();
207
+ const match = projects.find((project) => project.id === value) ??
208
+ projects.find((project) => project.name.toLowerCase() === lower);
209
+ if (!match)
210
+ throw new CliError(`No project "${value}" is visible to this token`);
211
+ return match;
212
+ }
213
+ async function resolvePipeline(ctx, value) {
214
+ const lower = value.toLowerCase();
215
+ const exact = (pipelines) => pipelines.filter((pipeline) => pipeline.id === value || pipeline.name.toLowerCase() === lower);
216
+ if (ctx.project) {
217
+ const project = await resolveProject(ctx, ctx.project);
218
+ const { pipelines } = await listPipelines({
219
+ client: ctx.client,
220
+ path: { project_id: project.id },
221
+ query: { limit: 200 },
222
+ });
223
+ const [match] = exact(pipelines);
224
+ if (!match)
225
+ throw new CliError(`No pipeline "${value}" in project ${project.name}`);
226
+ return match;
227
+ }
228
+ try {
229
+ return (await getPipeline({ client: ctx.client, path: { pipeline_id: value } })).pipeline;
230
+ }
231
+ catch (error) {
232
+ if (!notFound(error))
233
+ throw error;
234
+ }
235
+ const { projects } = await listProjects({ client: ctx.client, query: { limit: 200 } });
236
+ const matches = [];
237
+ for (const project of projects) {
238
+ const { pipelines } = await listPipelines({
239
+ client: ctx.client,
240
+ path: { project_id: project.id },
241
+ query: { limit: 200, search: value },
242
+ });
243
+ for (const pipeline of exact(pipelines))
244
+ matches.push({ pipeline, project });
245
+ }
246
+ if (matches.length === 0)
247
+ throw new CliError(`No pipeline "${value}" is visible to this token`);
248
+ if (matches.length > 1) {
249
+ const list = matches.map(({ pipeline, project }) => ` ${project.name}/${pipeline.name} ${pipeline.id}`);
250
+ throw new CliError(`"${value}" matches several pipelines; pass --project or a pipeline ID:\n${list.join('\n')}`);
251
+ }
252
+ return matches[0].pipeline;
253
+ }
254
+ // ── run ─────────────────────────────────────────────────────────
255
+ async function run(ctx, pipelineRef, options) {
256
+ const pipeline = await resolvePipeline(ctx, pipelineRef);
257
+ const { build } = await createBuild({
258
+ body: {
259
+ branch: options.branch ?? null,
260
+ pipeline_id: pipeline.id,
261
+ platforms: options.platforms ?? null,
262
+ },
263
+ client: ctx.client,
264
+ path: { project_id: pipeline.project_id },
265
+ });
266
+ const on = build.branch ? ` on ${build.branch}` : '';
267
+ const platforms = options.platforms ? ` for ${options.platforms.join(', ')}` : '';
268
+ ctx.io.stdout(`Queued build #${build.build_number} of ${pipeline.name}${on}${platforms} (${build.id})\n`);
269
+ if (!options.follow) {
270
+ ctx.io.stdout(`Follow it with: ${CLI_NAME} logs -f ${build.id}\n`);
271
+ return 0;
272
+ }
273
+ return follow(ctx, build.id);
274
+ }
275
+ // ── logs ────────────────────────────────────────────────────────
276
+ async function logs(ctx, buildId, followLogs) {
277
+ if (followLogs)
278
+ return follow(ctx, buildId);
279
+ await printPendingLogs(ctx, buildId, -1);
280
+ return 0;
281
+ }
282
+ const writeChunk = (io, chunk) => {
283
+ const text = chunk.content.endsWith('\n') ? chunk.content : `${chunk.content}\n`;
284
+ if (chunk.stream === 'stderr')
285
+ io.stderr(text);
286
+ else
287
+ io.stdout(text);
288
+ };
289
+ const LOG_PAGE_SIZE = 500;
290
+ /** Prints every stored chunk after `after` and returns the last sequence. */
291
+ async function printPendingLogs(ctx, buildId, after) {
292
+ for (;;) {
293
+ const { logs } = await getBuildLogs({
294
+ client: ctx.client,
295
+ path: { build_id: buildId },
296
+ query: { after_sequence: after, limit: LOG_PAGE_SIZE },
297
+ });
298
+ for (const chunk of logs) {
299
+ if (chunk.sequence > after) {
300
+ writeChunk(ctx.io, chunk);
301
+ after = chunk.sequence;
302
+ }
303
+ }
304
+ if (logs.length < LOG_PAGE_SIZE)
305
+ return after;
306
+ }
307
+ }
308
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
309
+ /**
310
+ * Prints the stored logs, then streams new ones until the build finishes.
311
+ * The stream uses a one-use stream token, which works for API tokens as well
312
+ * as session tokens; if it drops, the CLI falls back to polling.
313
+ */
314
+ async function follow(ctx, buildId) {
315
+ const { client, io } = ctx;
316
+ let after = await printPendingLogs(ctx, buildId, -1);
317
+ let build = (await getBuild({ client, path: { build_id: buildId } })).build;
318
+ if (!isFinished(build.status)) {
319
+ try {
320
+ const { token } = await createStreamToken({ client, path: { build_id: buildId } });
321
+ for await (const event of streamBuildLogEvents({
322
+ client,
323
+ path: { build_id: buildId },
324
+ query: { token },
325
+ signal: io.signal,
326
+ sseMaxRetryAttempts: 1,
327
+ })) {
328
+ if (event.type === 'log' && event.chunk.sequence > after) {
329
+ writeChunk(io, event.chunk);
330
+ after = event.chunk.sequence;
331
+ }
332
+ else if (event.type === 'done') {
333
+ break;
334
+ }
335
+ }
336
+ }
337
+ catch {
338
+ // Polling below covers a stream that cannot open or drops.
339
+ }
340
+ const sleep = io.sleep ?? defaultSleep;
341
+ for (;;) {
342
+ if (io.signal?.aborted)
343
+ return 130;
344
+ after = await printPendingLogs(ctx, buildId, after);
345
+ build = (await getBuild({ client, path: { build_id: buildId } })).build;
346
+ if (isFinished(build.status))
347
+ break;
348
+ await sleep(io.pollIntervalMs ?? 2_000);
349
+ }
350
+ after = await printPendingLogs(ctx, buildId, after);
351
+ }
352
+ io.stderr(`Build #${build.build_number} ${build.status.replace('_', ' ')}\n`);
353
+ return build.status === 'succeeded' ? 0 : 1;
354
+ }
355
+ // ── install ─────────────────────────────────────────────────────
356
+ const INSTALLABLE = { apk: 'android', ipa: 'ios' };
357
+ async function install(ctx, buildId, options) {
358
+ if (options.platform && options.platform !== 'android' && options.platform !== 'ios') {
359
+ throw new CliError('--platform must be android or ios for install', 2);
360
+ }
361
+ const { artifacts } = await listBuildArtifacts({
362
+ body: { build_ids: [buildId] },
363
+ client: ctx.client,
364
+ });
365
+ const installable = artifacts.filter((artifact) => artifact.state === 'available' &&
366
+ INSTALLABLE[artifact.artifact_type] !== undefined &&
367
+ (!options.artifact || artifact.id === options.artifact) &&
368
+ (!options.platform || INSTALLABLE[artifact.artifact_type] === options.platform));
369
+ if (installable.length === 0) {
370
+ throw new CliError(options.artifact
371
+ ? `Artifact ${options.artifact} is not an installable APK or IPA of build ${buildId}`
372
+ : `Build ${buildId} has no installable APK or IPA${options.platform ? ` for ${options.platform}` : ''}`);
373
+ }
374
+ if (installable.length > 1) {
375
+ const list = installable.map((artifact) => ` ${artifact.id} ${artifact.artifact_type} ${artifact.name}`);
376
+ throw new CliError(`Build ${buildId} has several installable artifacts; pass --platform or --artifact:\n${list.join('\n')}`);
377
+ }
378
+ const artifact = installable[0];
379
+ const link = await createArtifactInstallLink({
380
+ client: ctx.client,
381
+ path: { artifact_id: artifact.id },
382
+ });
383
+ const expires = new Date(link.expires_at * 1000).toLocaleTimeString();
384
+ ctx.io.stdout(`${artifact.name} (${link.platform}), link expires at ${expires}\n`);
385
+ if (options.qr) {
386
+ const color = ctx.io.isTTY === true && !ctx.io.env.NO_COLOR;
387
+ ctx.io.stdout(`${renderQr(encodeQr(link.install_url), { color })}\n`);
388
+ }
389
+ ctx.io.stdout(`${link.install_url}\n`);
390
+ return 0;
391
+ }
392
+ // ── status ──────────────────────────────────────────────────────
393
+ const age = (seconds, now) => {
394
+ const delta = Math.max(0, now - seconds);
395
+ if (delta < 60)
396
+ return `${delta}s ago`;
397
+ if (delta < 3600)
398
+ return `${Math.floor(delta / 60)}m ago`;
399
+ if (delta < 86_400)
400
+ return `${Math.floor(delta / 3600)}h ago`;
401
+ return `${Math.floor(delta / 86_400)}d ago`;
402
+ };
403
+ async function status(ctx, limit) {
404
+ const { client, io } = ctx;
405
+ const { user } = await getMe({ client });
406
+ const project = ctx.project ? await resolveProject(ctx, ctx.project) : undefined;
407
+ const { builds, total } = await listBuilds({
408
+ client,
409
+ query: {
410
+ direction: 'desc',
411
+ limit,
412
+ sort: 'created_at',
413
+ ...(project ? { project_id: project.id } : {}),
414
+ },
415
+ });
416
+ io.stdout(`${ctx.url} ${user.email} (${user.role})\n`);
417
+ if (project)
418
+ io.stdout(`Project: ${project.name}\n`);
419
+ if (builds.length === 0) {
420
+ io.stdout('No builds yet.\n');
421
+ return 0;
422
+ }
423
+ const now = Math.floor(Date.now() / 1000);
424
+ const rows = builds.map((build) => [
425
+ `#${build.build_number}`,
426
+ build.status.replace('_', ' '),
427
+ build.branch ?? '-',
428
+ age(build.created_at, now),
429
+ build.id,
430
+ ]);
431
+ const widths = rows[0].map((_, i) => Math.max(...rows.map((row) => row[i].length)));
432
+ for (const row of rows) {
433
+ io.stdout(`${row.map((cell, i) => cell.padEnd(widths[i])).join(' ').trimEnd()}\n`);
434
+ }
435
+ if (total > builds.length)
436
+ io.stdout(`Showing ${builds.length} of ${total} builds.\n`);
437
+ return 0;
438
+ }
@@ -592,6 +592,10 @@ export type CreateNotificationChannelRequest = {
592
592
  * `users:read.email`, used to mention the triggering user. Optional.
593
593
  */
594
594
  secret?: string | null;
595
+ /**
596
+ * Slack member IDs to mention (Slack only). Works without a bot token.
597
+ */
598
+ slack_mentions?: Array<SlackMention>;
595
599
  smtp_config?: null | SmtpConfig;
596
600
  /**
597
601
  * Incoming webhook URL (required for webhook, mattermost and slack).
@@ -847,6 +851,10 @@ export type GooglePlayCredentialsTestResponse = {
847
851
  * The key's `client_email`, when the secret is a service account key.
848
852
  */
849
853
  service_account_email?: string | null;
854
+ /**
855
+ * The secret that was checked.
856
+ */
857
+ service_account_secret: string;
850
858
  };
851
859
  /**
852
860
  * Per-pipeline Google Play upload settings. Credentials are referenced by
@@ -1157,6 +1165,12 @@ export type NotificationChannel = {
1157
1165
  * offline.
1158
1166
  */
1159
1167
  project_id?: string | null;
1168
+ /**
1169
+ * Slack member IDs to mention, matched against the person behind each
1170
+ * event (Slack only). Checked before the bot token lookup. Absent from
1171
+ * daemons that predate it, which is the same as none.
1172
+ */
1173
+ slack_mentions?: Array<SlackMention>;
1160
1174
  smtp_summary?: null | SmtpConfigSummary;
1161
1175
  updated_at: number;
1162
1176
  /**
@@ -1295,6 +1309,7 @@ export type PipelineExecutionConfig = {
1295
1309
  };
1296
1310
  export type PipelineGooglePlayPublishResponse = {
1297
1311
  config?: null | GooglePlayPublishConfig;
1312
+ last_credentials_check?: null | GooglePlayCredentialsTestResponse;
1298
1313
  pipeline_id: string;
1299
1314
  /**
1300
1315
  * Whether the referenced service account secret exists in a visible scope.
@@ -2012,6 +2027,20 @@ export type SetupTrustedProxyConfigureResponse = {
2012
2027
  setup_owner_email?: string | null;
2013
2028
  state: SetupState;
2014
2029
  };
2030
+ /**
2031
+ * Maps a person to the Slack member mentioned in their notifications.
2032
+ */
2033
+ export type SlackMention = {
2034
+ /**
2035
+ * Oore sign-in email (manual and API builds, releases) or Git username
2036
+ * (push and merge request builds). Matched without regard to case.
2037
+ */
2038
+ identity: string;
2039
+ /**
2040
+ * Slack member ID, such as `U0123ABCD`.
2041
+ */
2042
+ member_id: string;
2043
+ };
2015
2044
  export type SmtpConfig = {
2016
2045
  from_address: string;
2017
2046
  host: string;
@@ -2260,6 +2289,11 @@ export type UpdateNotificationChannelRequest = {
2260
2289
  * pass empty string to clear.
2261
2290
  */
2262
2291
  secret?: string | null;
2292
+ /**
2293
+ * New Slack member mappings (Slack only). Omit to keep existing; pass an
2294
+ * empty list to clear.
2295
+ */
2296
+ slack_mentions?: Array<SlackMention> | null;
2263
2297
  smtp_config?: null | UpdateSmtpConfig;
2264
2298
  /**
2265
2299
  * New URL. Omit to keep existing.
@@ -6,6 +6,10 @@ export declare const vCreateNotificationChannelBody: v.ObjectSchema<{
6
6
  readonly name: v.StringSchema<undefined>;
7
7
  readonly project_id: v.NullishSchema<v.StringSchema<undefined>, undefined>;
8
8
  readonly secret: v.NullishSchema<v.StringSchema<undefined>, undefined>;
9
+ readonly slack_mentions: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
10
+ readonly identity: v.StringSchema<undefined>;
11
+ readonly member_id: v.StringSchema<undefined>;
12
+ }, undefined>, undefined>, undefined>;
9
13
  readonly smtp_config: v.NullishSchema<v.ObjectSchema<{
10
14
  readonly from_address: v.StringSchema<undefined>;
11
15
  readonly host: v.StringSchema<undefined>;
@@ -6,6 +6,10 @@ export declare const vCreateNotificationChannelRequest: v.ObjectSchema<{
6
6
  readonly name: v.StringSchema<undefined>;
7
7
  readonly project_id: v.NullishSchema<v.StringSchema<undefined>, undefined>;
8
8
  readonly secret: v.NullishSchema<v.StringSchema<undefined>, undefined>;
9
+ readonly slack_mentions: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
10
+ readonly identity: v.StringSchema<undefined>;
11
+ readonly member_id: v.StringSchema<undefined>;
12
+ }, undefined>, undefined>, undefined>;
9
13
  readonly smtp_config: v.NullishSchema<v.ObjectSchema<{
10
14
  readonly from_address: v.StringSchema<undefined>;
11
15
  readonly host: v.StringSchema<undefined>;
@@ -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 { vSmtpConfig } from './v-smtp-config.js';
5
6
  export const vCreateNotificationChannelRequest = v.object({
6
7
  channel_type: vNotificationChannelType,
@@ -9,6 +10,7 @@ export const vCreateNotificationChannelRequest = v.object({
9
10
  name: v.string(),
10
11
  project_id: v.nullish(v.string()),
11
12
  secret: v.nullish(v.string()),
13
+ slack_mentions: v.optional(v.array(vSlackMention)),
12
14
  smtp_config: v.nullish(vSmtpConfig),
13
15
  url: v.nullish(v.string())
14
16
  });
@@ -12,6 +12,10 @@ export declare const vCreateNotificationChannelResponse: 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>;
@@ -12,6 +12,10 @@ export declare const vGetNotificationChannelResponse: 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 vGetPipelineGooglePlayPublishResponse: 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>;
@@ -5,4 +5,5 @@ export declare const vGooglePlayCredentialsTestResponse: 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>;
@@ -9,5 +9,6 @@ export const vGooglePlayCredentialsTestResponse = v.object({
9
9
  message: v.string(),
10
10
  ok: v.boolean(),
11
11
  package_name: v.nullish(v.string()),
12
- service_account_email: v.nullish(v.string())
12
+ service_account_email: v.nullish(v.string()),
13
+ service_account_secret: v.string()
13
14
  });
@@ -12,6 +12,10 @@ export declare const vListNotificationChannelsResponse: 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>;
@@ -12,6 +12,10 @@ export declare const vListNotificationChannelsResponse2: 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>;
@@ -12,6 +12,10 @@ export declare const vNotificationChannelResponse: 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>;
@@ -11,6 +11,10 @@ export declare const vNotificationChannel: v.ObjectSchema<{
11
11
  readonly id: v.StringSchema<undefined>;
12
12
  readonly name: v.StringSchema<undefined>;
13
13
  readonly project_id: v.NullishSchema<v.StringSchema<undefined>, undefined>;
14
+ readonly slack_mentions: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
15
+ readonly identity: v.StringSchema<undefined>;
16
+ readonly member_id: v.StringSchema<undefined>;
17
+ }, undefined>, undefined>, undefined>;
14
18
  readonly smtp_summary: v.NullishSchema<v.ObjectSchema<{
15
19
  readonly from_address: v.StringSchema<undefined>;
16
20
  readonly host: v.StringSchema<undefined>;