From e003bd855a526793b35f656d1e73ec9817d121af Mon Sep 17 00:00:00 2001 From: William Phetsinorath Date: Tue, 14 Apr 2026 10:03:50 +0200 Subject: [PATCH 1/2] chore: add hook wrapper second retry logging Signed-off-by: William Phetsinorath --- apps/server/src/utils/hook-wrapper.ts | 56 +++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/apps/server/src/utils/hook-wrapper.ts b/apps/server/src/utils/hook-wrapper.ts index 8f68d3c857..0df3d89a4b 100644 --- a/apps/server/src/utils/hook-wrapper.ts +++ b/apps/server/src/utils/hook-wrapper.ts @@ -3,6 +3,7 @@ import type { AsyncReturnType } from '@cpn-console/shared' import type { Cluster, Kubeconfig, Project, ProjectMembers, ProjectRole, Zone } from '@prisma/client' import type { ConfigRecords } from '@/resources/project-service/business.js' import { hooks } from '@cpn-console/hooks' +import { logger as baseLogger } from '@cpn-console/logger' import { getPermsByUserRoles, ProjectAuthorized, resourceListToDict } from '@cpn-console/shared' import { dbToObj } from '@/resources/project-service/business.js' import { archiveProject, getAdminPlugin, getAdminRoleById, getClusterByIdOrThrow, getClusterNamesByZoneId, getClustersAssociatedWithProject, getHookProjectInfos, getHookRepository, getProjectStore, getRole, getZoneByIdOrThrow, saveProjectStore, updateProjectClusterHistory, updateProjectCreated, updateProjectFailed, updateProjectWarning } from '@/resources/queries-index.js' @@ -11,6 +12,30 @@ import { genericProxy } from './proxy.js' export type ReposCreds = Record export type ProjectInfos = AsyncReturnType +const logger = baseLogger.child({ scope: 'utils:hook-wrapper' }) + +function summarizeHookResultForLogs(hookResult: HookResult) { + const nonOkResults = Object.fromEntries( + Object.entries(hookResult.results ?? {}) + .filter(([_pluginName, result]) => result?.status?.result !== 'OK') + .map(([pluginName, result]) => [ + pluginName, + { + result: result.status.result, + message: result.status.result === 'OK' ? undefined : result.status.message, + }, + ]), + ) + + return { + failed: hookResult.failed, + warning: hookResult.warning, + messageResume: hookResult.messageResume, + totalExecutionTime: hookResult.totalExecutionTime, + nonOkResults, + } +} + async function getProjectPayload(projectId: Project['id'], reposCreds?: ReposCreds) { const [ project, @@ -52,9 +77,34 @@ async function upsertProject(projectId: Project['id'], reposCreds?: ReposCreds) } const project = { upsert: async (projectId: Project['id'], reposCreds?: ReposCreds) => { - const results = await upsertProject(projectId, reposCreds) - // automatically retry one time if it fails - return results.results.failed ? upsertProject(projectId, reposCreds) : results + const first = await upsertProject(projectId, reposCreds) + if (!first.results.failed) { + return first + } + + logger.warn({ + action: 'upsertProject', + projectId, + attempt: 1, + maxAttempts: 2, + ...summarizeHookResultForLogs(first.results), + }, 'Hook upsertProject failed, retrying once') + + const second = await upsertProject(projectId, reposCreds) + const logPayload = { + action: 'upsertProject', + projectId, + attempt: 2, + maxAttempts: 2, + ...summarizeHookResultForLogs(second.results), + } + if (second.results.failed) { + logger.error(logPayload, 'Hook upsertProject retry failed') + } else { + logger.info(logPayload, 'Hook upsertProject retry succeeded') + } + + return second }, delete: async (projectId: Project['id']) => { const [payload, config] = await Promise.all([ From 080ec29204c7380680ab3911f9b000b0ad005e75 Mon Sep 17 00:00:00 2001 From: William Phetsinorath Date: Tue, 14 Apr 2026 18:16:18 +0200 Subject: [PATCH 2/2] feat: refactor gitlab to alchemy Signed-off-by: William Phetsinorath --- apps/server/package.json | 2 +- apps/server/prisma.config.ts | 19 +- apps/server/src/prisma/schema/schema.prisma | 9 + apps/server/src/utils/hook-wrapper.spec.ts | 146 +- apps/server/src/utils/hook-wrapper.ts | 23 +- packages/miracle/eslint.config.js | 3 + packages/miracle/package.json | 50 + packages/miracle/src/gitlab/client.ts | 388 ++ packages/miracle/src/gitlab/commit.ts | 35 + packages/miracle/src/gitlab/ensure-files.ts | 60 + .../src/gitlab/group-custom-attribute.ts | 37 + packages/miracle/src/gitlab/group-member.ts | 47 + packages/miracle/src/gitlab/group.ts | 50 + packages/miracle/src/gitlab/http.ts | 3 + packages/miracle/src/gitlab/index.ts | 15 + packages/miracle/src/gitlab/mirror-creds.ts | 51 + .../src/gitlab/mirror-trigger-token.ts | 35 + packages/miracle/src/gitlab/pagination.ts | 42 + .../src/gitlab/project-custom-attribute.ts | 37 + packages/miracle/src/gitlab/project.ts | 41 + packages/miracle/src/gitlab/resources.spec.ts | 292 ++ packages/miracle/src/gitlab/types.ts | 112 + .../src/gitlab/user-custom-attribute.ts | 37 + packages/miracle/src/gitlab/user.ts | 54 + packages/miracle/src/index.ts | 6 + .../miracle/src/state/memory-state-store.ts | 63 + .../src/state/prisma-state-store.spec.ts | 83 + .../miracle/src/state/prisma-state-store.ts | 101 + packages/miracle/src/time/index.ts | 1 + packages/miracle/src/time/rotation.ts | 55 + packages/miracle/src/vault/index.ts | 7 + packages/miracle/src/vault/resources.spec.ts | 52 + packages/miracle/src/vault/resources.ts | 36 + packages/miracle/tsconfig.eslint.json | 16 + packages/miracle/tsconfig.json | 20 + plugins/gitlab/package.json | 12 +- plugins/gitlab/src/class.ts | 686 ---- plugins/gitlab/src/custom-attributes.ts | 22 - plugins/gitlab/src/functions.ts | 487 ++- plugins/gitlab/src/group.ts | 7 - plugins/gitlab/src/index.ts | 34 +- plugins/gitlab/src/members.ts | 32 +- plugins/gitlab/src/monitor.ts | 7 +- plugins/gitlab/src/project.ts | 52 - plugins/gitlab/src/repositories.ts | 93 - plugins/gitlab/src/user.ts | 88 +- plugins/gitlab/src/utils.ts | 200 +- pnpm-lock.yaml | 3392 ++++++++++++++++- pnpm-workspace.yaml | 1 + 49 files changed, 5718 insertions(+), 1423 deletions(-) create mode 100644 packages/miracle/eslint.config.js create mode 100644 packages/miracle/package.json create mode 100644 packages/miracle/src/gitlab/client.ts create mode 100644 packages/miracle/src/gitlab/commit.ts create mode 100644 packages/miracle/src/gitlab/ensure-files.ts create mode 100644 packages/miracle/src/gitlab/group-custom-attribute.ts create mode 100644 packages/miracle/src/gitlab/group-member.ts create mode 100644 packages/miracle/src/gitlab/group.ts create mode 100644 packages/miracle/src/gitlab/http.ts create mode 100644 packages/miracle/src/gitlab/index.ts create mode 100644 packages/miracle/src/gitlab/mirror-creds.ts create mode 100644 packages/miracle/src/gitlab/mirror-trigger-token.ts create mode 100644 packages/miracle/src/gitlab/pagination.ts create mode 100644 packages/miracle/src/gitlab/project-custom-attribute.ts create mode 100644 packages/miracle/src/gitlab/project.ts create mode 100644 packages/miracle/src/gitlab/resources.spec.ts create mode 100644 packages/miracle/src/gitlab/types.ts create mode 100644 packages/miracle/src/gitlab/user-custom-attribute.ts create mode 100644 packages/miracle/src/gitlab/user.ts create mode 100644 packages/miracle/src/index.ts create mode 100644 packages/miracle/src/state/memory-state-store.ts create mode 100644 packages/miracle/src/state/prisma-state-store.spec.ts create mode 100644 packages/miracle/src/state/prisma-state-store.ts create mode 100644 packages/miracle/src/time/index.ts create mode 100644 packages/miracle/src/time/rotation.ts create mode 100644 packages/miracle/src/vault/index.ts create mode 100644 packages/miracle/src/vault/resources.spec.ts create mode 100644 packages/miracle/src/vault/resources.ts create mode 100644 packages/miracle/tsconfig.eslint.json create mode 100644 packages/miracle/tsconfig.json delete mode 100644 plugins/gitlab/src/class.ts delete mode 100644 plugins/gitlab/src/group.ts delete mode 100644 plugins/gitlab/src/project.ts delete mode 100644 plugins/gitlab/src/repositories.ts diff --git a/apps/server/package.json b/apps/server/package.json index 54c792467c..973a0bb6b2 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -34,7 +34,7 @@ "prepublishOnly": "pnpm run db:generate", "prestart": "npm run db:deploy", "start": "node --enable-source-maps dist/bootstrap.js", - "pretest": "pnpm run db:generate", + "pretest": "pnpm --filter @cpn-console/miracle run build && pnpm run db:generate", "test": "vitest run", "pretest:cov": "pnpm run db:generate", "test:cov": "vitest run --coverage", diff --git a/apps/server/prisma.config.ts b/apps/server/prisma.config.ts index 34e0d644d9..a23f1bdae0 100644 --- a/apps/server/prisma.config.ts +++ b/apps/server/prisma.config.ts @@ -1,22 +1,19 @@ -import path from 'node:path' -import * as dotenv from 'dotenv' -import { defineConfig } from 'prisma/config' +import { defineConfig, env } from 'prisma/config' if (process.env.DOCKER !== 'true') { - dotenv.config({ path: '.env' }) + process.loadEnvFile('.env') } if (process.env.INTEGRATION === 'true') { - const envInteg = dotenv.config({ path: '.env.integ' }) - process.env = { - ...process.env, - ...(envInteg?.parsed ?? {}), - } + process.loadEnvFile('.env.integ') } export default defineConfig({ - schema: path.join('src', 'prisma', 'schema'), + schema: 'src/prisma/schema', migrations: { - path: path.join('src', 'prisma', 'migrations'), + path: 'src/prisma/migrations', + }, + datasource: { + url: env('DB_URL'), }, }) diff --git a/apps/server/src/prisma/schema/schema.prisma b/apps/server/src/prisma/schema/schema.prisma index aadf7fea13..760e745db2 100644 --- a/apps/server/src/prisma/schema/schema.prisma +++ b/apps/server/src/prisma/schema/schema.prisma @@ -19,3 +19,12 @@ model Log { project Project? @relation(fields: [projectId], references: [id]) user User? @relation(fields: [userId], references: [id]) } + +model AlchemyState { + scope String @db.Text + key String @db.Text + value Json + updatedAt DateTime @updatedAt + + @@id([scope, key]) +} diff --git a/apps/server/src/utils/hook-wrapper.spec.ts b/apps/server/src/utils/hook-wrapper.spec.ts index c2f4e3ebcc..af0ccc3188 100644 --- a/apps/server/src/utils/hook-wrapper.spec.ts +++ b/apps/server/src/utils/hook-wrapper.spec.ts @@ -222,18 +222,148 @@ describe('transformToHookProject', () => { }))) // Assert sur la transformation des environnements - expect(result.environments).toEqual(project.environments.map(({ permissions: _, stage, quota, ...environment }) => ({ - quota, - stage: stage.name, - permissions: [{ permissions: { rw: true, ro: true }, userId: project.ownerId }], - ...environment, - apis: {}, - }))) + expect(result.environments).toEqual(project.environments.map((env: any) => { + const { permissions: _permissions, stage, quota, ...environment } = env + return { + quota, + stage: stage.name, + permissions: [{ permissions: { rw: true, ro: true }, userId: project.ownerId }], + ...environment, + apis: {}, + } + })) // Assert sur la transformation des repositories - expect(result.repositories).toEqual(project.repositories.map(repo => ({ ...repo, newCreds: mockReposCreds[repo.internalRepoName] }))) + expect(result.repositories).toEqual(project.repositories.map((repo: any) => ({ ...repo, newCreds: mockReposCreds[repo.internalRepoName] }))) // Assert sur le store expect(result.store).toEqual(mockStore) }) }) + +describe('hook.project.upsert retry policy', () => { + it('does not retry when HOOK_UPSERT_PROJECT_MAX_ATTEMPTS=1', async () => { + const previous = process.env.HOOK_UPSERT_PROJECT_MAX_ATTEMPTS + process.env.HOOK_UPSERT_PROJECT_MAX_ATTEMPTS = '1' + + const { vi } = await import('vitest') + vi.resetModules() + vi.doMock('@cpn-console/hooks', () => ({ + hooks: { + upsertProject: { + execute: vi.fn().mockResolvedValue({ + args: {}, + results: {}, + failed: ['observability'], + warning: [], + totalExecutionTime: 1, + config: {}, + messageResume: 'Errors:\nobservability: failed;', + }), + }, + deleteProject: { execute: vi.fn() }, + getProjectSecrets: { execute: vi.fn() }, + upsertProjectMember: { execute: vi.fn() }, + deleteProjectMember: { execute: vi.fn() }, + upsertProjectRole: { execute: vi.fn() }, + deleteProjectRole: { execute: vi.fn() }, + upsertCluster: { execute: vi.fn() }, + deleteCluster: { execute: vi.fn() }, + upsertZone: { execute: vi.fn() }, + deleteZone: { execute: vi.fn() }, + checkServices: { execute: vi.fn() }, + syncRepository: { execute: vi.fn() }, + retrieveUserByEmail: { execute: vi.fn() }, + upsertAdminRole: { execute: vi.fn() }, + deleteAdminRole: { execute: vi.fn() }, + }, + })) + + vi.doMock('@/resources/project-service/business.js', () => ({ + dbToObj: (v: any) => v ?? {}, + })) + + vi.doMock('@/resources/queries-index.js', () => { + const projectId = 'p1' + return { + archiveProject: vi.fn(), + getAdminPlugin: vi.fn().mockResolvedValue({}), + getAdminRoleById: vi.fn(), + getClusterByIdOrThrow: vi.fn(), + getClusterNamesByZoneId: vi.fn(), + getClustersAssociatedWithProject: vi.fn().mockResolvedValue([]), + getHookProjectInfos: vi.fn().mockResolvedValue({ + id: projectId, + slug: 'fpdafpdfa', + name: 'infra-observability', + description: null, + status: 'initializing', + locked: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + everyonePerms: 0n, + ownerId: 'u1', + members: [], + environments: [ + { + id: 'e1', + name: 'dev', + projectId, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + clusterId: 'c1', + quotaId: 'q1', + stageId: 's1', + quota: { id: 'q1', memory: '1Gi', cpu: 1, name: 'small', isPrivate: false }, + stage: { id: 's1', name: 'dev' }, + cluster: { + id: 'c1', + infos: null, + label: 'cluster', + privacy: 'dedicated', + secretName: 'sn1', + kubeconfig: { id: 'k1', user: {}, cluster: {} }, + clusterResources: false, + zone: { id: 'z1', slug: 'default' }, + }, + }, + ], + repositories: [], + plugins: [], + owner: { + id: 'u1', + firstName: 'Jean', + lastName: 'Dupont', + email: 'jean@example.com', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + adminRoleIds: [], + }, + roles: [], + clusters: [], + }), + getHookRepository: vi.fn(), + getProjectStore: vi.fn().mockResolvedValue({}), + getRole: vi.fn(), + getZoneByIdOrThrow: vi.fn(), + saveProjectStore: vi.fn(), + updateProjectClusterHistory: vi.fn(), + updateProjectCreated: vi.fn().mockResolvedValue({ id: projectId }), + updateProjectFailed: vi.fn().mockResolvedValue({ id: projectId }), + updateProjectWarning: vi.fn().mockResolvedValue({ id: projectId }), + } + }) + + const { hook } = await import('./hook-wrapper.ts') + await hook.project.upsert('p1') + + const { hooks } = await import('@cpn-console/hooks') + expect((hooks.upsertProject.execute as any).mock.calls).toHaveLength(1) + + if (previous === undefined) { + delete process.env.HOOK_UPSERT_PROJECT_MAX_ATTEMPTS + } else { + process.env.HOOK_UPSERT_PROJECT_MAX_ATTEMPTS = previous + } + }) +}) diff --git a/apps/server/src/utils/hook-wrapper.ts b/apps/server/src/utils/hook-wrapper.ts index 0df3d89a4b..d084123568 100644 --- a/apps/server/src/utils/hook-wrapper.ts +++ b/apps/server/src/utils/hook-wrapper.ts @@ -75,18 +75,37 @@ async function upsertProject(projectId: Project['id'], reposCreds?: ReposCreds) project, } } + +function getUpsertProjectMaxAttempts() { + const raw = process.env.HOOK_UPSERT_PROJECT_MAX_ATTEMPTS + const parsed = raw === undefined ? 2 : Number(raw) + const safe = Number.isFinite(parsed) ? Math.trunc(parsed) : 2 + return Math.min(2, Math.max(1, safe)) +} const project = { upsert: async (projectId: Project['id'], reposCreds?: ReposCreds) => { + const maxAttempts = getUpsertProjectMaxAttempts() const first = await upsertProject(projectId, reposCreds) if (!first.results.failed) { return first } + if (maxAttempts <= 1) { + logger.warn({ + action: 'upsertProject', + projectId, + attempt: 1, + maxAttempts, + ...summarizeHookResultForLogs(first.results), + }, 'Hook upsertProject failed, no retry configured') + return first + } + logger.warn({ action: 'upsertProject', projectId, attempt: 1, - maxAttempts: 2, + maxAttempts, ...summarizeHookResultForLogs(first.results), }, 'Hook upsertProject failed, retrying once') @@ -95,7 +114,7 @@ const project = { action: 'upsertProject', projectId, attempt: 2, - maxAttempts: 2, + maxAttempts, ...summarizeHookResultForLogs(second.results), } if (second.results.failed) { diff --git a/packages/miracle/eslint.config.js b/packages/miracle/eslint.config.js new file mode 100644 index 0000000000..5a664d2b58 --- /dev/null +++ b/packages/miracle/eslint.config.js @@ -0,0 +1,3 @@ +import eslintConfigBase from '@cpn-console/eslint-config' + +export default eslintConfigBase diff --git a/packages/miracle/package.json b/packages/miracle/package.json new file mode 100644 index 0000000000..0cbdbc87a6 --- /dev/null +++ b/packages/miracle/package.json @@ -0,0 +1,50 @@ +{ + "name": "@cpn-console/miracle", + "type": "module", + "version": "0.1.0", + "private": false, + "description": "Alchemy-based wrappers for external service APIs", + "repository": { + "type": "git", + "url": "https://github.com/cloud-pi-native/console" + }, + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./dist/index.js" + } + }, + "main": "dist/index.js", + "module": "dist/index.js", + "types": "types/index.d.ts", + "files": [ + "dist", + "types" + ], + "scripts": { + "build": "tsc", + "build:clean": "node --input-type=module -e \"import { rm } from 'node:fs/promises'; await rm('./dist', { recursive: true, force: true }); await rm('./types', { recursive: true, force: true }); await rm('./tsconfig.tsbuildinfo', { force: true });\"", + "format": "eslint ./ --fix", + "lint": "eslint ./", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@gitbeaker/core": "^40.6.0", + "@gitbeaker/rest": "^40.6.0", + "@prisma/client": "6.19.2", + "alchemy": "^0.23.0" + }, + "devDependencies": { + "@cpn-console/eslint-config": "workspace:^", + "@cpn-console/ts-config": "workspace:^", + "@types/node": "^24.12.0", + "typescript": "^5.9.3", + "vitest": "^2.1.9" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public", + "tag": "latest" + } +} diff --git a/packages/miracle/src/gitlab/client.ts b/packages/miracle/src/gitlab/client.ts new file mode 100644 index 0000000000..1e39c739c3 --- /dev/null +++ b/packages/miracle/src/gitlab/client.ts @@ -0,0 +1,388 @@ +import type { AccessTokenScopes, CommitAction as GitbeakerCommitAction, Gitlab as GitbeakerGitlab } from '@gitbeaker/core' +import type { + BranchSchema, + CommitAction, + CondensedProjectSchema, + GitlabListResponse, + GroupAccessTokenCreateResponse, + GroupAccessTokenSchema, + GroupSchema, + MemberSchema, + PipelineSchema, + PipelineTriggerTokenSchema, + ProjectSchema, + RepositoryFileExpandedSchema, + SimpleUserSchema, + VariableSchema, + Visibility, +} from './types.js' +import { Gitlab } from '@gitbeaker/rest' +import { encodePathSegment } from './http.js' +import { parseOffsetPagination } from './pagination.js' + +const trailingSlashRegexp = /\/$/ + +export interface GitlabClientConfig { + host: string + token: string +} + +type GitlabQuery = Record + +export class GitlabClient { + public readonly host: string + private readonly token: string + private readonly api: GitbeakerGitlab + + constructor(config: GitlabClientConfig) { + this.host = config.host.replace(trailingSlashRegexp, '') + this.token = config.token + this.api = new Gitlab({ host: this.host, token: this.token, camelize: false }) as unknown as GitbeakerGitlab + } + + async groupsAll(query: GitlabQuery = {}, page?: number, perPage?: number): Promise> { + const res = await this.api.Groups.all({ + ...query, + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async groupsShow(groupId: number | string) { + return this.api.Groups.show(groupId) + } + + async groupsAllSubgroups(groupId: number, query: GitlabQuery = {}, page?: number, perPage?: number): Promise> { + const res = await this.api.Groups.allSubgroups(groupId, { + ...query, + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async groupsAllProjects(groupId: number, query: GitlabQuery = {}, page?: number, perPage?: number): Promise> { + const res = await this.api.Groups.allProjects(groupId, { + ...query, + page, + perPage, + pagination: 'offset', + showExpanded: true, + simple: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async groupsCreate(name: string, path: string, args: { parentId?: number, visibility?: Visibility, projectCreationLevel?: 'noone' | 'maintainer' | 'developer', subgroupCreationLevel?: string, defaultBranchProtection?: 0 | 1 | 2 | 3, description?: string } = {}): Promise { + return this.api.Groups.create(name, path, { + parentId: args.parentId, + visibility: args.visibility, + projectCreationLevel: args.projectCreationLevel, + subgroupCreationLevel: args.subgroupCreationLevel, + defaultBranchProtection: args.defaultBranchProtection, + description: args.description, + }) + } + + async groupsEdit(groupId: number, args: { name?: string, path?: string }) { + return this.api.Groups.edit(groupId, { + name: args.name, + path: args.path, + }) + } + + async groupsRemove(groupId: number, args: { permanentlyRemove?: boolean, fullPath?: string } = {}): Promise { + await this.api.Groups.remove(groupId, { + permanentlyRemove: args.permanentlyRemove, + fullPath: args.fullPath, + }) + } + + async groupCustomAttributesSet(groupId: number, key: string, value: string): Promise { + await this.api.GroupCustomAttributes.set(groupId, key, value) + } + + async projectCustomAttributesSet(projectId: number, key: string, value: string): Promise { + await this.api.ProjectCustomAttributes.set(projectId, key, value) + } + + async projectCustomAttributesDelete(projectId: number, key: string): Promise { + await this.api.ProjectCustomAttributes.remove(projectId, key) + } + + async userCustomAttributesSet(userId: number, key: string, value: string): Promise { + await this.api.UserCustomAttributes.set(userId, key, value) + } + + async projectsAll(query: GitlabQuery = {}, page?: number, perPage?: number): Promise> { + const res = await this.api.Projects.all({ + ...query, + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async projectsCreate(args: { name: string, path: string, namespaceId: number, description?: string, ciConfigPath?: string }): Promise { + return this.api.Projects.create({ + name: args.name, + path: args.path, + namespaceId: args.namespaceId, + description: args.description, + ciConfigPath: args.ciConfigPath, + }) + } + + async projectsEdit(projectId: number, args: { topics?: string[] }) { + return this.api.Projects.edit(projectId, { topics: args.topics }) + } + + async projectsShow(projectId: number) { + return this.api.Projects.show(projectId) + } + + async projectsRemove(projectId: number, args: { permanentlyRemove?: boolean, fullPath?: string } = {}): Promise { + await this.api.Projects.remove(projectId, { + permanentlyRemove: args.permanentlyRemove, + fullPath: args.fullPath, + }) + } + + async usersAll(query: GitlabQuery = {}, page?: number, perPage?: number): Promise> { + const res = await this.api.Users.all({ + ...query, + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async usersCreate(args: { + name: string + username: string + email: string + externUid?: string + provider?: string + admin?: boolean + auditor?: boolean + canCreateGroup?: boolean + forceRandomPassword?: boolean + password?: string + projectsLimit?: number + skipConfirmation?: boolean + }): Promise { + return this.api.Users.create({ + name: args.name, + username: args.username, + email: args.email, + externUid: args.externUid, + provider: args.provider, + admin: args.admin, + auditor: args.auditor, + canCreateGroup: args.canCreateGroup, + forceRandomPassword: args.forceRandomPassword, + password: args.password, + projectsLimit: args.projectsLimit, + skipConfirmation: args.skipConfirmation, + }) + } + + async usersEdit(userId: number, args: { + name?: string + username?: string + email?: string + externUid?: string + provider?: string + admin?: boolean + auditor?: boolean + canCreateGroup?: boolean + }) { + await this.api.Users.edit(userId, { + name: args.name, + username: args.username, + email: args.email, + externUid: args.externUid, + provider: args.provider, + admin: args.admin, + auditor: args.auditor, + canCreateGroup: args.canCreateGroup, + }) + } + + async usersRemove(userId: number, args: { hardDelete?: boolean } = {}) { + await this.api.Users.remove(userId, { hardDelete: args.hardDelete }) + } + + async groupMembersAll(groupId: number, page?: number, perPage?: number): Promise> { + const res = await this.api.GroupMembers.all(groupId, { + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async groupMembersAdd(groupId: number, userId: number, accessLevel: number) { + return this.api.GroupMembers.add(groupId, userId, accessLevel) + } + + async groupMembersEdit(groupId: number, userId: number, accessLevel: number) { + return this.api.GroupMembers.edit(groupId, userId, accessLevel) + } + + async groupMembersRemove(groupId: number, userId: number) { + await this.api.GroupMembers.remove(groupId, userId) + } + + async groupAccessTokensAll(groupId: number, page?: number, perPage?: number): Promise> { + const res = await this.api.GroupAccessTokens.all(groupId, { + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async groupAccessTokensCreate(groupId: number, name: string, scopes: string[], expiresAt: string) { + const res = await this.api.GroupAccessTokens.create(groupId, name, scopes as unknown as AccessTokenScopes[], expiresAt) + return res as unknown as GroupAccessTokenCreateResponse + } + + async groupAccessTokensRevoke(groupId: number, tokenId: number) { + await this.api.GroupAccessTokens.revoke(groupId, tokenId) + } + + async groupVariablesAll(groupId: number, page?: number, perPage?: number): Promise> { + const res = await this.api.GroupVariables.all(groupId, { + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async groupVariablesCreate(groupId: number, key: string, value: string, options: { variableType?: VariableSchema['variable_type'], masked?: boolean, protected?: boolean } = {}) { + await this.api.GroupVariables.create(groupId, key, value, { + variableType: options.variableType, + masked: options.masked, + protected: options.protected, + }) + } + + async groupVariablesEdit(groupId: number, key: string, value: string, options: { variableType?: VariableSchema['variable_type'], masked?: boolean, protected?: boolean, filter?: { environment_scope: string } } = {}) { + await this.api.GroupVariables.edit(groupId, key, value, { + variableType: options.variableType, + masked: options.masked, + protected: options.protected, + filter: options.filter ?? { environment_scope: '*' }, + }) + } + + async projectVariablesAll(projectId: number, page?: number, perPage?: number): Promise> { + const res = await this.api.ProjectVariables.all(projectId, { + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async projectVariablesCreate(projectId: number, key: string, value: string, options: { variableType?: VariableSchema['variable_type'], masked?: boolean, protected?: boolean, environmentScope?: string } = {}) { + await this.api.ProjectVariables.create(projectId, key, value, { + variableType: options.variableType, + masked: options.masked, + protected: options.protected, + environmentScope: options.environmentScope, + }) + } + + async projectVariablesEdit(projectId: number, key: string, value: string, options: { variableType?: VariableSchema['variable_type'], masked?: boolean, protected?: boolean, filter?: { environment_scope: string } } = {}) { + await this.api.ProjectVariables.edit(projectId, key, value, { + variableType: options.variableType, + masked: options.masked, + protected: options.protected, + filter: options.filter ?? { environment_scope: '*' }, + }) + } + + async branchesAll(projectId: number, page?: number, perPage?: number): Promise> { + const res = await this.api.Branches.all(projectId, { + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async repositoryFilesShow(projectId: number, filePath: string, ref: string): Promise { + return this.api.RepositoryFiles.show(projectId, encodePathSegment(filePath), ref) + } + + async repositoriesAllRepositoryTrees(projectId: number, query: { path?: string, ref?: string, recursive?: boolean } = {}, page?: number, perPage?: number): Promise> { + const url = new URL(`${this.host}/api/v4/projects/${projectId}/repository/tree`) + url.search = new URLSearchParams({ + ...Object.fromEntries(Object.entries(query).filter(([, v]) => v !== undefined).map(([k, v]) => [k, String(v)])), + ...(page ? { page: String(page) } : {}), + ...(perPage ? { per_page: String(perPage) } : {}), + }).toString() + + const res = await fetch(url, { headers: { 'PRIVATE-TOKEN': this.token } }) + const data = await res.json() as any[] + return { data, paginationInfo: parseOffsetPagination(res.headers) } + } + + async commitsCreate(projectId: number, branch: string, commitMessage: string, actions: CommitAction[]) { + const mappedActions: GitbeakerCommitAction[] = actions.map(action => ({ + action: action.action, + filePath: action.file_path, + previousPath: action.previous_path, + content: action.content, + encoding: action.encoding, + lastCommitId: action.last_commit_id, + execute_filemode: action.execute_filemode, + })) + + await this.api.Commits.create(projectId, branch, commitMessage, mappedActions) + } + + async pipelineTriggerTokensAll(projectId: number, page?: number, perPage?: number): Promise> { + const res = await this.api.PipelineTriggerTokens.all(projectId, { + page, + perPage, + pagination: 'offset', + showExpanded: true, + }) + return { data: res.data, paginationInfo: { next: res.paginationInfo.next } } + } + + async pipelineTriggerTokensCreate(projectId: number, description: string): Promise { + return this.api.PipelineTriggerTokens.create(projectId, description) + } + + async pipelineTriggerTokensRemove(projectId: number, triggerId: number) { + await this.api.PipelineTriggerTokens.remove(projectId, triggerId) + } + + async pipelinesCreate(projectId: number, ref: string, variables: { key: string, value: string }[]): Promise { + return this.api.Pipelines.create(projectId, ref, { variables }) + } + + async validateTokenForGroup(groupId: number, token: string) { + const api = new Gitlab({ host: this.host, token, camelize: false }) as unknown as GitbeakerGitlab + await api.Groups.show(groupId) + } +} diff --git a/packages/miracle/src/gitlab/commit.ts b/packages/miracle/src/gitlab/commit.ts new file mode 100644 index 0000000000..4cd59b325a --- /dev/null +++ b/packages/miracle/src/gitlab/commit.ts @@ -0,0 +1,35 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import type { CommitAction } from './types.js' +import { Resource } from 'alchemy' + +export interface GitlabCommitProps { + client: GitlabClient + projectId: number + branch: string + commitMessage: string + actions: CommitAction[] +} + +export interface GitlabCommitState { + projectId: number + branch: string + commitMessage: string + actionsCount: number +} + +export type GitlabCommitOutput = Resource<'gitlab:Commit'> & GitlabCommitState + +export const GitlabCommit = Resource('gitlab:Commit', async function ( + this: Context, + _id: string, + props: GitlabCommitProps, +) { + if (this.phase === 'create' || this.phase === 'update') { + await props.client.commitsCreate(props.projectId, props.branch, props.commitMessage, props.actions) + return this.create({ projectId: props.projectId, branch: props.branch, commitMessage: props.commitMessage, actionsCount: props.actions.length }) + } else if (this.phase === 'delete') { + return this.destroy() + } + throw new Error('Unexpected phase') +}) diff --git a/packages/miracle/src/gitlab/ensure-files.ts b/packages/miracle/src/gitlab/ensure-files.ts new file mode 100644 index 0000000000..a1afdd2d29 --- /dev/null +++ b/packages/miracle/src/gitlab/ensure-files.ts @@ -0,0 +1,60 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import type { CommitAction } from './types.js' +import { Resource } from 'alchemy' + +function getGitlabHttpStatus(error: unknown): number | undefined { + if (!(error instanceof Error)) return undefined + const cause = (error as Error & { cause?: unknown }).cause + if (!cause || typeof cause !== 'object') return undefined + const status = (cause as { response?: { status?: unknown } }).response?.status + return typeof status === 'number' ? status : undefined +} + +export interface GitlabEnsureFilesProps { + client: GitlabClient + projectId: number + branch: string + commitMessage: string + files: Array<{ path: string, content: string, executable: boolean }> +} + +export interface GitlabEnsureFilesState { + projectId: number + ensuredPaths: string[] +} + +export type GitlabEnsureFilesOutput = Resource<'gitlab:EnsureFiles'> & GitlabEnsureFilesState + +export const GitlabEnsureFiles = Resource('gitlab:EnsureFiles', async function ( + this: Context, + _id: string, + props: GitlabEnsureFilesProps, +) { + if (this.phase === 'delete') return this.destroy() + if (this.phase !== 'create' && this.phase !== 'update') throw new Error('Unexpected phase') + + const actions: CommitAction[] = [] + for (const file of props.files) { + try { + await props.client.repositoryFilesShow(props.projectId, file.path, props.branch) + } catch (err) { + if (getGitlabHttpStatus(err) === 404) { + actions.push({ + action: 'create', + file_path: file.path, + content: file.content, + execute_filemode: file.executable, + }) + } else { + throw err + } + } + } + + if (actions.length) { + await props.client.commitsCreate(props.projectId, props.branch, props.commitMessage, actions) + } + + return this.create({ projectId: props.projectId, ensuredPaths: props.files.map(f => f.path) }) +}) diff --git a/packages/miracle/src/gitlab/group-custom-attribute.ts b/packages/miracle/src/gitlab/group-custom-attribute.ts new file mode 100644 index 0000000000..cd4c908114 --- /dev/null +++ b/packages/miracle/src/gitlab/group-custom-attribute.ts @@ -0,0 +1,37 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import { Resource } from 'alchemy' + +export interface GitlabGroupCustomAttributeProps { + client: GitlabClient + groupId: number + key: string + value: string +} + +export interface GitlabGroupCustomAttributeState { + groupId: number + key: string + value: string +} + +export type GitlabGroupCustomAttributeOutput = Resource<'gitlab:GroupCustomAttribute'> & GitlabGroupCustomAttributeState + +export const GitlabGroupCustomAttribute = Resource('gitlab:GroupCustomAttribute', async function ( + this: Context, + _id: string, + props: GitlabGroupCustomAttributeProps, +) { + if (this.phase === 'create') { + await props.client.groupCustomAttributesSet(props.groupId, props.key, props.value) + return this.create({ groupId: props.groupId, key: props.key, value: props.value }) + } else if (this.phase === 'update') { + if (this.output.value !== props.value) { + await props.client.groupCustomAttributesSet(props.groupId, props.key, props.value) + } + return this.create({ groupId: props.groupId, key: props.key, value: props.value }) + } else if (this.phase === 'delete') { + return this.destroy() + } + throw new Error('Unexpected phase') +}) diff --git a/packages/miracle/src/gitlab/group-member.ts b/packages/miracle/src/gitlab/group-member.ts new file mode 100644 index 0000000000..76d9b18306 --- /dev/null +++ b/packages/miracle/src/gitlab/group-member.ts @@ -0,0 +1,47 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import type { MemberSchema } from './types.js' +import { Resource } from 'alchemy' + +export interface GitlabGroupMemberProps { + client: GitlabClient + groupId: number + userId: number + accessLevel: number +} + +export interface GitlabGroupMemberState { + groupId: number + userId: number + accessLevel: number +} + +export type GitlabGroupMemberOutput = Resource<'gitlab:GroupMember'> & GitlabGroupMemberState + +export const GitlabGroupMember = Resource('gitlab:GroupMember', async function ( + this: Context, + _id: string, + props: GitlabGroupMemberProps, +) { + if (this.phase === 'create') { + const members = await props.client.groupMembersAll(props.groupId) + const existing = members.data.find((m: MemberSchema) => m.id === props.userId) + if (existing) { + const existingAccessLevel = existing.access_level ?? existing.accessLevel + if (existingAccessLevel !== props.accessLevel) { + await props.client.groupMembersEdit(props.groupId, props.userId, props.accessLevel) + } + } else { + await props.client.groupMembersAdd(props.groupId, props.userId, props.accessLevel) + } + return this.create({ groupId: props.groupId, userId: props.userId, accessLevel: props.accessLevel }) + } else if (this.phase === 'update') { + if (this.output.accessLevel !== props.accessLevel) { + await props.client.groupMembersEdit(props.groupId, props.userId, props.accessLevel) + } + return this.create({ groupId: props.groupId, userId: props.userId, accessLevel: props.accessLevel }) + } else if (this.phase === 'delete') { + return this.destroy() + } + throw new Error('Unexpected phase') +}) diff --git a/packages/miracle/src/gitlab/group.ts b/packages/miracle/src/gitlab/group.ts new file mode 100644 index 0000000000..2c58c0c1c9 --- /dev/null +++ b/packages/miracle/src/gitlab/group.ts @@ -0,0 +1,50 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import type { GroupSchema, Visibility } from './types.js' +import { Resource } from 'alchemy' + +export interface GitlabGroupProps { + client: GitlabClient + name: string + path: string + parentId?: number + createArgs?: { + visibility?: Visibility + projectCreationLevel?: 'noone' | 'maintainer' | 'developer' + subgroupCreationLevel?: string + defaultBranchProtection?: 0 | 1 | 2 | 3 + description?: string + } +} + +export type GitlabGroupOutput = Resource<'gitlab:Group'> & GroupSchema + +export const GitlabGroup = Resource('gitlab:Group', async function ( + this: Context, + _id: string, + props: GitlabGroupProps, +) { + if (this.phase === 'create') { + const existing = await props.client.groupsShow(props.path).catch(() => undefined) + if (existing) return this.create(existing) + + const group = await props.client.groupsCreate(props.name, props.path, { + parentId: props.parentId, + visibility: props.createArgs?.visibility, + projectCreationLevel: props.createArgs?.projectCreationLevel, + subgroupCreationLevel: props.createArgs?.subgroupCreationLevel, + defaultBranchProtection: props.createArgs?.defaultBranchProtection, + description: props.createArgs?.description, + }) + return this.create(group) + } else if (this.phase === 'update') { + const group = await props.client.groupsEdit(this.output.id, { + name: props.name, + path: props.path, + }) + return this.create(group) + } else if (this.phase === 'delete') { + return this.destroy() + } + throw new Error('Unexpected phase') +}) diff --git a/packages/miracle/src/gitlab/http.ts b/packages/miracle/src/gitlab/http.ts new file mode 100644 index 0000000000..103efa2ac3 --- /dev/null +++ b/packages/miracle/src/gitlab/http.ts @@ -0,0 +1,3 @@ +export function encodePathSegment(path: string) { + return encodeURIComponent(path) +} diff --git a/packages/miracle/src/gitlab/index.ts b/packages/miracle/src/gitlab/index.ts new file mode 100644 index 0000000000..737639f21b --- /dev/null +++ b/packages/miracle/src/gitlab/index.ts @@ -0,0 +1,15 @@ +export * from './client.js' +export * from './commit.js' +export * from './ensure-files.js' +export * from './group-custom-attribute.js' +export * from './group-member.js' +export * from './group.js' +export * from './http.js' +export * from './mirror-creds.js' +export * from './mirror-trigger-token.js' +export * from './pagination.js' +export * from './project-custom-attribute.js' +export * from './project.js' +export * from './types.js' +export * from './user-custom-attribute.js' +export * from './user.js' diff --git a/packages/miracle/src/gitlab/mirror-creds.ts b/packages/miracle/src/gitlab/mirror-creds.ts new file mode 100644 index 0000000000..34e8d53038 --- /dev/null +++ b/packages/miracle/src/gitlab/mirror-creds.ts @@ -0,0 +1,51 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import type { GroupAccessTokenCreateResponse } from './types.js' +import { Resource } from 'alchemy' + +export interface GitlabMirrorCredsProps { + client: GitlabClient + groupId: number + tokenName: string + rotate?: boolean +} + +export interface GitlabMirrorCredsState { + MIRROR_USER: string + MIRROR_TOKEN: string + groupId: number +} + +export type GitlabMirrorCredsOutput = Resource<'gitlab:MirrorCreds'> & GitlabMirrorCredsState + +export const GitlabMirrorCreds = Resource('gitlab:MirrorCreds', async function ( + this: Context, + _id: string, + props: GitlabMirrorCredsProps, +) { + if (this.phase === 'delete') return this.destroy() + if (this.phase !== 'create' && this.phase !== 'update') throw new Error('Unexpected phase') + + const shouldReuseExisting = this.phase === 'update' && props.rotate !== true && this.output.groupId === props.groupId + if (shouldReuseExisting) { + try { + await props.client.validateTokenForGroup(props.groupId, this.output.MIRROR_TOKEN) + return this.create({ MIRROR_USER: this.output.MIRROR_USER, MIRROR_TOKEN: this.output.MIRROR_TOKEN, groupId: props.groupId }) + } catch { + void 0 + } + } + + const expiryDate = new Date() + expiryDate.setFullYear(expiryDate.getFullYear() + 1) + const expiresAt = expiryDate.toISOString().slice(0, 10) + + const newToken: GroupAccessTokenCreateResponse = await props.client.groupAccessTokensCreate( + props.groupId, + props.tokenName, + ['write_repository', 'read_repository', 'read_api'], + expiresAt, + ) + const nextCreds = { MIRROR_USER: newToken.name, MIRROR_TOKEN: newToken.token, groupId: props.groupId } + return this.create(nextCreds) +}) diff --git a/packages/miracle/src/gitlab/mirror-trigger-token.ts b/packages/miracle/src/gitlab/mirror-trigger-token.ts new file mode 100644 index 0000000000..9a5b54be66 --- /dev/null +++ b/packages/miracle/src/gitlab/mirror-trigger-token.ts @@ -0,0 +1,35 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import type { PipelineTriggerTokenSchema } from './types.js' +import { Resource } from 'alchemy' + +export interface GitlabMirrorTriggerTokenProps { + client: GitlabClient + repoId: number + description: string + rotate?: boolean +} + +export interface GitlabMirrorTriggerTokenState { + token: string + repoId: number +} + +export type GitlabMirrorTriggerTokenOutput = Resource<'gitlab:MirrorTriggerToken'> & GitlabMirrorTriggerTokenState + +export const GitlabMirrorTriggerToken = Resource('gitlab:MirrorTriggerToken', async function ( + this: Context, + _id: string, + props: GitlabMirrorTriggerTokenProps, +) { + if (this.phase === 'delete') return this.destroy() + if (this.phase !== 'create' && this.phase !== 'update') throw new Error('Unexpected phase') + + const shouldReuseExisting = this.phase === 'update' && props.rotate !== true && this.output.repoId === props.repoId + if (shouldReuseExisting) { + return this.create({ token: this.output.token, repoId: props.repoId }) + } + + const triggerToken: PipelineTriggerTokenSchema = await props.client.pipelineTriggerTokensCreate(props.repoId, props.description) + return this.create({ token: triggerToken.token, repoId: props.repoId }) +}) diff --git a/packages/miracle/src/gitlab/pagination.ts b/packages/miracle/src/gitlab/pagination.ts new file mode 100644 index 0000000000..e1d9a3b9fc --- /dev/null +++ b/packages/miracle/src/gitlab/pagination.ts @@ -0,0 +1,42 @@ +import type { GitlabListResponse, OffsetPagination } from './types.js' + +export function parseOffsetPagination(headers: Headers): OffsetPagination { + const nextPage = headers.get('x-next-page') + if (!nextPage) return { next: null } + const parsed = Number(nextPage) + return { next: Number.isFinite(parsed) && parsed > 0 ? parsed : null } +} + +export async function* offsetPaginate( + request: (options: { page: number, perPage?: number }) => Promise>, + options?: { startPage?: number, perPage?: number, maxPages?: number }, +): AsyncGenerator { + let page: number | null = options?.startPage ?? 1 + let pagesFetched = 0 + + while (page !== null) { + if (options?.maxPages && pagesFetched >= options.maxPages) break + const { data, paginationInfo } = await request({ page, perPage: options?.perPage }) + pagesFetched += 1 + for (const item of data) { + yield item + } + page = paginationInfo.next + } +} + +export async function getAll(iterable: AsyncIterable): Promise { + const items: T[] = [] + for await (const item of iterable) items.push(item) + return items +} + +export async function find( + iterable: AsyncIterable, + predicate: (item: T) => boolean, +): Promise { + for await (const item of iterable) { + if (predicate(item)) return item + } + return undefined +} diff --git a/packages/miracle/src/gitlab/project-custom-attribute.ts b/packages/miracle/src/gitlab/project-custom-attribute.ts new file mode 100644 index 0000000000..1e0300398b --- /dev/null +++ b/packages/miracle/src/gitlab/project-custom-attribute.ts @@ -0,0 +1,37 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import { Resource } from 'alchemy' + +export interface GitlabProjectCustomAttributeProps { + client: GitlabClient + projectId: number + key: string + value: string +} + +export interface GitlabProjectCustomAttributeState { + projectId: number + key: string + value: string +} + +export type GitlabProjectCustomAttributeOutput = Resource<'gitlab:ProjectCustomAttribute'> & GitlabProjectCustomAttributeState + +export const GitlabProjectCustomAttribute = Resource('gitlab:ProjectCustomAttribute', async function ( + this: Context, + _id: string, + props: GitlabProjectCustomAttributeProps, +) { + if (this.phase === 'create') { + await props.client.projectCustomAttributesSet(props.projectId, props.key, props.value) + return this.create({ projectId: props.projectId, key: props.key, value: props.value }) + } else if (this.phase === 'update') { + if (this.output.value !== props.value) { + await props.client.projectCustomAttributesSet(props.projectId, props.key, props.value) + } + return this.create({ projectId: props.projectId, key: props.key, value: props.value }) + } else if (this.phase === 'delete') { + return this.destroy() + } + throw new Error('Unexpected phase') +}) diff --git a/packages/miracle/src/gitlab/project.ts b/packages/miracle/src/gitlab/project.ts new file mode 100644 index 0000000000..8e8bb6383f --- /dev/null +++ b/packages/miracle/src/gitlab/project.ts @@ -0,0 +1,41 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import type { ProjectSchema } from './types.js' +import { Resource } from 'alchemy' + +export interface GitlabProjectProps { + client: GitlabClient + name: string + path: string + namespaceId: number + description?: string + ciConfigPath?: string +} + +export type GitlabProjectOutput = Resource<'gitlab:Project'> & ProjectSchema + +export const GitlabProject = Resource('gitlab:Project', async function ( + this: Context, + _id: string, + props: GitlabProjectProps, +) { + if (this.phase === 'create') { + const projects = await props.client.groupsAllProjects(props.namespaceId, { search: props.name }) + const existing = projects.data.find(p => p.path === props.path) + if (existing) return this.create(existing) + + const project = await props.client.projectsCreate({ + name: props.name, + path: props.path, + namespaceId: props.namespaceId, + description: props.description, + ciConfigPath: props.ciConfigPath, + }) + return this.create(project) + } else if (this.phase === 'update') { + return this.create(this.output) + } else if (this.phase === 'delete') { + return this.destroy() + } + throw new Error('Unexpected phase') +}) diff --git a/packages/miracle/src/gitlab/resources.spec.ts b/packages/miracle/src/gitlab/resources.spec.ts new file mode 100644 index 0000000000..c6e5a536af --- /dev/null +++ b/packages/miracle/src/gitlab/resources.spec.ts @@ -0,0 +1,292 @@ +import { PROVIDERS } from 'alchemy' +import { describe, expect, it, vi } from 'vitest' +import './index.js' + +function createCtx(phase: string, output?: Output) { + const ctx: any = { + phase, + output, + create: vi.fn((next: Output) => { + ctx.output = next + return {} + }), + destroy: vi.fn(() => ({})), + } + return ctx +} + +describe('gitlab resources', () => { + it('gitlabGroup create: does not create when group exists', async () => { + const provider = PROVIDERS.get('gitlab:Group') + if (!provider) throw new Error('Missing provider') + + const client = { + groupsShow: vi.fn().mockResolvedValue({ id: 1, name: 'n', path: 'p' }), + groupsCreate: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'group-x', { client, name: 'n', path: 'p', parentId: 99 }) + + expect(client.groupsCreate).not.toHaveBeenCalled() + expect(ctx.create).toHaveBeenCalledWith({ id: 1, name: 'n', path: 'p' }) + }) + + it('gitlabGroup create: creates when group does not exist', async () => { + const provider = PROVIDERS.get('gitlab:Group') + if (!provider) throw new Error('Missing provider') + + const client = { + groupsShow: vi.fn().mockRejectedValue(new Error('404')), + groupsCreate: vi.fn().mockResolvedValue({ id: 2, name: 'n', path: 'p' }), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'group-x', { client, name: 'n', path: 'p', parentId: 99 }) + + expect(client.groupsCreate).toHaveBeenCalledWith('n', 'p', expect.objectContaining({ parentId: 99 })) + expect(ctx.create).toHaveBeenCalledWith({ id: 2, name: 'n', path: 'p' }) + }) + + it('gitlabProject create: does not create when project exists in namespace', async () => { + const provider = PROVIDERS.get('gitlab:Project') + if (!provider) throw new Error('Missing provider') + + const client = { + groupsAllProjects: vi.fn().mockResolvedValue({ data: [{ id: 10, name: 'repo', path: 'repo' }] }), + projectsCreate: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'repo-x', { client, name: 'repo', path: 'repo', namespaceId: 1 }) + + expect(client.projectsCreate).not.toHaveBeenCalled() + expect(ctx.create).toHaveBeenCalledWith({ id: 10, name: 'repo', path: 'repo' }) + }) + + it('gitlabProject create: creates when project is missing in namespace', async () => { + const provider = PROVIDERS.get('gitlab:Project') + if (!provider) throw new Error('Missing provider') + + const client = { + groupsAllProjects: vi.fn().mockResolvedValue({ data: [] }), + projectsCreate: vi.fn().mockResolvedValue({ id: 11, name: 'repo', path: 'repo' }), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'repo-x', { client, name: 'repo', path: 'repo', namespaceId: 1, description: 'd', ciConfigPath: '.gitlab-ci.yml' }) + + expect(client.projectsCreate).toHaveBeenCalledWith({ + name: 'repo', + path: 'repo', + namespaceId: 1, + description: 'd', + ciConfigPath: '.gitlab-ci.yml', + }) + expect(ctx.create).toHaveBeenCalledWith({ id: 11, name: 'repo', path: 'repo' }) + }) + + it('gitlabGroupMember create: edits access level when member exists with different access', async () => { + const provider = PROVIDERS.get('gitlab:GroupMember') + if (!provider) throw new Error('Missing provider') + + const client = { + groupMembersAll: vi.fn().mockResolvedValue({ data: [{ id: 42, access_level: 10 }] }), + groupMembersEdit: vi.fn(), + groupMembersAdd: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'member-x', { client, groupId: 1, userId: 42, accessLevel: 30 }) + + expect(client.groupMembersEdit).toHaveBeenCalledWith(1, 42, 30) + expect(client.groupMembersAdd).not.toHaveBeenCalled() + expect(ctx.create).toHaveBeenCalledWith({ groupId: 1, userId: 42, accessLevel: 30 }) + }) + + it('gitlabGroupMember create: adds member when missing', async () => { + const provider = PROVIDERS.get('gitlab:GroupMember') + if (!provider) throw new Error('Missing provider') + + const client = { + groupMembersAll: vi.fn().mockResolvedValue({ data: [] }), + groupMembersEdit: vi.fn(), + groupMembersAdd: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'member-x', { client, groupId: 1, userId: 42, accessLevel: 30 }) + + expect(client.groupMembersAdd).toHaveBeenCalledWith(1, 42, 30) + expect(client.groupMembersEdit).not.toHaveBeenCalled() + expect(ctx.create).toHaveBeenCalledWith({ groupId: 1, userId: 42, accessLevel: 30 }) + }) + + it('gitlabUser create: reuses existing and edits flags when isAdmin/isAuditor provided', async () => { + const provider = PROVIDERS.get('gitlab:User') + if (!provider) throw new Error('Missing provider') + + const client = { + usersAll: vi.fn().mockResolvedValue({ data: [{ id: 7, username: 'u', email: 'e', name: 'n' }] }), + usersEdit: vi.fn(), + usersCreate: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'user-x', { client, email: 'e', username: 'u', name: 'n', isAdmin: true, isAuditor: false }) + + expect(client.usersCreate).not.toHaveBeenCalled() + expect(client.usersEdit).toHaveBeenCalledWith(7, { admin: true, auditor: false, canCreateGroup: true }) + expect(ctx.create).toHaveBeenCalledWith({ id: 7, username: 'u', email: 'e', name: 'n' }) + }) + + it('gitlabProjectCustomAttribute update: only sets when value changes', async () => { + const provider = PROVIDERS.get('gitlab:ProjectCustomAttribute') + if (!provider) throw new Error('Missing provider') + + const client = { + projectCustomAttributesSet: vi.fn(), + } + + const ctx = createCtx('update', { projectId: 1, key: 'k', value: 'old' }) + await provider.handler.call(ctx, 'attr-x', { client, projectId: 1, key: 'k', value: 'new' }) + + expect(client.projectCustomAttributesSet).toHaveBeenCalledWith(1, 'k', 'new') + expect(ctx.create).toHaveBeenCalledWith({ projectId: 1, key: 'k', value: 'new' }) + }) + + it('gitlabGroupCustomAttribute update: only sets when value changes', async () => { + const provider = PROVIDERS.get('gitlab:GroupCustomAttribute') + if (!provider) throw new Error('Missing provider') + + const client = { + groupCustomAttributesSet: vi.fn(), + } + + const ctx = createCtx('update', { groupId: 1, key: 'k', value: 'old' }) + await provider.handler.call(ctx, 'attr-x', { client, groupId: 1, key: 'k', value: 'new' }) + + expect(client.groupCustomAttributesSet).toHaveBeenCalledWith(1, 'k', 'new') + expect(ctx.create).toHaveBeenCalledWith({ groupId: 1, key: 'k', value: 'new' }) + }) + + it('gitlabUserCustomAttribute create: sets value', async () => { + const provider = PROVIDERS.get('gitlab:UserCustomAttribute') + if (!provider) throw new Error('Missing provider') + + const client = { + userCustomAttributesSet: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'attr-x', { client, userId: 7, key: 'k', value: 'v' }) + + expect(client.userCustomAttributesSet).toHaveBeenCalledWith(7, 'k', 'v') + expect(ctx.create).toHaveBeenCalledWith({ userId: 7, key: 'k', value: 'v' }) + }) + + it('gitlabCommit create: creates commit', async () => { + const provider = PROVIDERS.get('gitlab:Commit') + if (!provider) throw new Error('Missing provider') + + const client = { + commitsCreate: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'commit-x', { client, projectId: 1, branch: 'main', commitMessage: 'm', actions: [] }) + + expect(client.commitsCreate).toHaveBeenCalledWith(1, 'main', 'm', []) + expect(ctx.create).toHaveBeenCalledWith({ projectId: 1, branch: 'main', commitMessage: 'm', actionsCount: 0 }) + }) + + it('gitlabMirrorCreds create: creates token', async () => { + const provider = PROVIDERS.get('gitlab:MirrorCreds') + if (!provider) throw new Error('Missing provider') + + const client = { + validateTokenForGroup: vi.fn(), + groupAccessTokensCreate: vi.fn().mockResolvedValue({ name: 'bot', token: 'tok' }), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'mirror-creds-x', { client, groupId: 1, tokenName: 't' }) + + expect(client.groupAccessTokensCreate).toHaveBeenCalledWith(1, 't', ['write_repository', 'read_repository', 'read_api'], expect.any(String)) + expect(ctx.create).toHaveBeenCalledWith({ MIRROR_USER: 'bot', MIRROR_TOKEN: 'tok', groupId: 1 }) + }) + + it('gitlabMirrorCreds update: reuses when token is valid and not rotating', async () => { + const provider = PROVIDERS.get('gitlab:MirrorCreds') + if (!provider) throw new Error('Missing provider') + + const client = { + validateTokenForGroup: vi.fn().mockResolvedValue(undefined), + groupAccessTokensCreate: vi.fn(), + } + + const ctx = createCtx('update', { MIRROR_USER: 'bot', MIRROR_TOKEN: 'tok', groupId: 1 }) + await provider.handler.call(ctx, 'mirror-creds-x', { client, groupId: 1, tokenName: 't' }) + + expect(client.groupAccessTokensCreate).not.toHaveBeenCalled() + expect(ctx.create).toHaveBeenCalledWith({ MIRROR_USER: 'bot', MIRROR_TOKEN: 'tok', groupId: 1 }) + }) + + it('gitlabMirrorTriggerToken create: creates trigger token', async () => { + const provider = PROVIDERS.get('gitlab:MirrorTriggerToken') + if (!provider) throw new Error('Missing provider') + + const client = { + pipelineTriggerTokensCreate: vi.fn().mockResolvedValue({ token: 'tok' }), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'mirror-trigger-x', { client, repoId: 123, description: 'd' }) + + expect(ctx.create).toHaveBeenCalledWith({ token: 'tok', repoId: 123 }) + }) + + it('gitlabMirrorTriggerToken update: reuses when not rotating and repo matches', async () => { + const provider = PROVIDERS.get('gitlab:MirrorTriggerToken') + if (!provider) throw new Error('Missing provider') + + const client = { + pipelineTriggerTokensCreate: vi.fn(), + } + + const ctx = createCtx('update', { token: 'tok', repoId: 123 }) + await provider.handler.call(ctx, 'mirror-trigger-x', { client, repoId: 123, description: 'd' }) + + expect(client.pipelineTriggerTokensCreate).not.toHaveBeenCalled() + expect(ctx.create).toHaveBeenCalledWith({ token: 'tok', repoId: 123 }) + }) + + it('gitlabEnsureFiles create: creates missing files and commits once', async () => { + const provider = PROVIDERS.get('gitlab:EnsureFiles') + if (!provider) throw new Error('Missing provider') + + const client = { + repositoryFilesShow: vi.fn() + .mockResolvedValueOnce({}) + .mockRejectedValueOnce(Object.assign(new Error('not found'), { cause: { response: { status: 404 } } })), + commitsCreate: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'ensure-x', { + client, + projectId: 1, + branch: 'main', + commitMessage: 'm', + files: [ + { path: 'exists.txt', content: 'a', executable: false }, + { path: 'missing.txt', content: 'b', executable: true }, + ], + }) + + expect(client.commitsCreate).toHaveBeenCalledWith(1, 'main', 'm', [ + { action: 'create', file_path: 'missing.txt', content: 'b', execute_filemode: true }, + ]) + expect(ctx.create).toHaveBeenCalledWith({ projectId: 1, ensuredPaths: ['exists.txt', 'missing.txt'] }) + }) +}) diff --git a/packages/miracle/src/gitlab/types.ts b/packages/miracle/src/gitlab/types.ts new file mode 100644 index 0000000000..aa4e5e69ae --- /dev/null +++ b/packages/miracle/src/gitlab/types.ts @@ -0,0 +1,112 @@ +export enum AccessLevel { + NO_ACCESS = 0, + MINIMAL_ACCESS = 5, + GUEST = 10, + REPORTER = 20, + DEVELOPER = 30, + MAINTAINER = 40, + OWNER = 50, +} + +export type Visibility = 'private' | 'internal' | 'public' + +export interface OffsetPagination { + next: number | null +} + +export interface GitlabListResponse { + data: T[] + paginationInfo: OffsetPagination +} + +export interface GroupSchema { + id: number + name: string + path?: string + full_path?: string + fullPath?: string + parent_id?: number | null + parentId?: number | null +} + +export interface ProjectSchema { + id: number + name: string + path: string + path_with_namespace?: string + pathWithNamespace?: string + topics?: string[] | null +} + +export type CondensedProjectSchema = ProjectSchema + +export interface SimpleUserSchema { + id: number + name: string + username: string + email?: string + extern_uid?: string | null + externUid?: string | null + provider?: string | null + admin?: boolean + auditor?: boolean +} + +export interface MemberSchema { + id: number + username: string + access_level: number + accessLevel?: number +} + +export interface VariableSchema { + key: string + value: string + variable_type: 'env_var' | 'file' + variableType?: 'env_var' | 'file' + protected: boolean + masked: boolean + environment_scope?: string + environmentScope?: string +} + +export interface ProjectVariableSchema extends VariableSchema { + environment_scope: string +} + +export interface CommitAction { + action: 'create' | 'delete' | 'move' | 'update' | 'chmod' + file_path: string + previous_path?: string + content?: string + encoding?: string + last_commit_id?: string + execute_filemode?: boolean +} + +export interface BranchSchema { + name: string +} + +export interface RepositoryFileExpandedSchema { + content_sha256?: string +} + +export interface GroupAccessTokenSchema { + id: number + name: string +} + +export interface GroupAccessTokenCreateResponse extends GroupAccessTokenSchema { + token: string +} + +export interface PipelineTriggerTokenSchema { + id: number + description: string + token: string +} + +export interface PipelineSchema { + id: number +} diff --git a/packages/miracle/src/gitlab/user-custom-attribute.ts b/packages/miracle/src/gitlab/user-custom-attribute.ts new file mode 100644 index 0000000000..313ca4e945 --- /dev/null +++ b/packages/miracle/src/gitlab/user-custom-attribute.ts @@ -0,0 +1,37 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import { Resource } from 'alchemy' + +export interface GitlabUserCustomAttributeProps { + client: GitlabClient + userId: number + key: string + value: string +} + +export interface GitlabUserCustomAttributeState { + userId: number + key: string + value: string +} + +export type GitlabUserCustomAttributeOutput = Resource<'gitlab:UserCustomAttribute'> & GitlabUserCustomAttributeState + +export const GitlabUserCustomAttribute = Resource('gitlab:UserCustomAttribute', async function ( + this: Context, + _id: string, + props: GitlabUserCustomAttributeProps, +) { + if (this.phase === 'create') { + await props.client.userCustomAttributesSet(props.userId, props.key, props.value) + return this.create({ userId: props.userId, key: props.key, value: props.value }) + } else if (this.phase === 'update') { + if (this.output.value !== props.value) { + await props.client.userCustomAttributesSet(props.userId, props.key, props.value) + } + return this.create({ userId: props.userId, key: props.key, value: props.value }) + } else if (this.phase === 'delete') { + return this.destroy() + } + throw new Error('Unexpected phase') +}) diff --git a/packages/miracle/src/gitlab/user.ts b/packages/miracle/src/gitlab/user.ts new file mode 100644 index 0000000000..6554b71ad3 --- /dev/null +++ b/packages/miracle/src/gitlab/user.ts @@ -0,0 +1,54 @@ +import type { Context } from 'alchemy' +import type { GitlabClient } from './client.js' +import type { SimpleUserSchema } from './types.js' +import { Resource } from 'alchemy' + +export interface GitlabUserProps { + client: GitlabClient + email: string + username: string + name: string + externUid?: string + provider?: string + isAdmin?: boolean + isAuditor?: boolean +} + +export type GitlabUserOutput = Resource<'gitlab:User'> & SimpleUserSchema + +export const GitlabUser = Resource('gitlab:User', async function ( + this: Context, + _id: string, + props: GitlabUserProps, +) { + if (this.phase === 'create') { + const existing = await props.client.usersAll({ username: props.username }).then(res => res.data[0]) + if (existing) { + if (props.isAdmin !== undefined && props.isAuditor !== undefined) { + await props.client.usersEdit(existing.id, { admin: props.isAdmin, auditor: props.isAuditor, canCreateGroup: props.isAdmin }) + } + return this.create(existing) + } + const user = await props.client.usersCreate({ + email: props.email, + username: props.username, + name: props.name, + externUid: props.externUid, + provider: props.provider, + password: Math.random().toString(36).slice(-8), + skipConfirmation: true, + admin: props.isAdmin ?? false, + auditor: props.isAuditor ?? false, + canCreateGroup: props.isAdmin ?? false, + }) + return this.create(user) + } else if (this.phase === 'update') { + if (props.isAdmin !== undefined && props.isAuditor !== undefined) { + await props.client.usersEdit(this.output.id, { admin: props.isAdmin, auditor: props.isAuditor, canCreateGroup: props.isAdmin }) + } + return this.create(this.output) + } else if (this.phase === 'delete') { + return this.destroy() + } + throw new Error('Unexpected phase') +}) diff --git a/packages/miracle/src/index.ts b/packages/miracle/src/index.ts new file mode 100644 index 0000000000..6b6633ae39 --- /dev/null +++ b/packages/miracle/src/index.ts @@ -0,0 +1,6 @@ +export * from './gitlab/index.js' +export * from './state/memory-state-store.js' +export * from './state/prisma-state-store.js' +export * from './time/index.js' +export * from './vault/index.js' +export { default as alchemy, Resource } from 'alchemy' diff --git a/packages/miracle/src/state/memory-state-store.ts b/packages/miracle/src/state/memory-state-store.ts new file mode 100644 index 0000000000..ac468179da --- /dev/null +++ b/packages/miracle/src/state/memory-state-store.ts @@ -0,0 +1,63 @@ +import type { State, StateStore } from 'alchemy' + +export class MemoryStateStore implements StateStore { + private readonly stateMap: Map = new Map() + + constructor(private readonly options: { prefix?: string } = {}) {} + + async init(): Promise {} + + async deinit(): Promise {} + + private get prefix() { + return this.options.prefix ?? '' + } + + private key(key: string) { + return this.prefix ? `${this.prefix}/${key}` : key + } + + async list(): Promise { + const prefix = this.prefix + if (!prefix) return [...this.stateMap.keys()] + + const prefixWithSlash = `${prefix}/` + const out: string[] = [] + for (const k of this.stateMap.keys()) { + if (k.startsWith(prefixWithSlash)) { + out.push(k.slice(prefixWithSlash.length)) + } + } + return out + } + + async count(): Promise { + return (await this.list()).length + } + + async get(key: string): Promise { + return this.stateMap.get(this.key(key)) + } + + async getBatch(ids: string[]): Promise> { + const out: Record = {} + for (const id of ids) { + const value = await this.get(id) + if (value) out[id] = value + } + return out + } + + async all(): Promise> { + const keys = await this.list() + return this.getBatch(keys) + } + + async set(key: string, value: State): Promise { + this.stateMap.set(this.key(key), value) + } + + async delete(key: string): Promise { + this.stateMap.delete(this.key(key)) + } +} diff --git a/packages/miracle/src/state/prisma-state-store.spec.ts b/packages/miracle/src/state/prisma-state-store.spec.ts new file mode 100644 index 0000000000..b7cc0845e2 --- /dev/null +++ b/packages/miracle/src/state/prisma-state-store.spec.ts @@ -0,0 +1,83 @@ +import { ResourceID, ResourceScope } from 'alchemy' +import { describe, expect, it, vi } from 'vitest' + +const store = new Map>() + +vi.mock('@prisma/client', () => { + class PrismaClient { + public readonly alchemyState = { + count: async ({ where }: any = {}) => { + if (!where?.scope) { + let total = 0 + for (const scoped of store.values()) total += scoped.size + return total + } + return store.get(where.scope)?.size ?? 0 + }, + delete: async ({ where }: any) => { + const { scope, key } = where.scope_key + store.get(scope)?.delete(key) + }, + findMany: async ({ where }: any = {}) => { + if (!where?.scope) { + const rows: Array<{ scope: string, key: string, value: any }> = [] + for (const [scope, scoped] of store.entries()) { + for (const [key, value] of scoped.entries()) rows.push({ scope, key, value }) + } + return rows + } + const scoped = store.get(where.scope) ?? new Map() + const keys: string[] = where?.key?.in ? where.key.in : [...scoped.keys()] + return keys + .filter(k => scoped.has(k)) + .map(key => ({ scope: where.scope, key, value: scoped.get(key) })) + }, + findUnique: async ({ where, select }: any) => { + const { scope, key } = where.scope_key + const value = store.get(scope)?.get(key) + if (!value) return null + if (select?.value) return { value } + return { scope, key, value } + }, + upsert: async ({ where, create, update }: any) => { + const { scope, key } = where.scope_key + const scoped = store.get(scope) ?? new Map() + store.set(scope, scoped) + const existing = scoped.get(key) + const next = existing ? update.value : create.value + scoped.set(key, next) + return { scope, key, value: next } + }, + } + } + + return { PrismaClient } +}) + +describe('prisma state store', () => { + it('roundtrips state by key prefix', async () => { + store.clear() + const scope: any = { chain: ['test', 'scope'], state: { set: vi.fn() } } + const { PrismaStateStore } = await import('./prisma-state-store.js') + const stateStore = new PrismaStateStore(scope, { prefix: 'pfx' }) + + const state: any = { + status: 'ok', + kind: 'k', + id: 'id1', + fqn: 'fqn1', + seq: 1, + props: { a: 1 }, + data: {}, + output: { [ResourceID]: 'id1' }, + } + + await stateStore.init() + await stateStore.set('id1', state) + const loaded = await stateStore.get('id1') + + expect(loaded?.output?.[ResourceID]).toBe('id1') + expect(loaded?.output?.[ResourceScope]).toBe(scope) + expect(loaded?.props).toEqual({ a: 1 }) + }) +}) diff --git a/packages/miracle/src/state/prisma-state-store.ts b/packages/miracle/src/state/prisma-state-store.ts new file mode 100644 index 0000000000..8d3ec2df8e --- /dev/null +++ b/packages/miracle/src/state/prisma-state-store.ts @@ -0,0 +1,101 @@ +import type { Prisma } from '@prisma/client' +import type { Scope, State, StateStore, StateStoreType } from 'alchemy' +import { PrismaClient } from '@prisma/client' +import { deserializeState, ResourceScope, serialize } from 'alchemy' + +const globalPrisma = new PrismaClient() + +function isInputJsonValue(value: unknown): value is Prisma.InputJsonValue { + if (value === null) return false + const t = typeof value + if (t === 'string' || t === 'number' || t === 'boolean') return true + if (Array.isArray(value)) return value.every(isInputJsonValue) + if (t === 'object') return Object.values(value as Record).every(isInputJsonValue) + return false +} + +export function prismaStateStore(opts: { prefix?: string } = {}): StateStoreType { + return (scope: Scope) => new PrismaStateStore(scope, opts) +} + +export class PrismaStateStore implements StateStore { + private readonly scope: Scope + private readonly storeScope: string + + constructor( + scope: Scope, + opts: { prefix?: string } = {}, + ) { + this.scope = scope + this.storeScope = opts.prefix ?? scope.chain.join('/') + } + + async init(): Promise {} + + async deinit(): Promise {} + + async list(): Promise { + const rows = await globalPrisma.alchemyState.findMany({ + where: { scope: this.storeScope }, + orderBy: { key: 'asc' }, + select: { key: true }, + }) + return rows.map(r => r.key) + } + + async count(): Promise { + return globalPrisma.alchemyState.count({ where: { scope: this.storeScope } }) + } + + async get(key: string): Promise { + const row = await globalPrisma.alchemyState.findUnique({ + where: { scope_key: { scope: this.storeScope, key } }, + select: { value: true }, + }) + if (!row?.value) return undefined + const state = await deserializeState(this.scope, JSON.stringify(row.value)) + state.output[ResourceScope] = this.scope + return state + } + + async getBatch(ids: string[]): Promise> { + const out: Record = {} + if (!ids.length) return out + + const rows = await globalPrisma.alchemyState.findMany({ + where: { scope: this.storeScope, key: { in: ids } }, + select: { key: true, value: true }, + }) + + for (const row of rows) { + if (!row.value) continue + const parsed = await deserializeState(this.scope, JSON.stringify(row.value)) + parsed.output[ResourceScope] = this.scope + out[row.key] = parsed + } + return out + } + + async all(): Promise> { + const keys = await this.list() + return this.getBatch(keys) + } + + async set(key: string, value: State): Promise { + const serialized = await serialize(this.scope, value) + if (!isInputJsonValue(serialized)) throw new Error('State is not JSON serializable') + await globalPrisma.alchemyState.upsert({ + where: { scope_key: { scope: this.storeScope, key } }, + create: { scope: this.storeScope, key, value: serialized }, + update: { value: serialized }, + }) + } + + async delete(key: string): Promise { + try { + await globalPrisma.alchemyState.delete({ + where: { scope_key: { scope: this.storeScope, key } }, + }) + } catch {} + } +} diff --git a/packages/miracle/src/time/index.ts b/packages/miracle/src/time/index.ts new file mode 100644 index 0000000000..f01cc67a24 --- /dev/null +++ b/packages/miracle/src/time/index.ts @@ -0,0 +1 @@ +export * from './rotation.js' diff --git a/packages/miracle/src/time/rotation.ts b/packages/miracle/src/time/rotation.ts new file mode 100644 index 0000000000..6865cf3cdb --- /dev/null +++ b/packages/miracle/src/time/rotation.ts @@ -0,0 +1,55 @@ +import type { Context } from 'alchemy' +import { Resource } from 'alchemy' + +export interface TimeRotationProps { + tick: string + rotateEveryDays: number +} + +export interface TimeRotationState { + tick: string + rotatedAt: string + generation: number + rotated: boolean +} + +export type TimeRotationOutput = Resource<'time:Rotation'> & TimeRotationState + +const dayRegExp = /^(\d{4})-(\d{2})-(\d{2})$/ + +function parseDay(value: string): number { + const match = dayRegExp.exec(value) + if (!match) throw new Error(`Invalid day format: ${value}`) + const year = Number(match[1]) + const month = Number(match[2]) + const day = Number(match[3]) + return Date.UTC(year, month - 1, day) +} + +function diffDays(fromDay: string, toDay: string): number { + const from = parseDay(fromDay) + const to = parseDay(toDay) + return Math.floor((from - to) / (24 * 60 * 60 * 1000)) +} + +export const TimeRotation = Resource('time:Rotation', async function ( + this: Context, + _id: string, + props: TimeRotationProps, +) { + if (this.phase === 'delete') return this.destroy() + if (this.phase !== 'create' && this.phase !== 'update') throw new Error('Unexpected phase') + + if (props.rotateEveryDays <= 0) throw new Error('rotateEveryDays must be > 0') + + const previous = this.phase === 'update' ? this.output : undefined + if (!previous) { + return this.create({ tick: props.tick, rotatedAt: props.tick, generation: 1, rotated: true }) + } + + const shouldRotate = diffDays(props.tick, previous.rotatedAt) >= props.rotateEveryDays + if (shouldRotate) { + return this.create({ tick: props.tick, rotatedAt: props.tick, generation: previous.generation + 1, rotated: true }) + } + return this.create({ tick: props.tick, rotatedAt: previous.rotatedAt, generation: previous.generation, rotated: false }) +}) diff --git a/packages/miracle/src/vault/index.ts b/packages/miracle/src/vault/index.ts new file mode 100644 index 0000000000..55fb897313 --- /dev/null +++ b/packages/miracle/src/vault/index.ts @@ -0,0 +1,7 @@ +export * from './resources.js' + +export interface VaultKvClient { + write: (path: string, data: Record) => Promise + read: (path: string, opts?: { throwIfNoEntry?: boolean }) => Promise<{ data: any } | undefined> + destroy: (path: string) => Promise +} diff --git a/packages/miracle/src/vault/resources.spec.ts b/packages/miracle/src/vault/resources.spec.ts new file mode 100644 index 0000000000..fe8e2904ae --- /dev/null +++ b/packages/miracle/src/vault/resources.spec.ts @@ -0,0 +1,52 @@ +import { PROVIDERS } from 'alchemy' +import { describe, expect, it, vi } from 'vitest' +import './resources.js' + +function createCtx(phase: string, output?: Output) { + const ctx: any = { + phase, + output, + create: vi.fn((next: Output) => { + ctx.output = next + return {} + }), + destroy: vi.fn(() => ({})), + } + return ctx +} + +describe('vault resources', () => { + it('vaultKvSecret create: writes secret when missing', async () => { + const provider = PROVIDERS.get('vault:KvSecret') + if (!provider) throw new Error('Missing provider') + + const vault = { + read: vi.fn().mockResolvedValue(undefined), + write: vi.fn(), + destroy: vi.fn(), + } + + const ctx = createCtx('create') + await provider.handler.call(ctx, 'secret-x', { client: vault, path: 'p', data: { a: 1 } }) + + expect(vault.write).toHaveBeenCalledWith('p', { a: 1 }) + expect(ctx.create).toHaveBeenCalledWith({ path: 'p' }) + }) + + it('vaultKvSecret update: avoids write when secret is identical', async () => { + const provider = PROVIDERS.get('vault:KvSecret') + if (!provider) throw new Error('Missing provider') + + const vault = { + read: vi.fn().mockResolvedValue({ data: { a: 1 } }), + write: vi.fn(), + destroy: vi.fn(), + } + + const ctx = createCtx('update', { path: 'p', data: { a: 1 } }) + await provider.handler.call(ctx, 'secret-x', { client: vault, path: 'p', data: { a: 1 } }) + + expect(vault.write).not.toHaveBeenCalled() + expect(ctx.create).toHaveBeenCalledWith({ path: 'p' }) + }) +}) diff --git a/packages/miracle/src/vault/resources.ts b/packages/miracle/src/vault/resources.ts new file mode 100644 index 0000000000..f67a04110b --- /dev/null +++ b/packages/miracle/src/vault/resources.ts @@ -0,0 +1,36 @@ +import type { VaultKvClient } from './index.js' +import { Resource } from 'alchemy' + +interface ResourceContext { + phase: unknown + output?: Output + create: (output: Output) => Resource + destroy: () => Resource +} + +export interface VaultKvSecretProps { + client: VaultKvClient + path: string + data: any +} + +export interface VaultKvSecretOutput { + path: string +} + +export const VaultKvSecret = Resource('vault:KvSecret', async function (this: ResourceContext, _id: string, props: VaultKvSecretProps) { + const phase = this.phase as string + if (phase === 'create' || phase === 'update') { + const current = await props.client.read(props.path, { throwIfNoEntry: false }) + const currentJson = current?.data ? JSON.stringify(current.data) : undefined + const nextJson = props.data ? JSON.stringify(props.data) : undefined + if (currentJson !== nextJson) { + await props.client.write(props.path, props.data) + } + return this.create({ path: props.path }) + } + if (phase === 'delete') { + return this.destroy() + } + throw new Error(`Unexpected phase: ${phase}`) +}) diff --git a/packages/miracle/tsconfig.eslint.json b/packages/miracle/tsconfig.eslint.json new file mode 100644 index 0000000000..982c60cf77 --- /dev/null +++ b/packages/miracle/tsconfig.eslint.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "./**/*.ts", + "./**/*.js", + "./**/*.cjs", + "./**/*.mjs" + ], + "exclude": [ + "./node_modules", + "./dist", + "./types", + "./coverage", + "./**/*.d.ts" + ] +} diff --git a/packages/miracle/tsconfig.json b/packages/miracle/tsconfig.json new file mode 100644 index 0000000000..8c4e9f94c0 --- /dev/null +++ b/packages/miracle/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@cpn-console/ts-config/tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./", + "rootDir": "./src", + "paths": { + "@/*": ["src/*"] + }, + "declarationDir": "./types", + "outDir": "./dist" + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.js" + ], + "exclude": [ + "./src/**/*.spec.ts", + "./src/**/__mocks__" + ] +} diff --git a/plugins/gitlab/package.json b/plugins/gitlab/package.json index 615da966b4..3f0f694e95 100644 --- a/plugins/gitlab/package.json +++ b/plugins/gitlab/package.json @@ -18,7 +18,7 @@ ], "scripts": { "build": "tsc", - "build:clean": "rimraf ./dist ./types ./tsconfig.tsbuildinfo", + "build:clean": "node --input-type=module -e \"import { rm } from 'node:fs/promises'; await rm('./dist', { recursive: true, force: true }); await rm('./types', { recursive: true, force: true }); await rm('./tsconfig.tsbuildinfo', { force: true });\"", "dev": "nodemon --watch src --ext .ts --exec 'pnpm run build'", "format": "eslint ./ --fix", "lint": "eslint ./" @@ -26,22 +26,16 @@ "dependencies": { "@cpn-console/hooks": "workspace:^", "@cpn-console/logger": "workspace:^", + "@cpn-console/miracle": "workspace:^", "@cpn-console/shared": "workspace:^", - "@cpn-console/vault-plugin": "workspace:^", - "@gitbeaker/core": "^40.6.0", - "@gitbeaker/requester-utils": "^40.6.0", - "@gitbeaker/rest": "^40.6.0", - "axios": "^1.13.6", - "js-yaml": "4.1.0" + "@cpn-console/vault-plugin": "workspace:^" }, "devDependencies": { "@cpn-console/eslint-config": "workspace:^", "@cpn-console/ts-config": "workspace:^", - "@types/js-yaml": "4.0.9", "@types/node": "^24.12.0", "@vitest/coverage-v8": "^2.1.9", "nodemon": "^3.1.14", - "rimraf": "^6.1.3", "typescript": "^5.9.3", "vite": "^7.3.1", "vitest": "^2.1.9" diff --git a/plugins/gitlab/src/class.ts b/plugins/gitlab/src/class.ts deleted file mode 100644 index 4a46bdaad7..0000000000 --- a/plugins/gitlab/src/class.ts +++ /dev/null @@ -1,686 +0,0 @@ -import type { Project, ProjectMember, UniqueRepo } from '@cpn-console/hooks' -import type { VaultProjectApi } from '@cpn-console/vault-plugin/types/vault-project-api.js' -import type { AccessTokenScopes, AllRepositoryTreesOptions, CommitAction, CondensedProjectSchema, Gitlab, GroupSchema, MemberSchema, ProjectSchema, ProjectVariableSchema, RepositoryFileExpandedSchema, VariableSchema } from '@gitbeaker/core' -import type { GitbeakerRequestError } from '@gitbeaker/requester-utils' -import { createHash } from 'node:crypto' -import { PluginApi } from '@cpn-console/hooks' -import { objectEntries } from '@cpn-console/shared' -import { AccessLevel } from '@gitbeaker/core' -import config from './config.js' -import { - customAttributesFilter, - infraGroupCustomAttributeKey, - managedByConsoleCustomAttributeKey, - projectGroupCustomAttributeKey, - upsertCustomAttribute, -} from './custom-attributes.js' -import { logger } from './logger.js' -import { - find, - getAll, - getApi, - getGroupRootId, - infraAppsRepoName, - internalMirrorRepoName, - MAX_PAGINATION_PER_PAGE, - offsetPaginate, -} from './utils.js' - -type setVariableResult = 'created' | 'updated' | 'already up-to-date' -type AccessLevelAllowed = AccessLevel.NO_ACCESS | AccessLevel.MINIMAL_ACCESS | AccessLevel.GUEST | AccessLevel.REPORTER | AccessLevel.DEVELOPER | AccessLevel.MAINTAINER | AccessLevel.OWNER -const infraGroupName = 'Infra' -const infraGroupPath = 'infra' -export const pluginManagedTopic = 'plugin-managed' - -interface GitlabMirrorSecret { - MIRROR_USER: string - MIRROR_TOKEN: string -} - -interface RepoSelect { - mirror?: CondensedProjectSchema - target?: CondensedProjectSchema -} -type PendingCommits = Record -}> - -interface CreateEmptyRepositoryArgs { - repoName: string - description?: string -} - -export class GitlabApi extends PluginApi { - protected api: Gitlab - private pendingCommits: PendingCommits = {} - - constructor() { - super() - this.api = getApi() - } - - public async createEmptyRepository({ createFirstCommit, groupId, repoName, description, ciConfigPath }: CreateEmptyRepositoryArgs & { - createFirstCommit: boolean - groupId: number - ciConfigPath?: string - }) { - logger.debug({ action: 'createEmptyRepository', repoName, groupId, createFirstCommit, ciConfigPath }, 'Create empty repository') - const project = await this.api.Projects.create({ - name: repoName, - path: repoName, - ciConfigPath, - namespaceId: groupId, - description, - }) - try { - await upsertCustomAttribute('projects', project.id, managedByConsoleCustomAttributeKey, 'true') - } catch (err) { - logger.debug({ action: 'createEmptyRepository', projectId: project.id, err }, 'Failed to upsert project custom attribute') - } - // Dépôt tout juste créé, zéro branche => pas d'erreur (filesTree undefined) - if (createFirstCommit) { - await this.api.Commits.create(project.id, 'main', 'ci: 🌱 First commit', []) - } - return project - } - - public async commitCreateOrUpdate( - repoId: number, - fileContent: string, - filePath: string, - branch: string = 'main', - comment: string = 'ci: :robot_face: Update file content', - ): Promise { - logger.debug({ action: 'commitCreateOrUpdate', repoId, filePath, branch }, 'Schedule commit create/update') - let action: CommitAction['action'] = 'create' - - const existingBranch = await find(offsetPaginate(opts => this.api.Branches.all(repoId, opts), { perPage: MAX_PAGINATION_PER_PAGE }), b => b.name === branch) - if (existingBranch) { - let actualFile: RepositoryFileExpandedSchema | undefined - try { - actualFile = await this.api.RepositoryFiles.show(repoId, filePath, branch) - } catch {} - if (actualFile) { - const newContentDigest = createHash('sha256').update(fileContent).digest('hex') - if (actualFile.content_sha256 === newContentDigest) { - // Already up-to-date - return false - } - // Update needed - action = 'update' - } - } - - const commitAction: CommitAction = { - action, - filePath, - content: fileContent, - } - this.addActions(repoId, branch, comment, [commitAction]) - - return true - } - - /** - * Fonction pour supprimer une liste de fichiers d'un repo - * @param repoId - * @param files - * @param branch - * @param comment - */ - public async commitDelete( - repoId: number, - files: string[], - branch: string = 'main', - comment: string = 'ci: :robot_face: Delete files', - ): Promise { - logger.debug({ action: 'commitDelete', repoId, branch, filesCount: files.length }, 'Schedule commit delete') - if (files.length) { - const commitActions: CommitAction[] = files.map((filePath) => { - return { - action: 'delete', - filePath, - } - }) - this.addActions(repoId, branch, comment, commitActions) - return true - } - return false - } - - private addActions(repoId: number, branch: string, comment: string, commitActions: CommitAction[]) { - if (!this.pendingCommits[repoId]) { - this.pendingCommits[repoId] = { branches: {} } - } - if (this.pendingCommits[repoId].branches[branch]) { - this.pendingCommits[repoId].branches[branch].actions.push(...commitActions) - this.pendingCommits[repoId].branches[branch].messages.push(comment) - } else { - this.pendingCommits[repoId].branches[branch] = { - actions: commitActions, - messages: [comment], - } - } - } - - public async commitFiles() { - let filesUpdated: number = 0 - for (const [id, repo] of objectEntries(this.pendingCommits)) { - for (const [branch, details] of objectEntries(repo.branches)) { - const filesNumber = details.actions.length - if (filesNumber) { - filesUpdated += filesNumber - const message = [`ci: :robot_face: Update ${filesNumber} file${filesNumber > 1 ? 's' : ''}`, ...details.messages.filter(m => m)].join('\n') - logger.debug({ action: 'commitFiles', repoId: id, branch, filesNumber }, 'Commit pending file changes') - await this.api.Commits.create(id, branch, message, details.actions) - } - } - } - return filesUpdated - } - - public async listFiles(repoId: number, options: AllRepositoryTreesOptions = {}) { - options.path = options?.path ?? '/' - options.ref = options?.ref ?? 'main' - options.recursive = options?.recursive ?? false - try { - const files = await this.api.Repositories.allRepositoryTrees(repoId, options) - // if (depth >= 0) { - // for (const file of files) { - // if (file.type !== 'tree') { - // return [] - // } - // const childrenFiles = await this.listFiles(repoId, { depth: depth - 1, ...options, path: file.path }) - // files.push(...childrenFiles) - // } - // } - return files - } catch (error) { - const { cause } = error as GitbeakerRequestError - if (cause?.description.includes('Not Found')) { - // Empty repository, with zero commit ==> Zero files - return [] - } else { - throw error - } - } - } - - public async deleteRepository(repoId: number, fullPath: string) { - logger.info({ action: 'deleteRepository', repoId, fullPath }, 'Delete repository') - await this.api.Projects.remove(repoId) // Marks for deletion - return this.api.Projects.remove(repoId, { permanentlyRemove: true, fullPath: `${fullPath}-deletion_scheduled-${repoId}` }) // Effective deletion - } -} - -export class GitlabZoneApi extends GitlabApi { - private infraProjectsByZoneSlug: Map - - constructor() { - super() - this.infraProjectsByZoneSlug = new Map() - } - - // Group Infra - public async getOrCreateInfraGroup(): Promise { - logger.debug({ action: 'getOrCreateInfraGroup', infraGroupName }, 'Get/create infra group') - const rootId = await getGroupRootId() - // Get or create projects_root_dir/infra group - const fast = await find( - offsetPaginate(opts => this.api.Groups.all({ - ...customAttributesFilter(infraGroupCustomAttributeKey, 'true'), - ...opts, - })), - group => group.parent_id === rootId, - ) - - const existingParentGroup = fast ?? await find( - offsetPaginate(opts => this.api.Groups.all({ - search: infraGroupName, - ...opts, - })), - group => group.parent_id === rootId && group.name === infraGroupName, - ) - - const group = existingParentGroup || await this.api.Groups.create(infraGroupName, infraGroupPath, { - parentId: rootId, - projectCreationLevel: 'maintainer', - subgroupCreationLevel: 'owner', - defaultBranchProtection: 0, - description: 'Group that hosts infrastructure-as-code repositories for all zones (ArgoCD pull targets).', - }) - try { - await upsertCustomAttribute('groups', group.id, infraGroupCustomAttributeKey, 'true') - await upsertCustomAttribute('groups', group.id, managedByConsoleCustomAttributeKey, 'true') - } catch (err) { - logger.debug({ action: 'getOrCreateInfraGroup', groupId: group.id, err }, 'Failed to upsert infra group custom attribute') - } - return group - } - - public async getOrCreateInfraProject(zone: string): Promise { - logger.debug({ action: 'getOrCreateInfraProject', zone }, 'Get/create infra project for zone') - if (this.infraProjectsByZoneSlug.has(zone)) { - return this.infraProjectsByZoneSlug.get(zone)! - } - const infraGroup = await this.getOrCreateInfraGroup() - // Get or create projects_root_dir/infra/zone - const fast = await find( - offsetPaginate(opts => this.api.Groups.allProjects(infraGroup.id, { - ...customAttributesFilter(managedByConsoleCustomAttributeKey, 'true'), - search: zone, - simple: true, - ...opts, - })), - repo => repo.name === zone, - ) - const project = fast ?? await find( - offsetPaginate(opts => this.api.Groups.allProjects(infraGroup.id, { - search: zone, - simple: true, - ...opts, - })), - repo => repo.name === zone, - ) ?? await this.createEmptyRepository({ - repoName: zone, - groupId: infraGroup.id, - description: 'Repository hosting deployment files for this zone.', - createFirstCommit: true, - }) - this.infraProjectsByZoneSlug.set(zone, project) - return project - } -} - -export class GitlabProjectApi extends GitlabApi { - private project: Project | UniqueRepo | ProjectMember['project'] - private gitlabGroup: GroupSchema | undefined - private specialRepositories: string[] = [infraAppsRepoName, internalMirrorRepoName] - private zoneApi: GitlabZoneApi - - constructor(project: Project | UniqueRepo | ProjectMember['project']) { - super() - this.project = project - this.api = getApi() - this.zoneApi = new GitlabZoneApi() - } - - // Group Project - private async createProjectGroup(): Promise { - logger.info({ action: 'createProjectGroup', projectSlug: this.project.slug }, 'Create project group') - const parentId = await getGroupRootId() - const existingGroup = await find(offsetPaginate(opts => this.api.Groups.all({ - search: this.project.slug, - ...opts, - }), { perPage: MAX_PAGINATION_PER_PAGE }), group => group.parent_id === parentId && group.name === this.project.slug) - - if (existingGroup) return existingGroup - - const group = await this.api.Groups.create(this.project.slug, this.project.slug, { - parentId, - projectCreationLevel: 'maintainer', - subgroupCreationLevel: 'owner', - defaultBranchProtection: 0, - }) - try { - await upsertCustomAttribute('groups', group.id, projectGroupCustomAttributeKey, this.project.slug) - await upsertCustomAttribute('groups', group.id, managedByConsoleCustomAttributeKey, 'true') - } catch (err) { - logger.debug({ action: 'createProjectGroup', groupId: group.id, err }, 'Failed to upsert project group custom attribute') - } - return group - } - - public async getProjectGroup(): Promise { - logger.debug({ action: 'getProjectGroup', projectSlug: this.project.slug }, 'Get project group') - if (!this.gitlabGroup) { - logger.debug({ action: 'getProjectGroup', projectSlug: this.project.slug }, 'Search project group') - const parentId = await getGroupRootId() - const fast = await find( - offsetPaginate(opts => this.api.Groups.allSubgroups(parentId, { - ...customAttributesFilter(projectGroupCustomAttributeKey, this.project.slug), - ...opts, - })), - group => group.name === this.project.slug, - ) - - this.gitlabGroup = fast ?? await find( - offsetPaginate(opts => this.api.Groups.allSubgroups(parentId, { - search: this.project.slug, - ...opts, - })), - group => group.name === this.project.slug, - ) - - if (this.gitlabGroup) { - try { - await upsertCustomAttribute('groups', this.gitlabGroup.id, projectGroupCustomAttributeKey, this.project.slug) - await upsertCustomAttribute('groups', this.gitlabGroup.id, managedByConsoleCustomAttributeKey, 'true') - } catch (err) { - logger.debug({ action: 'getProjectGroup', groupId: this.gitlabGroup.id, err }, 'Failed to upsert project group custom attribute') - } - } - } - return this.gitlabGroup - } - - public async getOrCreateProjectGroup(): Promise { - logger.debug({ action: 'getOrCreateProjectGroup', projectSlug: this.project.slug }, 'Get/create project group') - const group = await this.getProjectGroup() - if (group) return group - return this.createProjectGroup() - } - - public async getPublicGroupUrl() { - return `${config().publicUrl}/${config().projectsRootDir}/${this.project.slug}` - } - - public async getInternalGroupUrl() { - return `${config().internalUrl}/${config().projectsRootDir}/${this.project.slug}` - } - - // Tokens - public async getProjectMirrorCreds(vaultApi: VaultProjectApi): Promise { - logger.debug({ action: 'getProjectMirrorCreds', projectSlug: this.project.slug }, 'Get/create project mirror credentials') - const tokenName = `${this.project.slug}-bot` - const currentToken = await this.getProjectToken(tokenName) - const creds: GitlabMirrorSecret = { - MIRROR_USER: '', - MIRROR_TOKEN: '', - } - if (currentToken) { - const vaultSecret = await vaultApi.read('tech/GITLAB_MIRROR', { throwIfNoEntry: false }) as { data: GitlabMirrorSecret } - if (vaultSecret) { - try { - const group = await this.getProjectGroup() - if (!group) throw new Error('Group not created yet') - - const res = await fetch(`${config().internalUrl}/api/v4/groups/${group.id}`, { - headers: { 'PRIVATE-TOKEN': vaultSecret.data.MIRROR_TOKEN }, - }) - - if (res.ok) { - return vaultSecret.data // valid token hence early exit - } - - throw new Error('Invalid token') - } catch (error) { - logger.warn({ action: 'getProjectMirrorCreds', err: error }, 'Mirror token invalid, revoking project token') - await this.revokeProjectToken(currentToken.id) - } - } - } - const newToken = await this.createProjectToken(tokenName, ['write_repository', 'read_repository', 'read_api']) - creds.MIRROR_TOKEN = newToken.token - creds.MIRROR_USER = newToken.name - await vaultApi.write(creds, 'tech/GITLAB_MIRROR') - return creds - } - - public async getProjectId(projectName: string) { - logger.debug({ action: 'getProjectId', projectName, projectSlug: this.project.slug }, 'Look up project id') - const projectGroup = await this.getProjectGroup() - if (!projectGroup) throw new Error(`Gitlab inaccessible, impossible de trouver le groupe ${this.project.slug}`) - - const fast = await find( - offsetPaginate(opts => this.api.Groups.allProjects(projectGroup.id, { - ...customAttributesFilter(managedByConsoleCustomAttributeKey, 'true'), - search: projectName, - simple: true, - ...opts, - })), - repo => repo.name === projectName, - ) - - const project = fast ?? await find( - offsetPaginate(opts => this.api.Groups.allProjects(projectGroup.id, { - search: projectName, - simple: true, - ...opts, - })), - repo => repo.name === projectName, - ) - - return project?.id - } - - public async getProjectById(projectId: number) { - return this.api.Projects.show(projectId) - } - - public async getOrCreateInfraProject(zone: string) { - return await this.zoneApi.getOrCreateInfraProject(zone) - } - - public async getProjectToken(tokenName: string) { - const group = await this.getProjectGroup() - if (!group) throw new Error('Unable to retrieve gitlab project group') - return find(offsetPaginate(opts => this.api.GroupAccessTokens.all(group.id, opts), { perPage: MAX_PAGINATION_PER_PAGE }), token => token.name === tokenName) - } - - public async createProjectToken(tokenName: string, scopes: AccessTokenScopes[]) { - logger.info({ action: 'createProjectToken', tokenName, projectSlug: this.project.slug }, 'Create project access token') - const group = await this.getProjectGroup() - if (!group) throw new Error('Unable to retrieve gitlab project group') - const expiryDate = new Date() - expiryDate.setFullYear(expiryDate.getFullYear() + 1) - return this.api.GroupAccessTokens.create(group.id, tokenName, scopes, expiryDate.toLocaleDateString('en-CA')) - } - - public async revokeProjectToken(tokenId: number) { - logger.info({ action: 'revokeProjectToken', tokenId, projectSlug: this.project.slug }, 'Revoke project access token') - const group = await this.getProjectGroup() - if (!group) throw new Error('Unable to retrieve gitlab project group') - return this.api.GroupAccessTokens.revoke(group.id, tokenId) - } - - // Triggers - public async getMirrorProjectTriggerToken(vaultApi: VaultProjectApi) { - logger.debug({ action: 'getMirrorProjectTriggerToken', projectSlug: this.project.slug }, 'Get mirror project trigger token') - const tokenDescription = 'mirroring-from-external-repo' - const gitlabRepositories = await this.listRepositories() - const mirrorRepo = gitlabRepositories.find(repo => repo.name === internalMirrorRepoName) - if (!mirrorRepo) throw new Error('Don\'t know how mirror repo could not exist') - const currentTriggerToken = await find(offsetPaginate(opts => this.api.PipelineTriggerTokens.all(mirrorRepo.id, opts), { perPage: MAX_PAGINATION_PER_PAGE }), token => token.description === tokenDescription) - - const tokenVaultSecret = await vaultApi.read('GITLAB', { throwIfNoEntry: false }) - - if (currentTriggerToken && !tokenVaultSecret?.data?.GIT_MIRROR_TOKEN) { - await this.api.PipelineTriggerTokens.remove(mirrorRepo.id, currentTriggerToken.id) - } - const triggerToken = await this.api.PipelineTriggerTokens.create(mirrorRepo.id, tokenDescription) - return { token: triggerToken.token, repoId: mirrorRepo.id } - } - - // Repositories - public async getPublicRepoUrl(repoName: string) { - return `${await this.getPublicGroupUrl()}/${repoName}.git` - } - - public async getInternalRepoUrl(repoName: string) { - return `${await this.getInternalGroupUrl()}/${repoName}.git` - } - - public async listRepositories() { - const group = await this.getOrCreateProjectGroup() - const projects = await getAll(offsetPaginate(opts => this.api.Groups.allProjects(group.id, { simple: false, ...opts }), { perPage: MAX_PAGINATION_PER_PAGE })) // to refactor with https://github.com/jdalrymple/gitbeaker/pull/3624 - return Promise.all(projects.map(async (project) => { - if (this.specialRepositories.includes(project.name) && (!project.topics || !project.topics.includes(pluginManagedTopic))) { - return this.api.Projects.edit(project.id, { topics: project.topics ? [...project.topics, pluginManagedTopic] : [pluginManagedTopic] }) - } - return project - })) - } - - public async createEmptyProjectRepository({ repoName, description, clone }: CreateEmptyRepositoryArgs & { clone?: boolean }) { - logger.info({ action: 'createEmptyProjectRepository', repoName, projectSlug: this.project.slug, clone }, 'Create empty project repository') - const namespaceId = (await this.getOrCreateProjectGroup()).id - return this.createEmptyRepository({ - repoName, - groupId: namespaceId, - description, - ciConfigPath: clone ? '.gitlab-ci-dso.yml' : undefined, - createFirstCommit: !clone, - }) - } - - // Special Repositories - public async getSpecialRepositories(): Promise { - return this.specialRepositories - } - - public async addSpecialRepositories(name: string) { - logger.debug({ action: 'addSpecialRepositories', name, projectSlug: this.project.slug }, 'Register special repository') - if (!this.specialRepositories.includes(name)) { - this.specialRepositories.push(name) - } - } - - // Group members - public async getGroupMembers() { - const group = await this.getOrCreateProjectGroup() - return getAll(offsetPaginate(opts => this.api.GroupMembers.all(group.id, opts), { perPage: MAX_PAGINATION_PER_PAGE })) - } - - public async addGroupMember(userId: number, accessLevel: AccessLevelAllowed = AccessLevel.DEVELOPER): Promise { - logger.info({ action: 'addGroupMember', accessLevel, projectSlug: this.project.slug }, 'Add group member') - const group = await this.getOrCreateProjectGroup() - return this.api.GroupMembers.add(group.id, userId, accessLevel) - } - - public async editGroupMember(userId: number, accessLevel: AccessLevelAllowed = AccessLevel.DEVELOPER): Promise { - logger.info({ action: 'editGroupMember', accessLevel, projectSlug: this.project.slug }, 'Edit group member') - const group = await this.getOrCreateProjectGroup() - return this.api.GroupMembers.edit(group.id, userId, accessLevel) - } - - public async removeGroupMember(userId: number) { - logger.info({ action: 'removeGroupMember', projectSlug: this.project.slug }, 'Remove group member') - const group = await this.getOrCreateProjectGroup() - return this.api.GroupMembers.remove(group.id, userId) - } - - // CI Variables - public async getGitlabGroupVariables(): Promise { - const group = await this.getOrCreateProjectGroup() - return await getAll(offsetPaginate(opts => this.api.GroupVariables.all(group.id, opts), { perPage: MAX_PAGINATION_PER_PAGE })) - } - - public async setGitlabGroupVariable(listVars: VariableSchema[], toSetVariable: VariableSchema): Promise { - const group = await this.getOrCreateProjectGroup() - const currentVariable = listVars.find(v => v.key === toSetVariable.key) - if (currentVariable) { - if ( - currentVariable.masked !== toSetVariable.masked - || currentVariable.value !== toSetVariable.value - || currentVariable.protected !== toSetVariable.protected - || currentVariable.variable_type !== toSetVariable.variable_type - ) { - await this.api.GroupVariables.edit( - group.id, - toSetVariable.key, - toSetVariable.value, - { - variableType: toSetVariable.variable_type, - masked: toSetVariable.masked, - protected: toSetVariable.protected, - filter: { environment_scope: '*' }, - }, - ) - return 'updated' - } - return 'already up-to-date' - } - await this.api.GroupVariables.create( - group.id, - toSetVariable.key, - toSetVariable.value, - { - variableType: toSetVariable.variable_type, - masked: toSetVariable.masked, - protected: toSetVariable.protected, - - }, - ) - return 'created' - } - - public async getGitlabRepoVariables(repoId: number): Promise { - return await getAll(offsetPaginate(opts => this.api.ProjectVariables.all(repoId, opts), { perPage: MAX_PAGINATION_PER_PAGE })) - } - - public async setGitlabRepoVariable(repoId: number, listVars: VariableSchema[], toSetVariable: ProjectVariableSchema): Promise { - const currentVariable = listVars.find(v => v.key === toSetVariable.key) - if (currentVariable) { - if ( - currentVariable.masked !== toSetVariable.masked - || currentVariable.value !== toSetVariable.value - || currentVariable.protected !== toSetVariable.protected - || currentVariable.variable_type !== toSetVariable.variable_type - ) { - await this.api.ProjectVariables.edit( - repoId, - toSetVariable.key, - toSetVariable.value, - { - variableType: toSetVariable.variable_type, - masked: toSetVariable.masked, - protected: toSetVariable.protected, - filter: { - environment_scope: toSetVariable.environment_scope, - }, - }, - ) - return 'updated' - } - return 'already up-to-date' - } - await this.api.ProjectVariables.create( - repoId, - toSetVariable.key, - toSetVariable.value, - { - variableType: toSetVariable.variable_type, - masked: toSetVariable.masked, - protected: toSetVariable.protected, - }, - ) - return 'created' - } - - // Mirror - public async triggerMirror(targetRepo: string, syncAllBranches: boolean, branchName?: string) { - logger.info({ action: 'triggerMirror', targetRepo, syncAllBranches, branchName, projectSlug: this.project.slug }, 'Trigger repository mirror') - if ((await this.getSpecialRepositories()).includes(targetRepo)) { - throw new Error('User requested for invalid mirroring') - } - const repos = await this.listRepositories() - const { mirror, target }: RepoSelect = repos.reduce((acc, repository) => { - if (repository.name === 'mirror') { - acc.mirror = repository - } - if (repository.name === targetRepo) { - acc.target = repository - } - return acc - }, {} as RepoSelect) - if (!mirror) throw new Error('Unable to find mirror repository') - if (!target) throw new Error('Unable to find target repository') - return this.api.Pipelines.create(mirror.id, 'main', { - variables: [ - { - key: 'SYNC_ALL', - value: syncAllBranches.toString(), - }, - { - key: 'GIT_BRANCH_DEPLOY', - value: branchName ?? '', - }, - { - key: 'PROJECT_NAME', - value: targetRepo, - }, - ], - }) - } -} diff --git a/plugins/gitlab/src/custom-attributes.ts b/plugins/gitlab/src/custom-attributes.ts index f9ba81bf56..2076e53172 100644 --- a/plugins/gitlab/src/custom-attributes.ts +++ b/plugins/gitlab/src/custom-attributes.ts @@ -1,6 +1,3 @@ -import { logger } from './logger.js' -import { getApi } from './utils.js' - export const groupRootCustomAttributeKey = 'cpn_projects_root_dir' export const infraGroupCustomAttributeKey = 'cpn_infra_group' export const projectGroupCustomAttributeKey = 'cpn_project_slug' @@ -10,22 +7,3 @@ export const managedByConsoleCustomAttributeKey = 'cpn_managed_by_console' export function customAttributesFilter(key: string, value: string) { return { [`custom_attributes[${key}]`]: value } as Record } - -export async function upsertCustomAttribute(resource: 'groups' | 'projects' | 'users', id: number, key: string, value: string): Promise { - logger.info({ action: 'upsertCustomAttribute', resource, id, key }, 'Upsert custom attribute') - logger.debug({ action: 'upsertCustomAttribute', resource, id, key }, 'Upsert custom attribute details') - const api = getApi() - try { - if (resource === 'groups') { - await api.GroupCustomAttributes.set(id, key, value) - } else if (resource === 'projects') { - await api.ProjectCustomAttributes.set(id, key, value) - } else { - await api.UserCustomAttributes.set(id, key, value) - } - } catch (err) { - logger.warn({ action: 'upsertCustomAttribute', resource, id, key, err }, 'Failed to upsert custom attribute') - throw err - } - logger.info({ action: 'upsertCustomAttribute', resource, id, key }, 'Custom attribute upserted') -} diff --git a/plugins/gitlab/src/functions.ts b/plugins/gitlab/src/functions.ts index 392245d2da..6269d3a477 100644 --- a/plugins/gitlab/src/functions.ts +++ b/plugins/gitlab/src/functions.ts @@ -1,17 +1,352 @@ -import type { AdminRole, ClusterObject, PluginResult, Project, ProjectLite, ProjectMember, StepCall, UniqueRepo, ZoneObject } from '@cpn-console/hooks' -import type { GitlabProjectApi } from './class.js' +import type { AdminRole, Config, PluginResult, Project, ProjectLite, ProjectMember, StepCall, UniqueRepo, ZoneObject } from '@cpn-console/hooks' +import type { GitlabClient, GitlabMirrorCredsOutput, GitlabMirrorTriggerTokenOutput, VaultKvClient } from '@cpn-console/miracle' import type { VaultSecrets } from './utils.js' +import * as fs from 'node:fs/promises' +import { fileURLToPath } from 'node:url' import { okStatus, specificallyDisabled } from '@cpn-console/hooks' +import { + alchemy, + GitlabCommit, + GitlabEnsureFiles, + GitlabGroup, + GitlabGroupCustomAttribute, + GitlabGroupMember, + GitlabMirrorCreds, + GitlabMirrorTriggerToken, + GitlabProject, + GitlabProjectCustomAttribute, + GitlabUser, + GitlabUserCustomAttribute, + prismaStateStore, + TimeRotation, + VaultKvSecret, +} from '@cpn-console/miracle' import config from './config.js' -import { deleteGroup } from './group.js' +import { groupRootCustomAttributeKey, infraGroupCustomAttributeKey, managedByConsoleCustomAttributeKey, projectGroupCustomAttributeKey, userIdCustomAttributeKey } from './custom-attributes.js' import { DEFAULT_ADMIN_GROUP_PATH, DEFAULT_AUDITOR_GROUP_PATH, } from './infos.js' -import { ensureGroup } from './members.js' -import { ensureRepositories } from './repositories.js' -import { createUsername, getUser, upsertUser } from './user.js' -import { cleanGitlabError } from './utils.js' +import { getGroupAccessLevel } from './members.js' +import { createUsername, getUser } from './user.js' +import { cleanGitlabError, getClient, getGroupRootId, infraAppsRepoName, internalMirrorRepoName } from './utils.js' + +interface VaultProjectApiLike { + write: (body: object, path?: string) => Promise + read: (path?: string, options?: { throwIfNoEntry: boolean }) => Promise<{ data: unknown } | undefined> + destroy: (path?: string) => Promise +} + +function isVaultProjectApiLike(api: unknown): api is VaultProjectApiLike { + if (typeof api !== 'object' || api === null) return false + return ( + 'write' in api && typeof (api as { write?: unknown }).write === 'function' + && 'read' in api && typeof (api as { read?: unknown }).read === 'function' + && 'destroy' in api && typeof (api as { destroy?: unknown }).destroy === 'function' + ) +} + +function vaultProjectApiToKvClient(vaultProjectApi: VaultProjectApiLike): VaultKvClient { + return { + write: async (path, data) => { + await vaultProjectApi.write(data, path) + }, + read: (path, opts) => vaultProjectApi.read(path, { throwIfNoEntry: opts?.throwIfNoEntry ?? false }), + destroy: async (path) => { + await vaultProjectApi.destroy(path) + }, + } +} + +async function readPluginFile(relativePath: string) { + const filePath = fileURLToPath(new URL(`../files/${relativePath}`, import.meta.url)) + return fs.readFile(filePath, 'utf8') +} + +const urnRegexp = /:\/\/(.*)/s + +function assertHasOutput(resource: unknown, name: string): asserts resource is { output: T } { + if (typeof resource !== 'object' || resource === null || !('output' in resource)) { + throw new Error(`${name} did not return an output-bearing resource`) + } +} + +async function runGitlabProjectStack(client: GitlabClient, vault: VaultKvClient, project: Project, hookConfig: Config) { + const scopeName = `project-${project.slug}` + return alchemy.run(scopeName, { phase: 'up', stateStore: prismaStateStore() }, async () => { + const parentId = await getGroupRootId(true) + + await GitlabGroupCustomAttribute(`group-root-dir-${project.slug}`, { + client, + groupId: parentId, + key: groupRootCustomAttributeKey, + value: config().projectsRootDir, + }) + await GitlabGroupCustomAttribute(`group-root-managed-${project.slug}`, { + client, + groupId: parentId, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + + const groupResource = await GitlabGroup(`group-${project.slug}`, { + client, + name: project.slug, + path: project.slug, + parentId, + }) + assertHasOutput<{ id: number }>(groupResource, 'GitlabGroup') + + await GitlabGroupCustomAttribute(`group-project-slug-${project.slug}`, { + client, + groupId: groupResource.output.id, + key: projectGroupCustomAttributeKey, + value: project.slug, + }) + await GitlabGroupCustomAttribute(`group-managed-${project.slug}`, { + client, + groupId: groupResource.output.id, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + + const infraAppsRepoResource = await GitlabProject(`repo-${project.slug}-${infraAppsRepoName}`, { + client, + name: infraAppsRepoName, + path: infraAppsRepoName, + namespaceId: groupResource.output.id, + }) + assertHasOutput<{ id: number }>(infraAppsRepoResource, 'GitlabProject(infraAppsRepo)') + + await GitlabProjectCustomAttribute(`repo-managed-${project.slug}-${infraAppsRepoName}`, { + client, + projectId: infraAppsRepoResource.output.id, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + + void infraAppsRepoResource + + const mirrorRepoResource = await GitlabProject(`repo-${project.slug}-${internalMirrorRepoName}`, { + client, + name: internalMirrorRepoName, + path: internalMirrorRepoName, + namespaceId: groupResource.output.id, + }) + assertHasOutput<{ id: number }>(mirrorRepoResource, 'GitlabProject(mirrorRepo)') + + await GitlabProjectCustomAttribute(`repo-managed-${project.slug}-${internalMirrorRepoName}`, { + client, + projectId: mirrorRepoResource.output.id, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + + const gitlabCiYml = await readPluginFile('.gitlab-ci.yml') + const mirrorSh = await readPluginFile('mirror.sh') + await GitlabEnsureFiles(`mirror-provision-${project.slug}`, { + client, + projectId: mirrorRepoResource.output.id, + branch: 'main', + commitMessage: 'ci: :construction_worker: first mirror', + files: [ + { path: '.gitlab-ci.yml', content: gitlabCiYml, executable: false }, + { path: 'mirror.sh', content: mirrorSh, executable: true }, + ], + }) + + const tick = new Date().toISOString().slice(0, 10) + const mirrorCredsRotation = await TimeRotation(`rotation-mirror-creds-${project.slug}`, { tick, rotateEveryDays: 365 }) + assertHasOutput<{ rotated: boolean }>(mirrorCredsRotation, 'TimeRotation(mirrorCreds)') + const mirrorCredsResource = await GitlabMirrorCreds(`mirror-creds-${project.slug}`, { + client, + groupId: groupResource.output.id, + tokenName: `${project.slug}-bot`, + rotate: mirrorCredsRotation.output.rotated, + }) + assertHasOutput(mirrorCredsResource, 'GitlabMirrorCreds') + + const triggerTokenRotation = await TimeRotation(`rotation-mirror-trigger-${project.slug}`, { tick, rotateEveryDays: 365 }) + assertHasOutput<{ rotated: boolean }>(triggerTokenRotation, 'TimeRotation(triggerToken)') + const triggerTokenResource = await GitlabMirrorTriggerToken(`mirror-trigger-${project.slug}`, { + client, + repoId: mirrorRepoResource.output.id, + description: 'mirroring-from-external-repo', + rotate: triggerTokenRotation.output.rotated, + }) + assertHasOutput(triggerTokenResource, 'GitlabMirrorTriggerToken') + + await VaultKvSecret(`vault-gitlab-${project.slug}`, { + client: vault, + path: 'GITLAB', + data: { + PROJECT_SLUG: project.slug, + GIT_MIRROR_PROJECT_ID: triggerTokenResource.output.repoId, + GIT_MIRROR_TOKEN: triggerTokenResource.output.token, + }, + }) + + for (const repo of project.repositories) { + const repoResource: unknown = await GitlabProject(`repo-${project.slug}-${repo.internalRepoName}`, { + client, + name: repo.internalRepoName, + path: repo.internalRepoName, + namespaceId: groupResource.output.id, + ciConfigPath: repo.externalRepoUrl ? '.gitlab-ci-dso.yml' : undefined, + }) + assertHasOutput<{ id: number }>(repoResource, 'GitlabProject(repo)') + + await GitlabProjectCustomAttribute(`repo-managed-${project.slug}-${repo.internalRepoName}`, { + client, + projectId: repoResource.output.id, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + + if (repo.externalRepoUrl) { + const externalRepoUrn = repo.externalRepoUrl.split(urnRegexp)[1] + const internalRepoUrl = `${config().internalUrl}/${config().projectsRootDir}/${project.slug}/${repo.internalRepoName}.git` + const mirrorSecretData = { + GIT_INPUT_URL: externalRepoUrn, + GIT_INPUT_USER: repo.isPrivate ? repo.newCreds?.username : undefined, + GIT_INPUT_PASSWORD: repo.isPrivate ? repo.newCreds?.token : undefined, + GIT_OUTPUT_URL: internalRepoUrl.split(urnRegexp)[1], + GIT_OUTPUT_USER: mirrorCredsResource.output.MIRROR_USER, + GIT_OUTPUT_PASSWORD: mirrorCredsResource.output.MIRROR_TOKEN, + } + await VaultKvSecret(`vault-mirror-${project.slug}-${repo.internalRepoName}`, { + client: vault, + path: `${repo.internalRepoName}-mirror`, + data: mirrorSecretData, + }) + } + } + + for (const user of project.users) { + const gitlabUserResource = await GitlabUser(`user-${user.id}`, { + client, + email: user.email, + username: createUsername(user.email), + name: `${user.firstName} ${user.lastName}`, + externUid: user.email, + provider: 'openid_connect', + }) + assertHasOutput<{ id: number }>(gitlabUserResource, 'GitlabUser') + + await GitlabUserCustomAttribute(`user-attr-${user.id}`, { + client, + userId: gitlabUserResource.output.id, + key: userIdCustomAttributeKey, + value: user.id, + }) + + const accessLevel = getGroupAccessLevel(project, user, hookConfig) + if (accessLevel) { + await GitlabGroupMember(`member-${user.id}`, { + client, + groupId: groupResource.output.id, + userId: gitlabUserResource.output.id, + accessLevel, + }) + } + } + }) +} + +async function runGitlabInfraStack(client: GitlabClient, zoneSlug: string) { + return alchemy.run(`zone-${zoneSlug}`, { phase: 'up', stateStore: prismaStateStore() }, async () => { + const rootId = await getGroupRootId(true) + + await GitlabGroupCustomAttribute(`group-root-dir-zone-${zoneSlug}`, { + client, + groupId: rootId, + key: groupRootCustomAttributeKey, + value: config().projectsRootDir, + }) + await GitlabGroupCustomAttribute(`group-root-managed-zone-${zoneSlug}`, { + client, + groupId: rootId, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + + const infraGroupResource = await GitlabGroup('group-infra', { + client, + name: 'Infra', + path: 'infra', + parentId: rootId, + createArgs: { + projectCreationLevel: 'maintainer', + subgroupCreationLevel: 'owner', + defaultBranchProtection: 0, + description: 'Group that hosts infrastructure-as-code repositories for all zones (ArgoCD pull targets).', + }, + }) + assertHasOutput<{ id: number }>(infraGroupResource, 'GitlabGroup(infra)') + + await GitlabGroupCustomAttribute('group-infra-attr', { + client, + groupId: infraGroupResource.output.id, + key: infraGroupCustomAttributeKey, + value: 'true', + }) + await GitlabGroupCustomAttribute('group-infra-managed', { + client, + groupId: infraGroupResource.output.id, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + + const infraRepoResource = await GitlabProject(`infra-repo-${zoneSlug}`, { + client, + name: zoneSlug, + path: zoneSlug, + namespaceId: infraGroupResource.output.id, + description: 'Repository hosting deployment files for this zone.', + }) + assertHasOutput<{ id: number }>(infraRepoResource, 'GitlabProject(infraRepo)') + + await GitlabProjectCustomAttribute(`infra-repo-managed-${zoneSlug}`, { + client, + projectId: infraRepoResource.output.id, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + + await GitlabCommit(`infra-repo-first-commit-${zoneSlug}`, { + client, + projectId: infraRepoResource.output.id, + branch: 'main', + commitMessage: 'ci: 🌱 First commit', + actions: [], + }) + }) +} + +async function runGitlabAdminRoleStack(client: GitlabClient, role: AdminRole, flags: { isAdmin?: boolean, isAuditor?: boolean }) { + return alchemy.run(`admin-role-${role.oidcGroup}`, { phase: 'up', stateStore: prismaStateStore() }, async () => { + for (const member of role.members) { + const userResource = await GitlabUser(`admin-role-user-${member.id}`, { + client, + email: member.email, + username: createUsername(member.email), + name: `${member.firstName} ${member.lastName}`, + externUid: member.email, + provider: 'openid_connect', + isAdmin: flags.isAdmin, + isAuditor: flags.isAuditor, + }) + assertHasOutput<{ id: number }>(userResource, 'GitlabUser(adminRole)') + + await GitlabUserCustomAttribute(`admin-role-user-attr-${member.id}`, { + client, + userId: userResource.output.id, + key: userIdCustomAttributeKey, + value: member.id, + }) + } + }) +} // Check export const checkApi: StepCall = async (payload) => { @@ -100,38 +435,11 @@ export const upsertDsoProject: StepCall = async (payload) => { } try { const project = payload.args - const { gitlab: gitlabApi, vault: vaultApi } = payload.apis - - await gitlabApi.getOrCreateProjectGroup() - - await Promise.all(project.users.map(user => - ensureGroup(gitlabApi, project, user, payload.config), - )) - - const projectMirrorCreds = await gitlabApi.getProjectMirrorCreds(vaultApi) - await ensureRepositories(gitlabApi, project, vaultApi, { - botAccount: projectMirrorCreds.MIRROR_USER, - token: projectMirrorCreds.MIRROR_TOKEN, - }) - - const destroySecrets = (await vaultApi.list()) - .filter(path => path.endsWith('-mirror')) - .map(path => path.slice(1, path.length - 7)) - .filter(repoName => !project.repositories.some(projectRepo => projectRepo.internalRepoName === repoName)) - - await Promise.all(destroySecrets - .map(repoName => vaultApi.destroy(`${repoName}-mirror`)), - ) - - const mirrorTriggerToken = await gitlabApi.getMirrorProjectTriggerToken(vaultApi) - - const gitlabSecret: VaultSecrets['GITLAB'] = { - PROJECT_SLUG: project.slug, - GIT_MIRROR_PROJECT_ID: mirrorTriggerToken.repoId, - GIT_MIRROR_TOKEN: mirrorTriggerToken.token, - } + const vaultApi: unknown = payload.apis.vault + if (!isVaultProjectApiLike(vaultApi)) throw new Error('Vault API is missing or incompatible for Gitlab plugin') + const vaultClient = vaultProjectApiToKvClient(vaultApi) - await vaultApi.write(gitlabSecret, 'GITLAB') + await runGitlabProjectStack(getClient(), vaultClient, project, payload.config) return returnResult } catch (error) { @@ -142,15 +450,12 @@ export const upsertDsoProject: StepCall = async (payload) => { } } -export const deleteDsoProject: StepCall = async (payload) => { +export const deleteDsoProject: StepCall = async (_payload) => { try { - const group = await payload.apis.gitlab.getProjectGroup() - if (group) await deleteGroup(group.id, group.full_path) - return { status: { result: 'OK', - message: 'Deleted', + message: 'No-op (non-destructive mode)', }, } } catch (error) { @@ -166,9 +471,19 @@ export const deleteDsoProject: StepCall = async (payload) => { export const syncRepository: StepCall = async (payload) => { const targetRepo = payload.args.repo - const gitlabApi = payload.apis.gitlab try { - await gitlabApi.triggerMirror(targetRepo.internalRepoName, targetRepo.syncAllBranches, targetRepo.branchName) + const vaultApi: unknown = payload.apis.vault + if (!isVaultProjectApiLike(vaultApi)) throw new Error('Vault API is missing or incompatible for Gitlab plugin') + const vaultClient = vaultProjectApiToKvClient(vaultApi) + const gitlab = await vaultClient.read('GITLAB', { throwIfNoEntry: true }) + const mirrorProjectId = (gitlab?.data as VaultSecrets['GITLAB'] | undefined)?.GIT_MIRROR_PROJECT_ID + if (!mirrorProjectId) throw new Error('Missing mirror repository id in vault secret GITLAB') + + await getClient().pipelinesCreate(mirrorProjectId, 'main', [ + { key: 'SYNC_ALL', value: targetRepo.syncAllBranches.toString() }, + { key: 'GIT_BRANCH_DEPLOY', value: targetRepo.branchName ?? '' }, + { key: 'PROJECT_NAME', value: targetRepo.internalRepoName }, + ]) return { status: { result: 'OK', @@ -189,28 +504,7 @@ export const syncRepository: StepCall = async (payload) => { export const upsertZone: StepCall = async (payload) => { const returnResult: PluginResult = okStatus try { - const gitlabApi = payload.apis.gitlab - await gitlabApi.getOrCreateInfraProject(payload.args.slug) - return returnResult - } catch (error) { - returnResult.error = cleanGitlabError(error) - returnResult.status.result = 'KO' - returnResult.status.message = 'Can\'t reconcile please inspect logs' - return returnResult - } -} - -export const deleteZone: StepCall = async (payload) => { - const returnResult: PluginResult = { - status: { - result: 'OK', - message: 'Deleted', - }, - } - try { - const gitlabApi = payload.apis.gitlab - const zoneRepo = await gitlabApi.getOrCreateInfraProject(payload.args.slug) - await gitlabApi.deleteRepository(zoneRepo.id, zoneRepo.path_with_namespace) + await runGitlabInfraStack(getClient(), payload.args.slug) return returnResult } catch (error) { returnResult.error = cleanGitlabError(error) @@ -220,18 +514,22 @@ export const deleteZone: StepCall = async (payload) => { } } -export const commitFiles: StepCall = async (payload) => { - const returnResult = payload.results.gitlab +export const deleteZone: StepCall = async (_payload) => { try { - const filesUpdated = await payload.apis.gitlab.commitFiles() - - returnResult.status.message = `${filesUpdated} file${filesUpdated > 1 ? 's' : ''} updated` - return returnResult + return { + status: { + result: 'OK', + message: 'No-op (non-destructive mode)', + }, + } } catch (error) { - returnResult.error = cleanGitlabError(error) - returnResult.status.result = 'KO' - returnResult.status.message = 'Failed to commit files' - return returnResult + return { + error: cleanGitlabError(error), + status: { + result: 'KO', + message: 'Can\'t reconcile please inspect logs', + }, + } } } @@ -253,9 +551,7 @@ export const upsertAdminRole: StepCall = async (payload) => { } } - for (const member of role.members) { - await upsertUser(member, isAdmin, isAuditor) - } + await runGitlabAdminRoleStack(getClient(), role, { isAdmin, isAuditor }) return { status: { @@ -292,9 +588,7 @@ export const deleteAdminRole: StepCall = async (payload) => { } } - for (const member of role.members) { - await upsertUser(member, isAdmin, isAuditor) - } + await runGitlabAdminRoleStack(getClient(), role, { isAdmin, isAuditor }) return { status: { @@ -315,12 +609,12 @@ export const deleteAdminRole: StepCall = async (payload) => { export const upsertProjectMember: StepCall = async (payload) => { const member = payload.args - const { gitlab: gitlabApi } = payload.apis as { gitlab: GitlabProjectApi } // TODO: apis is never type for some resaon try { - await Promise.all(member.project.users.map(user => - ensureGroup(gitlabApi, member.project, user, payload.config), - )) + const vaultApi: unknown = payload.apis.vault + if (!isVaultProjectApiLike(vaultApi)) throw new Error('Vault API is missing or incompatible for Gitlab plugin') + const vaultClient = vaultProjectApiToKvClient(vaultApi) + await runGitlabProjectStack(getClient(), vaultClient, member.project, payload.config) return { status: { @@ -339,27 +633,12 @@ export const upsertProjectMember: StepCall = async (payload) => { } } -export const deleteProjectMember: StepCall = async (payload) => { - const member = payload.args - const { gitlab: gitlabApi } = payload.apis as { gitlab: GitlabProjectApi } // TODO: apis is never type for some resaon - +export const deleteProjectMember: StepCall = async (_payload) => { try { - const userInfos = await getUser({ ...member, id: member.userId, username: createUsername(member.email) }) - if (!userInfos) { - return { - status: { - result: 'OK', - message: 'User not found in GitLab', - }, - } - } - - await gitlabApi.removeGroupMember(userInfos.id) - return { status: { result: 'OK', - message: 'Member deleted', + message: 'No-op (non-destructive mode)', }, } } catch (error) { diff --git a/plugins/gitlab/src/group.ts b/plugins/gitlab/src/group.ts deleted file mode 100644 index eabf036561..0000000000 --- a/plugins/gitlab/src/group.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { getApi } from './utils.js' - -export async function deleteGroup(groupId: number, fullPath: string) { - const api = getApi() - await api.Groups.remove(groupId) // Marks for deletion - return api.Groups.remove(groupId, { permanentlyRemove: true, fullPath: `${fullPath}-deletion_scheduled-${groupId}` }) // Effective deletion -} diff --git a/plugins/gitlab/src/index.ts b/plugins/gitlab/src/index.ts index 1eb773c6c6..bd1f3ea0a5 100644 --- a/plugins/gitlab/src/index.ts +++ b/plugins/gitlab/src/index.ts @@ -1,8 +1,6 @@ -import type { DeclareModuleGenerator, DefaultArgs, Plugin, Project, ProjectMember, UniqueRepo, ZoneObject } from '@cpn-console/hooks' -import { GitlabProjectApi, GitlabZoneApi } from './class.js' +import type { DeclareModuleGenerator, Plugin } from '@cpn-console/hooks' import { checkApi, - commitFiles, deleteDsoProject, deleteProjectMember, deleteZone, @@ -18,8 +16,6 @@ import { logger } from './logger.js' import monitor from './monitor.js' import { getOrCreateGroupRoot } from './utils.js' -const onlyApi = { api: (project: Project | UniqueRepo) => new GitlabProjectApi(project) } - function start() { getOrCreateGroupRoot().catch((error) => { logger.error({ action: 'start', err: error }, 'Hook failed') @@ -31,49 +27,34 @@ export const plugin: Plugin = { infos, subscribedHooks: { deleteProject: { - ...onlyApi, steps: { main: deleteDsoProject, - post: commitFiles, }, }, upsertProject: { - ...onlyApi, steps: { check: checkApi, main: upsertDsoProject, - post: commitFiles, }, }, getProjectSecrets: { steps: { main: getDsoProjectSecrets } }, syncRepository: { - ...onlyApi, steps: { main: syncRepository, - post: commitFiles, }, }, upsertCluster: { - api: () => new GitlabZoneApi(), - steps: { - post: commitFiles, - }, + steps: {}, }, deleteCluster: { - api: () => new GitlabZoneApi(), - steps: { - post: commitFiles, - }, + steps: {}, }, upsertZone: { - api: () => new GitlabZoneApi(), steps: { pre: upsertZone, - post: commitFiles, }, }, deleteZone: { - api: () => new GitlabZoneApi(), steps: { main: deleteZone, }, @@ -84,13 +65,11 @@ export const plugin: Plugin = { }, }, upsertProjectMember: { - api: member => new GitlabProjectApi(member.project), steps: { main: upsertProjectMember, }, }, deleteProjectMember: { - api: member => new GitlabProjectApi(member.project), steps: { post: deleteProjectMember, }, @@ -101,13 +80,6 @@ export const plugin: Plugin = { } declare module '@cpn-console/hooks' { - interface HookPayloadApis { - gitlab: Args extends Project | UniqueRepo | ProjectMember['project'] - ? GitlabProjectApi - : Args extends ZoneObject - ? GitlabZoneApi - : never - } interface ProjectStore extends DeclareModuleGenerator {} interface Config extends DeclareModuleGenerator {} interface PluginResult { diff --git a/plugins/gitlab/src/members.ts b/plugins/gitlab/src/members.ts index bca9bbd71e..7112f9c059 100644 --- a/plugins/gitlab/src/members.ts +++ b/plugins/gitlab/src/members.ts @@ -1,12 +1,10 @@ import type { Config, Project, UserObject } from '@cpn-console/hooks' -import type { GitlabProjectApi } from './class.js' -import { AccessLevel } from '@gitbeaker/core' +import { AccessLevel } from '@cpn-console/miracle' import { DEFAULT_PROJECT_DEVELOPER_GROUP_PATH_SUFFIX, DEFAULT_PROJECT_MAINTAINER_GROUP_PATH_SUFFIX, DEFAULT_PROJECT_REPORTER_GROUP_PATH_SUFFIX, } from './infos.js' -import { createUsername, upsertUser } from './user.js' import { matchRole } from './utils.js' export function getGroupAccessLevelFromProjectRole(project: Project, user: UserObject, config: Config) { @@ -35,31 +33,3 @@ export function getGroupAccessLevel(project: Project, user: UserObject, config: if (project.owner.id === user.id) return AccessLevel.OWNER return getGroupAccessLevelFromProjectRole(project, user, config) } - -export async function ensureGroup( - gitlabApi: GitlabProjectApi, - project: Project, - user: UserObject, - config: Config, -) { - const gitlabUser = await upsertUser({ - id: user.id, - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - }) - - const groupMembers = await gitlabApi.getGroupMembers() - const existingMember = groupMembers.find(m => m.username === createUsername(user.email)) - const maxAccessLevel = getGroupAccessLevel(project, user, config) - - if (existingMember && maxAccessLevel) { - if (existingMember.access_level !== maxAccessLevel) { - await gitlabApi.editGroupMember(gitlabUser.id, maxAccessLevel) - } - } else if (maxAccessLevel) { - await gitlabApi.addGroupMember(gitlabUser.id, maxAccessLevel) - } else if (existingMember) { - await gitlabApi.removeGroupMember(gitlabUser.id) - } -} diff --git a/plugins/gitlab/src/monitor.ts b/plugins/gitlab/src/monitor.ts index d190d6ed51..55fabe2562 100644 --- a/plugins/gitlab/src/monitor.ts +++ b/plugins/gitlab/src/monitor.ts @@ -1,6 +1,5 @@ import type { MonitorInfos } from '@cpn-console/shared' import { Monitor, MonitorStatus } from '@cpn-console/shared' -import axios from 'axios' import config from './config.js' enum HealthStatus { @@ -13,11 +12,9 @@ const coreComponents = ['gitaly_check', 'master_check', 'db_check', 'sessions_ch async function monitor(instance: Monitor): Promise { instance.lastStatus.lastUpdateTimestamp = Date.now() try { - const res = await axios.get(`${config().internalUrl}/-/readiness?all=1`, { - validateStatus: res => res === 200, - }) + const res = await fetch(`${config().internalUrl}/-/readiness?all=1`) if (res.status === 200) { // 200 only means api responds - const data = res.data as GitlabRes + const data = await res.json() as GitlabRes const failedComponents = Object.entries(data) .reduce((acc, [name, value]) => { if (value.status === HealthStatus.failed) return [...acc, name] diff --git a/plugins/gitlab/src/project.ts b/plugins/gitlab/src/project.ts deleted file mode 100644 index df3dc8e67e..0000000000 --- a/plugins/gitlab/src/project.ts +++ /dev/null @@ -1,52 +0,0 @@ -import * as fs from 'node:fs/promises' -import path from 'node:path' - -import { getApi } from './utils.js' - -export async function provisionMirror(repoId: number) { - const baseDir = path.resolve(import.meta.url, '../../files/').split(':')[1] - - const gitlabCiYml = ( - await fs.readFile(`${baseDir}/.gitlab-ci.yml`) - ).toString() - const mirrorSh = (await fs.readFile(`${baseDir}/mirror.sh`)).toString() - - const mirrorFirstActions: CommitAction[] = [ - { - action: 'create', - filePath: '.gitlab-ci.yml', - content: gitlabCiYml, - execute_filemode: false, - }, - { - action: 'create', - filePath: 'mirror.sh', - content: mirrorSh, - execute_filemode: true, - }, - ] - const api = getApi() - await api.Commits.create( - repoId, - 'main', - 'ci: :construction_worker: first mirror', - mirrorFirstActions, - ) -} - -interface CommitAction { - /** The action to perform */ - action: 'create' | 'delete' | 'move' | 'update' | 'chmod' - /** Full path to the file. Ex. lib/class.rb */ - filePath: string - /** Original full path to the file being moved.Ex.lib / class1.rb */ - previousPath?: string - /** File content, required for all except delete. Optional for move */ - content?: string - /** text or base64. text is default. */ - encoding?: string - /** Last known file commit id. Will be only considered in update, move and delete actions. */ - lastCommitId?: string - /** When true/false enables/disables the execute flag on the file. Only considered for chmod action. */ - execute_filemode?: boolean -} diff --git a/plugins/gitlab/src/repositories.ts b/plugins/gitlab/src/repositories.ts deleted file mode 100644 index 00797cf446..0000000000 --- a/plugins/gitlab/src/repositories.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { Project, Repository } from '@cpn-console/hooks' -import type { VaultProjectApi } from '@cpn-console/vault-plugin/types/vault-project-api.js' -import type { CondensedProjectSchema, ProjectSchema } from '@gitbeaker/rest' -import type { GitlabProjectApi } from './class.js' -import { shallowEqual } from '@cpn-console/shared' -import { pluginManagedTopic } from './class.js' -import { provisionMirror } from './project.js' -import { infraAppsRepoName, internalMirrorRepoName } from './utils.js' - -interface ProjectMirrorCreds { - botAccount: string - token: string -} - -export async function ensureRepositories(gitlabApi: GitlabProjectApi, project: Project, vaultApi: VaultProjectApi, projectMirrorCreds: ProjectMirrorCreds) { - const specialRepos = await gitlabApi.getSpecialRepositories() - const gitlabRepositories = await gitlabApi.listRepositories() - - const promises: Promise[] = [ - // delete excess repositories - ...gitlabRepositories - .filter(gitlabRepository => ( - !specialRepos.includes(gitlabRepository.name) - && !gitlabRepository.topics?.includes(pluginManagedTopic) - && !project.repositories.some(repo => repo.internalRepoName === gitlabRepository.name))) - .map(gitlabRepository => gitlabApi.deleteRepository(gitlabRepository.id, gitlabRepository.path_with_namespace)), - // create missing repositories - ...project.repositories.map(repo => ensureRepositoryExists(gitlabRepositories, repo, gitlabApi, projectMirrorCreds, vaultApi)), - ] - - if (!gitlabRepositories.some(repo => repo.name === infraAppsRepoName)) { - promises.push( - gitlabApi.createEmptyProjectRepository({ repoName: infraAppsRepoName, clone: false }), - ) - } - if (!gitlabRepositories.some(repo => repo.name === internalMirrorRepoName)) { - promises.push( - gitlabApi.createEmptyProjectRepository({ repoName: internalMirrorRepoName, clone: false }) - .then(mirrorRepo => provisionMirror(mirrorRepo.id)), - ) - } - - await Promise.all(promises) -} - -const urnRegexp = /:\/\/(.*)/s - -async function ensureRepositoryExists(gitlabRepositories: CondensedProjectSchema[], repository: Repository, gitlabApi: GitlabProjectApi, projectMirrorCreds: ProjectMirrorCreds, vaultApi: VaultProjectApi) { - const gitlabRepository: CondensedProjectSchema | ProjectSchema | void = gitlabRepositories.find(gitlabRepository => gitlabRepository.name === repository.internalRepoName) - const externalRepoUrn = repository.externalRepoUrl.split(urnRegexp)[1] - const vaultCredsPath = `${repository.internalRepoName}-mirror` - const currentVaultSecret = await vaultApi.read(vaultCredsPath, { throwIfNoEntry: false }) - - if (!gitlabRepository) { - await gitlabApi.createEmptyProjectRepository({ - repoName: repository.internalRepoName, - description: undefined, - clone: !!repository.externalRepoUrl, - }) - } - - if (!repository.externalRepoUrl) { - return currentVaultSecret && vaultApi.destroy(vaultCredsPath) - } - - let gitInputUser: string | undefined - let gitInputPassword: string | undefined - if (currentVaultSecret?.data) { - gitInputUser = currentVaultSecret.data.GIT_INPUT_USER - gitInputPassword = currentVaultSecret.data.GIT_INPUT_PASSWORD - } - - const internalRepoUrl = await gitlabApi.getInternalRepoUrl(repository.internalRepoName) - - const mirrorSecretData = { - GIT_INPUT_URL: externalRepoUrn, - GIT_INPUT_USER: repository.isPrivate - ? (repository.newCreds?.username || gitInputUser) - : undefined, - GIT_INPUT_PASSWORD: repository.isPrivate - ? (repository.newCreds?.token || gitInputPassword) - : undefined, - GIT_OUTPUT_URL: internalRepoUrl.split(urnRegexp)[1], - GIT_OUTPUT_USER: projectMirrorCreds.botAccount, - GIT_OUTPUT_PASSWORD: projectMirrorCreds.token, - } - if ( - !currentVaultSecret?.data - || !shallowEqual(mirrorSecretData, currentVaultSecret.data) - ) { - await vaultApi.write(mirrorSecretData, vaultCredsPath) - } -} diff --git a/plugins/gitlab/src/user.ts b/plugins/gitlab/src/user.ts index 5f638e4bc1..287f8a00f9 100644 --- a/plugins/gitlab/src/user.ts +++ b/plugins/gitlab/src/user.ts @@ -1,8 +1,6 @@ -import type { UserObject } from '@cpn-console/hooks' -import type { CreateUserOptions, SimpleUserSchema } from '@gitbeaker/rest' -import { upsertCustomAttribute, userIdCustomAttributeKey } from './custom-attributes.js' +import type { SimpleUserSchema } from '@cpn-console/miracle' import { logger } from './logger.js' -import { find, getApi, offsetPaginate } from './utils.js' +import { find, getClient, offsetPaginate } from './utils.js' export function createUsername(email: string) { const parts = email.split('@') @@ -13,22 +11,23 @@ export function createUsername(email: string) { } export async function getUser(user: { email: string, username: string, id: string }): Promise { - const api = getApi() + const api = getClient() const isUser = (gitlabUser: SimpleUserSchema) => gitlabUser?.externUid === user.id || gitlabUser?.externUid === user.email + || gitlabUser?.extern_uid === user.id + || gitlabUser?.extern_uid === user.email || gitlabUser.email === user.email || gitlabUser.username === user.username const fast = await find( - offsetPaginate(opts => api.Users.all({ - externUid: user.email, + offsetPaginate((opts: { page: number, perPage?: number }) => api.usersAll({ + extern_uid: user.email, provider: 'openid_connect', - orderBy: 'username', - asAdmin: true, - ...opts, - })), + order_by: 'username', + as_admin: true, + }, opts.page, opts.perPage)), isUser, ) @@ -37,71 +36,10 @@ export async function getUser(user: { email: string, username: string, id: strin } return fast ?? await find( - offsetPaginate(opts => api.Users.all({ + offsetPaginate((opts: { page: number, perPage?: number }) => api.usersAll({ search: user.username, - asAdmin: true, - ...opts, - })), + as_admin: true, + }, opts.page, opts.perPage)), isUser, ) } - -export async function upsertUser(user: UserObject, isAdmin?: boolean, isAuditor?: boolean): Promise { - const api = getApi() - const username = createUsername(user.email) - const existingUser = await getUser({ ...user, username }) - - const userDefinitionBase: CreateUserOptions = { - // required options - name: `${user.firstName} ${user.lastName}`, - username, - email: user.email, - // sso options - externUid: user.email, - provider: 'openid_connect', - admin: isAdmin, - auditor: isAuditor, - } - - if (existingUser) { - const incorrectProps = Object.entries(userDefinitionBase).reduce((acc, [key, value]) => { - if (existingUser[key] !== value) { - acc.push({ - key, - curr: existingUser[key], - new: value, - }) - } - return acc - }, [] as { key: string, curr: any, new: any }[]) - - if (incorrectProps.length) { - logger.debug({ action: 'upsertUser', changes: incorrectProps }, 'User properties differ from expected') - try { - await api.Users.edit(existingUser.id, userDefinitionBase) - } catch (err) { - logger.error({ action: 'upsertUser', err }, 'Failed to update user') - } - } - try { - await upsertCustomAttribute('users', existingUser.id, userIdCustomAttributeKey, user.id) - } catch (err) { - logger.debug({ action: 'upsertUser', userId: existingUser.id, err }, 'Failed to upsert user custom attribute') - } - return existingUser - } - - const created = await api.Users.create({ - ...userDefinitionBase, - canCreateGroup: false, - forceRandomPassword: true, - projectsLimit: 0, - skipConfirmation: true, - }) - try { - await upsertCustomAttribute('users', created.id, userIdCustomAttributeKey, user.id) - } catch (err) { - logger.debug({ action: 'upsertUser', userId: created.id, err }, 'Failed to upsert user custom attribute') - } - return created -} diff --git a/plugins/gitlab/src/utils.ts b/plugins/gitlab/src/utils.ts index 3dbe69b772..30dc5bf766 100644 --- a/plugins/gitlab/src/utils.ts +++ b/plugins/gitlab/src/utils.ts @@ -1,35 +1,31 @@ -import type { BaseRequestOptions, Gitlab as IGitlab, OffsetPagination, PaginationRequestOptions } from '@gitbeaker/core' -import { GitbeakerRequestError } from '@gitbeaker/requester-utils' -import { Gitlab } from '@gitbeaker/rest' +import type { GitlabClient as IGitlabClient } from '@cpn-console/miracle' +import { alchemy, find, GitlabClient, GitlabGroup, GitlabGroupCustomAttribute, offsetPaginate, prismaStateStore } from '@cpn-console/miracle' import config from './config.js' -import { customAttributesFilter, groupRootCustomAttributeKey, managedByConsoleCustomAttributeKey, upsertCustomAttribute } from './custom-attributes.js' +import { customAttributesFilter, groupRootCustomAttributeKey, managedByConsoleCustomAttributeKey } from './custom-attributes.js' import { logger } from './logger.js' -let api: IGitlab | undefined +let client: IGitlabClient | undefined -let groupRootId: number +let groupRootId: number | undefined export const MAX_PAGINATION_PER_PAGE = 100 export async function getGroupRootId(throwIfNotFound?: true): Promise export async function getGroupRootId(throwIfNotFound?: false): Promise export async function getGroupRootId(throwIfNotFound?: boolean): Promise { - const gitlabApi = getApi() const projectRootDir = config().projectsRootDir logger.debug({ action: 'getGroupRootId', projectRootDir }, 'Resolve group root id') if (groupRootId) return groupRootId const fast = await find( - offsetPaginate(opts => gitlabApi.Groups.all({ + offsetPaginate(opts => getClient().groupsAll({ ...customAttributesFilter(groupRootCustomAttributeKey, projectRootDir), - ...opts, - })), + }, opts.page, opts.perPage), { perPage: MAX_PAGINATION_PER_PAGE }), grp => grp.full_path === projectRootDir, ) const groupRoot = fast ?? await find( - offsetPaginate(opts => gitlabApi.Groups.all({ + offsetPaginate(opts => getClient().groupsAll({ search: projectRootDir, - ...opts, - })), + }, opts.page, opts.perPage), { perPage: MAX_PAGINATION_PER_PAGE }), grp => grp.full_path === projectRootDir, ) logger.debug({ action: 'getGroupRootId', groupRootId: groupRoot?.id, groupRootPath: groupRoot?.full_path }, 'Resolved group root') @@ -41,68 +37,67 @@ export async function getGroupRootId(throwIfNotFound?: boolean): Promise { - logger.info({ action: 'createGroupRoot', projectRootDir: config().projectsRootDir }, 'Create group root hierarchy') - const gitlabApi = getApi() const projectRootDir = config().projectsRootDir - const projectRootDirArray = projectRootDir.split('/') + logger.info({ action: 'createGroupRoot', projectRootDir }, 'Create group root hierarchy') - const rootGroupPath = projectRootDirArray.shift() - if (!rootGroupPath) { - throw new Error('No projectRootDir available') - } + const parts = projectRootDir.split('/').filter(Boolean) + if (parts.length === 0) throw new Error('No projectRootDir available') - let parentGroup = await find(offsetPaginate(opts => gitlabApi.Groups.all({ - search: rootGroupPath, - ...opts, - }), { perPage: MAX_PAGINATION_PER_PAGE }), grp => grp.full_path === rootGroupPath) ?? await gitlabApi.Groups.create(rootGroupPath, rootGroupPath) - - if (parentGroup.full_path === projectRootDir) { - try { - await upsertCustomAttribute('groups', parentGroup.id, groupRootCustomAttributeKey, projectRootDir) - await upsertCustomAttribute('groups', parentGroup.id, managedByConsoleCustomAttributeKey, 'true') - } catch (err) { - logger.debug({ action: 'createGroupRoot', groupRootId: parentGroup.id, err }, 'Failed to upsert group root custom attribute') - } - return parentGroup.id - } + const client = getClient() + + return alchemy.run(`group-root-${projectRootDir}`, { phase: 'up', stateStore: prismaStateStore() }, async () => { + let parentId: number | undefined + let fullPath = '' - for (const path of projectRootDirArray) { - const futureFullPath = `${parentGroup.full_path}/${path}` - parentGroup = await find(offsetPaginate(opts => gitlabApi.Groups.all({ - search: futureFullPath, - ...opts, - }), { perPage: MAX_PAGINATION_PER_PAGE }), grp => grp.full_path === futureFullPath) ?? await gitlabApi.Groups.create(path, path, { parentId: parentGroup.id, visibility: 'internal' }) - - if (parentGroup.full_path === projectRootDir) { - try { - await upsertCustomAttribute('groups', parentGroup.id, groupRootCustomAttributeKey, projectRootDir) - await upsertCustomAttribute('groups', parentGroup.id, managedByConsoleCustomAttributeKey, 'true') - } catch (err) { - logger.debug({ action: 'createGroupRoot', groupRootId: parentGroup.id, err }, 'Failed to upsert group root custom attribute') + for (const [idx, part] of parts.entries()) { + const id = parts.slice(0, idx + 1).join('-') + const groupResource = await GitlabGroup(`group-root-${id}`, { + client, + name: part, + path: part, + parentId, + createArgs: parentId ? { visibility: 'internal' } : undefined, + }) + assertHasOutput<{ id: number }>(groupResource, 'GitlabGroup(groupRoot)') + parentId = groupResource.output.id + fullPath = fullPath ? `${fullPath}/${part}` : part + + if (fullPath === projectRootDir) { + await GitlabGroupCustomAttribute(`group-root-dir-${projectRootDir}`, { + client, + groupId: parentId, + key: groupRootCustomAttributeKey, + value: projectRootDir, + }) + await GitlabGroupCustomAttribute(`group-root-managed-${projectRootDir}`, { + client, + groupId: parentId, + key: managedByConsoleCustomAttributeKey, + value: 'true', + }) + groupRootId = parentId + return parentId } - return parentGroup.id } - } - throw new Error('No projectRootDir available or is malformed') + + throw new Error('No projectRootDir available or is malformed') + }) } export async function getOrCreateGroupRoot(): Promise { return await getGroupRootId(false) ?? createGroupRoot() } -export function getApi(): IGitlab { - api ??= new Gitlab({ token: config().token, host: config().internalUrl }) - return api +export function getClient(): IGitlabClient { + client ??= new GitlabClient({ + host: config().internalUrl, + token: config().token, + }) + return client } export const infraAppsRepoName = 'infra-apps' @@ -116,12 +111,22 @@ export interface VaultSecrets { } } -// eslint-disable-next-line regexp/no-super-linear-backtracking -const keyValueRegExp = /\/\/(.*):(.*)@/g +const keyValueRegExp = /\/\/[^/@][^/:@]*:[^/@]*@/g export function cleanGitlabError(error: T): T { - if (error instanceof GitbeakerRequestError && error.cause?.description) { - error.cause.description = String(error.cause.description).replaceAll(keyValueRegExp, '//MASKED:MASKED@') + if (error instanceof Error) { + error.message = error.message.replace(keyValueRegExp, '//MASKED:MASKED@') + const cause = (error as Error & { cause?: unknown }).cause + if (cause && typeof cause === 'object') { + const maybeDescription = (cause as { description?: unknown }).description + if (typeof maybeDescription === 'string') { + ;(cause as { description: string }).description = maybeDescription.replace(keyValueRegExp, '//MASKED:MASKED@') + } + const maybeUrl = (cause as { url?: unknown }).url + if (typeof maybeUrl === 'string') { + ;(cause as { url: string }).url = maybeUrl.replace(keyValueRegExp, '//MASKED:MASKED@') + } + } } return error } @@ -130,69 +135,12 @@ export function matchRole(projectSlug: string, roleOidcGroup: string, configured return configuredRolePath.some(path => roleOidcGroup === `/${projectSlug}${path}`) } -export interface OffsetPaginateOptions { - startPage?: number - perPage?: number - maxPages?: number -} +export { find } from '@cpn-console/miracle' +export { getAll } from '@cpn-console/miracle' +export { offsetPaginate } from '@cpn-console/miracle' -export async function* offsetPaginate( - request: (options: PaginationRequestOptions<'offset'> & BaseRequestOptions) => Promise<{ data: T[], paginationInfo: OffsetPagination }>, - options?: OffsetPaginateOptions, -): AsyncGenerator { - let page: number | null = options?.startPage ?? 1 - let pagesFetched = 0 - let total: number = 0 - logger.debug({ action: 'offsetPaginate', page }, 'Pagination start') - while (page !== null) { - if (options?.maxPages && pagesFetched >= options.maxPages) { - page = null - continue - } - try { - const { data, paginationInfo } = await request({ - page, - perPage: options?.perPage, - maxPages: options?.maxPages, - showExpanded: true, - pagination: 'offset', - }) - pagesFetched += 1 - total += data.length - logger.debug( - { action: 'offsetPaginate', page, nextPage: paginationInfo.next, items: data.length, total }, - 'Pagination page fetched', - ) - for (const item of data) { - yield item - } - page = paginationInfo.next - } catch (error) { - logger.error({ action: 'offsetPaginate', page, err: error }, 'Pagination request failed') - throw error - } - } - logger.debug({ action: 'offsetPaginate', total }, 'Pagination done') -} - -export async function getAll( - iterable: AsyncIterable, -): Promise { - const items: T[] = [] - for await (const item of iterable) { - items.push(item) - } - return items -} - -export async function find( - iterable: AsyncIterable, - predicate: (item: T) => boolean, -): Promise { - for await (const item of iterable) { - if (predicate(item)) { - return item - } +function assertHasOutput(resource: unknown, name: string): asserts resource is { output: T } { + if (typeof resource !== 'object' || resource === null || !('output' in resource)) { + throw new Error(`${name} did not return an output-bearing resource`) } - return undefined } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 920f2aca05..190bf79ea4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -200,10 +200,10 @@ importers: dependencies: '@cpn-console/argocd-plugin': specifier: workspace:^ - version: link:../../plugins/argocd + version: file:plugins/argocd(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/gitlab-plugin': specifier: workspace:^ - version: link:../../plugins/gitlab + version: file:plugins/gitlab(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/harbor-plugin': specifier: workspace:^ version: link:../../plugins/harbor @@ -218,13 +218,13 @@ importers: version: link:../../packages/logger '@cpn-console/nexus-plugin': specifier: workspace:^ - version: link:../../plugins/nexus + version: file:plugins/nexus(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/shared': specifier: workspace:^ version: link:../../packages/shared '@cpn-console/sonarqube-plugin': specifier: workspace:^ - version: link:../../plugins/sonarqube + version: file:plugins/sonarqube(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/vault-plugin': specifier: workspace:^ version: link:../../plugins/vault @@ -366,10 +366,10 @@ importers: dependencies: '@cpn-console/argocd-plugin': specifier: workspace:^ - version: file:plugins/argocd(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) + version: file:plugins/argocd(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/gitlab-plugin': specifier: workspace:^ - version: file:plugins/gitlab(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) + version: file:plugins/gitlab(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/harbor-plugin': specifier: workspace:^ version: file:plugins/harbor(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) @@ -384,13 +384,13 @@ importers: version: link:../../packages/logger '@cpn-console/nexus-plugin': specifier: workspace:^ - version: file:plugins/nexus(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) + version: file:plugins/nexus(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/shared': specifier: workspace:^ version: file:packages/shared(@types/node@22.19.15) '@cpn-console/sonarqube-plugin': specifier: workspace:^ - version: file:plugins/sonarqube(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) + version: file:plugins/sonarqube(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/vault-plugin': specifier: workspace:^ version: file:plugins/vault(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) @@ -547,7 +547,7 @@ importers: version: 9.9.0 '@nestjs/cli': specifier: ^11.0.16 - version: 11.0.16(@types/node@22.19.15) + version: 11.0.16(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15) '@nestjs/schematics': specifier: ^11.0.9 version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) @@ -595,10 +595,10 @@ importers: version: 7.2.2 ts-loader: specifier: ^9.5.4 - version: 9.5.4(typescript@5.9.3)(webpack@5.104.1) + version: 9.5.4(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19))) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@22.19.15)(typescript@5.9.3) + version: 10.9.2(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(typescript@5.9.3) ts-patch: specifier: ^3.3.0 version: 3.3.0 @@ -713,6 +713,37 @@ importers: specifier: ^5.9.3 version: 5.9.3 + packages/miracle: + dependencies: + '@gitbeaker/core': + specifier: ^40.6.0 + version: 40.6.0 + '@gitbeaker/rest': + specifier: ^40.6.0 + version: 40.6.0 + '@prisma/client': + specifier: 6.19.2 + version: 6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3) + alchemy: + specifier: ^0.23.0 + version: 0.23.0(@ai-sdk/openai-compatible@0.2.16(zod@3.25.76))(@ai-sdk/openai@1.3.24(zod@3.25.76))(@aws-sdk/client-dynamodb@3.1029.0)(@aws-sdk/client-iam@3.1029.0)(@aws-sdk/client-lambda@3.1029.0)(@aws-sdk/client-s3@3.1029.0)(@aws-sdk/client-sagemaker@3.1029.0)(@aws-sdk/client-ses@3.1029.0)(@aws-sdk/client-sesv2@3.1029.0)(@aws-sdk/client-sqs@3.1029.0)(@aws-sdk/client-ssm@3.1029.0)(@aws-sdk/client-sts@3.1029.0)(@aws-sdk/credential-providers@3.1029.0)(@aws-sdk/node-config-provider@3.374.0)(@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24))(@cloudflare/workers-shared@0.17.5)(@iarna/toml@2.2.5)(@octokit/rest@21.1.1)(@swc/core@1.15.24(@swc/helpers@0.5.19))(ai@4.3.19(react@19.2.5)(zod@3.25.76))(arktype@2.2.0)(aws4fetch@1.0.20)(cloudflare@4.5.0)(diff@4.0.4)(esbuild@0.27.3)(fast-json-patch@3.1.1)(glob@13.0.6)(hono@4.12.12)(jszip@3.10.1)(libsodium-wrappers@0.7.16)(prettier@3.8.2)(stripe@17.7.0)(turndown@7.2.4)(xdg-app-paths@8.3.0)(yaml@2.8.2)(zod@3.25.76) + devDependencies: + '@cpn-console/eslint-config': + specifier: workspace:^ + version: link:../eslintconfig + '@cpn-console/ts-config': + specifier: workspace:^ + version: link:../tsconfig + '@types/node': + specifier: ^24.12.0 + version: 24.12.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^2.1.9 + version: 2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0) + packages/shared: dependencies: '@cpn-console/logger': @@ -817,7 +848,7 @@ importers: dependencies: '@cpn-console/gitlab-plugin': specifier: workspace:^ - version: link:../gitlab + version: file:plugins/gitlab(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/hooks': specifier: workspace:^ version: link:../../packages/hooks @@ -882,27 +913,15 @@ importers: '@cpn-console/logger': specifier: workspace:^ version: link:../../packages/logger + '@cpn-console/miracle': + specifier: workspace:^ + version: link:../../packages/miracle '@cpn-console/shared': specifier: workspace:^ version: link:../../packages/shared '@cpn-console/vault-plugin': specifier: workspace:^ version: link:../vault - '@gitbeaker/core': - specifier: ^40.6.0 - version: 40.6.0 - '@gitbeaker/requester-utils': - specifier: ^40.6.0 - version: 40.6.0 - '@gitbeaker/rest': - specifier: ^40.6.0 - version: 40.6.0 - axios: - specifier: ^1.13.6 - version: 1.13.6 - js-yaml: - specifier: 4.1.0 - version: 4.1.0 devDependencies: '@cpn-console/eslint-config': specifier: workspace:^ @@ -910,9 +929,6 @@ importers: '@cpn-console/ts-config': specifier: workspace:^ version: link:../../packages/tsconfig - '@types/js-yaml': - specifier: 4.0.9 - version: 4.0.9 '@types/node': specifier: ^24.12.0 version: 24.12.0 @@ -922,9 +938,6 @@ importers: nodemon: specifier: ^3.1.14 version: 3.1.14 - rimraf: - specifier: ^6.1.3 - version: 6.1.3 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -982,7 +995,7 @@ importers: version: 6.1.3 swagger-typescript-api: specifier: ^13.3.1 - version: 13.3.1(magicast@0.3.5) + version: 13.3.1(magicast@0.3.5)(react@19.2.5) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1046,7 +1059,7 @@ importers: dependencies: '@cpn-console/gitlab-plugin': specifier: workspace:^ - version: link:../gitlab + version: file:plugins/gitlab(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/hooks': specifier: workspace:^ version: link:../../packages/hooks @@ -1095,7 +1108,7 @@ importers: dependencies: '@cpn-console/gitlab-plugin': specifier: workspace:^ - version: link:../gitlab + version: file:plugins/gitlab(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/hooks': specifier: workspace:^ version: link:../../packages/hooks @@ -1188,6 +1201,44 @@ importers: packages: + '@ai-sdk/openai-compatible@0.2.16': + resolution: {integrity: sha512-LkvfcM8slJedRyJa/MiMiaOzcMjV1zNDwzTHEGz7aAsgsQV0maLfmJRi/nuSwf5jmp0EouC+JXXDUj2l94HgQw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/openai@1.3.24': + resolution: {integrity: sha512-GYXnGJTHRTZc4gJMSmFRgEQudjqd4PUN0ZjQhPwOAYH1yOAvQoG/Ikqs+HyISRbLPCrhbZnPKCNHuRU4OfpW0Q==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/provider-utils@2.2.8': + resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.23.8 + + '@ai-sdk/provider@1.1.3': + resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} + engines: {node: '>=18'} + + '@ai-sdk/react@1.2.12': + resolution: {integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/ui-utils@1.2.11': + resolution: {integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.23.8 + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -1321,9 +1372,243 @@ packages: peerDependencies: openapi-types: '>=7' + '@ark/schema@0.56.0': + resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==} + + '@ark/util@0.56.0': + resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-cognito-identity@3.1029.0': + resolution: {integrity: sha512-wmQpZI+DweZ8mKGvkGXZFLxgyR2PoSqsnSvS8wHEuq9U282eD91zfkFsTK+rgQZK+ZYuCKwlBTjHbKKlQiJEjw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-dynamodb@3.1029.0': + resolution: {integrity: sha512-5y5BMzg2O8yHBmDfKVnqFCtg7JqpDeMVnRqtccuNZmuG9AjXQD3WeGqxaUPAYrnek3l5fIBGB0PRwoSuelguYw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-iam@3.1029.0': + resolution: {integrity: sha512-v/5wWvrX3fveCP5UQ4qTCvvD9KCQ3dpnY6uEOCGpkAigli+xzEixl8xNQDCRi9G3KyrhvGaeE2SEfuuoCHX+gw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-lambda@3.1029.0': + resolution: {integrity: sha512-qEJY6h1GMA/3VS54/j9mOvl0MHhdP+CvewNI1fqjj9eFFJoj2h731a6cVTpctg6fatCl7Qr0RXQCvup6Q3IMwg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-s3@3.1029.0': + resolution: {integrity: sha512-OuA8RZTxsAaHDcI25j2NGLMaYFI2WpJdDzK3uLmVBmaHwjQKQZOUDVVBcln8pNo3IgkY+HRSJhRR4/xlM//UyQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-sagemaker@3.1029.0': + resolution: {integrity: sha512-GLvDRwE0BEtoXiIrkVUAzwGg634p0q8USeBh84Y7eSAHSRVYJFkms1sHkFq/AfSRKsIfgqwhgzBuwLpZGy+X0A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-ses@3.1029.0': + resolution: {integrity: sha512-9VFXUR0IoddbVIFhkX9jTt7PePlvi/udfyXVl1ev4anshkJYI6Aa6X6geiepHxdoBY9sHKjVZHF2B0+tf21kDQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-sesv2@3.1029.0': + resolution: {integrity: sha512-ZL2E13IaLQjevcL9nHqT7Jf8EeYfDhgwNyuOh1bp/2ECga+0BzJaHKiASZdvBCZHV+cCDqH3SiGKePUMOxakYA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-sqs@3.1029.0': + resolution: {integrity: sha512-WFzc+/Pj6lWK0xdPjlmyX9J9A4bVA1UA4pGFFBESHdSGrmshxMs95MmmwCUtbYLZUroF+4CgF7TVqYoyZPIc9g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-ssm@3.1029.0': + resolution: {integrity: sha512-LthC1Dkh7r4ihZ7EI+6Sms9Ml0XQXoBZbw5LmtT1EJElriMugAfMnG5pKzDAcWpLiZgVBSZVai7moQR/QM/cCw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-sts@3.1029.0': + resolution: {integrity: sha512-9C2WAs0ECcQvaQWRBetVGjxlvNpVpNWTwIuf3oA106JOtb2EjxJ2s4JQQUPCiCH1qP9HzZ3Zf9MDEEJox0HT4Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.973.27': + resolution: {integrity: sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.6': + resolution: {integrity: sha512-NMbiqKdruhwwgI6nzBVe2jWMkXjaoQz2YOs3rFX+2F3gGyrJDkDPwMpV/RsTFeq2vAQ055wZNtOXFK4NYSkM8g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-cognito-identity@3.972.22': + resolution: {integrity: sha512-ih6ORpme4i2qJqGckOQ9Lt2iiZ+5tm3bnfsT5TwoPyFnuDURXv3OdhYa3Nr/m0iJr38biqKYKdGKb5GR1KB2hw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.25': + resolution: {integrity: sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.27': + resolution: {integrity: sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.29': + resolution: {integrity: sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.29': + resolution: {integrity: sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.30': + resolution: {integrity: sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.25': + resolution: {integrity: sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.29': + resolution: {integrity: sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.29': + resolution: {integrity: sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-providers@3.1029.0': + resolution: {integrity: sha512-oGkmHMuzj1tfvuCS9fWPvzy3vZqUQKClYClQ7QGAdMd1uH0QqrJQgJtX/jw2Be5nA0ZZ2DG7QEexqM1/TT1JHQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/dynamodb-codec@3.972.28': + resolution: {integrity: sha512-wx5jKLKPVJRsr/dwK9Xp26+SDb95xHlZU9Bgm2AglnMxQ0DlRlq3PyKlGi9y0OCuWZ7hLNcQJ7uDSN+PgsiuGg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/endpoint-cache@3.972.5': + resolution: {integrity: sha512-itVdge0NozgtgmtbZ25FVwWU3vGlE7x7feE/aOEJNkQfEpbkrF8Rj1QmnK+2blFfYE1xWt/iU+6/jUp/pv1+MA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.972.9': + resolution: {integrity: sha512-COToYKgquDyligbcAep7ygs48RK+mwe/IYprq4+TSrVFzNOYmzWvHf6werpnKV5VYpRiwdn+Wa5ZXkPqLVwcTg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-endpoint-discovery@3.972.10': + resolution: {integrity: sha512-b3hf8dPxWonxFKgxBijMehVblgbY0gPprTvyuHYMxnOPfiCIY467kZltPoeOCQYLr9v0v0HuL9fIGtT6utd15w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.9': + resolution: {integrity: sha512-V/FNCjFxnh4VGu+HdSiW4Yg5GELihA1MIDSAdsEPvuayXBVmr0Jaa6jdLAZLH38KYXl/vVjri9DQJWnTAujHEA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.974.7': + resolution: {integrity: sha512-uU4/ch2CLHB8Phu1oTKnnQ4e8Ujqi49zEnQYBhWYT53zfFvtJCdGsaOoypBr8Fm/pmCBssRmGoIQ4sixgdLP9w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.9': + resolution: {integrity: sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.9': + resolution: {integrity: sha512-TyfOi2XNdOZpNKeTJwRUsVAGa+14nkyMb2VVGG+eDgcWG/ed6+NUo72N3hT6QJioxym80NSinErD+LBRF0Ir1w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.9': + resolution: {integrity: sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.10': + resolution: {integrity: sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.28': + resolution: {integrity: sha512-qJHcJQH9UNPUrnPlRtCozKjtqAaypQ5IgQxTNoPsVYIQeuwNIA8Rwt3NvGij1vCDYDfCmZaPLpnJEHlZXeFqmg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-sqs@3.972.19': + resolution: {integrity: sha512-S7AWsrOTcs52AdS4uWPtP6n7tloOscfeNfJWK4wvNPJBI01lrfHb6g+tYRckwDzruhhdaPpn/CARZ+YPw6oMGw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.9': + resolution: {integrity: sha512-wSA2BR7L0CyBNDJeSrleIIzC+DzL93YNTdfU0KPGLiocK6YsRv1nPAzPF+BFSdcs0Qa5ku5Kcf4KvQcWwKGenQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.29': + resolution: {integrity: sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.996.19': + resolution: {integrity: sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/node-config-provider@3.374.0': + resolution: {integrity: sha512-RsUeDTtslQ9b/slyjAuVqEVZLnZ/jVdNbLaY30oF6FhvZnKpoiN8m7z4oiDjGQ6K2lVuQNdSRGjzI22W+mLwug==} + engines: {node: '>=14.0.0'} + deprecated: This package has moved to @smithy/node-config-provider + + '@aws-sdk/region-config-resolver@3.972.11': + resolution: {integrity: sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.16': + resolution: {integrity: sha512-EMdXYB4r/k5RWq86fugjRhid5JA+Z6MpS7n4sij4u5/C+STrkvuf9aFu41rJA9MjUzxCLzv8U2XL8cH2GSRYpQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1026.0': + resolution: {integrity: sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.7': + resolution: {integrity: sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.6': + resolution: {integrity: sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.9': + resolution: {integrity: sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==} + + '@aws-sdk/util-user-agent-node@3.973.15': + resolution: {integrity: sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.17': + resolution: {integrity: sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -1854,6 +2139,19 @@ packages: '@clack/prompts@1.1.0': resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==} + '@cloudflare/unenv-preset@2.16.0': + resolution: {integrity: sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workers-shared@0.17.5': + resolution: {integrity: sha512-e2tjozEy3/8JnPcddYFuMjW9As+aX0i7egciPE8b+mufS33QCtdFEzZKCK8utFzby0tx9TkxGFLJ+cmSrJ+tLw==} + engines: {node: '>=18.0.0'} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -1957,6 +2255,9 @@ packages: '@cpn-console/logger@file:packages/logger': resolution: {directory: packages/logger, type: directory} + '@cpn-console/miracle@file:packages/miracle': + resolution: {directory: packages/miracle, type: directory} + '@cpn-console/nexus-plugin@file:plugins/nexus': resolution: {directory: plugins/nexus, type: directory} @@ -2548,6 +2849,9 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iarna/toml@2.2.5': + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + '@iconify-json/ri@1.2.10': resolution: {integrity: sha512-WWMhoncVVM+Xmu9T5fgu2lhYRrKTEWhKk3Com0KiM111EeEsRLiASjpsFKnC/SrB6covhUp95r2mH8tGxhgd5Q==} @@ -2783,6 +3087,9 @@ packages: resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + '@mswjs/interceptors@0.41.3': resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==} engines: {node: '>=18'} @@ -2945,6 +3252,64 @@ packages: engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} hasBin: true + '@octokit/auth-token@5.1.2': + resolution: {integrity: sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.6': + resolution: {integrity: sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.4': + resolution: {integrity: sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.2.2': + resolution: {integrity: sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@24.2.0': + resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} + + '@octokit/openapi-types@25.1.0': + resolution: {integrity: sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==} + + '@octokit/plugin-paginate-rest@11.6.0': + resolution: {integrity: sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@5.3.1': + resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@13.5.0': + resolution: {integrity: sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@6.1.8': + resolution: {integrity: sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==} + engines: {node: '>= 18'} + + '@octokit/request@9.2.4': + resolution: {integrity: sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==} + engines: {node: '>= 18'} + + '@octokit/rest@21.1.1': + resolution: {integrity: sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==} + engines: {node: '>= 18'} + + '@octokit/types@13.10.0': + resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + + '@octokit/types@14.1.0': + resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -3938,46 +4303,367 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@smithy/chunked-blob-reader-native@4.2.3': + resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} + engines: {node: '>=18.0.0'} - '@stylistic/eslint-plugin@5.10.0': - resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^9.0.0 || ^10.0.0 + '@smithy/chunked-blob-reader@5.2.2': + resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==} + engines: {node: '>=18.0.0'} - '@surma/rollup-plugin-off-main-thread@2.2.3': - resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} + '@smithy/config-resolver@4.4.14': + resolution: {integrity: sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==} + engines: {node: '>=18.0.0'} - '@swc/helpers@0.5.19': - resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} + '@smithy/core@3.23.14': + resolution: {integrity: sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==} + engines: {node: '>=18.0.0'} - '@tokenizer/inflate@0.4.1': - resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} - engines: {node: '>=18'} + '@smithy/credential-provider-imds@4.2.13': + resolution: {integrity: sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==} + engines: {node: '>=18.0.0'} - '@tokenizer/token@0.3.0': - resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@smithy/eventstream-codec@4.2.13': + resolution: {integrity: sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==} + engines: {node: '>=18.0.0'} - '@ts-rest/core@3.52.1': - resolution: {integrity: sha512-tAjz7Kxq/grJodcTA1Anop4AVRDlD40fkksEV5Mmal88VoZeRKAG8oMHsDwdwPZz+B/zgnz0q2sF+cm5M7Bc7g==} - peerDependencies: - '@types/node': ^18.18.7 || >=20.8.4 - zod: ^3.22.3 - peerDependenciesMeta: - '@types/node': - optional: true - zod: - optional: true + '@smithy/eventstream-serde-browser@4.2.13': + resolution: {integrity: sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==} + engines: {node: '>=18.0.0'} - '@ts-rest/fastify@3.52.1': - resolution: {integrity: sha512-y+w3ENayNI77T0l2gcr9mobF3Nfc4yIb4487mvNMsMUkGLMHmaWzhLy3Mo/mswuoA+tVdOkrD8tJD38bLY7iYQ==} - peerDependencies: - '@ts-rest/core': ~3.52.0 - fastify: ^4.0.0 - zod: ^3.22.3 - peerDependenciesMeta: + '@smithy/eventstream-serde-config-resolver@4.3.13': + resolution: {integrity: sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.13': + resolution: {integrity: sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.13': + resolution: {integrity: sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.16': + resolution: {integrity: sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.2.14': + resolution: {integrity: sha512-rtQ5es8r/5v4rav7q5QTsfx9CtCyzrz/g7ZZZBH2xtMmd6G/KQrLOWfSHTvFOUPlVy59RQvxeBYJaLRoybMEyA==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.13': + resolution: {integrity: sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.2.13': + resolution: {integrity: sha512-WdQ7HwUjINXETeh6dqUeob1UHIYx8kAn9PSp1HhM2WWegiZBYVy2WXIs1lB07SZLan/udys9SBnQGt9MQbDpdg==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.13': + resolution: {integrity: sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.13': + resolution: {integrity: sha512-cNm7I9NXolFxtS20ojROddOEpSAeI1Obq6pd1Kj5HtHws3s9Fkk8DdHDfQSs5KuxCewZuVK6UqrJnfJmiMzDuQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.13': + resolution: {integrity: sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.29': + resolution: {integrity: sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.5.1': + resolution: {integrity: sha512-/zY+Gp7Qj2D2hVm3irkCyONER7E9MiX3cUUm/k2ZmhkzZkrPgwVS4aJ5NriZUEN/M0D1hhjrgjUmX04HhRwdWA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.17': + resolution: {integrity: sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.13': + resolution: {integrity: sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@1.1.0': + resolution: {integrity: sha512-2G4TlzUnmTrUY26VKTonQqydwb+gtM/mcl+TqDP8CnWtJKVL8ElPpKgLGScP04bPIRY9x2/10lDdoaRXDqPuCw==} + engines: {node: '>=14.0.0'} + + '@smithy/node-config-provider@4.3.13': + resolution: {integrity: sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.5.2': + resolution: {integrity: sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@1.2.0': + resolution: {integrity: sha512-qlJd9gT751i4T0t/hJAyNGfESfi08Fek8QiLcysoKPgR05qHhG0OYhlaCJHhpXy4ECW0lHyjvFM1smrCLIXVfw==} + engines: {node: '>=14.0.0'} + + '@smithy/property-provider@4.2.13': + resolution: {integrity: sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.13': + resolution: {integrity: sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.13': + resolution: {integrity: sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.13': + resolution: {integrity: sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.13': + resolution: {integrity: sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@1.1.0': + resolution: {integrity: sha512-S/v33zvCWzFyGZGlsEF0XsZtNNR281UhR7byk3nRfsgw5lGpg51rK/zjMgulM+h6NSuXaFILaYrw1I1v4kMcuA==} + engines: {node: '>=14.0.0'} + + '@smithy/shared-ini-file-loader@4.4.8': + resolution: {integrity: sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.13': + resolution: {integrity: sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.12.9': + resolution: {integrity: sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@1.2.0': + resolution: {integrity: sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==} + engines: {node: '>=14.0.0'} + + '@smithy/types@4.14.0': + resolution: {integrity: sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.13': + resolution: {integrity: sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.45': + resolution: {integrity: sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.49': + resolution: {integrity: sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.3.4': + resolution: {integrity: sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.13': + resolution: {integrity: sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.3.1': + resolution: {integrity: sha512-FwmicpgWOkP5kZUjN3y+3JIom8NLGqSAJBeoIgK0rIToI817TEBHCrd0A2qGeKQlgDeP+Jzn4i0H/NLAXGy9uQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.22': + resolution: {integrity: sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.15': + resolution: {integrity: sha512-oUt9o7n8hBv3BL56sLSneL0XeigZSuem0Hr78JaoK33D9oKieyCvVP8eTSe3j7g2mm/S1DvzxKieG7JEWNJUNg==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@stylistic/eslint-plugin@5.10.0': + resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + + '@surma/rollup-plugin-off-main-thread@2.2.3': + resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} + + '@swc/core-darwin-arm64@1.15.24': + resolution: {integrity: sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.24': + resolution: {integrity: sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.24': + resolution: {integrity: sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.24': + resolution: {integrity: sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-arm64-musl@1.15.24': + resolution: {integrity: sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/core-linux-ppc64-gnu@1.15.24': + resolution: {integrity: sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==} + engines: {node: '>=10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-s390x-gnu@1.15.24': + resolution: {integrity: sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==} + engines: {node: '>=10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-gnu@1.15.24': + resolution: {integrity: sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-musl@1.15.24': + resolution: {integrity: sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/core-win32-arm64-msvc@1.15.24': + resolution: {integrity: sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.24': + resolution: {integrity: sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.24': + resolution: {integrity: sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.24': + resolution: {integrity: sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.19': + resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} + + '@swc/types@0.1.26': + resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} + + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@ts-rest/core@3.52.1': + resolution: {integrity: sha512-tAjz7Kxq/grJodcTA1Anop4AVRDlD40fkksEV5Mmal88VoZeRKAG8oMHsDwdwPZz+B/zgnz0q2sF+cm5M7Bc7g==} + peerDependencies: + '@types/node': ^18.18.7 || >=20.8.4 + zod: ^3.22.3 + peerDependenciesMeta: + '@types/node': + optional: true + zod: + optional: true + + '@ts-rest/fastify@3.52.1': + resolution: {integrity: sha512-y+w3ENayNI77T0l2gcr9mobF3Nfc4yIb4487mvNMsMUkGLMHmaWzhLy3Mo/mswuoA+tVdOkrD8tJD38bLY7iYQ==} + peerDependencies: + '@ts-rest/core': ~3.52.0 + fastify: ^4.0.0 + zod: ^3.22.3 + peerDependenciesMeta: zod: optional: true @@ -4032,6 +4718,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/diff-match-patch@1.0.36': + resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -4083,6 +4772,12 @@ packages: '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} @@ -4464,6 +5159,10 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} @@ -4500,6 +5199,20 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + ai@4.3.19: + resolution: {integrity: sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + react: + optional: true + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -4553,6 +5266,46 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + alchemy@0.23.0: + resolution: {integrity: sha512-09KnlHfespC4/zsPgbal1b3zFoK1Ck5eIxljazG5Q5JjFaxY7aV3gFDLqjBSsxTJ+blpBB6nijTcZzMwH+oEcA==} + peerDependencies: + '@ai-sdk/openai': ^1.1.9 + '@ai-sdk/openai-compatible': ^0.2.2 + '@aws-sdk/client-dynamodb': ^3.0.0 + '@aws-sdk/client-iam': ^3.0.0 + '@aws-sdk/client-lambda': ^3.0.0 + '@aws-sdk/client-s3': ^3.0.0 + '@aws-sdk/client-sagemaker': ^3.0.0 + '@aws-sdk/client-ses': ^3.0.0 + '@aws-sdk/client-sesv2': ^3.0.0 + '@aws-sdk/client-sqs': ^3.0.0 + '@aws-sdk/client-ssm': ^3.0.0 + '@aws-sdk/client-sts': ^3.0.0 + '@aws-sdk/credential-providers': ^3.0.0 + '@aws-sdk/node-config-provider': ^3.374.0 + '@cloudflare/unenv-preset': ^2.3.1 + '@cloudflare/workers-shared': ^0.17.5 + '@iarna/toml': ^2.2.5 + '@octokit/rest': ^21.1.1 + '@swc/core': ^1.11.24 + ai: ^4.0.0 + arktype: ^2.0.0 + aws4fetch: ^1.0.20 + cloudflare: ^4.0.0 + diff: ^7.0.0 + esbuild: ^0.25.1 + fast-json-patch: ^3.1.1 + glob: ^10.0.0 + hono: ^4.0.0 + jszip: ^3.0.0 + libsodium-wrappers: ^0.7.15 + prettier: ^3.0.0 + stripe: ^17.0.0 + turndown: ^7.0.0 + xdg-app-paths: ^8.0.0 + yaml: ^2.0.0 + zod: ^3.0.0 + alien-signals@1.0.13: resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} @@ -4607,6 +5360,12 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + arkregex@0.0.5: + resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==} + + arktype@2.2.0: + resolution: {integrity: sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ==} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -4660,6 +5419,9 @@ packages: avvio@8.4.0: resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==} + aws4fetch@1.0.20: + resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==} + axios-retry@4.5.0: resolution: {integrity: sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==} peerDependencies: @@ -4698,6 +5460,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} @@ -4718,6 +5483,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + boxen@5.1.2: resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} engines: {node: '>=10'} @@ -4936,6 +5704,9 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + cloudflare@4.5.0: + resolution: {integrity: sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ==} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -5224,6 +5995,9 @@ packages: dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5668,6 +6442,10 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + eventemitter2@6.4.9: resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} @@ -5699,6 +6477,9 @@ packages: fast-content-type-parse@1.1.0: resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} + fast-copy@4.0.2: resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} @@ -5712,6 +6493,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-patch@3.1.1: + resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -5737,6 +6521,13 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-xml-builder@1.1.4: + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} + + fast-xml-parser@5.5.8: + resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} + hasBin: true + fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -5859,6 +6650,9 @@ packages: typescript: '>3.6.0' webpack: ^5.11.0 + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -5867,6 +6661,10 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -6135,6 +6933,10 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + hono@4.12.12: + resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==} + engines: {node: '>=16.9.0'} + hookified@1.15.1: resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} @@ -6174,6 +6976,9 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} @@ -6574,6 +7379,11 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsondiffpatch@0.6.0: + resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} @@ -6614,6 +7424,12 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libsodium-wrappers@0.7.16: + resolution: {integrity: sha512-Gtr/WBx4dKjvRL1pvfwZqu7gO6AfrQ0u9vFL+kXihtHf6NfkROR8pjYWn98MFDI3jN19Ii1ZUfPR9afGiPyfHg==} + + libsodium@0.7.16: + resolution: {integrity: sha512-3HrzSPuzm6Yt9aTYCDxYEG8x8/6C0+ag655Y7rhhWZM9PT4NpdnbqlzXhGZlDnkgR6MeSTnOt/VIyHLs9aSf+Q==} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -6978,6 +7794,9 @@ packages: mlly@1.8.1: resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==} + mnemonist@0.38.3: + resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==} + mnemonist@0.39.8: resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==} @@ -7159,6 +7978,9 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} + obliterator@1.6.1: + resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} + obliterator@2.0.5: resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} @@ -7201,6 +8023,10 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + os-paths@7.4.0: + resolution: {integrity: sha512-Ux1J4NUqC6tZayBqLN1kUlDAEvLiQlli/53sSddU4IN+h+3xxnv2HmRSMpVSvr1hvJzotfMs3ERvETGK+f4OwA==} + engines: {node: '>= 4.0'} + outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -7270,6 +8096,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -7448,6 +8278,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.8.2: + resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} + engines: {node: '>=14'} + hasBin: true + pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} @@ -7551,6 +8386,10 @@ packages: resolution: {integrity: sha512-VXUdgSiUrE/WZXn6gUIVVIsg0+Hp6VPZPOaHCay+OuFKy6u/8ktmeNEf+U5qSA8jzGGFsg8jrDNu1BeHpz2pJA==} engines: {node: '>=10'} + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -8065,6 +8904,13 @@ packages: strip-literal@2.1.1: resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} + stripe@17.7.0: + resolution: {integrity: sha512-aT2BU9KkizY9SATf14WhhYVv2uOapBWX0OFWF4xvcj1mPaNotlSc2CsxpS4DS46ZueSppmCF5BX1sNYBtwBvfw==} + engines: {node: '>=12.*'} + + strnum@2.2.3: + resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} + strtok3@10.3.4: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} @@ -8147,6 +8993,11 @@ packages: resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} hasBin: true + swr@2.4.1: + resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + symbol-observable@4.0.0: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} engines: {node: '>=0.10'} @@ -8216,6 +9067,10 @@ packages: resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} engines: {node: '>=20'} + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + tiny-emitter@2.1.0: resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} @@ -8375,6 +9230,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turndown@7.2.4: + resolution: {integrity: sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==} + engines: {node: '>=18', npm: '>=9'} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -8467,6 +9326,9 @@ packages: undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -8480,7 +9342,13 @@ packages: resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==} engines: {node: '>=20.18.1'} - unicode-canonical-property-names-ecmascript@2.0.1: + unenv@2.0.0-rc.15: + resolution: {integrity: sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA==} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -8519,6 +9387,9 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -8603,6 +9474,11 @@ packages: resolution: {integrity: sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -8799,6 +9675,10 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -8986,6 +9866,14 @@ packages: xcase@2.0.1: resolution: {integrity: sha512-UmFXIPU+9Eg3E9m/728Bii0lAIuoc+6nbrNUKaRPJOFp91ih44qqGlWtxMB6kXFrRD6po+86ksHM5XHCfk6iPw==} + xdg-app-paths@8.3.0: + resolution: {integrity: sha512-mgxlWVZw0TNWHoGmXq+NC3uhCIc55dDpAlDkMQUaIAcQzysb0kxctwv//fvuW61/nAAeUBJMQ8mnZjMmuYwOcQ==} + engines: {node: '>= 4.0'} + + xdg-portable@10.6.0: + resolution: {integrity: sha512-xrcqhWDvtZ7WLmt8G4f3hHy37iK7D2idtosRgkeiSPZEPmBShp0VfmRBLWAPC6zLF48APJ21yfea+RfQMF4/Aw==} + engines: {node: '>= 4.0'} + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -9052,6 +9940,11 @@ packages: react: optional: true + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod-validation-error@3.5.4: resolution: {integrity: sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==} engines: {node: '>=18.0.0'} @@ -9066,6 +9959,46 @@ packages: snapshots: + '@ai-sdk/openai-compatible@0.2.16(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + zod: 3.25.76 + + '@ai-sdk/openai@1.3.24(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + zod: 3.25.76 + + '@ai-sdk/provider-utils@2.2.8(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + nanoid: 3.3.11 + secure-json-parse: 2.7.0 + zod: 3.25.76 + + '@ai-sdk/provider@1.1.3': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@1.2.12(react@19.2.5)(zod@3.25.76)': + dependencies: + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) + react: 19.2.5 + swr: 2.4.1(react@19.2.5) + throttleit: 2.1.0 + optionalDependencies: + zod: 3.25.76 + + '@ai-sdk/ui-utils@1.2.11(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -9184,7 +10117,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.6.0 - tinyexec: 1.0.2 + tinyexec: 1.0.4 '@antfu/utils@0.7.10': {} @@ -9214,6 +10147,12 @@ snapshots: call-me-maybe: 1.0.2 openapi-types: 12.1.3 + '@ark/schema@0.56.0': + dependencies: + '@ark/util': 0.56.0 + + '@ark/util@0.56.0': {} + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -9222,6 +10161,977 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-cognito-identity@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-dynamodb@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/dynamodb-codec': 3.972.28 + '@aws-sdk/middleware-endpoint-discovery': 3.972.10 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-iam@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-lambda@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/eventstream-serde-browser': 4.2.13 + '@smithy/eventstream-serde-config-resolver': 4.3.13 + '@smithy/eventstream-serde-node': 4.2.13 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-s3@3.1029.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-bucket-endpoint': 3.972.9 + '@aws-sdk/middleware-expect-continue': 3.972.9 + '@aws-sdk/middleware-flexible-checksums': 3.974.7 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-location-constraint': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-sdk-s3': 3.972.28 + '@aws-sdk/middleware-ssec': 3.972.9 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/signature-v4-multi-region': 3.996.16 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/eventstream-serde-browser': 4.2.13 + '@smithy/eventstream-serde-config-resolver': 4.3.13 + '@smithy/eventstream-serde-node': 4.2.13 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-blob-browser': 4.2.14 + '@smithy/hash-node': 4.2.13 + '@smithy/hash-stream-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/md5-js': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sagemaker@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-ses@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sesv2@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/signature-v4-multi-region': 3.996.16 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sqs@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-sdk-sqs': 3.972.19 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/md5-js': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-ssm@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sts@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.973.27': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws-sdk/xml-builder': 3.972.17 + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.6': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-cognito-identity@3.972.22': + dependencies: + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-env@3.972.25': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.27': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/node-http-handler': 4.5.2 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-stream': 4.5.22 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-env': 3.972.25 + '@aws-sdk/credential-provider-http': 3.972.27 + '@aws-sdk/credential-provider-login': 3.972.29 + '@aws-sdk/credential-provider-process': 3.972.25 + '@aws-sdk/credential-provider-sso': 3.972.29 + '@aws-sdk/credential-provider-web-identity': 3.972.29 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.30': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.25 + '@aws-sdk/credential-provider-http': 3.972.27 + '@aws-sdk/credential-provider-ini': 3.972.29 + '@aws-sdk/credential-provider-process': 3.972.25 + '@aws-sdk/credential-provider-sso': 3.972.29 + '@aws-sdk/credential-provider-web-identity': 3.972.29 + '@aws-sdk/types': 3.973.7 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.25': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/token-providers': 3.1026.0 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-providers@3.1029.0': + dependencies: + '@aws-sdk/client-cognito-identity': 3.1029.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-cognito-identity': 3.972.22 + '@aws-sdk/credential-provider-env': 3.972.25 + '@aws-sdk/credential-provider-http': 3.972.27 + '@aws-sdk/credential-provider-ini': 3.972.29 + '@aws-sdk/credential-provider-login': 3.972.29 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/credential-provider-process': 3.972.25 + '@aws-sdk/credential-provider-sso': 3.972.29 + '@aws-sdk/credential-provider-web-identity': 3.972.29 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/dynamodb-codec@3.972.28': + dependencies: + '@aws-sdk/core': 3.973.27 + '@smithy/core': 3.23.14 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@aws-sdk/endpoint-cache@3.972.5': + dependencies: + mnemonist: 0.38.3 + tslib: 2.8.1 + + '@aws-sdk/middleware-bucket-endpoint@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-endpoint-discovery@3.972.10': + dependencies: + '@aws-sdk/endpoint-cache': 3.972.5 + '@aws-sdk/types': 3.973.7 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.974.7': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/crc64-nvme': 3.972.6 + '@aws-sdk/types': 3.973.7 + '@smithy/is-array-buffer': 4.2.2 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.28': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-sqs@3.972.19': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@smithy/core': 3.23.14 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-retry': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.996.19': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/node-config-provider@3.374.0': + dependencies: + '@smithy/node-config-provider': 1.1.0 + tslib: 2.8.1 + + '@aws-sdk/region-config-resolver@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/config-resolver': 4.4.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.16': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.28 + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1026.0': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.7': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.972.3': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.6': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-endpoints': 3.3.4 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.15': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/types': 3.973.7 + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.17': + dependencies: + '@smithy/types': 4.14.0 + fast-xml-parser: 5.5.8 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -9907,6 +11817,15 @@ snapshots: '@clack/core': 1.1.0 sisteransi: 1.0.5 + '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)': + dependencies: + unenv: 2.0.0-rc.24 + + '@cloudflare/workers-shared@0.17.5': + dependencies: + mime: 3.0.0 + zod: 3.25.76 + '@colors/colors@1.5.0': optional: true @@ -9992,7 +11911,7 @@ snapshots: '@commitlint/types': 20.5.0 git-raw-commits: 5.0.1(conventional-commits-parser@6.3.0) minimist: 1.2.8 - tinyexec: 1.0.2 + tinyexec: 1.0.4 transitivePeerDependencies: - conventional-commits-filter - conventional-commits-parser @@ -10032,9 +11951,9 @@ snapshots: optionalDependencies: conventional-commits-parser: 6.3.0 - '@cpn-console/argocd-plugin@file:plugins/argocd(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))': + '@cpn-console/argocd-plugin@file:plugins/argocd(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76)': dependencies: - '@cpn-console/gitlab-plugin': file:plugins/gitlab(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/gitlab-plugin': file:plugins/gitlab(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/hooks': file:packages/hooks(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) '@cpn-console/keycloak-plugin': file:plugins/keycloak(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) '@cpn-console/shared': file:packages/shared(@types/node@22.19.15) @@ -10044,27 +11963,201 @@ snapshots: axios: 1.13.6 js-yaml: 4.1.0 transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' + - '@types/node' + - ai + - arktype + - aws4fetch + - cloudflare + - debug + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown + - typescript + - vitest + - xdg-app-paths + - yaml + - zod + + '@cpn-console/argocd-plugin@file:plugins/argocd(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76)': + dependencies: + '@cpn-console/gitlab-plugin': file:plugins/gitlab(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) + '@cpn-console/hooks': file:packages/hooks(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/keycloak-plugin': file:plugins/keycloak(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/shared': file:packages/shared(@types/node@24.12.0) + '@cpn-console/vault-plugin': file:plugins/vault(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@himenon/argocd-typescript-openapi': 1.2.2 + '@types/js-yaml': 4.0.9 + axios: 1.13.6 + js-yaml: 4.1.0 + transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' - '@types/node' + - ai + - arktype + - aws4fetch + - cloudflare - debug + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown - typescript - vitest + - xdg-app-paths + - yaml + - zod - '@cpn-console/gitlab-plugin@file:plugins/gitlab(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))': + '@cpn-console/gitlab-plugin@file:plugins/gitlab(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76)': dependencies: '@cpn-console/hooks': file:packages/hooks(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) '@cpn-console/logger': file:packages/logger + '@cpn-console/miracle': file:packages/miracle(748074081c404d066ef4fc09dbd8b44c) '@cpn-console/shared': file:packages/shared(@types/node@22.19.15) '@cpn-console/vault-plugin': file:plugins/vault(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) - '@gitbeaker/core': 40.6.0 - '@gitbeaker/requester-utils': 40.6.0 - '@gitbeaker/rest': 40.6.0 - axios: 1.13.6 - js-yaml: 4.1.0 transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' + - '@types/node' + - ai + - arktype + - aws4fetch + - cloudflare + - debug + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown + - typescript + - vitest + - xdg-app-paths + - yaml + - zod + + '@cpn-console/gitlab-plugin@file:plugins/gitlab(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76)': + dependencies: + '@cpn-console/hooks': file:packages/hooks(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/logger': file:packages/logger + '@cpn-console/miracle': file:packages/miracle(748074081c404d066ef4fc09dbd8b44c) + '@cpn-console/shared': file:packages/shared(@types/node@24.12.0) + '@cpn-console/vault-plugin': file:plugins/vault(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' - '@types/node' + - ai + - arktype + - aws4fetch + - cloudflare - debug + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown - typescript - vitest + - xdg-app-paths + - yaml + - zod '@cpn-console/harbor-plugin@file:plugins/harbor(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))': dependencies: @@ -10094,6 +12187,18 @@ snapshots: - typescript - vitest + '@cpn-console/hooks@file:packages/hooks(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))': + dependencies: + '@cpn-console/logger': file:packages/logger + '@cpn-console/shared': file:packages/shared(@types/node@24.12.0) + json-schema: 0.4.0 + vitest-mock-extended: 2.0.2(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + zod: 3.25.76 + transitivePeerDependencies: + - '@types/node' + - typescript + - vitest + '@cpn-console/keycloak-plugin@file:plugins/keycloak(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))': dependencies: '@cpn-console/hooks': file:packages/hooks(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) @@ -10107,25 +12212,171 @@ snapshots: - typescript - vitest + '@cpn-console/keycloak-plugin@file:plugins/keycloak(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))': + dependencies: + '@cpn-console/hooks': file:packages/hooks(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/logger': file:packages/logger + '@cpn-console/shared': file:packages/shared(@types/node@24.12.0) + '@keycloak/keycloak-admin-client': 26.5.5 + axios: 1.13.6 + transitivePeerDependencies: + - '@types/node' + - debug + - typescript + - vitest + '@cpn-console/logger@file:packages/logger': dependencies: pino: 9.14.0 pino-pretty: 13.1.3 zod: 3.25.76 - '@cpn-console/nexus-plugin@file:plugins/nexus(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))': + '@cpn-console/miracle@file:packages/miracle(748074081c404d066ef4fc09dbd8b44c)': + dependencies: + '@gitbeaker/rest': 40.6.0 + '@prisma/client': 6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3) + alchemy: 0.23.0(@ai-sdk/openai-compatible@0.2.16(zod@3.25.76))(@ai-sdk/openai@1.3.24(zod@3.25.76))(@aws-sdk/client-dynamodb@3.1029.0)(@aws-sdk/client-iam@3.1029.0)(@aws-sdk/client-lambda@3.1029.0)(@aws-sdk/client-s3@3.1029.0)(@aws-sdk/client-sagemaker@3.1029.0)(@aws-sdk/client-ses@3.1029.0)(@aws-sdk/client-sesv2@3.1029.0)(@aws-sdk/client-sqs@3.1029.0)(@aws-sdk/client-ssm@3.1029.0)(@aws-sdk/client-sts@3.1029.0)(@aws-sdk/credential-providers@3.1029.0)(@aws-sdk/node-config-provider@3.374.0)(@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24))(@cloudflare/workers-shared@0.17.5)(@iarna/toml@2.2.5)(@octokit/rest@21.1.1)(@swc/core@1.15.24(@swc/helpers@0.5.19))(ai@4.3.19(react@19.2.5)(zod@3.25.76))(arktype@2.2.0)(aws4fetch@1.0.20)(cloudflare@4.5.0)(diff@4.0.4)(esbuild@0.27.3)(fast-json-patch@3.1.1)(glob@13.0.6)(hono@4.12.12)(jszip@3.10.1)(libsodium-wrappers@0.7.16)(prettier@3.8.2)(stripe@17.7.0)(turndown@7.2.4)(xdg-app-paths@8.3.0)(yaml@2.8.2)(zod@3.25.76) + transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' + - ai + - arktype + - aws4fetch + - cloudflare + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown + - typescript + - xdg-app-paths + - yaml + - zod + + '@cpn-console/nexus-plugin@file:plugins/nexus(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76)': dependencies: - '@cpn-console/gitlab-plugin': file:plugins/gitlab(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/gitlab-plugin': file:plugins/gitlab(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/hooks': file:packages/hooks(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) '@cpn-console/logger': file:packages/logger '@cpn-console/shared': file:packages/shared(@types/node@22.19.15) '@cpn-console/vault-plugin': file:plugins/vault(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) axios: 1.13.6 transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' + - '@types/node' + - ai + - arktype + - aws4fetch + - cloudflare + - debug + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown + - typescript + - vitest + - xdg-app-paths + - yaml + - zod + + '@cpn-console/nexus-plugin@file:plugins/nexus(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76)': + dependencies: + '@cpn-console/gitlab-plugin': file:plugins/gitlab(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) + '@cpn-console/hooks': file:packages/hooks(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/logger': file:packages/logger + '@cpn-console/shared': file:packages/shared(@types/node@24.12.0) + '@cpn-console/vault-plugin': file:plugins/vault(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + axios: 1.13.6 + transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' - '@types/node' + - ai + - arktype + - aws4fetch + - cloudflare - debug + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown - typescript - vitest + - xdg-app-paths + - yaml + - zod '@cpn-console/shared@file:packages/shared(@types/node@22.19.15)': dependencies: @@ -10137,9 +12388,19 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@cpn-console/sonarqube-plugin@file:plugins/sonarqube(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))': + '@cpn-console/shared@file:packages/shared(@types/node@24.12.0)': + dependencies: + '@cpn-console/logger': file:packages/logger + '@ts-rest/core': 3.52.1(@types/node@24.12.0)(zod@3.25.76) + short-uuid: 5.2.0 + zod: 3.25.76 + zod-validation-error: 3.5.4(zod@3.25.76) + transitivePeerDependencies: + - '@types/node' + + '@cpn-console/sonarqube-plugin@file:plugins/sonarqube(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76)': dependencies: - '@cpn-console/gitlab-plugin': file:plugins/gitlab(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/gitlab-plugin': file:plugins/gitlab(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) '@cpn-console/hooks': file:packages/hooks(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) '@cpn-console/keycloak-plugin': file:plugins/keycloak(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) '@cpn-console/logger': file:packages/logger @@ -10147,10 +12408,99 @@ snapshots: '@cpn-console/vault-plugin': file:plugins/vault(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) axios: 1.13.6 transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' - '@types/node' + - ai + - arktype + - aws4fetch + - cloudflare - debug + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown + - typescript + - vitest + - xdg-app-paths + - yaml + - zod + + '@cpn-console/sonarqube-plugin@file:plugins/sonarqube(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76)': + dependencies: + '@cpn-console/gitlab-plugin': file:plugins/gitlab(@types/node@24.12.0)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))(yaml@2.8.2)(zod@3.25.76) + '@cpn-console/hooks': file:packages/hooks(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/keycloak-plugin': file:plugins/keycloak(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/logger': file:packages/logger + '@cpn-console/shared': file:packages/shared(@types/node@24.12.0) + '@cpn-console/vault-plugin': file:plugins/vault(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + axios: 1.13.6 + transitivePeerDependencies: + - '@ai-sdk/openai' + - '@ai-sdk/openai-compatible' + - '@aws-sdk/client-dynamodb' + - '@aws-sdk/client-iam' + - '@aws-sdk/client-lambda' + - '@aws-sdk/client-s3' + - '@aws-sdk/client-sagemaker' + - '@aws-sdk/client-ses' + - '@aws-sdk/client-sesv2' + - '@aws-sdk/client-sqs' + - '@aws-sdk/client-ssm' + - '@aws-sdk/client-sts' + - '@aws-sdk/credential-providers' + - '@aws-sdk/node-config-provider' + - '@cloudflare/unenv-preset' + - '@cloudflare/workers-shared' + - '@iarna/toml' + - '@octokit/rest' + - '@swc/core' + - '@types/node' + - ai + - arktype + - aws4fetch + - cloudflare + - debug + - diff + - esbuild + - fast-json-patch + - glob + - hono + - jszip + - libsodium-wrappers + - prettier + - prisma + - stripe + - turndown - typescript - vitest + - xdg-app-paths + - yaml + - zod '@cpn-console/test-utils@file:packages/test-utils(@types/node@22.19.15)': dependencies: @@ -10171,6 +12521,18 @@ snapshots: - typescript - vitest + '@cpn-console/vault-plugin@file:plugins/vault(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0))': + dependencies: + '@cpn-console/hooks': file:packages/hooks(@types/node@24.12.0)(typescript@5.9.3)(vitest@2.1.9(@types/node@24.12.0)(jsdom@25.0.1)(msw@2.12.10(@types/node@24.12.0)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/logger': file:packages/logger + '@cpn-console/shared': file:packages/shared(@types/node@24.12.0) + axios: 1.13.6 + transitivePeerDependencies: + - '@types/node' + - debug + - typescript + - vitest + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -10649,6 +13011,8 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iarna/toml@2.2.5': {} + '@iconify-json/ri@1.2.10': dependencies: '@iconify/types': 2.0.0 @@ -10930,6 +13294,8 @@ snapshots: '@lukeed/ms@2.0.2': {} + '@mixmark-io/domino@2.2.0': {} + '@mswjs/interceptors@0.41.3': dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -10946,7 +13312,7 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@nestjs/cli@11.0.16(@types/node@22.19.15)': + '@nestjs/cli@11.0.16(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)': dependencies: '@angular-devkit/core': 19.2.19(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) @@ -10957,15 +13323,17 @@ snapshots: chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.104.1) + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19))) glob: 13.0.0 node-emoji: 1.11.0 ora: 5.4.1 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.9.3 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19)) webpack-node-externals: 3.0.0 + optionalDependencies: + '@swc/core': 1.15.24(@swc/helpers@0.5.19) transitivePeerDependencies: - '@types/node' - esbuild @@ -11080,6 +13448,74 @@ snapshots: dependencies: consola: 3.4.2 + '@octokit/auth-token@5.1.2': {} + + '@octokit/core@6.1.6': + dependencies: + '@octokit/auth-token': 5.1.2 + '@octokit/graphql': 8.2.2 + '@octokit/request': 9.2.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@10.1.4': + dependencies: + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@8.2.2': + dependencies: + '@octokit/request': 9.2.4 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@24.2.0': {} + + '@octokit/openapi-types@25.1.0': {} + + '@octokit/plugin-paginate-rest@11.6.0(@octokit/core@6.1.6)': + dependencies: + '@octokit/core': 6.1.6 + '@octokit/types': 13.10.0 + + '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.6)': + dependencies: + '@octokit/core': 6.1.6 + + '@octokit/plugin-rest-endpoint-methods@13.5.0(@octokit/core@6.1.6)': + dependencies: + '@octokit/core': 6.1.6 + '@octokit/types': 13.10.0 + + '@octokit/request-error@6.1.8': + dependencies: + '@octokit/types': 14.1.0 + + '@octokit/request@9.2.4': + dependencies: + '@octokit/endpoint': 10.1.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + fast-content-type-parse: 2.0.1 + universal-user-agent: 7.0.3 + + '@octokit/rest@21.1.1': + dependencies: + '@octokit/core': 6.1.6 + '@octokit/plugin-paginate-rest': 11.6.0(@octokit/core@6.1.6) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.6) + '@octokit/plugin-rest-endpoint-methods': 13.5.0(@octokit/core@6.1.6) + + '@octokit/types@13.10.0': + dependencies: + '@octokit/openapi-types': 24.2.0 + + '@octokit/types@14.1.0': + dependencies: + '@octokit/openapi-types': 25.1.0 + '@open-draft/deferred-promise@2.2.0': {} '@open-draft/logger@0.3.0': @@ -12086,96 +14522,450 @@ snapshots: '@rollup/pluginutils@5.3.0(rollup@2.80.0)': dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 2.80.0 + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 2.80.0 + + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + + '@simple-libs/child-process-utils@1.0.2': + dependencies: + '@simple-libs/stream-utils': 1.2.0 + + '@simple-libs/stream-utils@1.2.0': {} + + '@sindresorhus/base62@1.0.0': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@smithy/chunked-blob-reader-native@4.2.3': + dependencies: + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@5.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.14': + dependencies: + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + tslib: 2.8.1 + + '@smithy/core@3.23.14': + dependencies: + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.13': + dependencies: + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + tslib: 2.8.1 + + '@smithy/eventstream-codec@4.2.13': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.0 + '@smithy/util-hex-encoding': 4.2.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.13': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.3.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.2.13': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@4.2.13': + dependencies: + '@smithy/eventstream-codec': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.16': + dependencies: + '@smithy/protocol-http': 5.3.13 + '@smithy/querystring-builder': 4.2.13 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@4.2.14': + dependencies: + '@smithy/chunked-blob-reader': 5.2.2 + '@smithy/chunked-blob-reader-native': 4.2.3 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/hash-stream-node@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.13': + dependencies: + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.4.29': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/middleware-serde': 4.2.17 + '@smithy/node-config-provider': 4.3.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-middleware': 4.2.13 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.5.1': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/service-error-classification': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.17': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/node-config-provider@1.1.0': + dependencies: + '@smithy/property-provider': 1.2.0 + '@smithy/shared-ini-file-loader': 1.1.0 + '@smithy/types': 1.2.0 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.13': + dependencies: + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.5.2': + dependencies: + '@smithy/protocol-http': 5.3.13 + '@smithy/querystring-builder': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/property-provider@1.2.0': + dependencies: + '@smithy/types': 1.2.0 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-android-arm-eabi@4.59.0': - optional: true + '@smithy/protocol-http@5.3.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-android-arm64@4.59.0': - optional: true + '@smithy/querystring-builder@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-darwin-arm64@4.59.0': - optional: true + '@smithy/querystring-parser@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-darwin-x64@4.59.0': - optional: true + '@smithy/service-error-classification@4.2.13': + dependencies: + '@smithy/types': 4.14.0 - '@rollup/rollup-freebsd-arm64@4.59.0': - optional: true + '@smithy/shared-ini-file-loader@1.1.0': + dependencies: + '@smithy/types': 1.2.0 + tslib: 2.8.1 - '@rollup/rollup-freebsd-x64@4.59.0': - optional: true + '@smithy/shared-ini-file-loader@4.4.8': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - optional: true + '@smithy/signature-v4@5.3.13': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - optional: true + '@smithy/smithy-client@4.12.9': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-stack': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-stream': 4.5.22 + tslib: 2.8.1 - '@rollup/rollup-linux-arm64-gnu@4.59.0': - optional: true + '@smithy/types@1.2.0': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-linux-arm64-musl@4.59.0': - optional: true + '@smithy/types@4.14.0': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-linux-loong64-gnu@4.59.0': - optional: true + '@smithy/url-parser@4.2.13': + dependencies: + '@smithy/querystring-parser': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-loong64-musl@4.59.0': - optional: true + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - optional: true + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-linux-ppc64-musl@4.59.0': - optional: true + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - optional: true + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 - '@rollup/rollup-linux-riscv64-musl@4.59.0': - optional: true + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-linux-s390x-gnu@4.59.0': - optional: true + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-linux-x64-gnu@4.59.0': - optional: true + '@smithy/util-defaults-mode-browser@4.3.45': + dependencies: + '@smithy/property-provider': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-x64-musl@4.59.0': - optional: true + '@smithy/util-defaults-mode-node@4.2.49': + dependencies: + '@smithy/config-resolver': 4.4.14 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-openbsd-x64@4.59.0': - optional: true + '@smithy/util-endpoints@3.3.4': + dependencies: + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-openharmony-arm64@4.59.0': - optional: true + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-win32-arm64-msvc@4.59.0': - optional: true + '@smithy/util-middleware@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-win32-ia32-msvc@4.59.0': - optional: true + '@smithy/util-retry@4.3.1': + dependencies: + '@smithy/service-error-classification': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-win32-x64-gnu@4.59.0': - optional: true + '@smithy/util-stream@4.5.22': + dependencies: + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/node-http-handler': 4.5.2 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-win32-x64-msvc@4.59.0': - optional: true + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 - '@simple-libs/child-process-utils@1.0.2': + '@smithy/util-utf8@2.3.0': dependencies: - '@simple-libs/stream-utils': 1.2.0 + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 - '@simple-libs/stream-utils@1.2.0': {} + '@smithy/util-utf8@4.2.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 - '@sindresorhus/base62@1.0.0': {} + '@smithy/util-waiter@4.2.15': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@sindresorhus/merge-streams@4.0.0': {} + '@smithy/uuid@1.1.2': + dependencies: + tslib: 2.8.1 '@standard-schema/spec@1.1.0': {} @@ -12207,10 +14997,71 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 + '@swc/core-darwin-arm64@1.15.24': + optional: true + + '@swc/core-darwin-x64@1.15.24': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.24': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.24': + optional: true + + '@swc/core-linux-arm64-musl@1.15.24': + optional: true + + '@swc/core-linux-ppc64-gnu@1.15.24': + optional: true + + '@swc/core-linux-s390x-gnu@1.15.24': + optional: true + + '@swc/core-linux-x64-gnu@1.15.24': + optional: true + + '@swc/core-linux-x64-musl@1.15.24': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.24': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.24': + optional: true + + '@swc/core-win32-x64-msvc@1.15.24': + optional: true + + '@swc/core@1.15.24(@swc/helpers@0.5.19)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.26 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.24 + '@swc/core-darwin-x64': 1.15.24 + '@swc/core-linux-arm-gnueabihf': 1.15.24 + '@swc/core-linux-arm64-gnu': 1.15.24 + '@swc/core-linux-arm64-musl': 1.15.24 + '@swc/core-linux-ppc64-gnu': 1.15.24 + '@swc/core-linux-s390x-gnu': 1.15.24 + '@swc/core-linux-x64-gnu': 1.15.24 + '@swc/core-linux-x64-musl': 1.15.24 + '@swc/core-win32-arm64-msvc': 1.15.24 + '@swc/core-win32-ia32-msvc': 1.15.24 + '@swc/core-win32-x64-msvc': 1.15.24 + '@swc/helpers': 0.5.19 + + '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.19': dependencies: tslib: 2.8.1 + '@swc/types@0.1.26': + dependencies: + '@swc/counter': 0.1.3 + '@tokenizer/inflate@0.4.1': dependencies: debug: 4.4.3(supports-color@5.5.0) @@ -12317,6 +15168,8 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/diff-match-patch@1.0.36': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -12376,6 +15229,15 @@ snapshots: dependencies: '@types/node': 24.12.0 + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 24.12.0 + form-data: 4.0.5 + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@22.19.15': dependencies: undici-types: 6.21.0 @@ -13102,6 +15964,10 @@ snapshots: '@xtuc/long@4.2.2': {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + abstract-logging@2.0.1: {} accepts@2.0.0: @@ -13129,6 +15995,22 @@ snapshots: agent-base@7.1.4: {} + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + ai@4.3.19(react@19.2.5)(zod@3.25.76): + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + '@ai-sdk/react': 1.2.12(react@19.2.5)(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) + '@opentelemetry/api': 1.9.0 + jsondiffpatch: 0.6.0 + zod: 3.25.76 + optionalDependencies: + react: 19.2.5 + ajv-draft-04@1.0.0(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -13187,6 +16069,46 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + alchemy@0.23.0(@ai-sdk/openai-compatible@0.2.16(zod@3.25.76))(@ai-sdk/openai@1.3.24(zod@3.25.76))(@aws-sdk/client-dynamodb@3.1029.0)(@aws-sdk/client-iam@3.1029.0)(@aws-sdk/client-lambda@3.1029.0)(@aws-sdk/client-s3@3.1029.0)(@aws-sdk/client-sagemaker@3.1029.0)(@aws-sdk/client-ses@3.1029.0)(@aws-sdk/client-sesv2@3.1029.0)(@aws-sdk/client-sqs@3.1029.0)(@aws-sdk/client-ssm@3.1029.0)(@aws-sdk/client-sts@3.1029.0)(@aws-sdk/credential-providers@3.1029.0)(@aws-sdk/node-config-provider@3.374.0)(@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24))(@cloudflare/workers-shared@0.17.5)(@iarna/toml@2.2.5)(@octokit/rest@21.1.1)(@swc/core@1.15.24(@swc/helpers@0.5.19))(ai@4.3.19(react@19.2.5)(zod@3.25.76))(arktype@2.2.0)(aws4fetch@1.0.20)(cloudflare@4.5.0)(diff@4.0.4)(esbuild@0.27.3)(fast-json-patch@3.1.1)(glob@13.0.6)(hono@4.12.12)(jszip@3.10.1)(libsodium-wrappers@0.7.16)(prettier@3.8.2)(stripe@17.7.0)(turndown@7.2.4)(xdg-app-paths@8.3.0)(yaml@2.8.2)(zod@3.25.76): + dependencies: + '@ai-sdk/openai': 1.3.24(zod@3.25.76) + '@ai-sdk/openai-compatible': 0.2.16(zod@3.25.76) + '@aws-sdk/client-dynamodb': 3.1029.0 + '@aws-sdk/client-iam': 3.1029.0 + '@aws-sdk/client-lambda': 3.1029.0 + '@aws-sdk/client-s3': 3.1029.0 + '@aws-sdk/client-sagemaker': 3.1029.0 + '@aws-sdk/client-ses': 3.1029.0 + '@aws-sdk/client-sesv2': 3.1029.0 + '@aws-sdk/client-sqs': 3.1029.0 + '@aws-sdk/client-ssm': 3.1029.0 + '@aws-sdk/client-sts': 3.1029.0 + '@aws-sdk/credential-providers': 3.1029.0 + '@aws-sdk/node-config-provider': 3.374.0 + '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24) + '@cloudflare/workers-shared': 0.17.5 + '@iarna/toml': 2.2.5 + '@octokit/rest': 21.1.1 + '@swc/core': 1.15.24(@swc/helpers@0.5.19) + ai: 4.3.19(react@19.2.5)(zod@3.25.76) + arktype: 2.2.0 + aws4fetch: 1.0.20 + cloudflare: 4.5.0 + diff: 4.0.4 + esbuild: 0.27.3 + fast-json-patch: 3.1.1 + glob: 13.0.6 + hono: 4.12.12 + jszip: 3.10.1 + libsodium-wrappers: 0.7.16 + prettier: 3.8.2 + stripe: 17.7.0 + turndown: 7.2.4 + unenv: 2.0.0-rc.15 + xdg-app-paths: 8.3.0 + yaml: 2.8.2 + zod: 3.25.76 + alien-signals@1.0.13: {} ansi-align@3.0.1: @@ -13226,6 +16148,16 @@ snapshots: argparse@2.0.1: {} + arkregex@0.0.5: + dependencies: + '@ark/util': 0.56.0 + + arktype@2.2.0: + dependencies: + '@ark/schema': 0.56.0 + '@ark/util': 0.56.0 + arkregex: 0.0.5 + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -13277,6 +16209,8 @@ snapshots: '@fastify/error': 3.4.1 fastq: 1.20.1 + aws4fetch@1.0.20: {} + axios-retry@4.5.0(axios@1.13.6): dependencies: axios: 1.13.6 @@ -13322,6 +16256,8 @@ snapshots: baseline-browser-mapping@2.10.0: {} + before-after-hook@3.0.2: {} + bignumber.js@9.3.1: {} binary-extensions@2.3.0: {} @@ -13350,6 +16286,8 @@ snapshots: boolbase@1.0.0: {} + bowser@2.14.1: {} + boxen@5.1.2: dependencies: ansi-align: 3.0.1 @@ -13592,6 +16530,18 @@ snapshots: clone@1.0.4: {} + cloudflare@4.5.0: + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + clsx@2.1.1: {} color-convert@2.0.1: @@ -13838,6 +16788,8 @@ snapshots: asap: 2.0.6 wrappy: 1.0.2 + diff-match-patch@1.0.5: {} + diff-sequences@29.6.3: {} diff@4.0.4: {} @@ -14504,6 +17456,8 @@ snapshots: etag@1.8.1: {} + event-target-shim@5.0.1: {} + eventemitter2@6.4.9: {} eventemitter3@5.0.4: {} @@ -14555,6 +17509,8 @@ snapshots: fast-content-type-parse@1.1.0: {} + fast-content-type-parse@2.0.1: {} + fast-copy@4.0.2: {} fast-decode-uri-component@1.0.1: {} @@ -14569,6 +17525,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-patch@3.1.1: {} + fast-json-stable-stringify@2.1.0: {} fast-json-stringify@5.16.1: @@ -14600,6 +17558,16 @@ snapshots: fast-uri@3.1.0: {} + fast-xml-builder@1.1.4: + dependencies: + path-expression-matcher: 1.5.0 + + fast-xml-parser@5.5.8: + dependencies: + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.5.0 + strnum: 2.2.3 + fastest-levenshtein@1.0.16: {} fastfall@1.5.1: @@ -14754,7 +17722,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.104.1): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19))): dependencies: '@babel/code-frame': 7.29.0 chalk: 4.1.2 @@ -14769,7 +17737,9 @@ snapshots: semver: 7.7.4 tapable: 2.3.0 typescript: 5.9.3 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19)) + + form-data-encoder@1.7.2: {} form-data@4.0.5: dependencies: @@ -14781,6 +17751,11 @@ snapshots: format@0.2.2: {} + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -15070,6 +18045,8 @@ snapshots: minimalistic-crypto-utils: 1.0.1 optional: true + hono@4.12.12: {} + hookified@1.15.1: {} html-encoding-sniffer@4.0.0: @@ -15121,6 +18098,10 @@ snapshots: transitivePeerDependencies: - supports-color + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + husky@9.1.7: {} iconv-lite@0.6.3: @@ -15501,6 +18482,12 @@ snapshots: jsonc-parser@3.3.1: {} + jsondiffpatch@0.6.0: + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.6.2 + diff-match-patch: 1.0.5 + jsonfile@6.2.0: dependencies: universalify: 2.0.1 @@ -15555,6 +18542,12 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libsodium-wrappers@0.7.16: + dependencies: + libsodium: 0.7.16 + + libsodium@0.7.16: {} + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -16085,6 +19078,10 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 + mnemonist@0.38.3: + dependencies: + obliterator: 1.6.1 + mnemonist@0.39.8: dependencies: obliterator: 2.0.5 @@ -16272,7 +19269,7 @@ snapshots: dependencies: citty: 0.2.1 pathe: 2.0.3 - tinyexec: 1.0.2 + tinyexec: 1.0.4 oas-kit-common@1.0.8: dependencies: @@ -16324,6 +19321,8 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 + obliterator@1.6.1: {} + obliterator@2.0.5: {} ofetch@1.5.1: @@ -16379,6 +19378,10 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + os-paths@7.4.0: + optionalDependencies: + fsevents: 2.3.3 + outvariant@1.4.3: {} own-keys@1.0.1: @@ -16462,6 +19465,8 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.5.0: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -16653,6 +19658,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.8.2: {} + pretty-bytes@5.6.0: {} pretty-bytes@6.1.1: {} @@ -16766,6 +19773,8 @@ snapshots: re2-wasm@1.0.2: optional: true + react@19.2.5: {} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -17370,6 +20379,13 @@ snapshots: dependencies: js-tokens: 9.0.1 + stripe@17.7.0: + dependencies: + '@types/node': 24.12.0 + qs: 6.15.0 + + strnum@2.2.3: {} + strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 @@ -17486,7 +20502,7 @@ snapshots: swagger-schema-official@2.0.0-bab6bed: {} - swagger-typescript-api@13.3.1(magicast@0.3.5): + swagger-typescript-api@13.3.1(magicast@0.3.5)(react@19.2.5): dependencies: '@apidevtools/swagger-parser': 12.1.0(openapi-types@12.1.3) '@biomejs/js-api': 4.0.0(@biomejs/wasm-nodejs@2.4.6) @@ -17504,7 +20520,7 @@ snapshots: type-fest: 5.4.4 typescript: 5.9.3 yaml: 2.8.2 - yummies: 7.10.0 + yummies: 7.10.0(react@19.2.5) transitivePeerDependencies: - '@biomejs/wasm-bundler' - '@biomejs/wasm-web' @@ -17529,6 +20545,12 @@ snapshots: transitivePeerDependencies: - encoding + swr@2.4.1(react@19.2.5): + dependencies: + dequal: 2.0.3 + react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) + symbol-observable@4.0.0: {} symbol-tree@3.2.4: {} @@ -17562,13 +20584,15 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser-webpack-plugin@5.4.0(webpack@5.104.1): + terser-webpack-plugin@5.4.0(@swc/core@1.15.24(@swc/helpers@0.5.19))(webpack@5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19))): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19)) + optionalDependencies: + '@swc/core': 1.15.24(@swc/helpers@0.5.19) terser@5.46.0: dependencies: @@ -17591,6 +20615,8 @@ snapshots: dependencies: real-require: 0.2.0 + throttleit@2.1.0: {} + tiny-emitter@2.1.0: {} tinybench@2.9.0: {} @@ -17686,7 +20712,7 @@ snapshots: optionalDependencies: typescript: 5.9.3 - ts-loader@9.5.4(typescript@5.9.3)(webpack@5.104.1): + ts-loader@9.5.4(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19))): dependencies: chalk: 4.1.2 enhanced-resolve: 5.20.0 @@ -17694,9 +20720,9 @@ snapshots: semver: 7.7.4 source-map: 0.7.6 typescript: 5.9.3 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19)) - ts-node@10.9.2(@types/node@22.19.15)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.24(@swc/helpers@0.5.19))(@types/node@22.19.15)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 @@ -17713,6 +20739,8 @@ snapshots: typescript: 5.9.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.15.24(@swc/helpers@0.5.19) ts-patch@3.3.0: dependencies: @@ -17738,6 +20766,10 @@ snapshots: tslib@2.8.1: {} + turndown@7.2.4: + dependencies: + '@mixmark-io/domino': 2.2.0 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -17859,6 +20891,8 @@ snapshots: undefsafe@2.0.5: {} + undici-types@5.26.5: {} + undici-types@6.21.0: {} undici-types@7.16.0: {} @@ -17867,6 +20901,18 @@ snapshots: undici@7.24.5: {} + unenv@2.0.0-rc.15: + dependencies: + defu: 6.1.4 + exsolve: 1.0.8 + ohash: 2.0.11 + pathe: 2.0.3 + ufo: 1.6.3 + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -17922,6 +20968,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universal-user-agent@7.0.3: {} + universalify@2.0.1: {} unocss@66.6.6(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)): @@ -18015,6 +21063,10 @@ snapshots: url-template@3.1.1: {} + use-sync-external-store@1.6.0(react@19.2.5): + dependencies: + react: 19.2.5 + util-deprecate@1.0.2: {} uuid@8.3.2: {} @@ -18313,6 +21365,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-streams-polyfill@4.0.0-beta.3: {} + webidl-conversions@3.0.1: {} webidl-conversions@4.0.2: {} @@ -18325,7 +21379,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.104.1: + webpack@5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -18349,7 +21403,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.4.0(webpack@5.104.1) + terser-webpack-plugin: 5.4.0(@swc/core@1.15.24(@swc/helpers@0.5.19))(webpack@5.104.1(@swc/core@1.15.24(@swc/helpers@0.5.19))) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: @@ -18594,6 +21648,18 @@ snapshots: xcase@2.0.1: {} + xdg-app-paths@8.3.0: + dependencies: + xdg-portable: 10.6.0 + optionalDependencies: + fsevents: 2.3.3 + + xdg-portable@10.6.0: + dependencies: + os-paths: 7.4.0 + optionalDependencies: + fsevents: 2.3.3 + xml-name-validator@4.0.0: {} xml-name-validator@5.0.0: {} @@ -18633,7 +21699,7 @@ snapshots: yoctocolors-cjs@2.1.3: {} - yummies@7.10.0: + yummies@7.10.0(react@19.2.5): dependencies: class-variance-authority: 0.7.1 clsx: 2.1.1 @@ -18641,6 +21707,12 @@ snapshots: dompurify: 3.3.2 nanoid: 5.1.6 tailwind-merge: 3.5.0 + optionalDependencies: + react: 19.2.5 + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 zod-validation-error@3.5.4(zod@3.25.76): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6c8011a60c..72b6f8568f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,6 +12,7 @@ trustPolicyExclude: - semver@6.3.1 - chokidar@4.0.3 - pino@9.14.0 + - vite@5.4.21 autoInstallPeers: true injectWorkspacePackages: true