All files / src/entities/container_registry schema.ts

100% Statements 12/12
100% Branches 0/0
100% Functions 1/1
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 9417x                         17x 12x 12x 11x   1x             17x         17x     17x               17x             17x                                                                   17x                      
import { z } from 'zod';
 
// ============================================================================
// manage_registry - CQRS Command Tool (discriminated union schema)
// Actions: delete_repository, delete_tag, delete_tags_bulk
// Backed by the GitLab GraphQL Container Registry mutations
// (destroyContainerRepository, destroyContainerRepositoryTags). GraphQL has no
// native regex bulk-delete, so delete_tags_bulk is composed in the handler:
// list tags, filter by regex/keep_n/older_than, then destroy the resolved names.
// Gated behind USE_REGISTRY. Writes require the write_registry token scope.
// ============================================================================
 
/** True when the string compiles as a regular expression (fail fast at parse). */
const isValidRegex = (pattern: string): boolean => {
  try {
    new RegExp(pattern);
    return true;
  } catch {
    return false;
  }
};
 
// --- Shared fields ---
// Mutations address the repository directly by its numeric ID (as returned by
// browse_registry list_repositories); it is expanded to a global ID internally.
const repositoryIdField = z.coerce
  .number()
  .int()
  .positive()
  .describe('Numeric ID of the container repository (from browse_registry list_repositories)');
const tagNameField = z.string().min(1).describe('Container image tag name to delete');
 
// --- Action: delete_repository ---
const DeleteRepositorySchema = z.object({
  action: z
    .literal('delete_repository')
    .describe('Delete a container repository (queued as a background operation)'),
  repository_id: repositoryIdField,
});
 
// --- Action: delete_tag ---
const DeleteTagSchema = z.object({
  action: z.literal('delete_tag').describe('Delete a single tag from a container repository'),
  repository_id: repositoryIdField,
  tag_name: tagNameField,
});
 
// --- Action: delete_tags_bulk ---
const DeleteTagsBulkSchema = z.object({
  action: z
    .literal('delete_tags_bulk')
    .describe(
      'Bulk-delete tags by regex with optional retention rules. Destructive and irreversible. ' +
        'Tags are resolved client-side (regex match, keep the newest keep_n, drop only those older ' +
        'than older_than) and then deleted. Always pair name_regex_delete with keep_n and/or ' +
        'older_than to avoid deleting more than intended.',
    ),
  repository_id: repositoryIdField,
  name_regex_delete: z
    .string()
    .min(1)
    .refine(isValidRegex, 'Must be a valid regular expression')
    .describe('Regex for tag names to delete (e.g., ".*" for all, "^v.+" for version tags)'),
  name_regex_keep: z
    .string()
    .refine(isValidRegex, 'Must be a valid regular expression')
    .optional()
    .describe('Regex for tag names to always keep (takes precedence over name_regex_delete)'),
  keep_n: z.coerce
    .number()
    .int()
    .positive()
    .optional()
    .describe('Keep the N most recently created matching tags'),
  older_than: z
    .string()
    .regex(/^\d+(s|m|h|d)$/, 'Duration like "30m", "2h", "7d" (seconds/minutes/hours/days)')
    .optional()
    .describe('Only delete tags created longer ago than this duration (e.g., "7d", "12h")'),
});
 
// --- Discriminated union combining all actions ---
export const ManageRegistrySchema = z.discriminatedUnion('action', [
  DeleteRepositorySchema,
  DeleteTagSchema,
  DeleteTagsBulkSchema,
]);
 
// ============================================================================
// Type exports
// ============================================================================
 
export type ManageRegistryInput = z.infer<typeof ManageRegistrySchema>;