@eventcatalog/core 2.8.12 → 2.9.0
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/scripts/catalog-to-astro-content-directory.js +55 -232
- package/scripts/eventcatalog-config-file-utils.js +18 -0
- package/scripts/map-catalog-to-astro.js +162 -0
- package/scripts/watcher.js +117 -52
- package/src/content/config.ts +2 -0
- package/src/utils/collections/file-diffs.ts +1 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -3,277 +3,100 @@ import * as path from 'node:path';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import os from 'node:os';
|
|
6
|
-
import { verifyRequiredFieldsAreInCatalogConfigFile } from './eventcatalog-config-file-utils.js';
|
|
6
|
+
import { verifyRequiredFieldsAreInCatalogConfigFile, addPropertyToFrontMatter } from './eventcatalog-config-file-utils.js';
|
|
7
|
+
import { mapCatalogToAstro } from './map-catalog-to-astro.js';
|
|
7
8
|
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const scriptsDir = path.dirname(__filename);
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const cleanedRelativePath = relativePath.split(type);
|
|
14
|
-
const targetForEvents = path.join(type, cleanedRelativePath[1]);
|
|
15
|
-
return path.join(target, targetForEvents);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const ensureDirSync = async (filePath) => {
|
|
19
|
-
const dir = path.dirname(filePath);
|
|
20
|
-
|
|
21
|
-
if (!fs.existsSync(dir)) {
|
|
22
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const copyFiles = async ({ source, target, catalogFilesDir, pathToMarkdownFiles, pathToAllFiles, type, ignore = null }) => {
|
|
27
|
-
// Find all the event files
|
|
28
|
-
const markdownFiles = await glob(pathToMarkdownFiles, {
|
|
29
|
-
nodir: true,
|
|
30
|
-
windowsPathsNoEscape: os.platform() == 'win32',
|
|
31
|
-
ignore: ignore,
|
|
32
|
-
});
|
|
33
|
-
const files = await glob(pathToAllFiles, {
|
|
34
|
-
ignore: {
|
|
35
|
-
ignored: (p) => /\.md$/.test(p.name),
|
|
36
|
-
},
|
|
12
|
+
const copyFiles = async (source, target) => {
|
|
13
|
+
const files = await glob(path.join(source, '**'), {
|
|
37
14
|
nodir: true,
|
|
38
15
|
windowsPathsNoEscape: os.platform() == 'win32',
|
|
39
16
|
});
|
|
40
17
|
|
|
41
|
-
const publicDir = path.join(target, '../../public/generated');
|
|
42
|
-
|
|
43
|
-
// Copy markdown files into the astro content (collection) folder
|
|
44
|
-
for (const file of markdownFiles) {
|
|
45
|
-
let fileTarget = target;
|
|
46
|
-
|
|
47
|
-
// If they are change logs they need to go into their own content folder
|
|
48
|
-
if (file.includes('changelog.md')) {
|
|
49
|
-
fileTarget = path.join(target, 'changelogs');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const targetPath = getTargetPath(source, fileTarget, type, file);
|
|
53
|
-
|
|
54
|
-
//ensure the directory exists
|
|
55
|
-
ensureDirSync(path.dirname(targetPath));
|
|
56
|
-
|
|
57
|
-
fs.cpSync(file, targetPath.replace('index.md', 'index.mdx').replace('changelog.md', 'changelog.mdx'));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Copy all other files (non markdown) files into catalog-files directory (non collection)
|
|
61
18
|
for (const file of files) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
19
|
+
mapCatalogToAstro({
|
|
20
|
+
filePath: file,
|
|
21
|
+
astroDir: target,
|
|
22
|
+
projectDir: source,
|
|
23
|
+
})
|
|
24
|
+
.map((astroPath) => {
|
|
25
|
+
fs.cpSync(file, astroPath);
|
|
26
|
+
return { oldPath: file, newPath: astroPath };
|
|
27
|
+
})
|
|
28
|
+
.map(({ oldPath, newPath }) => {
|
|
29
|
+
if (!oldPath.endsWith('.md') && !oldPath.endsWith('.mdx')) return;
|
|
30
|
+
try {
|
|
31
|
+
// EventCatalog requires the original path to be in the frontmatter for Schemas and Changelogs
|
|
32
|
+
const content = fs.readFileSync(newPath, 'utf-8');
|
|
33
|
+
const frontmatter = addPropertyToFrontMatter(content, 'pathToFile', oldPath);
|
|
34
|
+
fs.writeFileSync(newPath, frontmatter);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// silent fail
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
78
41
|
|
|
79
|
-
|
|
80
|
-
|
|
42
|
+
const ensureAstroCollectionNotEmpty = async (astroDir) => {
|
|
43
|
+
// TODO: maybe import collections from `src/content/config.ts`...
|
|
44
|
+
const COLLECTIONS = ['events', 'commands', 'services', 'users', 'teams', 'domains', 'flows', 'pages', 'changelogs'];
|
|
81
45
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
46
|
+
// Check empty collections
|
|
47
|
+
const emptyCollections = [];
|
|
48
|
+
for (const collection of COLLECTIONS) {
|
|
49
|
+
const markdownFiles = await glob(path.join(astroDir, 'src/content/', collection, '**'), {
|
|
50
|
+
nodir: true,
|
|
51
|
+
windowsPathsNoEscape: os.platform() == 'win32',
|
|
52
|
+
});
|
|
85
53
|
|
|
86
|
-
|
|
54
|
+
if (markdownFiles.length === 0) emptyCollections.push(collection);
|
|
87
55
|
}
|
|
88
56
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const targetDir = path.join(target, type);
|
|
57
|
+
// Hydrate empty collections
|
|
58
|
+
const defaultCollectionFilesDir = path.join(scriptsDir, 'default-files-for-collections');
|
|
59
|
+
for (const collection of emptyCollections) {
|
|
60
|
+
const defaultFile = path.join(defaultCollectionFilesDir, `${collection}.md`);
|
|
61
|
+
const targetDir = path.join(astroDir, 'src/content/', collection);
|
|
95
62
|
if (!fs.existsSync(targetDir)) {
|
|
96
63
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
97
64
|
}
|
|
98
|
-
|
|
65
|
+
fs.cpSync(defaultFile, path.join(targetDir, `${collection}.md`));
|
|
99
66
|
}
|
|
100
67
|
};
|
|
101
68
|
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const componentsTarget = path.join(target, '../custom-defined-components');
|
|
105
|
-
|
|
106
|
-
// Clear the component directory before we copy files over
|
|
107
|
-
if (fs.existsSync(componentsTarget)) {
|
|
108
|
-
await fs.rmSync(componentsTarget, { recursive: true });
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (!fs.existsSync(componentsDir) || !fs.statSync(componentsDir).isDirectory()) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
69
|
+
export const catalogToAstro = async (source, astroDir) => {
|
|
70
|
+
const astroContentDir = path.join(astroDir, 'src/content/');
|
|
114
71
|
|
|
115
|
-
|
|
116
|
-
await fs.cpSync(componentsDir, componentsTarget, { recursive: true });
|
|
117
|
-
};
|
|
72
|
+
console.log(path.join(astroContentDir, 'config.ts'));
|
|
118
73
|
|
|
119
|
-
export const catalogToAstro = async (source, astroContentDir, catalogFilesDir) => {
|
|
120
74
|
// Config file
|
|
121
75
|
const astroConfigFile = fs.readFileSync(path.join(astroContentDir, 'config.ts'));
|
|
122
76
|
|
|
123
77
|
// Clear the astro directory before we copy files over
|
|
124
|
-
|
|
78
|
+
fs.rmSync(astroContentDir, { recursive: true });
|
|
125
79
|
|
|
126
80
|
// Create the folder again
|
|
127
81
|
fs.mkdirSync(astroContentDir);
|
|
128
82
|
|
|
129
83
|
// Write config file back
|
|
130
|
-
// ensureDirSync(astroContentDir);
|
|
131
84
|
fs.writeFileSync(path.join(astroContentDir, 'config.ts'), astroConfigFile);
|
|
132
85
|
|
|
133
|
-
// Copy the public directory files into the astro public directory
|
|
134
|
-
const usersPublicDirectory = path.join(source, 'public');
|
|
135
|
-
const astroPublicDir = path.join(astroContentDir, '../../public');
|
|
136
|
-
|
|
137
|
-
if (fs.existsSync(usersPublicDirectory)) {
|
|
138
|
-
// fs.mkdirSync(astroPublicDir, { recursive: true });
|
|
139
|
-
fs.cpSync(usersPublicDirectory, astroPublicDir, { recursive: true });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Copy all the event files over
|
|
143
|
-
await copyFiles({
|
|
144
|
-
source,
|
|
145
|
-
target: astroContentDir,
|
|
146
|
-
catalogFilesDir,
|
|
147
|
-
pathToMarkdownFiles: [
|
|
148
|
-
path.join(source, 'events/**/**/index.md'),
|
|
149
|
-
path.join(source, 'services/**/events/**/index.md'),
|
|
150
|
-
path.join(source, 'domains/**/events/**/index.md'),
|
|
151
|
-
path.join(source, 'events/**/**/changelog.md'),
|
|
152
|
-
],
|
|
153
|
-
pathToAllFiles: [
|
|
154
|
-
path.join(source, 'events/**'),
|
|
155
|
-
path.join(source, 'services/**/events/**'),
|
|
156
|
-
path.join(source, 'domains/**/events/**'),
|
|
157
|
-
],
|
|
158
|
-
type: 'events',
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// // copy commands
|
|
162
|
-
await copyFiles({
|
|
163
|
-
source,
|
|
164
|
-
target: astroContentDir,
|
|
165
|
-
catalogFilesDir,
|
|
166
|
-
pathToMarkdownFiles: [
|
|
167
|
-
path.join(source, 'commands/**/**/index.md'),
|
|
168
|
-
path.join(source, 'services/**/commands/**/index.md'),
|
|
169
|
-
path.join(source, 'domains/**/commands/**/index.md'),
|
|
170
|
-
path.join(source, 'commands/**/**/changelog.md'),
|
|
171
|
-
],
|
|
172
|
-
pathToAllFiles: [
|
|
173
|
-
path.join(source, 'commands/**'),
|
|
174
|
-
path.join(source, 'services/**/commands/**'),
|
|
175
|
-
path.join(source, 'domains/**/commands/**'),
|
|
176
|
-
],
|
|
177
|
-
type: 'commands',
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// // Copy all the service files over
|
|
181
|
-
await copyFiles({
|
|
182
|
-
source,
|
|
183
|
-
target: astroContentDir,
|
|
184
|
-
catalogFilesDir,
|
|
185
|
-
pathToMarkdownFiles: [
|
|
186
|
-
path.join(source, 'services/**/**/index.md'),
|
|
187
|
-
path.join(source, 'domains/**/services/**/index.md'),
|
|
188
|
-
path.join(source, 'services/**/**/changelog.md'),
|
|
189
|
-
],
|
|
190
|
-
pathToAllFiles: [path.join(source, 'services/**'), path.join(source, 'domains/**/services/**')],
|
|
191
|
-
ignore: [
|
|
192
|
-
path.join(source, 'services/**/events/**'),
|
|
193
|
-
path.join(source, 'services/**/commands/**'),
|
|
194
|
-
path.join(source, 'services/**/flows/**'),
|
|
195
|
-
],
|
|
196
|
-
type: 'services',
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// // Copy all the domain files over
|
|
200
|
-
await copyFiles({
|
|
201
|
-
source,
|
|
202
|
-
target: astroContentDir,
|
|
203
|
-
catalogFilesDir,
|
|
204
|
-
pathToMarkdownFiles: [path.join(source, 'domains/**/**/index.md'), path.join(source, 'domains/**/**/changelog.md')],
|
|
205
|
-
pathToAllFiles: [path.join(source, 'domains/**')],
|
|
206
|
-
ignore: [
|
|
207
|
-
path.join(source, 'domains/**/services/**'),
|
|
208
|
-
path.join(source, 'domains/**/commands/**'),
|
|
209
|
-
path.join(source, 'domains/**/events/**'),
|
|
210
|
-
path.join(source, 'domains/**/flows/**'),
|
|
211
|
-
],
|
|
212
|
-
type: 'domains',
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// // Copy all the flow files over
|
|
216
|
-
await copyFiles({
|
|
217
|
-
source,
|
|
218
|
-
target: astroContentDir,
|
|
219
|
-
catalogFilesDir,
|
|
220
|
-
pathToMarkdownFiles: [
|
|
221
|
-
path.join(source, 'flows/**/**/index.md'),
|
|
222
|
-
path.join(source, 'flows/**/**/changelog.md'),
|
|
223
|
-
path.join(source, 'services/**/flows/**/index.md'),
|
|
224
|
-
path.join(source, 'domains/**/flows/**/index.md'),
|
|
225
|
-
],
|
|
226
|
-
pathToAllFiles: [
|
|
227
|
-
path.join(source, 'flows/**'),
|
|
228
|
-
path.join(source, 'services/**/flows/**'),
|
|
229
|
-
path.join(source, 'domains/**/flows/**'),
|
|
230
|
-
],
|
|
231
|
-
type: 'flows',
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// // Copy all the users
|
|
235
|
-
await copyFiles({
|
|
236
|
-
source,
|
|
237
|
-
target: astroContentDir,
|
|
238
|
-
catalogFilesDir,
|
|
239
|
-
pathToMarkdownFiles: [path.join(source, 'users/**/*.md')],
|
|
240
|
-
pathToAllFiles: [],
|
|
241
|
-
type: 'users',
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// // Copy all the teams
|
|
245
|
-
await copyFiles({
|
|
246
|
-
source,
|
|
247
|
-
target: astroContentDir,
|
|
248
|
-
catalogFilesDir,
|
|
249
|
-
pathToMarkdownFiles: [path.join(source, 'teams/**/*.md')],
|
|
250
|
-
pathToAllFiles: [],
|
|
251
|
-
type: 'teams',
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// // Copy all the pages (optional)
|
|
255
|
-
await copyFiles({
|
|
256
|
-
source,
|
|
257
|
-
target: astroContentDir,
|
|
258
|
-
catalogFilesDir,
|
|
259
|
-
pathToMarkdownFiles: [path.join(source, 'pages/**/*.md')],
|
|
260
|
-
pathToAllFiles: [],
|
|
261
|
-
type: 'pages',
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Copy the components if they are defined
|
|
265
|
-
await copyComponents({ source, target: astroContentDir, catalogFilesDir });
|
|
266
|
-
|
|
267
86
|
// Verify required fields are in the catalog config file
|
|
268
87
|
await verifyRequiredFieldsAreInCatalogConfigFile(source);
|
|
88
|
+
|
|
89
|
+
await copyFiles(source, astroDir);
|
|
90
|
+
|
|
91
|
+
// Check if the directory is empty. EC (astro collections) requires at least 1 item in the collection
|
|
92
|
+
// insert empty one that is filtered out
|
|
93
|
+
await ensureAstroCollectionNotEmpty(astroDir);
|
|
269
94
|
};
|
|
270
95
|
|
|
271
96
|
if (process.env.NODE_ENV !== 'test') {
|
|
272
97
|
// // Get the project directory of the source
|
|
273
98
|
const source = process.env.PROJECT_DIR;
|
|
99
|
+
const astroDir = process.env.CATALOG_DIR;
|
|
274
100
|
|
|
275
|
-
|
|
276
|
-
const catalogFilesDir = path.join(process.env.CATALOG_DIR, 'src/catalog-files');
|
|
277
|
-
|
|
278
|
-
catalogToAstro(source, astroContentDir, catalogFilesDir);
|
|
101
|
+
catalogToAstro(source, astroDir);
|
|
279
102
|
}
|
|
@@ -80,3 +80,21 @@ export const verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirector
|
|
|
80
80
|
// fail silently, it's overly important
|
|
81
81
|
}
|
|
82
82
|
};
|
|
83
|
+
|
|
84
|
+
export function addPropertyToFrontMatter(input, newProperty, newValue) {
|
|
85
|
+
// Split the input into front matter and content
|
|
86
|
+
const [_, frontMatter, content] = input.split('---');
|
|
87
|
+
|
|
88
|
+
// Parse the front matter
|
|
89
|
+
const frontMatterLines = frontMatter.trim().split('\n');
|
|
90
|
+
const updatedFrontMatterLines = [...frontMatterLines];
|
|
91
|
+
|
|
92
|
+
// Add the new property
|
|
93
|
+
updatedFrontMatterLines.push(`${newProperty}: ${newValue}`);
|
|
94
|
+
|
|
95
|
+
// Reconstruct the updated input
|
|
96
|
+
const updatedFrontMatter = updatedFrontMatterLines.join('\n');
|
|
97
|
+
const updatedInput = `---\n${updatedFrontMatter}\n---\n${content.trim()}`;
|
|
98
|
+
|
|
99
|
+
return updatedInput;
|
|
100
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
const COLLECTION_KEYS = ['events', 'commands', 'services', 'users', 'teams', 'domains', 'flows', 'pages', 'changelogs'];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} MapCatalogToAstroParams
|
|
7
|
+
* @prop {string} filePath The catalog file path
|
|
8
|
+
* @prop {string} astroDir The astro directory
|
|
9
|
+
* @prop {string} projectDir The user's project directory
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {MapCatalogToAstroParams} params
|
|
15
|
+
* @returns {string[]} The astro file paths
|
|
16
|
+
*/
|
|
17
|
+
export function mapCatalogToAstro({ filePath, astroDir, projectDir }) {
|
|
18
|
+
const relativeFilePath = removeBasePath(filePath, projectDir);
|
|
19
|
+
|
|
20
|
+
if (!isCatalogRelated(relativeFilePath)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const baseTargetPaths = getBaseTargetPaths(relativeFilePath);
|
|
25
|
+
const relativeTargetPath = getRelativeTargetPath(relativeFilePath);
|
|
26
|
+
|
|
27
|
+
return baseTargetPaths.map((base) =>
|
|
28
|
+
path.join(astroDir, base, relativeTargetPath.replace('index.md', 'index.mdx').replace('changelog.md', 'changelog.mdx'))
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param {string} fullPath
|
|
35
|
+
* @param {string} basePath
|
|
36
|
+
* @returns {string} The fullPath without the basePath
|
|
37
|
+
*/
|
|
38
|
+
function removeBasePath(fullPath, basePath) {
|
|
39
|
+
const relativePath = path.relative(basePath, fullPath);
|
|
40
|
+
return relativePath.startsWith('..') ? fullPath : relativePath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if the key is an ASTRO COLLECTION KEY
|
|
45
|
+
* @param {string} key
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
function isCollectionKey(key) {
|
|
49
|
+
return COLLECTION_KEYS.includes(key);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks whether the given path is a configuration file, styles file, public asset file or collection file.
|
|
54
|
+
* @param {string} filePath - The file path without the projectDir prefix.
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
function isCatalogRelated(filePath) {
|
|
58
|
+
const filePathArr = filePath.split(path.sep).filter(Boolean);
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
[
|
|
62
|
+
'eventcatalog.config.js', // config file at root
|
|
63
|
+
'eventcatalog.styles.css', // custom styles file at root
|
|
64
|
+
'components', // custom components
|
|
65
|
+
'public', // public assets
|
|
66
|
+
...COLLECTION_KEYS,
|
|
67
|
+
].includes(filePathArr[0])
|
|
68
|
+
) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generates the base target path accordingly to the file path.
|
|
77
|
+
* @param {string} filePath The path to the file without PROJECT_DIR prefix.
|
|
78
|
+
* @returns {Array.<'src/content'|'public/generated'|'src/catalog-files'|'/'>} The base target path.
|
|
79
|
+
*/
|
|
80
|
+
function getBaseTargetPaths(filePath) {
|
|
81
|
+
const filePathArr = filePath.split(path.sep).filter(Boolean);
|
|
82
|
+
|
|
83
|
+
// Collection
|
|
84
|
+
if (isCollectionKey(filePathArr[0])) {
|
|
85
|
+
// Changelogs files
|
|
86
|
+
if (filePathArr[filePathArr.length - 1] == 'changelog.md') {
|
|
87
|
+
return [path.join('src', 'content', 'changelogs')];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Markdown files
|
|
91
|
+
if (filePathArr[filePathArr.length - 1].match(/\.md$/)) {
|
|
92
|
+
return [path.join('src', 'content')];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// This is a workaround to differentiate between a file and a directory.
|
|
96
|
+
// Of course this is not the best solution. But how differentiate? `fs.stats`
|
|
97
|
+
// could be used, but sometimes the filePath references a deleted file/directory
|
|
98
|
+
// which causes an error when using `fs.stats`.
|
|
99
|
+
const hasExtension = (str) => /\.[a-zA-Z0-9]{2,}$/.test(str);
|
|
100
|
+
|
|
101
|
+
// Assets files
|
|
102
|
+
if (hasExtension(filePath)) {
|
|
103
|
+
return [path.join('public', 'generated'), path.join('src', 'catalog-files')];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @parcel/watcher throttle and coalesce events for performance reasons.
|
|
108
|
+
*
|
|
109
|
+
* Consider the following:
|
|
110
|
+
* The user deletes a large `services/` dir from eventcatalog.
|
|
111
|
+
* The @parcel/watcher could emit only one delete event of the
|
|
112
|
+
* `services/` directory.
|
|
113
|
+
*
|
|
114
|
+
* In this situation we need delete all files from
|
|
115
|
+
* - `public/generated/services/`
|
|
116
|
+
* - `src/catalog-files/services/`
|
|
117
|
+
* - `src/content/services/`
|
|
118
|
+
*
|
|
119
|
+
* TODO: What happens if services contains commands/events inside of it??? How handle this?
|
|
120
|
+
*/
|
|
121
|
+
// Directories
|
|
122
|
+
return [path.join('public', 'generated'), path.join('src', 'catalog-files'), path.join('src', 'content')];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Custom components
|
|
126
|
+
if (filePathArr[0] == 'components') {
|
|
127
|
+
return [path.join('src', 'custom-defined-components')];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Public assets (public/*)
|
|
131
|
+
if (filePathArr[0] == 'public') {
|
|
132
|
+
return [path.join('public')];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Config files:
|
|
137
|
+
* - eventcatalog.config.js
|
|
138
|
+
* - eventcatalog.styles.css
|
|
139
|
+
*/
|
|
140
|
+
return [path.join('/')];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generates the path until the ASTRO_COLLECTION_KEY or the PROJECT_DIR root.
|
|
145
|
+
* @param {string} filePath The path to the file.
|
|
146
|
+
* @returns {string} The path until the COLLECTION_KEY or PROJECT_DIR root.
|
|
147
|
+
*/
|
|
148
|
+
function getRelativeTargetPath(filePath) {
|
|
149
|
+
const filePathArr = filePath.split(path.sep).filter(Boolean);
|
|
150
|
+
|
|
151
|
+
if (filePathArr[0] == 'public' || filePathArr[0] == 'components') {
|
|
152
|
+
filePathArr.shift();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const relativePath = [];
|
|
156
|
+
for (let i = filePathArr.length - 1; i >= 0; i--) {
|
|
157
|
+
relativePath.unshift(filePathArr[i]);
|
|
158
|
+
if (isCollectionKey(filePathArr[i])) break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return path.join(...relativePath);
|
|
162
|
+
}
|
package/scripts/watcher.js
CHANGED
|
@@ -1,69 +1,134 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import watcher from '@parcel/watcher';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { mapCatalogToAstro } from './map-catalog-to-astro.js';
|
|
5
|
+
import { rimrafSync } from 'rimraf';
|
|
6
|
+
import { addPropertyToFrontMatter } from './eventcatalog-config-file-utils.js';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} Event
|
|
10
|
+
* @property {string} path
|
|
11
|
+
* @property {"create"|"update"|"delete"} type
|
|
12
|
+
*
|
|
13
|
+
* @typedef {(err: Error | null, events: Event[]) => unknown} SubscribeCallback
|
|
14
|
+
*/
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param {string} projectDirectory
|
|
19
|
+
* @param {string} catalogDirectory
|
|
20
|
+
* @param {SubscribeCallback|undefined} callback
|
|
21
|
+
*/
|
|
22
|
+
export async function watch(projectDirectory, catalogDirectory, callback = undefined) {
|
|
23
|
+
const subscription = await watcher.subscribe(
|
|
24
|
+
projectDirectory,
|
|
25
|
+
compose(
|
|
26
|
+
/**
|
|
27
|
+
* @param {Error|null} err
|
|
28
|
+
* @param {Event[]} events
|
|
29
|
+
* @returns {unknown}
|
|
30
|
+
*/
|
|
31
|
+
(err, events) => {
|
|
32
|
+
if (err) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
13
35
|
|
|
14
|
-
|
|
15
|
-
|
|
36
|
+
for (let event of events) {
|
|
37
|
+
const { path: filePath, type } = event;
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
|
|
39
|
+
const astroPaths = mapCatalogToAstro({
|
|
40
|
+
filePath,
|
|
41
|
+
astroDir: catalogDirectory,
|
|
42
|
+
projectDir: projectDirectory,
|
|
43
|
+
});
|
|
19
44
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
45
|
+
for (const astroPath of astroPaths) {
|
|
46
|
+
switch (type) {
|
|
47
|
+
case 'create':
|
|
48
|
+
case 'update':
|
|
49
|
+
try {
|
|
50
|
+
// EventCatalog requires the original path to be in the frontmatter for Schemas and Changelogs
|
|
51
|
+
if (astroPath.endsWith('.mdx')) {
|
|
52
|
+
const content = fs.readFileSync(astroPath, 'utf-8');
|
|
53
|
+
const frontmatter = addPropertyToFrontMatter(content, 'pathToFile', filePath);
|
|
54
|
+
fs.writeFileSync(astroPath, frontmatter);
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// silent fail
|
|
58
|
+
}
|
|
24
59
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
watcher.subscribe(path.join(projectDirectory, item), (err, events) => {
|
|
28
|
-
if (err) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
for (let event of events) {
|
|
32
|
-
const { path: eventPath, type } = event;
|
|
33
|
-
const file = eventPath.split(item)[1];
|
|
34
|
-
let newPath = path.join(contentPath, item, extensionReplacer(item, file));
|
|
60
|
+
if (fs.statSync(filePath).isDirectory()) fs.mkdirSync(astroPath, { recursive: true });
|
|
61
|
+
else retryEPERM(fs.cpSync)(filePath, astroPath);
|
|
35
62
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
break;
|
|
64
|
+
case 'delete':
|
|
65
|
+
retryEPERM(rimrafSync)(astroPath);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
41
69
|
}
|
|
42
|
-
}
|
|
70
|
+
},
|
|
71
|
+
callback
|
|
72
|
+
),
|
|
73
|
+
{
|
|
74
|
+
ignore: [`**/${catalogDirectory}/!(${projectDirectory})**`],
|
|
75
|
+
}
|
|
76
|
+
);
|
|
43
77
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
newPath = newPath.replace('src/content/components', 'src/custom-defined-components');
|
|
47
|
-
if (os.platform() == 'win32') {
|
|
48
|
-
newPath = newPath.replace('src\\content\\components', 'src\\custom-defined-components');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
78
|
+
return () => subscription.unsubscribe();
|
|
79
|
+
}
|
|
51
80
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
/**
|
|
82
|
+
*
|
|
83
|
+
* @param {...Function} fns
|
|
84
|
+
* @returns {SubscribeCallback}
|
|
85
|
+
*/
|
|
86
|
+
function compose(...fns) {
|
|
87
|
+
return function (err, events) {
|
|
88
|
+
fns.filter(Boolean).forEach((fn, i) => {
|
|
89
|
+
try {
|
|
90
|
+
fn(err, events);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error({ error });
|
|
93
|
+
throw error;
|
|
56
94
|
}
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
}
|
|
57
98
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
fs.cpSync(eventPath, newPath);
|
|
61
|
-
}
|
|
99
|
+
const MAX_RETRIES = 5;
|
|
100
|
+
const DELAY_MS = 100;
|
|
62
101
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
// In win32 some tests failed when attempting copy the file at the same time
|
|
103
|
+
// that another process is editing it.
|
|
104
|
+
function retryEPERM(fn) {
|
|
105
|
+
return (...args) => {
|
|
106
|
+
let retries = 0;
|
|
107
|
+
|
|
108
|
+
while (retries < MAX_RETRIES) {
|
|
109
|
+
try {
|
|
110
|
+
return fn(...args);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err.code !== 'EPERM') throw err;
|
|
113
|
+
setTimeout(() => {}, DELAY_MS);
|
|
114
|
+
retries += 1;
|
|
66
115
|
}
|
|
67
116
|
}
|
|
68
|
-
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* TODO: call `watch` from the dev command.
|
|
122
|
+
* Calling `watch` there will avoid these if statement.
|
|
123
|
+
* The same could be done to `catalog-to-astro-content-directory`
|
|
124
|
+
*/
|
|
125
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
126
|
+
// Where the users project is located
|
|
127
|
+
const projectDirectory = process.env.PROJECT_DIR || process.cwd();
|
|
128
|
+
// Where the catalog code is located.
|
|
129
|
+
const catalogDirectory = process.env.CATALOG_DIR || path.join(projectDirectory, '.eventcatalog-core');
|
|
130
|
+
|
|
131
|
+
const unsub = await watch(projectDirectory, catalogDirectory);
|
|
132
|
+
|
|
133
|
+
process.on('exit', () => unsub());
|
|
69
134
|
}
|
package/src/content/config.ts
CHANGED
|
@@ -61,10 +61,12 @@ const baseSchema = z.object({
|
|
|
61
61
|
// Used by eventcatalog
|
|
62
62
|
versions: z.array(z.string()).optional(),
|
|
63
63
|
latestVersion: z.string().optional(),
|
|
64
|
+
pathToFile: z.string().optional(),
|
|
64
65
|
catalog: z
|
|
65
66
|
.object({
|
|
66
67
|
path: z.string(),
|
|
67
68
|
filePath: z.string(),
|
|
69
|
+
astroContentFilePath: z.string(),
|
|
68
70
|
publicPath: z.string(),
|
|
69
71
|
type: z.string(),
|
|
70
72
|
})
|
|
@@ -50,7 +50,7 @@ export async function getFilesForDiffInCollection(
|
|
|
50
50
|
collection: CollectionEntry<CollectionTypes>
|
|
51
51
|
): Promise<Array<{ file: string; dir: string }>> {
|
|
52
52
|
// @ts-ignore
|
|
53
|
-
const pathToFolder = collection.
|
|
53
|
+
const pathToFolder = collection.data.pathToFile;
|
|
54
54
|
if (!pathToFolder) return [];
|
|
55
55
|
|
|
56
56
|
const dir = dirname(pathToFolder);
|