Something went wrong. Try again.
[READ-ONLY]
a fast, modern browser for the npm registry forked from npmx.dev/npmx.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320import * as v from 'valibot'import validateNpmPackageName from 'validate-npm-package-name'
// Validation pattern for npm usernames/org names// These follow similar rules: lowercase alphanumeric with hyphens, can't start/end with hyphenconst NPM_USERNAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i
// ============================================================================// Base Schemas// ============================================================================
/** * Validates an npm package name using the official npm validation package * Accepts both new and legacy package name formats */export const PackageNameSchema = v.pipe( v.string(), v.nonEmpty('Package name is required'), v.check(input => { const result = validateNpmPackageName(input) return result.validForNewPackages || result.validForOldPackages }, 'Invalid package name format'),)
/** * Validates an npm package name for new packages only * Stricter than PackageNameSchema - rejects legacy formats (uppercase, etc.) * @internal */export const NewPackageNameSchema = v.pipe( v.string(), v.nonEmpty('Package name is required'), v.check(input => { const result = validateNpmPackageName(input) return result.validForNewPackages === true }, 'Invalid package name format. New packages must be lowercase and follow npm naming conventions.'),)
/** * Validates an npm username * Must be alphanumeric with hyphens, max 50 chars, can't start/end with hyphen */export const UsernameSchema = v.pipe( v.string(), v.nonEmpty('Username is required'), v.maxLength(50, 'Username is too long'), v.regex(NPM_USERNAME_RE, 'Invalid username format'),)
/** * Validates an npm org name (without the @ prefix) * Same rules as username */export const OrgNameSchema = v.pipe( v.string(), v.nonEmpty('Org name is required'), v.maxLength(50, 'Org name is too long'), v.regex(NPM_USERNAME_RE, 'Invalid org name format'),)
/** * Validates a scope:team format (e.g., @myorg:developers) */export const ScopeTeamSchema = v.pipe( v.string(), v.nonEmpty('Scope:team is required'), v.maxLength(100, 'Scope:team is too long'), v.check(input => { const match = input.match(/^@([^:]+):(.+)$/) if (!match) return false const [, scope, team] = match if (!scope || !NPM_USERNAME_RE.test(scope)) return false if (!team || !NPM_USERNAME_RE.test(team)) return false return true }, 'Invalid scope:team format. Expected @scope:team'),)
/** * Validates org roles * @internal */export const OrgRoleSchema = v.picklist( ['developer', 'admin', 'owner'], 'Invalid role. Must be developer, admin, or owner',)
/** * Validates access permissions * @internal */export const PermissionSchema = v.picklist( ['read-only', 'read-write'], 'Invalid permission. Must be read-only or read-write',)
/** * Validates operation types */export const OperationTypeSchema = v.picklist([ 'org:add-user', 'org:rm-user', 'org:set-role', 'team:create', 'team:destroy', 'team:add-user', 'team:rm-user', 'access:grant', 'access:revoke', 'owner:add', 'owner:rm', 'package:init',])
/** * Validates OTP (6-digit code) * @internal */export const OtpSchema = v.optional( v.pipe(v.string(), v.regex(/^\d{6}$/, 'OTP must be a 6-digit code')),)
/** * Validates a hex token (like session tokens and operation IDs) * @internal */export const HexTokenSchema = v.pipe( v.string(), v.nonEmpty('Token is required'), v.regex(/^[a-f0-9]+$/i, 'Invalid token format'),)
/** * Validates operation ID (16-char hex) * @internal */export const OperationIdSchema = v.pipe( v.string(), v.nonEmpty('Operation ID is required'), v.regex(/^[a-f0-9]{16}$/i, 'Invalid operation ID format'),)
// ============================================================================// Request Body Schemas// ============================================================================
/** * Schema for /connect request body */export const ConnectBodySchema = v.object({ token: HexTokenSchema,})
/** * Schema for /execute request body. * - `otp`: optional 6-digit OTP code for 2FA * - `interactive`: when true, commands run via a real PTY (node-pty) instead of execFile, so npm's OTP handler can activate. * - `openUrls`: when true (default), npm opens auth URLs in the user's browser automatically. When false, URLs are suppressed on the connector side and only returned in the response / exposed in /state */export const ExecuteBodySchema = v.object({ otp: OtpSchema, interactive: v.optional(v.boolean()), openUrls: v.optional(v.boolean()),})
/** * Schema for operation params based on type * Validates the params object for each operation type */const OperationParamsSchema = v.record(v.string(), v.string())
/** * Schema for single operation request body */export const CreateOperationBodySchema = v.object({ type: OperationTypeSchema, params: OperationParamsSchema, description: v.pipe(v.string(), v.nonEmpty('Description is required'), v.maxLength(500)), command: v.pipe(v.string(), v.nonEmpty('Command is required'), v.maxLength(1000)),})
/** * Schema for batch operation request body */export const BatchOperationsBodySchema = v.array(CreateOperationBodySchema)
// ============================================================================// Type-specific Operation Params Schemas// ============================================================================
/** @internal */export const OrgAddUserParamsSchema = v.object({ org: OrgNameSchema, user: UsernameSchema, role: OrgRoleSchema,})
const OrgRemoveUserParamsSchema = v.object({ org: OrgNameSchema, user: UsernameSchema,})
const TeamCreateParamsSchema = v.object({ scopeTeam: ScopeTeamSchema,})
const TeamDestroyParamsSchema = v.object({ scopeTeam: ScopeTeamSchema,})
const TeamAddUserParamsSchema = v.object({ scopeTeam: ScopeTeamSchema, user: UsernameSchema,})
const TeamRemoveUserParamsSchema = v.object({ scopeTeam: ScopeTeamSchema, user: UsernameSchema,})
/** @internal */export const AccessGrantParamsSchema = v.object({ permission: PermissionSchema, scopeTeam: ScopeTeamSchema, pkg: PackageNameSchema,})
const AccessRevokeParamsSchema = v.object({ scopeTeam: ScopeTeamSchema, pkg: PackageNameSchema,})
const OwnerAddParamsSchema = v.object({ user: UsernameSchema, pkg: PackageNameSchema,})
const OwnerRemoveParamsSchema = v.object({ user: UsernameSchema, pkg: PackageNameSchema,})
/** @internal */export const PackageInitParamsSchema = v.object({ name: NewPackageNameSchema, author: v.optional(UsernameSchema),})
// ============================================================================// Helper Functions// ============================================================================
/** * Validates operation params based on operation type * @throws ValiError if validation fails */export function validateOperationParams( type: v.InferOutput<typeof OperationTypeSchema>, params: Record<string, string>,): void { switch (type) { case 'org:add-user': v.parse(OrgAddUserParamsSchema, params) break case 'org:rm-user': v.parse(OrgRemoveUserParamsSchema, params) break case 'org:set-role': v.parse(OrgAddUserParamsSchema, params) // same params as add-user break case 'team:create': v.parse(TeamCreateParamsSchema, params) break case 'team:destroy': v.parse(TeamDestroyParamsSchema, params) break case 'team:add-user': v.parse(TeamAddUserParamsSchema, params) break case 'team:rm-user': v.parse(TeamRemoveUserParamsSchema, params) break case 'access:grant': v.parse(AccessGrantParamsSchema, params) break case 'access:revoke': v.parse(AccessRevokeParamsSchema, params) break case 'owner:add': v.parse(OwnerAddParamsSchema, params) break case 'owner:rm': v.parse(OwnerRemoveParamsSchema, params) break case 'package:init': v.parse(PackageInitParamsSchema, params) break }}
/** * Safe parse wrapper that returns a formatted error message */export function safeParse<T extends v.GenericSchema>( schema: T, input: unknown,): { success: true; data: v.InferOutput<T> } | { success: false; error: string } { const result = v.safeParse(schema, input) if (result.success) { return { success: true, data: result.output } } // Format the first error message const firstIssue = result.issues[0] const path = firstIssue?.path?.map(p => p.key).join('.') || '' const message = firstIssue?.message || 'Validation failed' return { success: false, error: path ? `${path}: ${message}` : message, }}