@eventcatalog/sdk 2.2.6 → 2.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels.js +23 -6
- package/dist/channels.js.map +1 -1
- package/dist/channels.mjs +27 -10
- package/dist/channels.mjs.map +1 -1
- package/dist/commands.d.mts +3 -1
- package/dist/commands.d.ts +3 -1
- package/dist/commands.js +13 -5
- package/dist/commands.js.map +1 -1
- package/dist/commands.mjs +19 -11
- package/dist/commands.mjs.map +1 -1
- package/dist/custom-docs.js +2 -1
- package/dist/custom-docs.js.map +1 -1
- package/dist/custom-docs.mjs +5 -3
- package/dist/custom-docs.mjs.map +1 -1
- package/dist/domains.d.mts +1 -0
- package/dist/domains.d.ts +1 -0
- package/dist/domains.js +12 -4
- package/dist/domains.js.map +1 -1
- package/dist/domains.mjs +17 -9
- package/dist/domains.mjs.map +1 -1
- package/dist/eventcatalog.js +38 -12
- package/dist/eventcatalog.js.map +1 -1
- package/dist/eventcatalog.mjs +48 -22
- package/dist/eventcatalog.mjs.map +1 -1
- package/dist/events.d.mts +3 -1
- package/dist/events.d.ts +3 -1
- package/dist/events.js +12 -5
- package/dist/events.js.map +1 -1
- package/dist/events.mjs +18 -11
- package/dist/events.mjs.map +1 -1
- package/dist/index.d.mts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +38 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +48 -22
- package/dist/index.mjs.map +1 -1
- package/dist/queries.d.mts +3 -1
- package/dist/queries.d.ts +3 -1
- package/dist/queries.js +13 -5
- package/dist/queries.js.map +1 -1
- package/dist/queries.mjs +19 -11
- package/dist/queries.mjs.map +1 -1
- package/dist/services.d.mts +3 -1
- package/dist/services.d.ts +3 -1
- package/dist/services.js +12 -5
- package/dist/services.js.map +1 -1
- package/dist/services.mjs +21 -14
- package/dist/services.mjs.map +1 -1
- package/dist/teams.js +2 -1
- package/dist/teams.js.map +1 -1
- package/dist/teams.mjs +7 -5
- package/dist/teams.mjs.map +1 -1
- package/dist/users.js +2 -1
- package/dist/users.js.map +1 -1
- package/dist/users.mjs +7 -5
- package/dist/users.mjs.map +1 -1
- package/package.json +1 -1
package/dist/commands.mjs
CHANGED
|
@@ -3,13 +3,13 @@ import fs2 from "node:fs/promises";
|
|
|
3
3
|
import { join as join3 } from "node:path";
|
|
4
4
|
|
|
5
5
|
// src/internal/resources.ts
|
|
6
|
-
import { dirname, join as join2 } from "path";
|
|
6
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/internal/utils.ts
|
|
9
9
|
import { globSync } from "glob";
|
|
10
10
|
import fsSync from "node:fs";
|
|
11
11
|
import { copy } from "fs-extra";
|
|
12
|
-
import { join } from "node:path";
|
|
12
|
+
import { join, dirname } from "node:path";
|
|
13
13
|
import matter from "gray-matter";
|
|
14
14
|
import { satisfies, validRange, valid } from "semver";
|
|
15
15
|
var versionExists = async (catalogDir, id, version) => {
|
|
@@ -44,7 +44,8 @@ var findFileById = async (catalogDir, id, version) => {
|
|
|
44
44
|
var getFiles = async (pattern, ignore = "") => {
|
|
45
45
|
try {
|
|
46
46
|
const ignoreList = Array.isArray(ignore) ? ignore : [ignore];
|
|
47
|
-
const
|
|
47
|
+
const baseDir = pattern.includes("**") ? pattern.split("**")[0] : dirname(pattern);
|
|
48
|
+
const files = globSync(pattern, { cwd: baseDir, ignore: ["node_modules/**", ...ignoreList] });
|
|
48
49
|
return files;
|
|
49
50
|
} catch (error) {
|
|
50
51
|
throw new Error(`Error finding files: ${error}`);
|
|
@@ -92,7 +93,7 @@ var versionResource = async (catalogDir, id) => {
|
|
|
92
93
|
throw new Error(`No resource found with id: ${id}`);
|
|
93
94
|
}
|
|
94
95
|
const file = matchedFiles[0];
|
|
95
|
-
const sourceDirectory =
|
|
96
|
+
const sourceDirectory = dirname2(file);
|
|
96
97
|
const { data: { version = "0.0.1" } = {} } = matter2.read(file);
|
|
97
98
|
const targetDirectory = getVersionedDirectory(sourceDirectory, version);
|
|
98
99
|
fsSync2.mkdirSync(targetDirectory, { recursive: true });
|
|
@@ -113,12 +114,14 @@ var writeResource = async (catalogDir, resource, options = {
|
|
|
113
114
|
path: "",
|
|
114
115
|
type: "",
|
|
115
116
|
override: false,
|
|
116
|
-
versionExistingContent: false
|
|
117
|
+
versionExistingContent: false,
|
|
118
|
+
format: "mdx"
|
|
117
119
|
}) => {
|
|
118
120
|
const path = options.path || `/${resource.id}`;
|
|
119
121
|
const fullPath = join2(catalogDir, path);
|
|
122
|
+
const format = options.format || "mdx";
|
|
120
123
|
fsSync2.mkdirSync(fullPath, { recursive: true });
|
|
121
|
-
const lockPath = join2(fullPath,
|
|
124
|
+
const lockPath = join2(fullPath, `index.${format}`);
|
|
122
125
|
if (!fsSync2.existsSync(lockPath)) {
|
|
123
126
|
fsSync2.writeFileSync(lockPath, "");
|
|
124
127
|
}
|
|
@@ -165,7 +168,7 @@ var getResourcePath = async (catalogDir, id, version) => {
|
|
|
165
168
|
return {
|
|
166
169
|
fullPath: file,
|
|
167
170
|
relativePath: file.replace(catalogDir, ""),
|
|
168
|
-
directory:
|
|
171
|
+
directory: dirname2(file.replace(catalogDir, ""))
|
|
169
172
|
};
|
|
170
173
|
};
|
|
171
174
|
var getResources = async (catalogDir, {
|
|
@@ -201,7 +204,7 @@ var rmResourceById = async (catalogDir, id, version, options) => {
|
|
|
201
204
|
} else {
|
|
202
205
|
await Promise.all(
|
|
203
206
|
matchedFiles.map(async (file) => {
|
|
204
|
-
const directory =
|
|
207
|
+
const directory = dirname2(file);
|
|
205
208
|
await fs.rm(directory, { recursive: true, force: true });
|
|
206
209
|
})
|
|
207
210
|
);
|
|
@@ -210,7 +213,7 @@ var rmResourceById = async (catalogDir, id, version, options) => {
|
|
|
210
213
|
var addFileToResource = async (catalogDir, id, file, version) => {
|
|
211
214
|
const pathToResource = await findFileById(catalogDir, id, version);
|
|
212
215
|
if (!pathToResource) throw new Error("Cannot find directory to write file to");
|
|
213
|
-
fsSync2.writeFileSync(join2(
|
|
216
|
+
fsSync2.writeFileSync(join2(dirname2(pathToResource), file.fileName), file.content);
|
|
214
217
|
};
|
|
215
218
|
var getVersionedDirectory = (sourceDirectory, version) => {
|
|
216
219
|
return join2(sourceDirectory, "versioned", version);
|
|
@@ -219,8 +222,13 @@ var getVersionedDirectory = (sourceDirectory, version) => {
|
|
|
219
222
|
// src/commands.ts
|
|
220
223
|
var getCommand = (directory) => async (id, version) => getResource(directory, id, version, { type: "command" });
|
|
221
224
|
var getCommands = (directory) => async (options) => getResources(directory, { type: "commands", ...options });
|
|
222
|
-
var writeCommand = (directory) => async (command, options = {
|
|
223
|
-
|
|
225
|
+
var writeCommand = (directory) => async (command, options = {
|
|
226
|
+
path: "",
|
|
227
|
+
override: false,
|
|
228
|
+
versionExistingContent: false,
|
|
229
|
+
format: "mdx"
|
|
230
|
+
}) => writeResource(directory, { ...command }, { ...options, type: "command" });
|
|
231
|
+
var writeCommandToService = (directory) => async (command, service, options = { path: "", format: "mdx" }) => {
|
|
224
232
|
const resourcePath = await getResourcePath(directory, service.id, service.version);
|
|
225
233
|
if (!resourcePath) {
|
|
226
234
|
throw new Error("Service not found");
|
package/dist/commands.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands.ts","../src/internal/resources.ts","../src/internal/utils.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Command } from './types';\nimport {\n addFileToResource,\n getResource,\n getResourcePath,\n getResources,\n rmResourceById,\n versionResource,\n writeResource,\n} from './internal/resources';\nimport { findFileById } from './internal/utils';\nimport { addMessageToService } from './services';\n\n/**\n * Returns a command from EventCatalog.\n *\n * You can optionally specify a version to get a specific version of the command\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCommand } = utils('/path/to/eventcatalog');\n *\n * // Gets the latest version of the command\n * const command = await getCommand('UpdateInventory');\n *\n * // Gets a version of the command\n * const command = await getCommand('UpdateInventory', '0.0.1');\n * ```\n */\nexport const getCommand =\n (directory: string) =>\n async (id: string, version?: string): Promise<Command> =>\n getResource(directory, id, version, { type: 'command' }) as Promise<Command>;\n\n/**\n * Returns all commands from EventCatalog.\n *\n * You can optionally specify if you want to get the latest version of the events.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCommands } = utils('/path/to/eventcatalog');\n *\n * // Gets all commands (and versions) from the catalog\n * const commands = await getCommands();\n *\n * // Gets all commands (only latest version) from the catalog\n * const commands = await getCommands({ latestOnly: true });\n * ```\n */\nexport const getCommands =\n (directory: string) =>\n async (options?: { latestOnly?: boolean }): Promise<Command[]> =>\n getResources(directory, { type: 'commands', ...options }) as Promise<Command[]>;\n\n/**\n * Write a command to EventCatalog.\n *\n * You can optionally override the path of the command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { writeCommand } = utils('/path/to/eventcatalog');\n *\n * // Write a command to the catalog\n * // Command would be written to commands/UpdateInventory\n * await writeCommand({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * });\n *\n * // Write a command to the catalog but override the path\n * // Command would be written to commands/Inventory/UpdateInventory\n * await writeCommand({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * }, { path: \"/Inventory/UpdateInventory\"});\n *\n * // Write a command to the catalog and override the existing content (if there is any)\n * await writeCommand({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * }, { override: true });\n *\n * // Write a command to the catalog and version the previous version\n * // only works if the new version is greater than the previous version\n * await writeCommand({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * }, { versionExistingContent: true });\n *\n * ```\n */\nexport const writeCommand =\n (directory: string) =>\n async (command: Command, options: { path?: string; override?: boolean; versionExistingContent?: boolean } = { path: '' }) =>\n writeResource(directory, { ...command }, { ...options, type: 'command' });\n\n/**\n * Write an command to a service in EventCatalog.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { writeCommandToService } = utils('/path/to/eventcatalog');\n *\n * // Write an event to a given service in the catalog\n * // Event would be written to services/Inventory/commands/UpdateInventory\n * await writeCommandToService({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * }, { id: 'Inventory' });\n * ```\n */\nexport const writeCommandToService =\n (directory: string) =>\n async (command: Command, service: { id: string; version?: string }, options: { path: string } = { path: '' }) => {\n const resourcePath = await getResourcePath(directory, service.id, service.version);\n if (!resourcePath) {\n throw new Error('Service not found');\n }\n\n let pathForCommand =\n service.version && service.version !== 'latest'\n ? `${resourcePath.directory}/versioned/${service.version}/commands`\n : `${resourcePath.directory}/commands`;\n pathForCommand = join(pathForCommand, command.id);\n\n await writeResource(directory, { ...command }, { ...options, path: pathForCommand, type: 'command' });\n };\n\n/**\n * Delete a command at it's given path.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { rmCommand } = utils('/path/to/eventcatalog');\n *\n * // removes a command at the given path (commands dir is appended to the given path)\n * // Removes the command at commands/UpdateInventory\n * await rmCommand('/UpdateInventory');\n * ```\n */\nexport const rmCommand = (directory: string) => async (path: string) => {\n await fs.rm(join(directory, path), { recursive: true });\n};\n\n/**\n * Delete a command by it's id.\n *\n * Optionally specify a version to delete a specific version of the command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { rmCommandById } = utils('/path/to/eventcatalog');\n *\n * // deletes the latest UpdateInventory command\n * await rmCommandById('UpdateInventory');\n *\n * // deletes a specific version of the UpdateInventory command\n * await rmCommandById('UpdateInventory', '0.0.1');\n * ```\n */\nexport const rmCommandById = (directory: string) => async (id: string, version?: string, persistFiles?: boolean) =>\n rmResourceById(directory, id, version, { type: 'command', persistFiles });\n\n/**\n * Version a command by it's id.\n *\n * Takes the latest command and moves it to a versioned directory.\n * All files with this command are also versioned (e.g /commands/UpdateInventory/schema.json)\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { versionCommand } = utils('/path/to/eventcatalog');\n *\n * // moves the latest UpdateInventory command to a versioned directory\n * // the version within that command is used as the version number.\n * await versionCommand('UpdateInventory');\n *\n * ```\n */\nexport const versionCommand = (directory: string) => async (id: string) => versionResource(directory, id);\n\n/**\n * Add a file to a command by it's id.\n *\n * Optionally specify a version to add a file to a specific version of the command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { addFileToCommand } = utils('/path/to/eventcatalog');\n *\n * // adds a file to the latest UpdateInventory command\n * await addFileToCommand('UpdateInventory', { content: 'Hello world', fileName: 'hello.txt' });\n *\n * // adds a file to a specific version of the UpdateInventory command\n * await addFileToCommand('UpdateInventory', { content: 'Hello world', fileName: 'hello.txt' }, '0.0.1');\n *\n * ```\n */\nexport const addFileToCommand =\n (directory: string) => async (id: string, file: { content: string; fileName: string }, version?: string) =>\n addFileToResource(directory, id, file, version);\n\n/**\n * Add a schema to a command by it's id.\n *\n * Optionally specify a version to add a schema to a specific version of the command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { addSchemaToCommand } = utils('/path/to/eventcatalog');\n *\n * // JSON schema example\n * const schema = {\n * \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n * \"type\": \"object\",\n * \"properties\": {\n * \"name\": {\n * \"type\": \"string\"\n * },\n * \"age\": {\n * \"type\": \"number\"\n * }\n * },\n * \"required\": [\"name\", \"age\"]\n * };\n *\n * // adds a schema to the latest UpdateInventory command\n * await addSchemaToCommand('UpdateInventory', { schema, fileName: 'schema.json' });\n *\n * // adds a file to a specific version of the UpdateInventory command\n * await addSchemaToCommand('UpdateInventory', { schema, fileName: 'schema.json' }, '0.0.1');\n *\n * ```\n */\nexport const addSchemaToCommand =\n (directory: string) => async (id: string, schema: { schema: string; fileName: string }, version?: string) => {\n await addFileToCommand(directory)(id, { content: schema.schema, fileName: schema.fileName }, version);\n };\n\n/**\n * Check to see if the catalog has a version for the given command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { commandHasVersion } = utils('/path/to/eventcatalog');\n *\n * // returns true if version is found for the given event and version (supports semver)\n * await commandHasVersion('InventoryAdjusted', '0.0.1');\n * await commandHasVersion('InventoryAdjusted', 'latest');\n * await commandHasVersion('InventoryAdjusted', '0.0.x');*\n *\n * ```\n */\nexport const commandHasVersion = (directory: string) => async (id: string, version?: string) => {\n const file = await findFileById(directory, id, version);\n return !!file;\n};\n","import { dirname, join } from 'path';\nimport { copyDir, findFileById, getFiles, searchFilesForId, versionExists } from './utils';\nimport matter from 'gray-matter';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { Message, Service, CustomDoc } from '../types';\nimport { satisfies } from 'semver';\nimport { lock, unlock } from 'proper-lockfile';\n\ntype Resource = Service | Message | CustomDoc;\n\nexport const versionResource = async (catalogDir: string, id: string) => {\n // Find all the events in the directory\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = await searchFilesForId(files, id);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No resource found with id: ${id}`);\n }\n\n // Event that is in the route of the project\n const file = matchedFiles[0];\n const sourceDirectory = dirname(file);\n const { data: { version = '0.0.1' } = {} } = matter.read(file);\n const targetDirectory = getVersionedDirectory(sourceDirectory, version);\n\n fsSync.mkdirSync(targetDirectory, { recursive: true });\n\n // Copy the event to the versioned directory\n await copyDir(catalogDir, sourceDirectory, targetDirectory, (src) => {\n return !src.includes('versioned');\n });\n\n // Remove all the files in the root of the resource as they have now been versioned\n await fs.readdir(sourceDirectory).then(async (resourceFiles) => {\n await Promise.all(\n resourceFiles.map(async (file) => {\n if (file !== 'versioned') {\n fsSync.rmSync(join(sourceDirectory, file), { recursive: true });\n }\n })\n );\n });\n};\n\nexport const writeResource = async (\n catalogDir: string,\n resource: Resource,\n options: { path?: string; type: string; override?: boolean; versionExistingContent?: boolean } = {\n path: '',\n type: '',\n override: false,\n versionExistingContent: false,\n }\n) => {\n const path = options.path || `/${resource.id}`;\n const fullPath = join(catalogDir, path);\n\n // Create directory if it doesn't exist\n fsSync.mkdirSync(fullPath, { recursive: true });\n\n // Create or get lock file path\n const lockPath = join(fullPath, 'index.mdx');\n\n // Ensure the file exists before attempting to lock it\n if (!fsSync.existsSync(lockPath)) {\n fsSync.writeFileSync(lockPath, '');\n }\n\n try {\n // Acquire lock with retry\n await lock(lockPath, {\n retries: 5,\n stale: 10000, // 10 seconds\n });\n\n const exists = await versionExists(catalogDir, resource.id, resource.version);\n\n if (exists && !options.override) {\n throw new Error(`Failed to write ${resource.id} (${options.type}) as the version ${resource.version} already exists`);\n }\n\n const { markdown, ...frontmatter } = resource;\n\n if (options.versionExistingContent && !exists) {\n const currentResource = await getResource(catalogDir, resource.id);\n\n if (currentResource) {\n if (satisfies(resource.version, `>${currentResource.version}`)) {\n await versionResource(catalogDir, resource.id);\n } else {\n throw new Error(`New version ${resource.version} is not greater than current version ${currentResource.version}`);\n }\n }\n }\n\n const document = matter.stringify(markdown.trim(), frontmatter);\n fsSync.writeFileSync(lockPath, document);\n } finally {\n // Always release the lock\n await unlock(lockPath).catch(() => {});\n }\n};\n\nexport const getResource = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string }\n): Promise<Resource | undefined> => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n const { data, content } = matter.read(file);\n\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n};\n\nexport const getResourcePath = async (catalogDir: string, id: string, version?: string) => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n return {\n fullPath: file,\n relativePath: file.replace(catalogDir, ''),\n directory: dirname(file.replace(catalogDir, '')),\n };\n};\n\nexport const getResources = async (\n catalogDir: string,\n {\n type,\n latestOnly = false,\n ignore = [],\n pattern = '',\n }: { type: string; pattern?: string; latestOnly?: boolean; ignore?: string[] }\n): Promise<Resource[] | undefined> => {\n const ignoreList = latestOnly ? `**/versioned/**` : '';\n const filePattern = pattern || `${catalogDir}/**/${type}/**/index.{md,mdx}`;\n const files = await getFiles(filePattern, [ignoreList, ...ignore]);\n\n if (files.length === 0) return;\n\n return files.map((file) => {\n const { data, content } = matter.read(file);\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n });\n};\n\nexport const rmResourceById = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string; persistFiles?: boolean }\n) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = await searchFilesForId(files, id, version);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No ${options?.type || 'resource'} found with id: ${id}`);\n }\n\n if (options?.persistFiles) {\n await Promise.all(\n matchedFiles.map(async (file) => {\n await fs.rm(file, { recursive: true });\n })\n );\n } else {\n await Promise.all(\n matchedFiles.map(async (file) => {\n const directory = dirname(file);\n await fs.rm(directory, { recursive: true, force: true });\n })\n );\n }\n};\n\nexport const addFileToResource = async (\n catalogDir: string,\n id: string,\n file: { content: string; fileName: string },\n version?: string\n) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory to write file to');\n\n fsSync.writeFileSync(join(dirname(pathToResource), file.fileName), file.content);\n};\n\nexport const getFileFromResource = async (catalogDir: string, id: string, file: { fileName: string }, version?: string) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory of resource');\n\n const exists = await fs\n .access(join(dirname(pathToResource), file.fileName))\n .then(() => true)\n .catch(() => false);\n if (!exists) throw new Error(`File ${file.fileName} does not exist in resource ${id} v(${version})`);\n\n return fsSync.readFileSync(join(dirname(pathToResource), file.fileName), 'utf-8');\n};\nexport const getVersionedDirectory = (sourceDirectory: string, version: any): string => {\n return join(sourceDirectory, 'versioned', version);\n};\n","import { glob, globSync } from 'glob';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { copy, copySync, CopyFilterAsync, CopyFilterSync } from 'fs-extra';\nimport { join } from 'node:path';\nimport matter from 'gray-matter';\nimport { satisfies, validRange, valid } from 'semver';\n\n/**\n * Returns true if a given version of a resource id exists in the catalog\n */\nexport const versionExists = async (catalogDir: string, id: string, version: string) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = (await searchFilesForId(files, id, version)) || [];\n return matchedFiles.length > 0;\n};\n\nexport const findFileById = async (catalogDir: string, id: string, version?: string): Promise<string | undefined> => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = (await searchFilesForId(files, id)) || [];\n const latestVersion = matchedFiles.find((path) => !path.includes('versioned'));\n\n // If no version is provided, return the latest version\n if (!version) {\n return latestVersion;\n }\n\n // map files into gray matter to get versions\n const parsedFiles = matchedFiles.map((path) => {\n const { data } = matter.read(path);\n return { ...data, path };\n }) as any[];\n\n const semverRange = validRange(version);\n\n if (semverRange && valid(version)) {\n const match = parsedFiles.filter((c) => satisfies(c.version, semverRange));\n return match.length > 0 ? match[0].path : undefined;\n }\n\n // Order by version\n const sorted = parsedFiles.sort((a, b) => {\n return a.version.localeCompare(b.version);\n });\n\n // latest version\n const match = sorted.length > 0 ? [sorted[sorted.length - 1]] : [];\n\n if (match.length > 0) {\n return match[0].path;\n }\n};\n\nexport const getFiles = async (pattern: string, ignore: string | string[] = '') => {\n try {\n const ignoreList = Array.isArray(ignore) ? ignore : [ignore];\n const files = globSync(pattern, { ignore: ['node_modules/**', ...ignoreList] });\n return files;\n } catch (error) {\n throw new Error(`Error finding files: ${error}`);\n }\n};\n\nexport const readMdxFile = async (path: string) => {\n const { data } = matter.read(path);\n const { markdown, ...frontmatter } = data;\n return { ...frontmatter, markdown };\n};\n\nexport const searchFilesForId = async (files: string[], id: string, version?: string) => {\n const idRegex = new RegExp(`^id:\\\\s*(['\"]|>-)?\\\\s*${id}['\"]?\\\\s*$`, 'm');\n const versionRegex = new RegExp(`^version:\\\\s*['\"]?${version}['\"]?\\\\s*$`, 'm');\n\n const matches = files.map((file) => {\n const content = fsSync.readFileSync(file, 'utf-8');\n const hasIdMatch = content.match(idRegex);\n\n // Check version if provided\n if (version && !content.match(versionRegex)) {\n return undefined;\n }\n\n if (hasIdMatch) {\n return file;\n }\n });\n\n return matches.filter(Boolean).filter((file) => file !== undefined);\n};\n\n/**\n * Function to copy a directory from source to target, uses a tmp directory\n * @param catalogDir\n * @param source\n * @param target\n * @param filter\n */\nexport const copyDir = async (catalogDir: string, source: string, target: string, filter?: CopyFilterAsync | CopyFilterSync) => {\n const tmpDirectory = join(catalogDir, 'tmp');\n fsSync.mkdirSync(tmpDirectory, { recursive: true });\n\n // Copy everything over\n await copy(source, tmpDirectory, {\n overwrite: true,\n filter,\n });\n\n await copy(tmpDirectory, target, {\n overwrite: true,\n filter,\n });\n\n // Remove the tmp directory\n fsSync.rmSync(tmpDirectory, { recursive: true });\n};\n\n// Makes sure values in sends/recieves are unique\nexport const uniqueVersions = (messages: { id: string; version: string }[]): { id: string; version: string }[] => {\n const uniqueSet = new Set();\n\n return messages.filter((message) => {\n const key = `${message.id}-${message.version}`;\n if (!uniqueSet.has(key)) {\n uniqueSet.add(key);\n return true;\n }\n return false;\n });\n};\n"],"mappings":"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","names":["fs","join","join","match","matter","fsSync","satisfies","file","join","join","join","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/commands.ts","../src/internal/resources.ts","../src/internal/utils.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Command } from './types';\nimport {\n addFileToResource,\n getResource,\n getResourcePath,\n getResources,\n rmResourceById,\n versionResource,\n writeResource,\n} from './internal/resources';\nimport { findFileById } from './internal/utils';\nimport { addMessageToService } from './services';\n\n/**\n * Returns a command from EventCatalog.\n *\n * You can optionally specify a version to get a specific version of the command\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCommand } = utils('/path/to/eventcatalog');\n *\n * // Gets the latest version of the command\n * const command = await getCommand('UpdateInventory');\n *\n * // Gets a version of the command\n * const command = await getCommand('UpdateInventory', '0.0.1');\n * ```\n */\nexport const getCommand =\n (directory: string) =>\n async (id: string, version?: string): Promise<Command> =>\n getResource(directory, id, version, { type: 'command' }) as Promise<Command>;\n\n/**\n * Returns all commands from EventCatalog.\n *\n * You can optionally specify if you want to get the latest version of the events.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCommands } = utils('/path/to/eventcatalog');\n *\n * // Gets all commands (and versions) from the catalog\n * const commands = await getCommands();\n *\n * // Gets all commands (only latest version) from the catalog\n * const commands = await getCommands({ latestOnly: true });\n * ```\n */\nexport const getCommands =\n (directory: string) =>\n async (options?: { latestOnly?: boolean }): Promise<Command[]> =>\n getResources(directory, { type: 'commands', ...options }) as Promise<Command[]>;\n\n/**\n * Write a command to EventCatalog.\n *\n * You can optionally override the path of the command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { writeCommand } = utils('/path/to/eventcatalog');\n *\n * // Write a command to the catalog\n * // Command would be written to commands/UpdateInventory\n * await writeCommand({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * });\n *\n * // Write a command to the catalog but override the path\n * // Command would be written to commands/Inventory/UpdateInventory\n * await writeCommand({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * }, { path: \"/Inventory/UpdateInventory\"});\n *\n * // Write a command to the catalog and override the existing content (if there is any)\n * await writeCommand({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * }, { override: true });\n *\n * // Write a command to the catalog and version the previous version\n * // only works if the new version is greater than the previous version\n * await writeCommand({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * }, { versionExistingContent: true });\n *\n * ```\n */\nexport const writeCommand =\n (directory: string) =>\n async (\n command: Command,\n options: { path?: string; override?: boolean; versionExistingContent?: boolean; format?: 'md' | 'mdx' } = {\n path: '',\n override: false,\n versionExistingContent: false,\n format: 'mdx',\n }\n ) =>\n writeResource(directory, { ...command }, { ...options, type: 'command' });\n\n/**\n * Write an command to a service in EventCatalog.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { writeCommandToService } = utils('/path/to/eventcatalog');\n *\n * // Write an event to a given service in the catalog\n * // Event would be written to services/Inventory/commands/UpdateInventory\n * await writeCommandToService({\n * id: 'UpdateInventory',\n * name: 'Update Inventory',\n * version: '0.0.1',\n * summary: 'This is a summary',\n * markdown: '# Hello world',\n * }, { id: 'Inventory' });\n * ```\n */\nexport const writeCommandToService =\n (directory: string) =>\n async (\n command: Command,\n service: { id: string; version?: string },\n options: { path?: string; format?: 'md' | 'mdx' } = { path: '', format: 'mdx' }\n ) => {\n const resourcePath = await getResourcePath(directory, service.id, service.version);\n if (!resourcePath) {\n throw new Error('Service not found');\n }\n\n let pathForCommand =\n service.version && service.version !== 'latest'\n ? `${resourcePath.directory}/versioned/${service.version}/commands`\n : `${resourcePath.directory}/commands`;\n pathForCommand = join(pathForCommand, command.id);\n\n await writeResource(directory, { ...command }, { ...options, path: pathForCommand, type: 'command' });\n };\n\n/**\n * Delete a command at it's given path.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { rmCommand } = utils('/path/to/eventcatalog');\n *\n * // removes a command at the given path (commands dir is appended to the given path)\n * // Removes the command at commands/UpdateInventory\n * await rmCommand('/UpdateInventory');\n * ```\n */\nexport const rmCommand = (directory: string) => async (path: string) => {\n await fs.rm(join(directory, path), { recursive: true });\n};\n\n/**\n * Delete a command by it's id.\n *\n * Optionally specify a version to delete a specific version of the command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { rmCommandById } = utils('/path/to/eventcatalog');\n *\n * // deletes the latest UpdateInventory command\n * await rmCommandById('UpdateInventory');\n *\n * // deletes a specific version of the UpdateInventory command\n * await rmCommandById('UpdateInventory', '0.0.1');\n * ```\n */\nexport const rmCommandById = (directory: string) => async (id: string, version?: string, persistFiles?: boolean) =>\n rmResourceById(directory, id, version, { type: 'command', persistFiles });\n\n/**\n * Version a command by it's id.\n *\n * Takes the latest command and moves it to a versioned directory.\n * All files with this command are also versioned (e.g /commands/UpdateInventory/schema.json)\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { versionCommand } = utils('/path/to/eventcatalog');\n *\n * // moves the latest UpdateInventory command to a versioned directory\n * // the version within that command is used as the version number.\n * await versionCommand('UpdateInventory');\n *\n * ```\n */\nexport const versionCommand = (directory: string) => async (id: string) => versionResource(directory, id);\n\n/**\n * Add a file to a command by it's id.\n *\n * Optionally specify a version to add a file to a specific version of the command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { addFileToCommand } = utils('/path/to/eventcatalog');\n *\n * // adds a file to the latest UpdateInventory command\n * await addFileToCommand('UpdateInventory', { content: 'Hello world', fileName: 'hello.txt' });\n *\n * // adds a file to a specific version of the UpdateInventory command\n * await addFileToCommand('UpdateInventory', { content: 'Hello world', fileName: 'hello.txt' }, '0.0.1');\n *\n * ```\n */\nexport const addFileToCommand =\n (directory: string) => async (id: string, file: { content: string; fileName: string }, version?: string) =>\n addFileToResource(directory, id, file, version);\n\n/**\n * Add a schema to a command by it's id.\n *\n * Optionally specify a version to add a schema to a specific version of the command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { addSchemaToCommand } = utils('/path/to/eventcatalog');\n *\n * // JSON schema example\n * const schema = {\n * \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n * \"type\": \"object\",\n * \"properties\": {\n * \"name\": {\n * \"type\": \"string\"\n * },\n * \"age\": {\n * \"type\": \"number\"\n * }\n * },\n * \"required\": [\"name\", \"age\"]\n * };\n *\n * // adds a schema to the latest UpdateInventory command\n * await addSchemaToCommand('UpdateInventory', { schema, fileName: 'schema.json' });\n *\n * // adds a file to a specific version of the UpdateInventory command\n * await addSchemaToCommand('UpdateInventory', { schema, fileName: 'schema.json' }, '0.0.1');\n *\n * ```\n */\nexport const addSchemaToCommand =\n (directory: string) => async (id: string, schema: { schema: string; fileName: string }, version?: string) => {\n await addFileToCommand(directory)(id, { content: schema.schema, fileName: schema.fileName }, version);\n };\n\n/**\n * Check to see if the catalog has a version for the given command.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { commandHasVersion } = utils('/path/to/eventcatalog');\n *\n * // returns true if version is found for the given event and version (supports semver)\n * await commandHasVersion('InventoryAdjusted', '0.0.1');\n * await commandHasVersion('InventoryAdjusted', 'latest');\n * await commandHasVersion('InventoryAdjusted', '0.0.x');*\n *\n * ```\n */\nexport const commandHasVersion = (directory: string) => async (id: string, version?: string) => {\n const file = await findFileById(directory, id, version);\n return !!file;\n};\n","import { dirname, join } from 'path';\nimport { copyDir, findFileById, getFiles, searchFilesForId, versionExists } from './utils';\nimport matter from 'gray-matter';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { Message, Service, CustomDoc } from '../types';\nimport { satisfies } from 'semver';\nimport { lock, unlock } from 'proper-lockfile';\n\ntype Resource = Service | Message | CustomDoc;\n\nexport const versionResource = async (catalogDir: string, id: string) => {\n // Find all the events in the directory\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = await searchFilesForId(files, id);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No resource found with id: ${id}`);\n }\n\n // Event that is in the route of the project\n const file = matchedFiles[0];\n const sourceDirectory = dirname(file);\n const { data: { version = '0.0.1' } = {} } = matter.read(file);\n const targetDirectory = getVersionedDirectory(sourceDirectory, version);\n\n fsSync.mkdirSync(targetDirectory, { recursive: true });\n\n // Copy the event to the versioned directory\n await copyDir(catalogDir, sourceDirectory, targetDirectory, (src) => {\n return !src.includes('versioned');\n });\n\n // Remove all the files in the root of the resource as they have now been versioned\n await fs.readdir(sourceDirectory).then(async (resourceFiles) => {\n await Promise.all(\n resourceFiles.map(async (file) => {\n if (file !== 'versioned') {\n fsSync.rmSync(join(sourceDirectory, file), { recursive: true });\n }\n })\n );\n });\n};\n\nexport const writeResource = async (\n catalogDir: string,\n resource: Resource,\n options: { path?: string; type: string; override?: boolean; versionExistingContent?: boolean; format?: 'md' | 'mdx' } = {\n path: '',\n type: '',\n override: false,\n versionExistingContent: false,\n format: 'mdx',\n }\n) => {\n const path = options.path || `/${resource.id}`;\n const fullPath = join(catalogDir, path);\n const format = options.format || 'mdx';\n\n // Create directory if it doesn't exist\n fsSync.mkdirSync(fullPath, { recursive: true });\n\n // Create or get lock file path\n const lockPath = join(fullPath, `index.${format}`);\n\n // Ensure the file exists before attempting to lock it\n if (!fsSync.existsSync(lockPath)) {\n fsSync.writeFileSync(lockPath, '');\n }\n\n try {\n // Acquire lock with retry\n await lock(lockPath, {\n retries: 5,\n stale: 10000, // 10 seconds\n });\n\n const exists = await versionExists(catalogDir, resource.id, resource.version);\n\n if (exists && !options.override) {\n throw new Error(`Failed to write ${resource.id} (${options.type}) as the version ${resource.version} already exists`);\n }\n\n const { markdown, ...frontmatter } = resource;\n\n if (options.versionExistingContent && !exists) {\n const currentResource = await getResource(catalogDir, resource.id);\n\n if (currentResource) {\n if (satisfies(resource.version, `>${currentResource.version}`)) {\n await versionResource(catalogDir, resource.id);\n } else {\n throw new Error(`New version ${resource.version} is not greater than current version ${currentResource.version}`);\n }\n }\n }\n\n const document = matter.stringify(markdown.trim(), frontmatter);\n fsSync.writeFileSync(lockPath, document);\n } finally {\n // Always release the lock\n await unlock(lockPath).catch(() => {});\n }\n};\n\nexport const getResource = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string }\n): Promise<Resource | undefined> => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n const { data, content } = matter.read(file);\n\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n};\n\nexport const getResourcePath = async (catalogDir: string, id: string, version?: string) => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n return {\n fullPath: file,\n relativePath: file.replace(catalogDir, ''),\n directory: dirname(file.replace(catalogDir, '')),\n };\n};\n\nexport const getResources = async (\n catalogDir: string,\n {\n type,\n latestOnly = false,\n ignore = [],\n pattern = '',\n }: { type: string; pattern?: string; latestOnly?: boolean; ignore?: string[] }\n): Promise<Resource[] | undefined> => {\n const ignoreList = latestOnly ? `**/versioned/**` : '';\n const filePattern = pattern || `${catalogDir}/**/${type}/**/index.{md,mdx}`;\n const files = await getFiles(filePattern, [ignoreList, ...ignore]);\n\n if (files.length === 0) return;\n\n return files.map((file) => {\n const { data, content } = matter.read(file);\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n });\n};\n\nexport const rmResourceById = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string; persistFiles?: boolean }\n) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = await searchFilesForId(files, id, version);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No ${options?.type || 'resource'} found with id: ${id}`);\n }\n\n if (options?.persistFiles) {\n await Promise.all(\n matchedFiles.map(async (file) => {\n await fs.rm(file, { recursive: true });\n })\n );\n } else {\n await Promise.all(\n matchedFiles.map(async (file) => {\n const directory = dirname(file);\n await fs.rm(directory, { recursive: true, force: true });\n })\n );\n }\n};\n\nexport const addFileToResource = async (\n catalogDir: string,\n id: string,\n file: { content: string; fileName: string },\n version?: string\n) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory to write file to');\n\n fsSync.writeFileSync(join(dirname(pathToResource), file.fileName), file.content);\n};\n\nexport const getFileFromResource = async (catalogDir: string, id: string, file: { fileName: string }, version?: string) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory of resource');\n\n const exists = await fs\n .access(join(dirname(pathToResource), file.fileName))\n .then(() => true)\n .catch(() => false);\n if (!exists) throw new Error(`File ${file.fileName} does not exist in resource ${id} v(${version})`);\n\n return fsSync.readFileSync(join(dirname(pathToResource), file.fileName), 'utf-8');\n};\nexport const getVersionedDirectory = (sourceDirectory: string, version: any): string => {\n return join(sourceDirectory, 'versioned', version);\n};\n","import { globSync } from 'glob';\nimport fsSync from 'node:fs';\nimport { copy, CopyFilterAsync, CopyFilterSync } from 'fs-extra';\nimport { join, dirname } from 'node:path';\nimport matter from 'gray-matter';\nimport { satisfies, validRange, valid } from 'semver';\n\n/**\n * Returns true if a given version of a resource id exists in the catalog\n */\nexport const versionExists = async (catalogDir: string, id: string, version: string) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = (await searchFilesForId(files, id, version)) || [];\n return matchedFiles.length > 0;\n};\n\nexport const findFileById = async (catalogDir: string, id: string, version?: string): Promise<string | undefined> => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = (await searchFilesForId(files, id)) || [];\n const latestVersion = matchedFiles.find((path) => !path.includes('versioned'));\n\n // If no version is provided, return the latest version\n if (!version) {\n return latestVersion;\n }\n\n // map files into gray matter to get versions\n const parsedFiles = matchedFiles.map((path) => {\n const { data } = matter.read(path);\n return { ...data, path };\n }) as any[];\n\n const semverRange = validRange(version);\n\n if (semverRange && valid(version)) {\n const match = parsedFiles.filter((c) => satisfies(c.version, semverRange));\n return match.length > 0 ? match[0].path : undefined;\n }\n\n // Order by version\n const sorted = parsedFiles.sort((a, b) => {\n return a.version.localeCompare(b.version);\n });\n\n // latest version\n const match = sorted.length > 0 ? [sorted[sorted.length - 1]] : [];\n\n if (match.length > 0) {\n return match[0].path;\n }\n};\n\nexport const getFiles = async (pattern: string, ignore: string | string[] = '') => {\n try {\n const ignoreList = Array.isArray(ignore) ? ignore : [ignore];\n const baseDir = pattern.includes('**') ? pattern.split('**')[0] : dirname(pattern);\n const files = globSync(pattern, { cwd: baseDir, ignore: ['node_modules/**', ...ignoreList] });\n return files;\n } catch (error) {\n throw new Error(`Error finding files: ${error}`);\n }\n};\n\nexport const readMdxFile = async (path: string) => {\n const { data } = matter.read(path);\n const { markdown, ...frontmatter } = data;\n return { ...frontmatter, markdown };\n};\n\nexport const searchFilesForId = async (files: string[], id: string, version?: string) => {\n const idRegex = new RegExp(`^id:\\\\s*(['\"]|>-)?\\\\s*${id}['\"]?\\\\s*$`, 'm');\n const versionRegex = new RegExp(`^version:\\\\s*['\"]?${version}['\"]?\\\\s*$`, 'm');\n\n const matches = files.map((file) => {\n const content = fsSync.readFileSync(file, 'utf-8');\n const hasIdMatch = content.match(idRegex);\n\n // Check version if provided\n if (version && !content.match(versionRegex)) {\n return undefined;\n }\n\n if (hasIdMatch) {\n return file;\n }\n });\n\n return matches.filter(Boolean).filter((file) => file !== undefined);\n};\n\n/**\n * Function to copy a directory from source to target, uses a tmp directory\n * @param catalogDir\n * @param source\n * @param target\n * @param filter\n */\nexport const copyDir = async (catalogDir: string, source: string, target: string, filter?: CopyFilterAsync | CopyFilterSync) => {\n const tmpDirectory = join(catalogDir, 'tmp');\n fsSync.mkdirSync(tmpDirectory, { recursive: true });\n\n // Copy everything over\n await copy(source, tmpDirectory, {\n overwrite: true,\n filter,\n });\n\n await copy(tmpDirectory, target, {\n overwrite: true,\n filter,\n });\n\n // Remove the tmp directory\n fsSync.rmSync(tmpDirectory, { recursive: true });\n};\n\n// Makes sure values in sends/recieves are unique\nexport const uniqueVersions = (messages: { id: string; version: string }[]): { id: string; version: string }[] => {\n const uniqueSet = new Set();\n\n return messages.filter((message) => {\n const key = `${message.id}-${message.version}`;\n if (!uniqueSet.has(key)) {\n uniqueSet.add(key);\n return true;\n }\n return false;\n });\n};\n"],"mappings":"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","names":["fs","join","dirname","join","match","matter","fsSync","satisfies","dirname","file","join","join","join","fs"]}
|
package/dist/custom-docs.js
CHANGED
|
@@ -48,7 +48,8 @@ var import_semver = require("semver");
|
|
|
48
48
|
var getFiles = async (pattern, ignore = "") => {
|
|
49
49
|
try {
|
|
50
50
|
const ignoreList = Array.isArray(ignore) ? ignore : [ignore];
|
|
51
|
-
const
|
|
51
|
+
const baseDir = pattern.includes("**") ? pattern.split("**")[0] : (0, import_node_path.dirname)(pattern);
|
|
52
|
+
const files = (0, import_glob.globSync)(pattern, { cwd: baseDir, ignore: ["node_modules/**", ...ignoreList] });
|
|
52
53
|
return files;
|
|
53
54
|
} catch (error) {
|
|
54
55
|
throw new Error(`Error finding files: ${error}`);
|
package/dist/custom-docs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/custom-docs.ts","../src/internal/utils.ts","../src/internal/resources.ts"],"sourcesContent":["import path, { join } from 'node:path';\nimport { readMdxFile } from './internal/utils';\nimport type { CustomDoc } from './types';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport matter from 'gray-matter';\nimport { getResources } from './internal/resources';\nimport slugify from 'slugify';\n\n/**\n * Returns a custom doc from EventCatalog by the given file path.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // Gets the custom doc by the given file path\n * const customDoc = await getCustomDoc('/guides/inventory-management.mdx');\n * ```\n */\nexport const getCustomDoc =\n (directory: string) =>\n async (filePath: string): Promise<CustomDoc | undefined> => {\n const fullPath = path.join(directory, filePath);\n const fullPathWithExtension = fullPath.endsWith('.mdx') ? fullPath : `${fullPath}.mdx`;\n const fileExists = fsSync.existsSync(fullPathWithExtension);\n if (!fileExists) {\n return undefined;\n }\n return readMdxFile(fullPathWithExtension) as Promise<CustomDoc>;\n };\n\n/**\n * Returns all custom docs for the project.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCustomDocs } = utils('/path/to/eventcatalog');\n *\n * // Gets all custom docs from the catalog\n * const customDocs = await getCustomDocs();\n *\n * // Gets all custom docs from the given path\n * const customDocs = await getCustomDocs({ path: '/guides' });\n * ```\n */\nexport const getCustomDocs =\n (directory: string) =>\n async (options?: { path?: string }): Promise<CustomDoc[]> => {\n if (options?.path) {\n const pattern = `${directory}/${options.path}/**/*.{md,mdx}`;\n return getResources(directory, { type: 'docs', pattern }) as Promise<CustomDoc[]>;\n }\n return getResources(directory, { type: 'docs', pattern: `${directory}/**/*.{md,mdx}` }) as Promise<CustomDoc[]>;\n };\n\n/**\n * Write a custom doc to EventCatalog.\n *\n * You can optionally override the path of the custom doc.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { writeCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // Write a custom doc to the catalog\n * // Custom doc would be written to docs/inventory-management.mdx\n * await writeCustomDoc({\n * title: 'Inventory Management',\n * summary: 'This is a summary',\n * owners: ['John Doe'],\n * badges: [{ content: 'Badge', backgroundColor: 'red', textColor: 'white' }],\n * markdown: '# Hello world',\n * fileName: 'inventory-management',\n * });\n *\n * // Write a custom doc to the catalog but override the path\n * // Custom doc would be written to docs/guides/inventory-management/introduction.mdx\n * await writeCustomDoc({\n * title: 'Inventory Management',\n * summary: 'This is a summary',\n * owners: ['John Doe'],\n * badges: [{ content: 'Badge', backgroundColor: 'red', textColor: 'white' }],\n * markdown: '# Hello world',\n * fileName: 'introduction',\n * }, { path: \"/guides/inventory-management\"});\n * ```\n */\nexport const writeCustomDoc =\n (directory: string) =>\n async (customDoc: CustomDoc, options: { path?: string } = { path: '' }): Promise<void> => {\n const { fileName, ...rest } = customDoc;\n const name = fileName || slugify(customDoc.title, { lower: true });\n const withExtension = name.endsWith('.mdx') ? name : `${name}.mdx`;\n const fullPath = path.join(directory, options.path || '', withExtension);\n\n fsSync.mkdirSync(path.dirname(fullPath), { recursive: true });\n const document = matter.stringify(customDoc.markdown.trim(), rest);\n fsSync.writeFileSync(fullPath, document);\n };\n\n/**\n * Delete a custom doc by its' path\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { rmCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // removes a custom doc at the given path\n * // Removes the custom doc at docs/guides/inventory-management/introduction.mdx\n * await rmCustomDoc('/guides/inventory-management/introduction');\n * ```\n */\nexport const rmCustomDoc = (directory: string) => async (filePath: string) => {\n const withExtension = filePath.endsWith('.mdx') ? filePath : `${filePath}.mdx`;\n await fs.rm(join(directory, withExtension), { recursive: true });\n};\n","import { glob, globSync } from 'glob';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { copy, copySync, CopyFilterAsync, CopyFilterSync } from 'fs-extra';\nimport { join } from 'node:path';\nimport matter from 'gray-matter';\nimport { satisfies, validRange, valid } from 'semver';\n\n/**\n * Returns true if a given version of a resource id exists in the catalog\n */\nexport const versionExists = async (catalogDir: string, id: string, version: string) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = (await searchFilesForId(files, id, version)) || [];\n return matchedFiles.length > 0;\n};\n\nexport const findFileById = async (catalogDir: string, id: string, version?: string): Promise<string | undefined> => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = (await searchFilesForId(files, id)) || [];\n const latestVersion = matchedFiles.find((path) => !path.includes('versioned'));\n\n // If no version is provided, return the latest version\n if (!version) {\n return latestVersion;\n }\n\n // map files into gray matter to get versions\n const parsedFiles = matchedFiles.map((path) => {\n const { data } = matter.read(path);\n return { ...data, path };\n }) as any[];\n\n const semverRange = validRange(version);\n\n if (semverRange && valid(version)) {\n const match = parsedFiles.filter((c) => satisfies(c.version, semverRange));\n return match.length > 0 ? match[0].path : undefined;\n }\n\n // Order by version\n const sorted = parsedFiles.sort((a, b) => {\n return a.version.localeCompare(b.version);\n });\n\n // latest version\n const match = sorted.length > 0 ? [sorted[sorted.length - 1]] : [];\n\n if (match.length > 0) {\n return match[0].path;\n }\n};\n\nexport const getFiles = async (pattern: string, ignore: string | string[] = '') => {\n try {\n const ignoreList = Array.isArray(ignore) ? ignore : [ignore];\n const files = globSync(pattern, { ignore: ['node_modules/**', ...ignoreList] });\n return files;\n } catch (error) {\n throw new Error(`Error finding files: ${error}`);\n }\n};\n\nexport const readMdxFile = async (path: string) => {\n const { data } = matter.read(path);\n const { markdown, ...frontmatter } = data;\n return { ...frontmatter, markdown };\n};\n\nexport const searchFilesForId = async (files: string[], id: string, version?: string) => {\n const idRegex = new RegExp(`^id:\\\\s*(['\"]|>-)?\\\\s*${id}['\"]?\\\\s*$`, 'm');\n const versionRegex = new RegExp(`^version:\\\\s*['\"]?${version}['\"]?\\\\s*$`, 'm');\n\n const matches = files.map((file) => {\n const content = fsSync.readFileSync(file, 'utf-8');\n const hasIdMatch = content.match(idRegex);\n\n // Check version if provided\n if (version && !content.match(versionRegex)) {\n return undefined;\n }\n\n if (hasIdMatch) {\n return file;\n }\n });\n\n return matches.filter(Boolean).filter((file) => file !== undefined);\n};\n\n/**\n * Function to copy a directory from source to target, uses a tmp directory\n * @param catalogDir\n * @param source\n * @param target\n * @param filter\n */\nexport const copyDir = async (catalogDir: string, source: string, target: string, filter?: CopyFilterAsync | CopyFilterSync) => {\n const tmpDirectory = join(catalogDir, 'tmp');\n fsSync.mkdirSync(tmpDirectory, { recursive: true });\n\n // Copy everything over\n await copy(source, tmpDirectory, {\n overwrite: true,\n filter,\n });\n\n await copy(tmpDirectory, target, {\n overwrite: true,\n filter,\n });\n\n // Remove the tmp directory\n fsSync.rmSync(tmpDirectory, { recursive: true });\n};\n\n// Makes sure values in sends/recieves are unique\nexport const uniqueVersions = (messages: { id: string; version: string }[]): { id: string; version: string }[] => {\n const uniqueSet = new Set();\n\n return messages.filter((message) => {\n const key = `${message.id}-${message.version}`;\n if (!uniqueSet.has(key)) {\n uniqueSet.add(key);\n return true;\n }\n return false;\n });\n};\n","import { dirname, join } from 'path';\nimport { copyDir, findFileById, getFiles, searchFilesForId, versionExists } from './utils';\nimport matter from 'gray-matter';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { Message, Service, CustomDoc } from '../types';\nimport { satisfies } from 'semver';\nimport { lock, unlock } from 'proper-lockfile';\n\ntype Resource = Service | Message | CustomDoc;\n\nexport const versionResource = async (catalogDir: string, id: string) => {\n // Find all the events in the directory\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = await searchFilesForId(files, id);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No resource found with id: ${id}`);\n }\n\n // Event that is in the route of the project\n const file = matchedFiles[0];\n const sourceDirectory = dirname(file);\n const { data: { version = '0.0.1' } = {} } = matter.read(file);\n const targetDirectory = getVersionedDirectory(sourceDirectory, version);\n\n fsSync.mkdirSync(targetDirectory, { recursive: true });\n\n // Copy the event to the versioned directory\n await copyDir(catalogDir, sourceDirectory, targetDirectory, (src) => {\n return !src.includes('versioned');\n });\n\n // Remove all the files in the root of the resource as they have now been versioned\n await fs.readdir(sourceDirectory).then(async (resourceFiles) => {\n await Promise.all(\n resourceFiles.map(async (file) => {\n if (file !== 'versioned') {\n fsSync.rmSync(join(sourceDirectory, file), { recursive: true });\n }\n })\n );\n });\n};\n\nexport const writeResource = async (\n catalogDir: string,\n resource: Resource,\n options: { path?: string; type: string; override?: boolean; versionExistingContent?: boolean } = {\n path: '',\n type: '',\n override: false,\n versionExistingContent: false,\n }\n) => {\n const path = options.path || `/${resource.id}`;\n const fullPath = join(catalogDir, path);\n\n // Create directory if it doesn't exist\n fsSync.mkdirSync(fullPath, { recursive: true });\n\n // Create or get lock file path\n const lockPath = join(fullPath, 'index.mdx');\n\n // Ensure the file exists before attempting to lock it\n if (!fsSync.existsSync(lockPath)) {\n fsSync.writeFileSync(lockPath, '');\n }\n\n try {\n // Acquire lock with retry\n await lock(lockPath, {\n retries: 5,\n stale: 10000, // 10 seconds\n });\n\n const exists = await versionExists(catalogDir, resource.id, resource.version);\n\n if (exists && !options.override) {\n throw new Error(`Failed to write ${resource.id} (${options.type}) as the version ${resource.version} already exists`);\n }\n\n const { markdown, ...frontmatter } = resource;\n\n if (options.versionExistingContent && !exists) {\n const currentResource = await getResource(catalogDir, resource.id);\n\n if (currentResource) {\n if (satisfies(resource.version, `>${currentResource.version}`)) {\n await versionResource(catalogDir, resource.id);\n } else {\n throw new Error(`New version ${resource.version} is not greater than current version ${currentResource.version}`);\n }\n }\n }\n\n const document = matter.stringify(markdown.trim(), frontmatter);\n fsSync.writeFileSync(lockPath, document);\n } finally {\n // Always release the lock\n await unlock(lockPath).catch(() => {});\n }\n};\n\nexport const getResource = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string }\n): Promise<Resource | undefined> => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n const { data, content } = matter.read(file);\n\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n};\n\nexport const getResourcePath = async (catalogDir: string, id: string, version?: string) => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n return {\n fullPath: file,\n relativePath: file.replace(catalogDir, ''),\n directory: dirname(file.replace(catalogDir, '')),\n };\n};\n\nexport const getResources = async (\n catalogDir: string,\n {\n type,\n latestOnly = false,\n ignore = [],\n pattern = '',\n }: { type: string; pattern?: string; latestOnly?: boolean; ignore?: string[] }\n): Promise<Resource[] | undefined> => {\n const ignoreList = latestOnly ? `**/versioned/**` : '';\n const filePattern = pattern || `${catalogDir}/**/${type}/**/index.{md,mdx}`;\n const files = await getFiles(filePattern, [ignoreList, ...ignore]);\n\n if (files.length === 0) return;\n\n return files.map((file) => {\n const { data, content } = matter.read(file);\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n });\n};\n\nexport const rmResourceById = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string; persistFiles?: boolean }\n) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = await searchFilesForId(files, id, version);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No ${options?.type || 'resource'} found with id: ${id}`);\n }\n\n if (options?.persistFiles) {\n await Promise.all(\n matchedFiles.map(async (file) => {\n await fs.rm(file, { recursive: true });\n })\n );\n } else {\n await Promise.all(\n matchedFiles.map(async (file) => {\n const directory = dirname(file);\n await fs.rm(directory, { recursive: true, force: true });\n })\n );\n }\n};\n\nexport const addFileToResource = async (\n catalogDir: string,\n id: string,\n file: { content: string; fileName: string },\n version?: string\n) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory to write file to');\n\n fsSync.writeFileSync(join(dirname(pathToResource), file.fileName), file.content);\n};\n\nexport const getFileFromResource = async (catalogDir: string, id: string, file: { fileName: string }, version?: string) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory of resource');\n\n const exists = await fs\n .access(join(dirname(pathToResource), file.fileName))\n .then(() => true)\n .catch(() => false);\n if (!exists) throw new Error(`File ${file.fileName} does not exist in resource ${id} v(${version})`);\n\n return fsSync.readFileSync(join(dirname(pathToResource), file.fileName), 'utf-8');\n};\nexport const getVersionedDirectory = (sourceDirectory: string, version: any): string => {\n return join(sourceDirectory, 'versioned', version);\n};\n"],"mappings":"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","names":["import_node_path","path","matter","import_node_fs","import_promises","import_gray_matter","import_gray_matter","import_node_fs","import_semver","matter","path","fsSync","slugify","matter","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/custom-docs.ts","../src/internal/utils.ts","../src/internal/resources.ts"],"sourcesContent":["import path, { join } from 'node:path';\nimport { readMdxFile } from './internal/utils';\nimport type { CustomDoc } from './types';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport matter from 'gray-matter';\nimport { getResources } from './internal/resources';\nimport slugify from 'slugify';\n\n/**\n * Returns a custom doc from EventCatalog by the given file path.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // Gets the custom doc by the given file path\n * const customDoc = await getCustomDoc('/guides/inventory-management.mdx');\n * ```\n */\nexport const getCustomDoc =\n (directory: string) =>\n async (filePath: string): Promise<CustomDoc | undefined> => {\n const fullPath = path.join(directory, filePath);\n const fullPathWithExtension = fullPath.endsWith('.mdx') ? fullPath : `${fullPath}.mdx`;\n const fileExists = fsSync.existsSync(fullPathWithExtension);\n if (!fileExists) {\n return undefined;\n }\n return readMdxFile(fullPathWithExtension) as Promise<CustomDoc>;\n };\n\n/**\n * Returns all custom docs for the project.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCustomDocs } = utils('/path/to/eventcatalog');\n *\n * // Gets all custom docs from the catalog\n * const customDocs = await getCustomDocs();\n *\n * // Gets all custom docs from the given path\n * const customDocs = await getCustomDocs({ path: '/guides' });\n * ```\n */\nexport const getCustomDocs =\n (directory: string) =>\n async (options?: { path?: string }): Promise<CustomDoc[]> => {\n if (options?.path) {\n const pattern = `${directory}/${options.path}/**/*.{md,mdx}`;\n return getResources(directory, { type: 'docs', pattern }) as Promise<CustomDoc[]>;\n }\n return getResources(directory, { type: 'docs', pattern: `${directory}/**/*.{md,mdx}` }) as Promise<CustomDoc[]>;\n };\n\n/**\n * Write a custom doc to EventCatalog.\n *\n * You can optionally override the path of the custom doc.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { writeCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // Write a custom doc to the catalog\n * // Custom doc would be written to docs/inventory-management.mdx\n * await writeCustomDoc({\n * title: 'Inventory Management',\n * summary: 'This is a summary',\n * owners: ['John Doe'],\n * badges: [{ content: 'Badge', backgroundColor: 'red', textColor: 'white' }],\n * markdown: '# Hello world',\n * fileName: 'inventory-management',\n * });\n *\n * // Write a custom doc to the catalog but override the path\n * // Custom doc would be written to docs/guides/inventory-management/introduction.mdx\n * await writeCustomDoc({\n * title: 'Inventory Management',\n * summary: 'This is a summary',\n * owners: ['John Doe'],\n * badges: [{ content: 'Badge', backgroundColor: 'red', textColor: 'white' }],\n * markdown: '# Hello world',\n * fileName: 'introduction',\n * }, { path: \"/guides/inventory-management\"});\n * ```\n */\nexport const writeCustomDoc =\n (directory: string) =>\n async (customDoc: CustomDoc, options: { path?: string } = { path: '' }): Promise<void> => {\n const { fileName, ...rest } = customDoc;\n const name = fileName || slugify(customDoc.title, { lower: true });\n const withExtension = name.endsWith('.mdx') ? name : `${name}.mdx`;\n const fullPath = path.join(directory, options.path || '', withExtension);\n\n fsSync.mkdirSync(path.dirname(fullPath), { recursive: true });\n const document = matter.stringify(customDoc.markdown.trim(), rest);\n fsSync.writeFileSync(fullPath, document);\n };\n\n/**\n * Delete a custom doc by its' path\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { rmCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // removes a custom doc at the given path\n * // Removes the custom doc at docs/guides/inventory-management/introduction.mdx\n * await rmCustomDoc('/guides/inventory-management/introduction');\n * ```\n */\nexport const rmCustomDoc = (directory: string) => async (filePath: string) => {\n const withExtension = filePath.endsWith('.mdx') ? filePath : `${filePath}.mdx`;\n await fs.rm(join(directory, withExtension), { recursive: true });\n};\n","import { globSync } from 'glob';\nimport fsSync from 'node:fs';\nimport { copy, CopyFilterAsync, CopyFilterSync } from 'fs-extra';\nimport { join, dirname } from 'node:path';\nimport matter from 'gray-matter';\nimport { satisfies, validRange, valid } from 'semver';\n\n/**\n * Returns true if a given version of a resource id exists in the catalog\n */\nexport const versionExists = async (catalogDir: string, id: string, version: string) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = (await searchFilesForId(files, id, version)) || [];\n return matchedFiles.length > 0;\n};\n\nexport const findFileById = async (catalogDir: string, id: string, version?: string): Promise<string | undefined> => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = (await searchFilesForId(files, id)) || [];\n const latestVersion = matchedFiles.find((path) => !path.includes('versioned'));\n\n // If no version is provided, return the latest version\n if (!version) {\n return latestVersion;\n }\n\n // map files into gray matter to get versions\n const parsedFiles = matchedFiles.map((path) => {\n const { data } = matter.read(path);\n return { ...data, path };\n }) as any[];\n\n const semverRange = validRange(version);\n\n if (semverRange && valid(version)) {\n const match = parsedFiles.filter((c) => satisfies(c.version, semverRange));\n return match.length > 0 ? match[0].path : undefined;\n }\n\n // Order by version\n const sorted = parsedFiles.sort((a, b) => {\n return a.version.localeCompare(b.version);\n });\n\n // latest version\n const match = sorted.length > 0 ? [sorted[sorted.length - 1]] : [];\n\n if (match.length > 0) {\n return match[0].path;\n }\n};\n\nexport const getFiles = async (pattern: string, ignore: string | string[] = '') => {\n try {\n const ignoreList = Array.isArray(ignore) ? ignore : [ignore];\n const baseDir = pattern.includes('**') ? pattern.split('**')[0] : dirname(pattern);\n const files = globSync(pattern, { cwd: baseDir, ignore: ['node_modules/**', ...ignoreList] });\n return files;\n } catch (error) {\n throw new Error(`Error finding files: ${error}`);\n }\n};\n\nexport const readMdxFile = async (path: string) => {\n const { data } = matter.read(path);\n const { markdown, ...frontmatter } = data;\n return { ...frontmatter, markdown };\n};\n\nexport const searchFilesForId = async (files: string[], id: string, version?: string) => {\n const idRegex = new RegExp(`^id:\\\\s*(['\"]|>-)?\\\\s*${id}['\"]?\\\\s*$`, 'm');\n const versionRegex = new RegExp(`^version:\\\\s*['\"]?${version}['\"]?\\\\s*$`, 'm');\n\n const matches = files.map((file) => {\n const content = fsSync.readFileSync(file, 'utf-8');\n const hasIdMatch = content.match(idRegex);\n\n // Check version if provided\n if (version && !content.match(versionRegex)) {\n return undefined;\n }\n\n if (hasIdMatch) {\n return file;\n }\n });\n\n return matches.filter(Boolean).filter((file) => file !== undefined);\n};\n\n/**\n * Function to copy a directory from source to target, uses a tmp directory\n * @param catalogDir\n * @param source\n * @param target\n * @param filter\n */\nexport const copyDir = async (catalogDir: string, source: string, target: string, filter?: CopyFilterAsync | CopyFilterSync) => {\n const tmpDirectory = join(catalogDir, 'tmp');\n fsSync.mkdirSync(tmpDirectory, { recursive: true });\n\n // Copy everything over\n await copy(source, tmpDirectory, {\n overwrite: true,\n filter,\n });\n\n await copy(tmpDirectory, target, {\n overwrite: true,\n filter,\n });\n\n // Remove the tmp directory\n fsSync.rmSync(tmpDirectory, { recursive: true });\n};\n\n// Makes sure values in sends/recieves are unique\nexport const uniqueVersions = (messages: { id: string; version: string }[]): { id: string; version: string }[] => {\n const uniqueSet = new Set();\n\n return messages.filter((message) => {\n const key = `${message.id}-${message.version}`;\n if (!uniqueSet.has(key)) {\n uniqueSet.add(key);\n return true;\n }\n return false;\n });\n};\n","import { dirname, join } from 'path';\nimport { copyDir, findFileById, getFiles, searchFilesForId, versionExists } from './utils';\nimport matter from 'gray-matter';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { Message, Service, CustomDoc } from '../types';\nimport { satisfies } from 'semver';\nimport { lock, unlock } from 'proper-lockfile';\n\ntype Resource = Service | Message | CustomDoc;\n\nexport const versionResource = async (catalogDir: string, id: string) => {\n // Find all the events in the directory\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = await searchFilesForId(files, id);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No resource found with id: ${id}`);\n }\n\n // Event that is in the route of the project\n const file = matchedFiles[0];\n const sourceDirectory = dirname(file);\n const { data: { version = '0.0.1' } = {} } = matter.read(file);\n const targetDirectory = getVersionedDirectory(sourceDirectory, version);\n\n fsSync.mkdirSync(targetDirectory, { recursive: true });\n\n // Copy the event to the versioned directory\n await copyDir(catalogDir, sourceDirectory, targetDirectory, (src) => {\n return !src.includes('versioned');\n });\n\n // Remove all the files in the root of the resource as they have now been versioned\n await fs.readdir(sourceDirectory).then(async (resourceFiles) => {\n await Promise.all(\n resourceFiles.map(async (file) => {\n if (file !== 'versioned') {\n fsSync.rmSync(join(sourceDirectory, file), { recursive: true });\n }\n })\n );\n });\n};\n\nexport const writeResource = async (\n catalogDir: string,\n resource: Resource,\n options: { path?: string; type: string; override?: boolean; versionExistingContent?: boolean; format?: 'md' | 'mdx' } = {\n path: '',\n type: '',\n override: false,\n versionExistingContent: false,\n format: 'mdx',\n }\n) => {\n const path = options.path || `/${resource.id}`;\n const fullPath = join(catalogDir, path);\n const format = options.format || 'mdx';\n\n // Create directory if it doesn't exist\n fsSync.mkdirSync(fullPath, { recursive: true });\n\n // Create or get lock file path\n const lockPath = join(fullPath, `index.${format}`);\n\n // Ensure the file exists before attempting to lock it\n if (!fsSync.existsSync(lockPath)) {\n fsSync.writeFileSync(lockPath, '');\n }\n\n try {\n // Acquire lock with retry\n await lock(lockPath, {\n retries: 5,\n stale: 10000, // 10 seconds\n });\n\n const exists = await versionExists(catalogDir, resource.id, resource.version);\n\n if (exists && !options.override) {\n throw new Error(`Failed to write ${resource.id} (${options.type}) as the version ${resource.version} already exists`);\n }\n\n const { markdown, ...frontmatter } = resource;\n\n if (options.versionExistingContent && !exists) {\n const currentResource = await getResource(catalogDir, resource.id);\n\n if (currentResource) {\n if (satisfies(resource.version, `>${currentResource.version}`)) {\n await versionResource(catalogDir, resource.id);\n } else {\n throw new Error(`New version ${resource.version} is not greater than current version ${currentResource.version}`);\n }\n }\n }\n\n const document = matter.stringify(markdown.trim(), frontmatter);\n fsSync.writeFileSync(lockPath, document);\n } finally {\n // Always release the lock\n await unlock(lockPath).catch(() => {});\n }\n};\n\nexport const getResource = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string }\n): Promise<Resource | undefined> => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n const { data, content } = matter.read(file);\n\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n};\n\nexport const getResourcePath = async (catalogDir: string, id: string, version?: string) => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n return {\n fullPath: file,\n relativePath: file.replace(catalogDir, ''),\n directory: dirname(file.replace(catalogDir, '')),\n };\n};\n\nexport const getResources = async (\n catalogDir: string,\n {\n type,\n latestOnly = false,\n ignore = [],\n pattern = '',\n }: { type: string; pattern?: string; latestOnly?: boolean; ignore?: string[] }\n): Promise<Resource[] | undefined> => {\n const ignoreList = latestOnly ? `**/versioned/**` : '';\n const filePattern = pattern || `${catalogDir}/**/${type}/**/index.{md,mdx}`;\n const files = await getFiles(filePattern, [ignoreList, ...ignore]);\n\n if (files.length === 0) return;\n\n return files.map((file) => {\n const { data, content } = matter.read(file);\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n });\n};\n\nexport const rmResourceById = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string; persistFiles?: boolean }\n) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = await searchFilesForId(files, id, version);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No ${options?.type || 'resource'} found with id: ${id}`);\n }\n\n if (options?.persistFiles) {\n await Promise.all(\n matchedFiles.map(async (file) => {\n await fs.rm(file, { recursive: true });\n })\n );\n } else {\n await Promise.all(\n matchedFiles.map(async (file) => {\n const directory = dirname(file);\n await fs.rm(directory, { recursive: true, force: true });\n })\n );\n }\n};\n\nexport const addFileToResource = async (\n catalogDir: string,\n id: string,\n file: { content: string; fileName: string },\n version?: string\n) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory to write file to');\n\n fsSync.writeFileSync(join(dirname(pathToResource), file.fileName), file.content);\n};\n\nexport const getFileFromResource = async (catalogDir: string, id: string, file: { fileName: string }, version?: string) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory of resource');\n\n const exists = await fs\n .access(join(dirname(pathToResource), file.fileName))\n .then(() => true)\n .catch(() => false);\n if (!exists) throw new Error(`File ${file.fileName} does not exist in resource ${id} v(${version})`);\n\n return fsSync.readFileSync(join(dirname(pathToResource), file.fileName), 'utf-8');\n};\nexport const getVersionedDirectory = (sourceDirectory: string, version: any): string => {\n return join(sourceDirectory, 'versioned', version);\n};\n"],"mappings":"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","names":["import_node_path","path","matter","import_node_fs","import_promises","import_gray_matter","import_gray_matter","import_node_fs","import_semver","matter","path","fsSync","slugify","matter","fs"]}
|
package/dist/custom-docs.mjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
// src/custom-docs.ts
|
|
2
|
-
import path, { join } from "node:path";
|
|
2
|
+
import path, { join as join2 } from "node:path";
|
|
3
3
|
|
|
4
4
|
// src/internal/utils.ts
|
|
5
5
|
import { globSync } from "glob";
|
|
6
6
|
import { copy } from "fs-extra";
|
|
7
|
+
import { join, dirname } from "node:path";
|
|
7
8
|
import matter from "gray-matter";
|
|
8
9
|
import { satisfies, validRange, valid } from "semver";
|
|
9
10
|
var getFiles = async (pattern, ignore = "") => {
|
|
10
11
|
try {
|
|
11
12
|
const ignoreList = Array.isArray(ignore) ? ignore : [ignore];
|
|
12
|
-
const
|
|
13
|
+
const baseDir = pattern.includes("**") ? pattern.split("**")[0] : dirname(pattern);
|
|
14
|
+
const files = globSync(pattern, { cwd: baseDir, ignore: ["node_modules/**", ...ignoreList] });
|
|
13
15
|
return files;
|
|
14
16
|
} catch (error) {
|
|
15
17
|
throw new Error(`Error finding files: ${error}`);
|
|
@@ -78,7 +80,7 @@ var writeCustomDoc = (directory) => async (customDoc, options = { path: "" }) =>
|
|
|
78
80
|
};
|
|
79
81
|
var rmCustomDoc = (directory) => async (filePath) => {
|
|
80
82
|
const withExtension = filePath.endsWith(".mdx") ? filePath : `${filePath}.mdx`;
|
|
81
|
-
await fs.rm(
|
|
83
|
+
await fs.rm(join2(directory, withExtension), { recursive: true });
|
|
82
84
|
};
|
|
83
85
|
export {
|
|
84
86
|
getCustomDoc,
|
package/dist/custom-docs.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/custom-docs.ts","../src/internal/utils.ts","../src/internal/resources.ts"],"sourcesContent":["import path, { join } from 'node:path';\nimport { readMdxFile } from './internal/utils';\nimport type { CustomDoc } from './types';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport matter from 'gray-matter';\nimport { getResources } from './internal/resources';\nimport slugify from 'slugify';\n\n/**\n * Returns a custom doc from EventCatalog by the given file path.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // Gets the custom doc by the given file path\n * const customDoc = await getCustomDoc('/guides/inventory-management.mdx');\n * ```\n */\nexport const getCustomDoc =\n (directory: string) =>\n async (filePath: string): Promise<CustomDoc | undefined> => {\n const fullPath = path.join(directory, filePath);\n const fullPathWithExtension = fullPath.endsWith('.mdx') ? fullPath : `${fullPath}.mdx`;\n const fileExists = fsSync.existsSync(fullPathWithExtension);\n if (!fileExists) {\n return undefined;\n }\n return readMdxFile(fullPathWithExtension) as Promise<CustomDoc>;\n };\n\n/**\n * Returns all custom docs for the project.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCustomDocs } = utils('/path/to/eventcatalog');\n *\n * // Gets all custom docs from the catalog\n * const customDocs = await getCustomDocs();\n *\n * // Gets all custom docs from the given path\n * const customDocs = await getCustomDocs({ path: '/guides' });\n * ```\n */\nexport const getCustomDocs =\n (directory: string) =>\n async (options?: { path?: string }): Promise<CustomDoc[]> => {\n if (options?.path) {\n const pattern = `${directory}/${options.path}/**/*.{md,mdx}`;\n return getResources(directory, { type: 'docs', pattern }) as Promise<CustomDoc[]>;\n }\n return getResources(directory, { type: 'docs', pattern: `${directory}/**/*.{md,mdx}` }) as Promise<CustomDoc[]>;\n };\n\n/**\n * Write a custom doc to EventCatalog.\n *\n * You can optionally override the path of the custom doc.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { writeCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // Write a custom doc to the catalog\n * // Custom doc would be written to docs/inventory-management.mdx\n * await writeCustomDoc({\n * title: 'Inventory Management',\n * summary: 'This is a summary',\n * owners: ['John Doe'],\n * badges: [{ content: 'Badge', backgroundColor: 'red', textColor: 'white' }],\n * markdown: '# Hello world',\n * fileName: 'inventory-management',\n * });\n *\n * // Write a custom doc to the catalog but override the path\n * // Custom doc would be written to docs/guides/inventory-management/introduction.mdx\n * await writeCustomDoc({\n * title: 'Inventory Management',\n * summary: 'This is a summary',\n * owners: ['John Doe'],\n * badges: [{ content: 'Badge', backgroundColor: 'red', textColor: 'white' }],\n * markdown: '# Hello world',\n * fileName: 'introduction',\n * }, { path: \"/guides/inventory-management\"});\n * ```\n */\nexport const writeCustomDoc =\n (directory: string) =>\n async (customDoc: CustomDoc, options: { path?: string } = { path: '' }): Promise<void> => {\n const { fileName, ...rest } = customDoc;\n const name = fileName || slugify(customDoc.title, { lower: true });\n const withExtension = name.endsWith('.mdx') ? name : `${name}.mdx`;\n const fullPath = path.join(directory, options.path || '', withExtension);\n\n fsSync.mkdirSync(path.dirname(fullPath), { recursive: true });\n const document = matter.stringify(customDoc.markdown.trim(), rest);\n fsSync.writeFileSync(fullPath, document);\n };\n\n/**\n * Delete a custom doc by its' path\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { rmCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // removes a custom doc at the given path\n * // Removes the custom doc at docs/guides/inventory-management/introduction.mdx\n * await rmCustomDoc('/guides/inventory-management/introduction');\n * ```\n */\nexport const rmCustomDoc = (directory: string) => async (filePath: string) => {\n const withExtension = filePath.endsWith('.mdx') ? filePath : `${filePath}.mdx`;\n await fs.rm(join(directory, withExtension), { recursive: true });\n};\n","import { glob, globSync } from 'glob';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { copy, copySync, CopyFilterAsync, CopyFilterSync } from 'fs-extra';\nimport { join } from 'node:path';\nimport matter from 'gray-matter';\nimport { satisfies, validRange, valid } from 'semver';\n\n/**\n * Returns true if a given version of a resource id exists in the catalog\n */\nexport const versionExists = async (catalogDir: string, id: string, version: string) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = (await searchFilesForId(files, id, version)) || [];\n return matchedFiles.length > 0;\n};\n\nexport const findFileById = async (catalogDir: string, id: string, version?: string): Promise<string | undefined> => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = (await searchFilesForId(files, id)) || [];\n const latestVersion = matchedFiles.find((path) => !path.includes('versioned'));\n\n // If no version is provided, return the latest version\n if (!version) {\n return latestVersion;\n }\n\n // map files into gray matter to get versions\n const parsedFiles = matchedFiles.map((path) => {\n const { data } = matter.read(path);\n return { ...data, path };\n }) as any[];\n\n const semverRange = validRange(version);\n\n if (semverRange && valid(version)) {\n const match = parsedFiles.filter((c) => satisfies(c.version, semverRange));\n return match.length > 0 ? match[0].path : undefined;\n }\n\n // Order by version\n const sorted = parsedFiles.sort((a, b) => {\n return a.version.localeCompare(b.version);\n });\n\n // latest version\n const match = sorted.length > 0 ? [sorted[sorted.length - 1]] : [];\n\n if (match.length > 0) {\n return match[0].path;\n }\n};\n\nexport const getFiles = async (pattern: string, ignore: string | string[] = '') => {\n try {\n const ignoreList = Array.isArray(ignore) ? ignore : [ignore];\n const files = globSync(pattern, { ignore: ['node_modules/**', ...ignoreList] });\n return files;\n } catch (error) {\n throw new Error(`Error finding files: ${error}`);\n }\n};\n\nexport const readMdxFile = async (path: string) => {\n const { data } = matter.read(path);\n const { markdown, ...frontmatter } = data;\n return { ...frontmatter, markdown };\n};\n\nexport const searchFilesForId = async (files: string[], id: string, version?: string) => {\n const idRegex = new RegExp(`^id:\\\\s*(['\"]|>-)?\\\\s*${id}['\"]?\\\\s*$`, 'm');\n const versionRegex = new RegExp(`^version:\\\\s*['\"]?${version}['\"]?\\\\s*$`, 'm');\n\n const matches = files.map((file) => {\n const content = fsSync.readFileSync(file, 'utf-8');\n const hasIdMatch = content.match(idRegex);\n\n // Check version if provided\n if (version && !content.match(versionRegex)) {\n return undefined;\n }\n\n if (hasIdMatch) {\n return file;\n }\n });\n\n return matches.filter(Boolean).filter((file) => file !== undefined);\n};\n\n/**\n * Function to copy a directory from source to target, uses a tmp directory\n * @param catalogDir\n * @param source\n * @param target\n * @param filter\n */\nexport const copyDir = async (catalogDir: string, source: string, target: string, filter?: CopyFilterAsync | CopyFilterSync) => {\n const tmpDirectory = join(catalogDir, 'tmp');\n fsSync.mkdirSync(tmpDirectory, { recursive: true });\n\n // Copy everything over\n await copy(source, tmpDirectory, {\n overwrite: true,\n filter,\n });\n\n await copy(tmpDirectory, target, {\n overwrite: true,\n filter,\n });\n\n // Remove the tmp directory\n fsSync.rmSync(tmpDirectory, { recursive: true });\n};\n\n// Makes sure values in sends/recieves are unique\nexport const uniqueVersions = (messages: { id: string; version: string }[]): { id: string; version: string }[] => {\n const uniqueSet = new Set();\n\n return messages.filter((message) => {\n const key = `${message.id}-${message.version}`;\n if (!uniqueSet.has(key)) {\n uniqueSet.add(key);\n return true;\n }\n return false;\n });\n};\n","import { dirname, join } from 'path';\nimport { copyDir, findFileById, getFiles, searchFilesForId, versionExists } from './utils';\nimport matter from 'gray-matter';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { Message, Service, CustomDoc } from '../types';\nimport { satisfies } from 'semver';\nimport { lock, unlock } from 'proper-lockfile';\n\ntype Resource = Service | Message | CustomDoc;\n\nexport const versionResource = async (catalogDir: string, id: string) => {\n // Find all the events in the directory\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = await searchFilesForId(files, id);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No resource found with id: ${id}`);\n }\n\n // Event that is in the route of the project\n const file = matchedFiles[0];\n const sourceDirectory = dirname(file);\n const { data: { version = '0.0.1' } = {} } = matter.read(file);\n const targetDirectory = getVersionedDirectory(sourceDirectory, version);\n\n fsSync.mkdirSync(targetDirectory, { recursive: true });\n\n // Copy the event to the versioned directory\n await copyDir(catalogDir, sourceDirectory, targetDirectory, (src) => {\n return !src.includes('versioned');\n });\n\n // Remove all the files in the root of the resource as they have now been versioned\n await fs.readdir(sourceDirectory).then(async (resourceFiles) => {\n await Promise.all(\n resourceFiles.map(async (file) => {\n if (file !== 'versioned') {\n fsSync.rmSync(join(sourceDirectory, file), { recursive: true });\n }\n })\n );\n });\n};\n\nexport const writeResource = async (\n catalogDir: string,\n resource: Resource,\n options: { path?: string; type: string; override?: boolean; versionExistingContent?: boolean } = {\n path: '',\n type: '',\n override: false,\n versionExistingContent: false,\n }\n) => {\n const path = options.path || `/${resource.id}`;\n const fullPath = join(catalogDir, path);\n\n // Create directory if it doesn't exist\n fsSync.mkdirSync(fullPath, { recursive: true });\n\n // Create or get lock file path\n const lockPath = join(fullPath, 'index.mdx');\n\n // Ensure the file exists before attempting to lock it\n if (!fsSync.existsSync(lockPath)) {\n fsSync.writeFileSync(lockPath, '');\n }\n\n try {\n // Acquire lock with retry\n await lock(lockPath, {\n retries: 5,\n stale: 10000, // 10 seconds\n });\n\n const exists = await versionExists(catalogDir, resource.id, resource.version);\n\n if (exists && !options.override) {\n throw new Error(`Failed to write ${resource.id} (${options.type}) as the version ${resource.version} already exists`);\n }\n\n const { markdown, ...frontmatter } = resource;\n\n if (options.versionExistingContent && !exists) {\n const currentResource = await getResource(catalogDir, resource.id);\n\n if (currentResource) {\n if (satisfies(resource.version, `>${currentResource.version}`)) {\n await versionResource(catalogDir, resource.id);\n } else {\n throw new Error(`New version ${resource.version} is not greater than current version ${currentResource.version}`);\n }\n }\n }\n\n const document = matter.stringify(markdown.trim(), frontmatter);\n fsSync.writeFileSync(lockPath, document);\n } finally {\n // Always release the lock\n await unlock(lockPath).catch(() => {});\n }\n};\n\nexport const getResource = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string }\n): Promise<Resource | undefined> => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n const { data, content } = matter.read(file);\n\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n};\n\nexport const getResourcePath = async (catalogDir: string, id: string, version?: string) => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n return {\n fullPath: file,\n relativePath: file.replace(catalogDir, ''),\n directory: dirname(file.replace(catalogDir, '')),\n };\n};\n\nexport const getResources = async (\n catalogDir: string,\n {\n type,\n latestOnly = false,\n ignore = [],\n pattern = '',\n }: { type: string; pattern?: string; latestOnly?: boolean; ignore?: string[] }\n): Promise<Resource[] | undefined> => {\n const ignoreList = latestOnly ? `**/versioned/**` : '';\n const filePattern = pattern || `${catalogDir}/**/${type}/**/index.{md,mdx}`;\n const files = await getFiles(filePattern, [ignoreList, ...ignore]);\n\n if (files.length === 0) return;\n\n return files.map((file) => {\n const { data, content } = matter.read(file);\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n });\n};\n\nexport const rmResourceById = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string; persistFiles?: boolean }\n) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = await searchFilesForId(files, id, version);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No ${options?.type || 'resource'} found with id: ${id}`);\n }\n\n if (options?.persistFiles) {\n await Promise.all(\n matchedFiles.map(async (file) => {\n await fs.rm(file, { recursive: true });\n })\n );\n } else {\n await Promise.all(\n matchedFiles.map(async (file) => {\n const directory = dirname(file);\n await fs.rm(directory, { recursive: true, force: true });\n })\n );\n }\n};\n\nexport const addFileToResource = async (\n catalogDir: string,\n id: string,\n file: { content: string; fileName: string },\n version?: string\n) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory to write file to');\n\n fsSync.writeFileSync(join(dirname(pathToResource), file.fileName), file.content);\n};\n\nexport const getFileFromResource = async (catalogDir: string, id: string, file: { fileName: string }, version?: string) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory of resource');\n\n const exists = await fs\n .access(join(dirname(pathToResource), file.fileName))\n .then(() => true)\n .catch(() => false);\n if (!exists) throw new Error(`File ${file.fileName} does not exist in resource ${id} v(${version})`);\n\n return fsSync.readFileSync(join(dirname(pathToResource), file.fileName), 'utf-8');\n};\nexport const getVersionedDirectory = (sourceDirectory: string, version: any): string => {\n return join(sourceDirectory, 'versioned', version);\n};\n"],"mappings":"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","names":["path","matter","matter","satisfies","matter","matter"]}
|
|
1
|
+
{"version":3,"sources":["../src/custom-docs.ts","../src/internal/utils.ts","../src/internal/resources.ts"],"sourcesContent":["import path, { join } from 'node:path';\nimport { readMdxFile } from './internal/utils';\nimport type { CustomDoc } from './types';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport matter from 'gray-matter';\nimport { getResources } from './internal/resources';\nimport slugify from 'slugify';\n\n/**\n * Returns a custom doc from EventCatalog by the given file path.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // Gets the custom doc by the given file path\n * const customDoc = await getCustomDoc('/guides/inventory-management.mdx');\n * ```\n */\nexport const getCustomDoc =\n (directory: string) =>\n async (filePath: string): Promise<CustomDoc | undefined> => {\n const fullPath = path.join(directory, filePath);\n const fullPathWithExtension = fullPath.endsWith('.mdx') ? fullPath : `${fullPath}.mdx`;\n const fileExists = fsSync.existsSync(fullPathWithExtension);\n if (!fileExists) {\n return undefined;\n }\n return readMdxFile(fullPathWithExtension) as Promise<CustomDoc>;\n };\n\n/**\n * Returns all custom docs for the project.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { getCustomDocs } = utils('/path/to/eventcatalog');\n *\n * // Gets all custom docs from the catalog\n * const customDocs = await getCustomDocs();\n *\n * // Gets all custom docs from the given path\n * const customDocs = await getCustomDocs({ path: '/guides' });\n * ```\n */\nexport const getCustomDocs =\n (directory: string) =>\n async (options?: { path?: string }): Promise<CustomDoc[]> => {\n if (options?.path) {\n const pattern = `${directory}/${options.path}/**/*.{md,mdx}`;\n return getResources(directory, { type: 'docs', pattern }) as Promise<CustomDoc[]>;\n }\n return getResources(directory, { type: 'docs', pattern: `${directory}/**/*.{md,mdx}` }) as Promise<CustomDoc[]>;\n };\n\n/**\n * Write a custom doc to EventCatalog.\n *\n * You can optionally override the path of the custom doc.\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { writeCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // Write a custom doc to the catalog\n * // Custom doc would be written to docs/inventory-management.mdx\n * await writeCustomDoc({\n * title: 'Inventory Management',\n * summary: 'This is a summary',\n * owners: ['John Doe'],\n * badges: [{ content: 'Badge', backgroundColor: 'red', textColor: 'white' }],\n * markdown: '# Hello world',\n * fileName: 'inventory-management',\n * });\n *\n * // Write a custom doc to the catalog but override the path\n * // Custom doc would be written to docs/guides/inventory-management/introduction.mdx\n * await writeCustomDoc({\n * title: 'Inventory Management',\n * summary: 'This is a summary',\n * owners: ['John Doe'],\n * badges: [{ content: 'Badge', backgroundColor: 'red', textColor: 'white' }],\n * markdown: '# Hello world',\n * fileName: 'introduction',\n * }, { path: \"/guides/inventory-management\"});\n * ```\n */\nexport const writeCustomDoc =\n (directory: string) =>\n async (customDoc: CustomDoc, options: { path?: string } = { path: '' }): Promise<void> => {\n const { fileName, ...rest } = customDoc;\n const name = fileName || slugify(customDoc.title, { lower: true });\n const withExtension = name.endsWith('.mdx') ? name : `${name}.mdx`;\n const fullPath = path.join(directory, options.path || '', withExtension);\n\n fsSync.mkdirSync(path.dirname(fullPath), { recursive: true });\n const document = matter.stringify(customDoc.markdown.trim(), rest);\n fsSync.writeFileSync(fullPath, document);\n };\n\n/**\n * Delete a custom doc by its' path\n *\n * @example\n * ```ts\n * import utils from '@eventcatalog/utils';\n *\n * const { rmCustomDoc } = utils('/path/to/eventcatalog');\n *\n * // removes a custom doc at the given path\n * // Removes the custom doc at docs/guides/inventory-management/introduction.mdx\n * await rmCustomDoc('/guides/inventory-management/introduction');\n * ```\n */\nexport const rmCustomDoc = (directory: string) => async (filePath: string) => {\n const withExtension = filePath.endsWith('.mdx') ? filePath : `${filePath}.mdx`;\n await fs.rm(join(directory, withExtension), { recursive: true });\n};\n","import { globSync } from 'glob';\nimport fsSync from 'node:fs';\nimport { copy, CopyFilterAsync, CopyFilterSync } from 'fs-extra';\nimport { join, dirname } from 'node:path';\nimport matter from 'gray-matter';\nimport { satisfies, validRange, valid } from 'semver';\n\n/**\n * Returns true if a given version of a resource id exists in the catalog\n */\nexport const versionExists = async (catalogDir: string, id: string, version: string) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = (await searchFilesForId(files, id, version)) || [];\n return matchedFiles.length > 0;\n};\n\nexport const findFileById = async (catalogDir: string, id: string, version?: string): Promise<string | undefined> => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = (await searchFilesForId(files, id)) || [];\n const latestVersion = matchedFiles.find((path) => !path.includes('versioned'));\n\n // If no version is provided, return the latest version\n if (!version) {\n return latestVersion;\n }\n\n // map files into gray matter to get versions\n const parsedFiles = matchedFiles.map((path) => {\n const { data } = matter.read(path);\n return { ...data, path };\n }) as any[];\n\n const semverRange = validRange(version);\n\n if (semverRange && valid(version)) {\n const match = parsedFiles.filter((c) => satisfies(c.version, semverRange));\n return match.length > 0 ? match[0].path : undefined;\n }\n\n // Order by version\n const sorted = parsedFiles.sort((a, b) => {\n return a.version.localeCompare(b.version);\n });\n\n // latest version\n const match = sorted.length > 0 ? [sorted[sorted.length - 1]] : [];\n\n if (match.length > 0) {\n return match[0].path;\n }\n};\n\nexport const getFiles = async (pattern: string, ignore: string | string[] = '') => {\n try {\n const ignoreList = Array.isArray(ignore) ? ignore : [ignore];\n const baseDir = pattern.includes('**') ? pattern.split('**')[0] : dirname(pattern);\n const files = globSync(pattern, { cwd: baseDir, ignore: ['node_modules/**', ...ignoreList] });\n return files;\n } catch (error) {\n throw new Error(`Error finding files: ${error}`);\n }\n};\n\nexport const readMdxFile = async (path: string) => {\n const { data } = matter.read(path);\n const { markdown, ...frontmatter } = data;\n return { ...frontmatter, markdown };\n};\n\nexport const searchFilesForId = async (files: string[], id: string, version?: string) => {\n const idRegex = new RegExp(`^id:\\\\s*(['\"]|>-)?\\\\s*${id}['\"]?\\\\s*$`, 'm');\n const versionRegex = new RegExp(`^version:\\\\s*['\"]?${version}['\"]?\\\\s*$`, 'm');\n\n const matches = files.map((file) => {\n const content = fsSync.readFileSync(file, 'utf-8');\n const hasIdMatch = content.match(idRegex);\n\n // Check version if provided\n if (version && !content.match(versionRegex)) {\n return undefined;\n }\n\n if (hasIdMatch) {\n return file;\n }\n });\n\n return matches.filter(Boolean).filter((file) => file !== undefined);\n};\n\n/**\n * Function to copy a directory from source to target, uses a tmp directory\n * @param catalogDir\n * @param source\n * @param target\n * @param filter\n */\nexport const copyDir = async (catalogDir: string, source: string, target: string, filter?: CopyFilterAsync | CopyFilterSync) => {\n const tmpDirectory = join(catalogDir, 'tmp');\n fsSync.mkdirSync(tmpDirectory, { recursive: true });\n\n // Copy everything over\n await copy(source, tmpDirectory, {\n overwrite: true,\n filter,\n });\n\n await copy(tmpDirectory, target, {\n overwrite: true,\n filter,\n });\n\n // Remove the tmp directory\n fsSync.rmSync(tmpDirectory, { recursive: true });\n};\n\n// Makes sure values in sends/recieves are unique\nexport const uniqueVersions = (messages: { id: string; version: string }[]): { id: string; version: string }[] => {\n const uniqueSet = new Set();\n\n return messages.filter((message) => {\n const key = `${message.id}-${message.version}`;\n if (!uniqueSet.has(key)) {\n uniqueSet.add(key);\n return true;\n }\n return false;\n });\n};\n","import { dirname, join } from 'path';\nimport { copyDir, findFileById, getFiles, searchFilesForId, versionExists } from './utils';\nimport matter from 'gray-matter';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { Message, Service, CustomDoc } from '../types';\nimport { satisfies } from 'semver';\nimport { lock, unlock } from 'proper-lockfile';\n\ntype Resource = Service | Message | CustomDoc;\n\nexport const versionResource = async (catalogDir: string, id: string) => {\n // Find all the events in the directory\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n const matchedFiles = await searchFilesForId(files, id);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No resource found with id: ${id}`);\n }\n\n // Event that is in the route of the project\n const file = matchedFiles[0];\n const sourceDirectory = dirname(file);\n const { data: { version = '0.0.1' } = {} } = matter.read(file);\n const targetDirectory = getVersionedDirectory(sourceDirectory, version);\n\n fsSync.mkdirSync(targetDirectory, { recursive: true });\n\n // Copy the event to the versioned directory\n await copyDir(catalogDir, sourceDirectory, targetDirectory, (src) => {\n return !src.includes('versioned');\n });\n\n // Remove all the files in the root of the resource as they have now been versioned\n await fs.readdir(sourceDirectory).then(async (resourceFiles) => {\n await Promise.all(\n resourceFiles.map(async (file) => {\n if (file !== 'versioned') {\n fsSync.rmSync(join(sourceDirectory, file), { recursive: true });\n }\n })\n );\n });\n};\n\nexport const writeResource = async (\n catalogDir: string,\n resource: Resource,\n options: { path?: string; type: string; override?: boolean; versionExistingContent?: boolean; format?: 'md' | 'mdx' } = {\n path: '',\n type: '',\n override: false,\n versionExistingContent: false,\n format: 'mdx',\n }\n) => {\n const path = options.path || `/${resource.id}`;\n const fullPath = join(catalogDir, path);\n const format = options.format || 'mdx';\n\n // Create directory if it doesn't exist\n fsSync.mkdirSync(fullPath, { recursive: true });\n\n // Create or get lock file path\n const lockPath = join(fullPath, `index.${format}`);\n\n // Ensure the file exists before attempting to lock it\n if (!fsSync.existsSync(lockPath)) {\n fsSync.writeFileSync(lockPath, '');\n }\n\n try {\n // Acquire lock with retry\n await lock(lockPath, {\n retries: 5,\n stale: 10000, // 10 seconds\n });\n\n const exists = await versionExists(catalogDir, resource.id, resource.version);\n\n if (exists && !options.override) {\n throw new Error(`Failed to write ${resource.id} (${options.type}) as the version ${resource.version} already exists`);\n }\n\n const { markdown, ...frontmatter } = resource;\n\n if (options.versionExistingContent && !exists) {\n const currentResource = await getResource(catalogDir, resource.id);\n\n if (currentResource) {\n if (satisfies(resource.version, `>${currentResource.version}`)) {\n await versionResource(catalogDir, resource.id);\n } else {\n throw new Error(`New version ${resource.version} is not greater than current version ${currentResource.version}`);\n }\n }\n }\n\n const document = matter.stringify(markdown.trim(), frontmatter);\n fsSync.writeFileSync(lockPath, document);\n } finally {\n // Always release the lock\n await unlock(lockPath).catch(() => {});\n }\n};\n\nexport const getResource = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string }\n): Promise<Resource | undefined> => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n const { data, content } = matter.read(file);\n\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n};\n\nexport const getResourcePath = async (catalogDir: string, id: string, version?: string) => {\n const file = await findFileById(catalogDir, id, version);\n if (!file) return;\n\n return {\n fullPath: file,\n relativePath: file.replace(catalogDir, ''),\n directory: dirname(file.replace(catalogDir, '')),\n };\n};\n\nexport const getResources = async (\n catalogDir: string,\n {\n type,\n latestOnly = false,\n ignore = [],\n pattern = '',\n }: { type: string; pattern?: string; latestOnly?: boolean; ignore?: string[] }\n): Promise<Resource[] | undefined> => {\n const ignoreList = latestOnly ? `**/versioned/**` : '';\n const filePattern = pattern || `${catalogDir}/**/${type}/**/index.{md,mdx}`;\n const files = await getFiles(filePattern, [ignoreList, ...ignore]);\n\n if (files.length === 0) return;\n\n return files.map((file) => {\n const { data, content } = matter.read(file);\n return {\n ...data,\n markdown: content.trim(),\n } as Resource;\n });\n};\n\nexport const rmResourceById = async (\n catalogDir: string,\n id: string,\n version?: string,\n options?: { type: string; persistFiles?: boolean }\n) => {\n const files = await getFiles(`${catalogDir}/**/index.{md,mdx}`);\n\n const matchedFiles = await searchFilesForId(files, id, version);\n\n if (matchedFiles.length === 0) {\n throw new Error(`No ${options?.type || 'resource'} found with id: ${id}`);\n }\n\n if (options?.persistFiles) {\n await Promise.all(\n matchedFiles.map(async (file) => {\n await fs.rm(file, { recursive: true });\n })\n );\n } else {\n await Promise.all(\n matchedFiles.map(async (file) => {\n const directory = dirname(file);\n await fs.rm(directory, { recursive: true, force: true });\n })\n );\n }\n};\n\nexport const addFileToResource = async (\n catalogDir: string,\n id: string,\n file: { content: string; fileName: string },\n version?: string\n) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory to write file to');\n\n fsSync.writeFileSync(join(dirname(pathToResource), file.fileName), file.content);\n};\n\nexport const getFileFromResource = async (catalogDir: string, id: string, file: { fileName: string }, version?: string) => {\n const pathToResource = await findFileById(catalogDir, id, version);\n\n if (!pathToResource) throw new Error('Cannot find directory of resource');\n\n const exists = await fs\n .access(join(dirname(pathToResource), file.fileName))\n .then(() => true)\n .catch(() => false);\n if (!exists) throw new Error(`File ${file.fileName} does not exist in resource ${id} v(${version})`);\n\n return fsSync.readFileSync(join(dirname(pathToResource), file.fileName), 'utf-8');\n};\nexport const getVersionedDirectory = (sourceDirectory: string, version: any): string => {\n return join(sourceDirectory, 'versioned', version);\n};\n"],"mappings":"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","names":["join","path","matter","matter","satisfies","matter","matter","join"]}
|
package/dist/domains.d.mts
CHANGED
package/dist/domains.d.ts
CHANGED
package/dist/domains.js
CHANGED
|
@@ -89,7 +89,8 @@ var findFileById = async (catalogDir, id, version) => {
|
|
|
89
89
|
var getFiles = async (pattern, ignore = "") => {
|
|
90
90
|
try {
|
|
91
91
|
const ignoreList = Array.isArray(ignore) ? ignore : [ignore];
|
|
92
|
-
const
|
|
92
|
+
const baseDir = pattern.includes("**") ? pattern.split("**")[0] : (0, import_node_path.dirname)(pattern);
|
|
93
|
+
const files = (0, import_glob.globSync)(pattern, { cwd: baseDir, ignore: ["node_modules/**", ...ignoreList] });
|
|
93
94
|
return files;
|
|
94
95
|
} catch (error) {
|
|
95
96
|
throw new Error(`Error finding files: ${error}`);
|
|
@@ -174,12 +175,14 @@ var writeResource = async (catalogDir, resource, options = {
|
|
|
174
175
|
path: "",
|
|
175
176
|
type: "",
|
|
176
177
|
override: false,
|
|
177
|
-
versionExistingContent: false
|
|
178
|
+
versionExistingContent: false,
|
|
179
|
+
format: "mdx"
|
|
178
180
|
}) => {
|
|
179
181
|
const path2 = options.path || `/${resource.id}`;
|
|
180
182
|
const fullPath = (0, import_path.join)(catalogDir, path2);
|
|
183
|
+
const format = options.format || "mdx";
|
|
181
184
|
import_node_fs2.default.mkdirSync(fullPath, { recursive: true });
|
|
182
|
-
const lockPath = (0, import_path.join)(fullPath,
|
|
185
|
+
const lockPath = (0, import_path.join)(fullPath, `index.${format}`);
|
|
183
186
|
if (!import_node_fs2.default.existsSync(lockPath)) {
|
|
184
187
|
import_node_fs2.default.writeFileSync(lockPath, "");
|
|
185
188
|
}
|
|
@@ -276,7 +279,12 @@ var getDomains = (directory) => async (options) => getResources(directory, {
|
|
|
276
279
|
ignore: ["**/services/**", "**/events/**", "**/commands/**", "**/queries/**", "**/flows/**"],
|
|
277
280
|
...options
|
|
278
281
|
});
|
|
279
|
-
var writeDomain = (directory) => async (domain, options = {
|
|
282
|
+
var writeDomain = (directory) => async (domain, options = {
|
|
283
|
+
path: "",
|
|
284
|
+
override: false,
|
|
285
|
+
versionExistingContent: false,
|
|
286
|
+
format: "mdx"
|
|
287
|
+
}) => {
|
|
280
288
|
const resource = { ...domain };
|
|
281
289
|
if (Array.isArray(domain.services)) {
|
|
282
290
|
resource.services = uniqueVersions(domain.services);
|