@eventcatalog/linter
Version:
A linter for EventCatalog to validate frontmatter and resource references
1,288 lines (1,266 loc) • 74.4 kB
JavaScript
#!/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