UNPKG

@eventcatalog/linter

Version:

A linter for EventCatalog to validate frontmatter and resource references

1,288 lines (1,266 loc) 74.4 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/cli/index.ts var import_commander = require("commander"); var import_chalk2 = __toESM(require("chalk")); var import_ora = __toESM(require("ora")); var import_path3 = __toESM(require("path")); // src/scanner/index.ts var import_fast_glob = __toESM(require("fast-glob")); var import_path = __toESM(require("path")); var RESOURCE_DIRECTORY_NAMES = { dataProduct: "data-products", dataStore: "containers", container: "containers", adr: "adrs" }; var RESOURCE_PATTERNS = { domain: [ "domains/*/index.{md,mdx}", "domains/*/versioned/*/index.{md,mdx}", "domains/*/subdomains/*/index.{md,mdx}", "domains/*/subdomains/*/versioned/*/index.{md,mdx}" ], system: [ "**/systems/**/index.{md,mdx}", "**/systems/**/versioned/*/index.{md,mdx}", "!**/systems/**/agents/**", "!**/systems/**/services/**", "!**/systems/**/events/**", "!**/systems/**/commands/**", "!**/systems/**/queries/**", "!**/systems/**/flows/**", "!**/systems/**/channels/**", "!**/systems/**/entities/**", "!**/systems/**/containers/**", "!**/systems/**/diagrams/**", "!**/systems/**/data-products/**", "!**/systems/**/adrs/**", "!**/systems/**/docs/**" ], service: [ "domains/*/services/*/index.{md,mdx}", "domains/*/services/*/versioned/*/index.{md,mdx}", "domains/*/subdomains/*/services/*/index.{md,mdx}", "domains/*/subdomains/*/services/*/versioned/*/index.{md,mdx}", "**/systems/**/services/*/index.{md,mdx}", "**/systems/**/services/*/versioned/*/index.{md,mdx}", "services/*/index.{md,mdx}", "services/*/versioned/*/index.{md,mdx}" ], agent: ["**/agents/*/index.{md,mdx}", "**/agents/*/versioned/*/index.{md,mdx}"], adr: ["**/adrs/*/index.{md,mdx}", "**/adrs/*/versioned/*/index.{md,mdx}"], event: ["**/events/*/index.{md,mdx}", "**/events/*/versioned/*/index.{md,mdx}"], command: ["**/commands/*/index.{md,mdx}", "**/commands/*/versioned/*/index.{md,mdx}"], query: ["**/queries/*/index.{md,mdx}", "**/queries/*/versioned/*/index.{md,mdx}"], channel: ["**/channels/**/index.{md,mdx}", "**/channels/**/versioned/*/index.{md,mdx}"], flow: ["**/flows/**/index.{md,mdx}", "**/flows/**/versioned/*/index.{md,mdx}"], entity: ["**/entities/*/index.{md,mdx}", "**/entities/*/versioned/*/index.{md,mdx}"], container: ["**/containers/**/index.{md,mdx}", "**/containers/**/versioned/*/index.{md,mdx}"], dataProduct: ["**/data-products/*/index.{md,mdx}", "**/data-products/*/versioned/*/index.{md,mdx}"], diagram: ["**/diagrams/**/index.{md,mdx}", "**/diagrams/**/versioned/*/index.{md,mdx}"], user: ["users/*.{md,mdx}"], team: ["teams/*.{md,mdx}"] }; var extractResourceInfo = (filePath, resourceType) => { const normalizedPath = filePath.replace(/\\/g, "/"); const relativePath = normalizedPath.split("/"); if (resourceType === "user" || resourceType === "team") { const filename = import_path.default.basename(filePath, import_path.default.extname(filePath)); return { id: filename }; } const resourceTypePattern = RESOURCE_DIRECTORY_NAMES[resourceType] || `${resourceType}s`; const resourceTypeIndex = relativePath.findIndex((part) => part === resourceTypePattern); if (resourceTypeIndex === -1) { const parts2 = relativePath.slice(1, -1); return { id: parts2[parts2.length - 1] }; } const parts = relativePath.slice(resourceTypeIndex + 1, -1); if (parts.length === 0) { return { id: "unknown" }; } if (parts.length >= 3 && parts[parts.length - 2] === "versioned") { const version = parts[parts.length - 1]; const resourceId = parts.slice(0, -2).join("/"); return { id: resourceId, version }; } if (resourceType === "domain" && parts.length >= 2) { const versionedIndex = parts.findIndex((part) => part === "versioned"); if (versionedIndex !== -1 && versionedIndex < parts.length - 1) { const version = parts[versionedIndex + 1]; const resourceId = parts.slice(0, versionedIndex).join("/"); return { id: resourceId, version }; } } if (parts.length === 1) { return { id: parts[0] }; } return { id: parts.join("/") }; }; var scanCatalogFiles = async (rootDir) => { const files = []; for (const [resourceType, patterns] of Object.entries(RESOURCE_PATTERNS)) { const foundFiles = await (0, import_fast_glob.default)(patterns, { cwd: rootDir, absolute: true, onlyFiles: true, followSymbolicLinks: false }); for (const filePath of foundFiles) { const relativePath = import_path.default.relative(rootDir, filePath); const { id, version } = extractResourceInfo(relativePath, resourceType); files.push({ path: filePath, relativePath, resourceType, resourceId: id, version }); } } return files; }; // src/parser/index.ts var import_promises = __toESM(require("fs/promises")); var import_gray_matter = __toESM(require("gray-matter")); var parseFrontmatter = async (file) => { try { const fileContent = await import_promises.default.readFile(file.path, "utf-8"); const { data, content } = (0, import_gray_matter.default)(fileContent); return { file, frontmatter: data, content, raw: fileContent }; } catch (error) { return { file, error: error instanceof Error ? error : new Error(String(error)) }; } }; var parseAllFiles = async (files) => { const results = await Promise.all(files.map(parseFrontmatter)); const parsed = []; const errors = []; for (const result of results) { if ("error" in result) { errors.push(result); } else { parsed.push(result); } } return { parsed, errors }; }; // src/validators/schema-validator.ts var import_zod16 = require("zod"); // src/schemas/common.ts var import_zod = require("zod"); var badgeSchema = import_zod.z.object({ content: import_zod.z.string(), backgroundColor: import_zod.z.string(), textColor: import_zod.z.string(), icon: import_zod.z.string().optional(), url: import_zod.z.string().optional() }); var ownerReferenceSchema = import_zod.z.union([ // The ID of the user or team import_zod.z.string(), // The full object with the ID and collection (keep compatibility with `reference`) import_zod.z.object({ id: import_zod.z.string(), collection: import_zod.z.enum(["users", "teams"]) }) ]).transform( // This transformation is needed to keep compatibility with `reference`. // The utilities `getTeams` and `getUsers` rely on this transformation. (lookup) => ({ id: typeof lookup === "string" ? lookup : lookup.id }) ); var specificationSchema = import_zod.z.union([ import_zod.z.object({ openapiPath: import_zod.z.string().optional(), asyncapiPath: import_zod.z.string().optional(), graphqlPath: import_zod.z.string().optional() }), import_zod.z.array( import_zod.z.object({ type: import_zod.z.enum(["openapi", "asyncapi", "graphql"]), path: import_zod.z.string(), name: import_zod.z.string().optional(), headers: import_zod.z.record(import_zod.z.string()).optional() }) ) ]); var repositorySchema = import_zod.z.object({ language: import_zod.z.string().optional(), url: import_zod.z.string().optional() }); var draftSchema = import_zod.z.union([ import_zod.z.boolean(), import_zod.z.object({ title: import_zod.z.string().optional(), message: import_zod.z.string() }) ]); var deprecatedSchema = import_zod.z.union([ import_zod.z.object({ message: import_zod.z.string().optional(), date: import_zod.z.union([import_zod.z.string(), import_zod.z.date()]).optional() }), import_zod.z.boolean().optional() ]); var pointerSchema = import_zod.z.object({ id: import_zod.z.string(), version: import_zod.z.string().optional().default("latest") }); var resourcePointerSchema = import_zod.z.object({ id: import_zod.z.string(), version: import_zod.z.string().optional().default("latest"), type: import_zod.z.enum([ "agent", "service", "event", "command", "query", "flow", "channel", "domain", "system", "user", "team", "container", "entity", "diagram", "data-product", "adr" ]) }); var channelPointerSchema = import_zod.z.object({ parameters: import_zod.z.record(import_zod.z.string()).optional() }).merge(pointerSchema); var detailPanelPropertySchema = import_zod.z.object({ visible: import_zod.z.boolean().optional() }).optional(); var sendsPointerSchema = import_zod.z.object({ id: import_zod.z.string(), version: import_zod.z.string().optional().default("latest"), fields: import_zod.z.array(import_zod.z.string()).optional(), group: import_zod.z.string().optional(), to: import_zod.z.array( channelPointerSchema.extend({ delivery_mode: import_zod.z.enum(["push", "pull", "push-pull"]).optional().default("push") }) ).optional() }); var receivesPointerSchema = import_zod.z.object({ id: import_zod.z.string(), version: import_zod.z.string().optional().default("latest"), fields: import_zod.z.array(import_zod.z.string()).optional(), group: import_zod.z.string().optional(), from: import_zod.z.array( channelPointerSchema.extend({ delivery_mode: import_zod.z.enum(["push", "pull", "push-pull"]).optional().default("push") }) ).optional() }); var semverSchema = import_zod.z.string().refine((version) => { if (version === "latest") return true; if (version.includes(".x")) { const xPattern = /^\d+(\.\d+)*\.x$/; return xPattern.test(version); } if (version.startsWith("^") || version.startsWith("~")) { const rangeVersion = version.substring(1); const semverRegex2 = /^\d+\.\d+\.\d+(-[\w\d-.]+)?(\+[\w\d-.]+)?$/; return semverRegex2.test(rangeVersion); } const semverRegex = /^\d+\.\d+\.\d+(-[\w\d-.]+)?(\+[\w\d-.]+)?$/; return semverRegex.test(version); }, "Invalid semantic version format"); var sidebarSchema = import_zod.z.object({ label: import_zod.z.string().optional(), badge: import_zod.z.string().optional(), color: import_zod.z.string().optional(), backgroundColor: import_zod.z.string().optional() }).optional(); var stylesSchema = import_zod.z.object({ icon: import_zod.z.string().optional(), node: import_zod.z.object({ color: import_zod.z.string().optional(), label: import_zod.z.string().optional() }).optional() }).optional(); var resourceGroupSchema = import_zod.z.array( import_zod.z.object({ id: import_zod.z.string().optional(), title: import_zod.z.string().optional(), items: import_zod.z.array(resourcePointerSchema), limit: import_zod.z.number().optional().default(10), sidebar: import_zod.z.boolean().optional().default(true) }) ).optional(); var catalogMetadataSchema = import_zod.z.object({ path: import_zod.z.string(), filePath: import_zod.z.string(), astroContentFilePath: import_zod.z.string(), publicPath: import_zod.z.string(), type: import_zod.z.string() }).optional(); var baseSchema = import_zod.z.object({ id: import_zod.z.string(), name: import_zod.z.string(), summary: import_zod.z.string().optional(), version: semverSchema, draft: import_zod.z.union([import_zod.z.boolean(), import_zod.z.object({ title: import_zod.z.string().optional(), message: import_zod.z.string() })]).optional(), badges: import_zod.z.array(badgeSchema).optional(), owners: import_zod.z.array(ownerReferenceSchema).optional(), schemaPath: import_zod.z.string().optional(), sidebar: sidebarSchema, repository: repositorySchema.optional(), specifications: specificationSchema.optional(), hidden: import_zod.z.boolean().optional(), editUrl: import_zod.z.string().optional(), resourceGroups: resourceGroupSchema, styles: stylesSchema, deprecated: deprecatedSchema.optional(), visualiser: import_zod.z.boolean().optional(), attachments: import_zod.z.array( import_zod.z.union([ import_zod.z.string().url(), import_zod.z.object({ url: import_zod.z.string().url(), title: import_zod.z.string().optional(), type: import_zod.z.string().optional(), description: import_zod.z.string().optional(), icon: import_zod.z.string().optional() }) ]) ).optional(), diagrams: import_zod.z.array(pointerSchema).optional(), versions: import_zod.z.array(import_zod.z.string()).optional(), latestVersion: import_zod.z.string().optional(), catalog: catalogMetadataSchema }); // src/schemas/domain.ts var import_zod2 = require("zod"); var domainSchema = import_zod2.z.object({ services: import_zod2.z.array(pointerSchema).optional(), agents: import_zod2.z.array(pointerSchema).optional(), domains: import_zod2.z.array(pointerSchema).optional(), systems: import_zod2.z.array(pointerSchema).optional(), entities: import_zod2.z.array(pointerSchema).optional(), "data-products": import_zod2.z.array(pointerSchema).optional(), dataProducts: import_zod2.z.array(pointerSchema).optional(), flows: import_zod2.z.array(pointerSchema).optional(), sends: import_zod2.z.array(sendsPointerSchema).optional(), receives: import_zod2.z.array(receivesPointerSchema).optional(), detailsPanel: import_zod2.z.object({ parentDomains: detailPanelPropertySchema, subdomains: detailPanelPropertySchema, systems: detailPanelPropertySchema, services: detailPanelPropertySchema, agents: detailPanelPropertySchema, entities: detailPanelPropertySchema, messages: detailPanelPropertySchema, ubiquitousLanguage: detailPanelPropertySchema, repository: detailPanelPropertySchema, versions: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, attachments: detailPanelPropertySchema }).optional() }).merge(baseSchema); // src/schemas/system.ts var import_zod3 = require("zod"); var systemRelationshipPointerSchema = import_zod3.z.object({ id: import_zod3.z.string(), version: import_zod3.z.string().optional().default("latest"), label: import_zod3.z.string().optional() }); var systemActorRelationshipSchema = import_zod3.z.object({ id: import_zod3.z.string(), name: import_zod3.z.string().optional(), label: import_zod3.z.string().optional(), direction: import_zod3.z.enum(["inbound", "outbound"]).optional().default("inbound") }); var systemSchema = import_zod3.z.object({ scope: import_zod3.z.enum(["internal", "external"]).optional().default("internal"), services: import_zod3.z.array(pointerSchema).optional(), flows: import_zod3.z.array(pointerSchema).optional(), entities: import_zod3.z.array(pointerSchema).optional(), containers: import_zod3.z.array(pointerSchema).optional(), relationships: import_zod3.z.array(systemRelationshipPointerSchema).optional(), actors: import_zod3.z.array(systemActorRelationshipSchema).optional(), detailsPanel: import_zod3.z.object({ versions: detailPanelPropertySchema, repository: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, attachments: detailPanelPropertySchema, services: detailPanelPropertySchema, flows: detailPanelPropertySchema, entities: detailPanelPropertySchema, containers: detailPanelPropertySchema, diagrams: detailPanelPropertySchema }).optional() }).merge(baseSchema); // src/schemas/service.ts var import_zod4 = require("zod"); var serviceSchema = import_zod4.z.object({ sends: import_zod4.z.array(sendsPointerSchema).optional(), receives: import_zod4.z.array(receivesPointerSchema).optional(), entities: import_zod4.z.array(pointerSchema).optional(), writesTo: import_zod4.z.array(pointerSchema).optional(), readsFrom: import_zod4.z.array(pointerSchema).optional(), flows: import_zod4.z.array(pointerSchema).optional(), externalSystem: import_zod4.z.boolean().optional(), detailsPanel: import_zod4.z.object({ domains: detailPanelPropertySchema, messages: detailPanelPropertySchema, versions: detailPanelPropertySchema, specifications: detailPanelPropertySchema, entities: detailPanelPropertySchema, repository: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, containers: detailPanelPropertySchema }).optional() }).merge(baseSchema); // src/schemas/message.ts var import_zod5 = require("zod"); var operationSchema = import_zod5.z.object({ method: import_zod5.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional(), path: import_zod5.z.string().optional(), statusCodes: import_zod5.z.array(import_zod5.z.string()).optional() }).optional(); var messageDetailsPanelSchema = import_zod5.z.object({ producers: detailPanelPropertySchema, consumers: detailPanelPropertySchema, channels: detailPanelPropertySchema, versions: detailPanelPropertySchema, repository: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, attachments: detailPanelPropertySchema }).optional(); var baseMessageSchema = import_zod5.z.object({ operation: operationSchema, producers: import_zod5.z.array(import_zod5.z.any()).optional(), // reference('services') consumers: import_zod5.z.array(import_zod5.z.any()).optional(), // reference('services') channels: import_zod5.z.array(channelPointerSchema).optional(), schemas: import_zod5.z.array( import_zod5.z.object({ id: import_zod5.z.string().optional(), ref: import_zod5.z.string().optional(), file: import_zod5.z.string().optional(), path: import_zod5.z.string().optional(), name: import_zod5.z.string().optional(), format: import_zod5.z.string().optional(), environments: import_zod5.z.array(import_zod5.z.string()).optional(), default: import_zod5.z.boolean().optional() }) ).optional(), messageChannels: import_zod5.z.array(import_zod5.z.any()).optional(), // reference('channels') detailsPanel: messageDetailsPanelSchema }).merge(baseSchema); var eventSchema = baseMessageSchema; var commandSchema = baseMessageSchema; var querySchema = baseMessageSchema; // src/schemas/channel.ts var import_zod6 = require("zod"); var parameterSchema = import_zod6.z.object({ enum: import_zod6.z.array(import_zod6.z.string()).optional(), default: import_zod6.z.string().optional(), examples: import_zod6.z.array(import_zod6.z.string()).optional(), description: import_zod6.z.string().optional() }); var channelSchema = import_zod6.z.object({ channels: import_zod6.z.array(channelPointerSchema).optional(), address: import_zod6.z.string().optional(), protocols: import_zod6.z.array(import_zod6.z.string()).optional(), deliveryGuarantee: import_zod6.z.enum(["at-most-once", "at-least-once", "exactly-once"]).optional(), routes: import_zod6.z.array(channelPointerSchema).optional(), parameters: import_zod6.z.record(parameterSchema).optional(), messages: import_zod6.z.array(import_zod6.z.object({ collection: import_zod6.z.string(), name: import_zod6.z.string(), ...pointerSchema.shape })).optional(), detailsPanel: import_zod6.z.object({ producers: detailPanelPropertySchema, consumers: detailPanelPropertySchema, messages: detailPanelPropertySchema, protocols: detailPanelPropertySchema, parameters: detailPanelPropertySchema, versions: detailPanelPropertySchema, repository: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, attachments: detailPanelPropertySchema }).optional() }).merge(baseSchema); // src/schemas/flow.ts var import_zod7 = require("zod"); var flowStep = import_zod7.z.union([ import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()]), import_zod7.z.object({ id: import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()]), label: import_zod7.z.string().optional() }).optional() ]).optional(); var flowStepSchema = import_zod7.z.object({ id: import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()]), type: import_zod7.z.enum(["node", "message", "agent", "user", "actor"]).optional(), title: import_zod7.z.string(), summary: import_zod7.z.string().optional(), message: pointerSchema.optional(), agent: pointerSchema.optional(), service: pointerSchema.optional(), flow: pointerSchema.optional(), container: pointerSchema.optional(), dataProduct: pointerSchema.optional(), actor: import_zod7.z.object({ name: import_zod7.z.string(), summary: import_zod7.z.string().optional() }).optional(), custom: import_zod7.z.object({ title: import_zod7.z.string(), icon: import_zod7.z.string().optional(), type: import_zod7.z.string().optional(), summary: import_zod7.z.string().optional(), url: import_zod7.z.string().url().optional(), color: import_zod7.z.string().optional(), properties: import_zod7.z.record(import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()])).optional(), height: import_zod7.z.number().optional(), menu: import_zod7.z.array( import_zod7.z.object({ label: import_zod7.z.string(), url: import_zod7.z.string().url().optional() }) ).optional() }).optional(), externalSystem: import_zod7.z.object({ name: import_zod7.z.string(), summary: import_zod7.z.string().optional(), url: import_zod7.z.string().url().optional() }).optional(), next_step: flowStep, next_steps: import_zod7.z.array(flowStep).optional() }).refine((data) => { if (data.next_step && data.next_steps) return false; const typesUsed = [ data.message, data.agent, data.service, data.flow, data.container, data.dataProduct, data.actor, data.custom ].filter((v) => v).length; return typesUsed === 0 || typesUsed === 1; }); var flowSchema = import_zod7.z.object({ steps: import_zod7.z.array(flowStepSchema) }).merge(baseSchema); // src/schemas/entity.ts var import_zod8 = require("zod"); var propertySchema = import_zod8.z.lazy( () => import_zod8.z.object({ name: import_zod8.z.string(), type: import_zod8.z.string(), required: import_zod8.z.boolean().optional(), description: import_zod8.z.string().optional(), references: import_zod8.z.string().optional(), referencesIdentifier: import_zod8.z.string().optional(), referenceTarget: import_zod8.z.literal("entity").optional(), relationType: import_zod8.z.string().optional(), enum: import_zod8.z.array(import_zod8.z.string()).optional(), properties: import_zod8.z.array(propertySchema).optional(), items: import_zod8.z.object({ type: import_zod8.z.string(), properties: import_zod8.z.array(propertySchema).optional() }).optional() }) ); var entitySchema = import_zod8.z.object({ aggregateRoot: import_zod8.z.boolean().optional(), identifier: import_zod8.z.string().optional(), properties: import_zod8.z.array(propertySchema).optional(), services: import_zod8.z.array(import_zod8.z.any()).optional(), // reference('services') domains: import_zod8.z.array(import_zod8.z.any()).optional(), // reference('domains') detailsPanel: import_zod8.z.object({ domains: detailPanelPropertySchema, services: detailPanelPropertySchema, messages: detailPanelPropertySchema, versions: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, attachments: detailPanelPropertySchema }).optional() }).merge(baseSchema); // src/schemas/user.ts var import_zod9 = require("zod"); var userSchema = import_zod9.z.object({ id: import_zod9.z.string(), name: import_zod9.z.string(), avatarUrl: import_zod9.z.string().optional(), role: import_zod9.z.string().optional(), hidden: import_zod9.z.boolean().optional(), source: import_zod9.z.object({ provider: import_zod9.z.string(), id: import_zod9.z.string().optional(), url: import_zod9.z.string().optional() }).optional(), readOnly: import_zod9.z.boolean().optional(), email: import_zod9.z.string().email().optional(), slackDirectMessageUrl: import_zod9.z.string().optional(), msTeamsDirectMessageUrl: import_zod9.z.string().optional(), ownedAgents: import_zod9.z.array(import_zod9.z.any()).optional(), // reference('agents') ownedDomains: import_zod9.z.array(import_zod9.z.any()).optional(), // reference('domains') ownedSystems: import_zod9.z.array(import_zod9.z.any()).optional(), // reference('systems') ownedServices: import_zod9.z.array(import_zod9.z.any()).optional(), // reference('services') ownedEvents: import_zod9.z.array(import_zod9.z.any()).optional(), // reference('events') ownedCommands: import_zod9.z.array(import_zod9.z.any()).optional(), // reference('commands') ownedQueries: import_zod9.z.array(import_zod9.z.any()).optional(), // reference('queries') associatedTeams: import_zod9.z.array(import_zod9.z.any()).optional() // reference('teams') }); // src/schemas/team.ts var import_zod10 = require("zod"); var teamSchema = import_zod10.z.object({ id: import_zod10.z.string(), name: import_zod10.z.string(), summary: import_zod10.z.string().optional(), email: import_zod10.z.string().email().optional(), hidden: import_zod10.z.boolean().optional(), source: import_zod10.z.object({ provider: import_zod10.z.string(), id: import_zod10.z.string().optional(), url: import_zod10.z.string().optional() }).optional(), readOnly: import_zod10.z.boolean().optional(), slackDirectMessageUrl: import_zod10.z.string().optional(), msTeamsDirectMessageUrl: import_zod10.z.string().optional(), members: import_zod10.z.array(import_zod10.z.any()).optional(), // reference('users') ownedAgents: import_zod10.z.array(import_zod10.z.any()).optional(), // reference('agents') ownedCommands: import_zod10.z.array(import_zod10.z.any()).optional(), // reference('commands') ownedQueries: import_zod10.z.array(import_zod10.z.any()).optional(), // reference('queries') ownedDomains: import_zod10.z.array(import_zod10.z.any()).optional(), // reference('domains') ownedSystems: import_zod10.z.array(import_zod10.z.any()).optional(), // reference('systems') ownedServices: import_zod10.z.array(import_zod10.z.any()).optional(), // reference('services') ownedEvents: import_zod10.z.array(import_zod10.z.any()).optional() // reference('events') }); // src/schemas/agent.ts var import_zod11 = require("zod"); var agentToolSchema = import_zod11.z.object({ name: import_zod11.z.string(), type: import_zod11.z.string(), icon: import_zod11.z.string().optional(), url: import_zod11.z.string().optional(), description: import_zod11.z.string().optional() }); var agentModelSchema = import_zod11.z.object({ provider: import_zod11.z.string().optional(), name: import_zod11.z.string().optional(), version: import_zod11.z.string().optional() }); var agentSchema = import_zod11.z.object({ sends: import_zod11.z.array(sendsPointerSchema).optional(), receives: import_zod11.z.array(receivesPointerSchema).optional(), writesTo: import_zod11.z.array(pointerSchema).optional(), readsFrom: import_zod11.z.array(pointerSchema).optional(), flows: import_zod11.z.array(pointerSchema).optional(), model: agentModelSchema.optional(), tools: import_zod11.z.array(agentToolSchema).optional(), specifications: import_zod11.z.undefined().optional(), detailsPanel: import_zod11.z.object({ domains: detailPanelPropertySchema, messages: detailPanelPropertySchema, versions: detailPanelPropertySchema, repository: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, containers: detailPanelPropertySchema, tools: detailPanelPropertySchema, model: detailPanelPropertySchema }).optional() }).merge(baseSchema.omit({ specifications: true })); // src/schemas/adr.ts var import_zod12 = require("zod"); var adrSchema = import_zod12.z.object({ status: import_zod12.z.enum(["proposed", "accepted", "rejected", "deprecated", "superseded"]), date: import_zod12.z.coerce.date(), decisionMakers: import_zod12.z.array(import_zod12.z.any()).optional(), appliesTo: import_zod12.z.array(resourcePointerSchema).optional(), supersedes: import_zod12.z.array(pointerSchema).optional(), supersededBy: import_zod12.z.array(pointerSchema).optional(), amends: import_zod12.z.array(pointerSchema).optional(), amendedBy: import_zod12.z.array(pointerSchema).optional(), related: import_zod12.z.array(pointerSchema).optional(), detailsPanel: import_zod12.z.object({ status: detailPanelPropertySchema, date: detailPanelPropertySchema, decisionMakers: detailPanelPropertySchema, appliesTo: detailPanelPropertySchema, relationships: detailPanelPropertySchema, owners: detailPanelPropertySchema, repository: detailPanelPropertySchema, changelog: detailPanelPropertySchema }).optional() }).merge(baseSchema); // src/schemas/container.ts var import_zod13 = require("zod"); var containerSchema = import_zod13.z.object({ container_type: import_zod13.z.enum([ "database", "cache", "objectStore", "searchIndex", "dataWarehouse", "dataLake", "externalSaaS", "other" ]), technology: import_zod13.z.string().optional(), authoritative: import_zod13.z.boolean().optional().default(false), access_mode: import_zod13.z.enum(["read", "write", "readWrite", "appendOnly"]).optional(), classification: import_zod13.z.enum(["public", "internal", "confidential", "regulated"]).optional(), residency: import_zod13.z.string().optional(), retention: import_zod13.z.string().optional(), detailsPanel: import_zod13.z.object({ versions: detailPanelPropertySchema, repository: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, attachments: detailPanelPropertySchema, services: detailPanelPropertySchema, flows: detailPanelPropertySchema }).optional(), services: import_zod13.z.array(import_zod13.z.any()).optional(), servicesThatWriteToContainer: import_zod13.z.array(import_zod13.z.any()).optional(), servicesThatReadFromContainer: import_zod13.z.array(import_zod13.z.any()).optional(), dataProductsThatWriteToContainer: import_zod13.z.array(import_zod13.z.any()).optional(), dataProductsThatReadFromContainer: import_zod13.z.array(import_zod13.z.any()).optional() }).merge(baseSchema); // src/schemas/data-product.ts var import_zod14 = require("zod"); var dataProductOutputPointerSchema = import_zod14.z.object({ id: import_zod14.z.string(), version: import_zod14.z.string().optional().default("latest"), contract: import_zod14.z.object({ path: import_zod14.z.string(), name: import_zod14.z.string(), type: import_zod14.z.string().optional() }).optional() }); var dataProductSchema = import_zod14.z.object({ inputs: import_zod14.z.array(pointerSchema).optional(), outputs: import_zod14.z.array(dataProductOutputPointerSchema).optional(), detailsPanel: import_zod14.z.object({ domains: detailPanelPropertySchema, inputs: detailPanelPropertySchema, outputs: detailPanelPropertySchema, versions: detailPanelPropertySchema, repository: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, flows: detailPanelPropertySchema }).optional() }).merge(baseSchema); // src/schemas/diagram.ts var import_zod15 = require("zod"); var diagramSchema = import_zod15.z.object({ detailsPanel: import_zod15.z.object({ versions: detailPanelPropertySchema, owners: detailPanelPropertySchema, changelog: detailPanelPropertySchema, attachments: detailPanelPropertySchema }).optional() }).merge(baseSchema); // src/schemas/index.ts var schemas = { domain: domainSchema, system: systemSchema, service: serviceSchema, agent: agentSchema, adr: adrSchema, event: eventSchema, command: commandSchema, query: querySchema, channel: channelSchema, flow: flowSchema, entity: entitySchema, container: containerSchema, dataProduct: dataProductSchema, diagram: diagramSchema, user: userSchema, team: teamSchema, // Backwards-compatible alias for older linter integrations. dataStore: containerSchema }; // src/validators/schema-validator.ts var validateSchema = (parsedFile) => { const { file, frontmatter } = parsedFile; const schema = schemas[file.resourceType]; const errors = []; try { schema.parse(frontmatter); } catch (error) { if (error instanceof import_zod16.z.ZodError) { for (const issue of error.issues) { const field = issue.path.join("."); let message = issue.message; if (issue.code === "invalid_type") { message = `Expected ${issue.expected}, but received ${issue.received}`; } let rule = "schema/required-fields"; if (issue.code === "invalid_type") { rule = "schema/valid-type"; } else if (issue.code === "invalid_string" && issue.validation === "email") { rule = "schema/valid-email"; } else if (field && field.includes("version")) { rule = "schema/valid-semver"; } errors.push({ type: "schema", resource: `${file.resourceType}/${file.resourceId}`, field: field || void 0, message: field ? `${field}: ${message}` : message, file: file.relativePath, rule }); } } else { errors.push({ type: "schema", resource: `${file.resourceType}/${file.resourceId}`, message: error instanceof Error ? error.message : String(error), file: file.relativePath, rule: "schema/validation-error" }); } } return errors; }; var validateAllSchemas = (parsedFiles) => { const errors = []; for (const parsedFile of parsedFiles) { errors.push(...validateSchema(parsedFile)); } return errors; }; // src/validators/reference-validator.ts var import_semver = __toESM(require("semver")); var MESSAGE_TYPES = ["event", "command", "query"]; var RESOURCE_TYPE_ALIASES = { container: ["container", "dataStore"], dataStore: ["container", "dataStore"] }; var normalizeResourceType = (type) => { if (typeof type !== "string") return void 0; const normalizedTypes = { agent: "agent", agents: "agent", adr: "adr", adrs: "adr", service: "service", services: "service", event: "event", events: "event", command: "command", commands: "command", query: "query", queries: "query", flow: "flow", flows: "flow", channel: "channel", channels: "channel", domain: "domain", domains: "domain", system: "system", systems: "system", user: "user", users: "user", team: "team", teams: "team", container: "container", containers: "container", dataStore: "container", dataStores: "container", "data-product": "dataProduct", "data-products": "dataProduct", dataProduct: "dataProduct", dataProducts: "dataProduct", entity: "entity", entities: "entity", diagram: "diagram", diagrams: "diagram" }; return normalizedTypes[type]; }; var getReference = (value) => { if (typeof value === "string") { return { id: value }; } if (!value || typeof value !== "object") { return void 0; } const ref = value; if (typeof ref.id !== "string") { return void 0; } return { id: ref.id, version: typeof ref.version === "string" ? ref.version : void 0 }; }; var addReference = (references, value, possibleTypes, field) => { const ref = getReference(value); if (ref) { references.push({ ref, possibleTypes, field }); } }; var addReferences = (references, values, possibleTypes, field) => { if (!Array.isArray(values)) return; values.forEach((value) => { addReference(references, value, possibleTypes, field); }); }; var addTypedReference = (references, value, field) => { if (!value || typeof value !== "object") return; const resourceType = normalizeResourceType(value.type); if (!resourceType) return; addReference(references, value, [resourceType], field); }; var buildResourceIndex = (parsedFiles, dependencies) => { const index = {}; for (const parsedFile of parsedFiles) { const { file, frontmatter } = parsedFile; const { resourceType } = file; let resourceId = file.resourceId; if (frontmatter.id && typeof frontmatter.id === "string") { resourceId = frontmatter.id; } if (!index[resourceType]) { index[resourceType] = {}; } if (!index[resourceType][resourceId]) { index[resourceType][resourceId] = /* @__PURE__ */ new Set(); } if (frontmatter.version && typeof frontmatter.version === "string") { index[resourceType][resourceId].add(frontmatter.version); } else { index[resourceType][resourceId].add("latest"); } } if (dependencies) { for (const [resourceType, entries] of Object.entries(dependencies)) { if (!index[resourceType]) { index[resourceType] = {}; } for (const entry of entries) { if (!index[resourceType][entry.id]) { index[resourceType][entry.id] = /* @__PURE__ */ new Set(); } index[resourceType][entry.id].add(entry.version || "latest"); } } } return index; }; var checkResourceExists = (ref, resourceType, index) => { const resourceTypes = RESOURCE_TYPE_ALIASES[resourceType] || [resourceType]; const resourceVersions = /* @__PURE__ */ new Set(); for (const type of resourceTypes) { const versions = index[type]?.[ref.id]; if (versions) { versions.forEach((version) => resourceVersions.add(version)); } } if (!resourceVersions || resourceVersions.size === 0) { return false; } if (!ref.version) { return true; } const refVersion = ref.version === "latest" ? ref.version : ref.version; const availableVersions = Array.from(resourceVersions); if (refVersion === "latest") { return availableVersions.includes("latest") || availableVersions.length > 0; } if (availableVersions.includes(refVersion)) { return true; } try { const semverVersions = availableVersions.filter((v) => v !== "latest" && import_semver.default.valid(v)); for (const availableVersion of semverVersions) { if (import_semver.default.satisfies(availableVersion, refVersion)) { return true; } } if (refVersion.includes(".x")) { const pattern = refVersion.replace(/\.x/g, ""); for (const availableVersion of semverVersions) { if (availableVersion.startsWith(pattern)) { return true; } } } return false; } catch (error) { return availableVersions.includes(refVersion); } }; var extractReferences = (parsedFile) => { const { file, frontmatter } = parsedFile; const references = []; if (frontmatter.owners && Array.isArray(frontmatter.owners)) { frontmatter.owners.forEach((owner, index) => { if (owner && typeof owner === "object" && "collection" in owner) { const ownerType = normalizeResourceType(owner.collection); if (ownerType) { addReference(references, owner, [ownerType], `owners[${index}]`); return; } } addReference(references, owner, ["user", "team"], `owners[${index}]`); }); } addReferences(references, frontmatter.diagrams, ["diagram"], "diagrams"); if (frontmatter.resourceGroups && Array.isArray(frontmatter.resourceGroups)) { frontmatter.resourceGroups.forEach((group, groupIndex) => { if (!group || typeof group !== "object") return; const items = group.items; if (!Array.isArray(items)) return; items.forEach( (item, itemIndex) => addTypedReference(references, item, `resourceGroups[${groupIndex}].items[${itemIndex}]`) ); }); } if (file.resourceType === "domain") { addReferences(references, frontmatter.services, ["service"], "services"); addReferences(references, frontmatter.agents, ["agent"], "agents"); addReferences(references, frontmatter.domains, ["domain"], "domains"); addReferences(references, frontmatter.systems, ["system"], "systems"); addReferences(references, frontmatter.entities, ["entity"], "entities"); addReferences(references, frontmatter["data-products"], ["dataProduct"], "data-products"); addReferences(references, frontmatter.dataProducts, ["dataProduct"], "dataProducts"); addReferences(references, frontmatter.flows, ["flow"], "flows"); addReferences(references, frontmatter.sends, MESSAGE_TYPES, "sends"); addReferences(references, frontmatter.receives, MESSAGE_TYPES, "receives"); } if (file.resourceType === "service" || file.resourceType === "agent") { addReferences(references, frontmatter.sends, MESSAGE_TYPES, "sends"); addReferences(references, frontmatter.receives, MESSAGE_TYPES, "receives"); if (frontmatter.writesTo && Array.isArray(frontmatter.writesTo)) { frontmatter.writesTo.forEach((ref) => addReference(references, ref, ["container"], "writesTo")); } if (frontmatter.readsFrom && Array.isArray(frontmatter.readsFrom)) { frontmatter.readsFrom.forEach((ref) => addReference(references, ref, ["container"], "readsFrom")); } addReferences(references, frontmatter.flows, ["flow"], "flows"); } if (file.resourceType === "service") { addReferences(references, frontmatter.entities, ["entity"], "entities"); } if (file.resourceType === "system") { addReferences(references, frontmatter.services, ["service"], "services"); addReferences(references, frontmatter.flows, ["flow"], "flows"); addReferences(references, frontmatter.entities, ["entity"], "entities"); addReferences(references, frontmatter.containers, ["container"], "containers"); addReferences(references, frontmatter.relationships, ["system"], "relationships"); } if (file.resourceType === "flow" && frontmatter.steps && Array.isArray(frontmatter.steps)) { frontmatter.steps.forEach((step, index) => { if (step.message) { addReference(references, step.message, MESSAGE_TYPES, `steps[${index}].message`); } if (step.service) { addReference(references, step.service, ["service"], `steps[${index}].service`); } if (step.agent) { addReference(references, step.agent, ["agent"], `steps[${index}].agent`); } if (step.flow) { addReference(references, step.flow, ["flow"], `steps[${index}].flow`); } if (step.container) { addReference(references, step.container, ["container"], `steps[${index}].container`); } if (step.dataProduct) { addReference(references, step.dataProduct, ["dataProduct"], `steps[${index}].dataProduct`); } }); } if (file.resourceType === "entity" && frontmatter.properties && Array.isArray(frontmatter.properties)) { frontmatter.properties.forEach((prop, index) => { if (prop.references) { references.push({ ref: { id: prop.references }, possibleTypes: ["entity"], field: `properties[${index}].references` }); } }); addReferences(references, frontmatter.services, ["service"], "services"); addReferences(references, frontmatter.domains, ["domain"], "domains"); } if (MESSAGE_TYPES.includes(file.resourceType)) { addReferences(references, frontmatter.producers, ["service"], "producers"); addReferences(references, frontmatter.consumers, ["service"], "consumers"); addReferences(references, frontmatter.channels, ["channel"], "channels"); addReferences(references, frontmatter.messageChannels, ["channel"], "messageChannels"); } if (file.resourceType === "channel") { addReferences(references, frontmatter.channels, ["channel"], "channels"); addReferences(references, frontmatter.routes, ["channel"], "routes"); if (frontmatter.messages && Array.isArray(frontmatter.messages)) { frontmatter.messages.forEach((message, index) => { if (!message || typeof message !== "object") return; const messageType = normalizeResourceType(message.collection); if (messageType && MESSAGE_TYPES.includes(messageType)) { addReference(references, message, [messageType], `messages[${index}]`); } }); } } if (file.resourceType === "container" || file.resourceType === "dataStore") { addReferences(references, frontmatter.services, ["service"], "services"); addReferences(references, frontmatter.servicesThatWriteToContainer, ["service"], "servicesThatWriteToContainer"); addReferences(references, frontmatter.servicesThatReadFromContainer, ["service"], "servicesThatReadFromContainer"); addReferences(references, frontmatter.dataProductsThatWriteToContainer, ["dataProduct"], "dataProductsThatWriteToContainer"); addReferences( references, frontmatter.dataProductsThatReadFromContainer, ["dataProduct"], "dataProductsThatReadFromContainer" ); } if (file.resourceType === "dataProduct") { const dataProductLinkTypes = [ "event", "command", "query", "service", "agent", "system", "container", "channel", "dataProduct" ]; addReferences(references, frontmatter.inputs, dataProductLinkTypes, "inputs"); addReferences(references, frontmatter.outputs, dataProductLinkTypes, "outputs"); } if (file.resourceType === "adr") { if (frontmatter.decisionMakers && Array.isArray(frontmatter.decisionMakers)) { frontmatter.decisionMakers.forEach((owner, index) => { addReference(references, owner, ["user", "team"], `decisionMakers[${index}]`); }); } if (frontmatter.appliesTo && Array.isArray(frontmatter.appliesTo)) { frontmatter.appliesTo.forEach((target, index) => { addTypedReference(references, target, `appliesTo[${index}]`); }); } addReferences(references, frontmatter.supersedes, ["adr"], "supersedes"); addReferences(references, frontmatter.supersededBy, ["adr"], "supersededBy"); addReferences(references, frontmatter.amends, ["adr"], "amends"); addReferences(references, frontmatter.amendedBy, ["adr"], "amendedBy"); addReferences(references, frontmatter.related, ["adr"], "related"); } if (file.resourceType === "team" && frontmatter.members && Array.isArray(frontmatter.members)) { frontmatter.members.forEach((member, index) => { addReference(references, member, ["user"], `members[${index}]`); }); } if (file.resourceType === "user" || file.resourceType === "team") { addReferences(references, frontmatter.ownedAgents, ["agent"], "ownedAgents"); addReferences(references, frontmatter.ownedDomains, ["domain"], "ownedDomains"); addReferences(references, frontmatter.ownedSystems, ["system"], "ownedSystems"); addReferences(references, frontmatter.ownedServices, ["service"], "ownedServices"); addReferences(references, frontmatter.ownedEvents, ["event"], "ownedEvents"); addReferences(references, frontmatter.ownedCommands, ["command"], "ownedCommands"); addReferences(references, frontmatter.ownedQueries, ["query"], "ownedQueries"); } if (file.resourceType === "user") { addReferences(references, frontmatter.associatedTeams, ["team"], "associatedTeams"); } return references; }; var extractChannelReferences = (parsedFile) => { const { file, frontmatter } = parsedFile; const references = []; if (file.resourceType !== "service" && file.resourceType !== "domain" && file.resourceType !== "agent") { return references; } const extractFromPointers = (pointers, parentField) => { if (!Array.isArray(pointers)) return; pointers.forEach((pointer, idx) => { if (pointer.to && Array.isArray(pointer.to)) { pointer.to.forEach((channelRef, cIdx) => { if (channelRef && channelRef.id) { references.push({ ref: { id: channelRef.id, version: channelRef.version }, possibleTypes: ["channel"], field: `${parentField}[${idx}].to[${cIdx}]` }); } }); } if (pointer.from && Array.isArray(pointer.from)) { pointer.from.forEach((channelRef, cIdx) => { if (channelRef && channelRef.id) { references.push({ ref: { id: channelRef.id, version: channelRef.version }, possibleTypes: ["channel"], field: `${parentField}[${idx}].from[${cIdx}]` }); } }); } }); }; if (frontmatter.sends && Array.isArray(frontmatter.sends)) { extractFromPointers(frontmatter.sends, "sends"); } if (frontmatt