@eventvisor/core 0.6.0 → 0.8.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 +19 -0
- package/lib/catalog/buildCatalog.d.ts +4 -0
- package/lib/catalog/buildCatalog.js +78 -0
- package/lib/catalog/buildCatalog.js.map +1 -0
- package/lib/catalog/exportCatalog.d.ts +2 -0
- package/lib/catalog/exportCatalog.js +62 -0
- package/lib/catalog/exportCatalog.js.map +1 -0
- package/lib/catalog/generateHistory.d.ts +3 -0
- package/lib/catalog/generateHistory.js +64 -0
- package/lib/catalog/generateHistory.js.map +1 -0
- package/lib/catalog/getLastModifiedFromHistory.d.ts +2 -0
- package/lib/catalog/getLastModifiedFromHistory.js +18 -0
- package/lib/catalog/getLastModifiedFromHistory.js.map +1 -0
- package/lib/catalog/getOwnerAndRepoFromUrl.d.ts +4 -0
- package/lib/catalog/getOwnerAndRepoFromUrl.js +20 -0
- package/lib/catalog/getOwnerAndRepoFromUrl.js.map +1 -0
- package/lib/catalog/getRelativePaths.d.ts +7 -0
- package/lib/catalog/getRelativePaths.js +50 -0
- package/lib/catalog/getRelativePaths.js.map +1 -0
- package/lib/catalog/getRepoDetails.d.ts +8 -0
- package/lib/catalog/getRepoDetails.js +48 -0
- package/lib/catalog/getRepoDetails.js.map +1 -0
- package/lib/catalog/index.d.ts +2 -0
- package/lib/catalog/index.js +37 -0
- package/lib/catalog/index.js.map +1 -0
- package/lib/catalog/serveCatalog.d.ts +2 -0
- package/lib/catalog/serveCatalog.js +88 -0
- package/lib/catalog/serveCatalog.js.map +1 -0
- package/lib/cli/plugins.js +8 -1
- package/lib/cli/plugins.js.map +1 -1
- package/lib/config/projectConfig.d.ts +2 -1
- package/lib/config/projectConfig.js +3 -2
- package/lib/config/projectConfig.js.map +1 -1
- package/lib/datasource/adapter.d.ts +3 -1
- package/lib/datasource/adapter.js.map +1 -1
- package/lib/datasource/datasource.d.ts +3 -1
- package/lib/datasource/datasource.js +7 -0
- package/lib/datasource/datasource.js.map +1 -1
- package/lib/datasource/filesystemAdapter.d.ts +8 -1
- package/lib/datasource/filesystemAdapter.js +130 -0
- package/lib/datasource/filesystemAdapter.js.map +1 -1
- package/lib/utils/git.d.ts +6 -0
- package/lib/utils/git.js +153 -0
- package/lib/utils/git.js.map +1 -0
- package/package.json +4 -4
- package/src/catalog/buildCatalog.ts +114 -0
- package/src/catalog/exportCatalog.ts +40 -0
- package/src/catalog/generateHistory.ts +40 -0
- package/src/catalog/getLastModifiedFromHistory.ts +21 -0
- package/src/catalog/getOwnerAndRepoFromUrl.ts +17 -0
- package/src/catalog/getRelativePaths.ts +23 -0
- package/src/catalog/getRepoDetails.ts +62 -0
- package/src/catalog/index.ts +40 -0
- package/src/catalog/serveCatalog.ts +60 -0
- package/src/cli/plugins.ts +8 -1
- package/src/config/projectConfig.ts +3 -1
- package/src/datasource/adapter.ts +5 -1
- package/src/datasource/datasource.ts +10 -0
- package/src/datasource/filesystemAdapter.ts +155 -1
- package/src/utils/git.ts +129 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
import { generateHistory } from "./generateHistory";
|
|
5
|
+
import { getRepoDetails } from "./getRepoDetails";
|
|
6
|
+
import { buildCatalog } from "./buildCatalog";
|
|
7
|
+
import { Dependencies } from "../dependencies";
|
|
8
|
+
|
|
9
|
+
export async function exportCatalog(deps: Dependencies) {
|
|
10
|
+
const { projectConfig } = deps;
|
|
11
|
+
|
|
12
|
+
fs.mkdirSync(projectConfig.catalogExportDirectoryPath, { recursive: true });
|
|
13
|
+
|
|
14
|
+
const catalogPackagePath = path.dirname(require.resolve("@eventvisor/catalog/package.json"));
|
|
15
|
+
|
|
16
|
+
// copy catalog dist
|
|
17
|
+
const catalogDistPath = path.join(catalogPackagePath, "dist");
|
|
18
|
+
fs.cpSync(catalogDistPath, projectConfig.catalogExportDirectoryPath, { recursive: true });
|
|
19
|
+
|
|
20
|
+
console.log("Catalog dist copied to:", projectConfig.catalogExportDirectoryPath);
|
|
21
|
+
|
|
22
|
+
// generate history
|
|
23
|
+
const fullHistory = await generateHistory(deps);
|
|
24
|
+
|
|
25
|
+
// site search index
|
|
26
|
+
const repoDetails = getRepoDetails();
|
|
27
|
+
const catalog = await buildCatalog(deps, fullHistory, repoDetails);
|
|
28
|
+
const catalogFilePath = path.join(projectConfig.catalogExportDirectoryPath, "catalog.json");
|
|
29
|
+
fs.writeFileSync(catalogFilePath, JSON.stringify(catalog));
|
|
30
|
+
console.log(`Catalog written at: ${catalogFilePath}`);
|
|
31
|
+
|
|
32
|
+
// copy datafiles
|
|
33
|
+
fs.cpSync(
|
|
34
|
+
projectConfig.datafilesDirectoryPath,
|
|
35
|
+
path.join(projectConfig.catalogExportDirectoryPath, "datafiles"),
|
|
36
|
+
{ recursive: true },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
|
|
4
|
+
import type { HistoryEntry } from "@eventvisor/types";
|
|
5
|
+
import { Dependencies } from "../dependencies";
|
|
6
|
+
|
|
7
|
+
export async function generateHistory(deps: Dependencies): Promise<HistoryEntry[]> {
|
|
8
|
+
const { projectConfig, datasource } = deps;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const fullHistory = await datasource.listHistoryEntries();
|
|
12
|
+
|
|
13
|
+
const filteredHistory = fullHistory
|
|
14
|
+
.map((historyEntry) => {
|
|
15
|
+
return {
|
|
16
|
+
...historyEntry,
|
|
17
|
+
entities: historyEntry.entities.filter((entity) => {
|
|
18
|
+
// ignore test specs
|
|
19
|
+
return entity.type !== "test";
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
})
|
|
23
|
+
.filter((historyEntry) => historyEntry.entities.length > 0);
|
|
24
|
+
|
|
25
|
+
const fullHistoryFilePath = path.join(
|
|
26
|
+
projectConfig.catalogExportDirectoryPath,
|
|
27
|
+
"history-full.json",
|
|
28
|
+
);
|
|
29
|
+
fs.writeFileSync(fullHistoryFilePath, JSON.stringify(filteredHistory));
|
|
30
|
+
console.log(`History (full) generated at: ${fullHistoryFilePath}`);
|
|
31
|
+
|
|
32
|
+
return filteredHistory;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(
|
|
35
|
+
`Error when generating history from git: ${error.status}\n${error.stderr.toString()}`,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HistoryEntry, LastModified } from "@eventvisor/types";
|
|
2
|
+
|
|
3
|
+
export function getLastModifiedFromHistory(
|
|
4
|
+
fullHistory: HistoryEntry[],
|
|
5
|
+
type,
|
|
6
|
+
key,
|
|
7
|
+
): LastModified | undefined {
|
|
8
|
+
const lastModified = fullHistory.find((entry) => {
|
|
9
|
+
return entry.entities.find((entity) => {
|
|
10
|
+
return entity.type === type && entity.key === key;
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (lastModified) {
|
|
15
|
+
return {
|
|
16
|
+
commit: lastModified.commit,
|
|
17
|
+
timestamp: lastModified.timestamp,
|
|
18
|
+
author: lastModified.author,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function getOwnerAndRepoFromUrl(url: string): { owner: string; repo: string } {
|
|
2
|
+
let owner;
|
|
3
|
+
let repo;
|
|
4
|
+
|
|
5
|
+
if (url.startsWith("https://")) {
|
|
6
|
+
const parts = url.split("/");
|
|
7
|
+
repo = (parts.pop() as string).replace(".git", "");
|
|
8
|
+
owner = parts.pop();
|
|
9
|
+
} else if (url.startsWith("git@")) {
|
|
10
|
+
const urlParts = url.split(":");
|
|
11
|
+
const parts = urlParts[1].split("/");
|
|
12
|
+
repo = (parts.pop() as string).replace(".git", "");
|
|
13
|
+
owner = parts.pop();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return { owner, repo };
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
|
|
3
|
+
import { ProjectConfig } from "../config";
|
|
4
|
+
|
|
5
|
+
export function getRelativePaths(rootDirectoryPath, projectConfig: ProjectConfig) {
|
|
6
|
+
const relativeEventsPath = path.relative(rootDirectoryPath, projectConfig.eventsDirectoryPath);
|
|
7
|
+
const relativeDestinationsPath = path.relative(
|
|
8
|
+
rootDirectoryPath,
|
|
9
|
+
projectConfig.destinationsDirectoryPath,
|
|
10
|
+
);
|
|
11
|
+
const relativeAttributesPath = path.relative(
|
|
12
|
+
rootDirectoryPath,
|
|
13
|
+
projectConfig.attributesDirectoryPath,
|
|
14
|
+
);
|
|
15
|
+
const relativeEffectsPath = path.relative(rootDirectoryPath, projectConfig.effectsDirectoryPath);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
relativeEventsPath,
|
|
19
|
+
relativeDestinationsPath,
|
|
20
|
+
relativeAttributesPath,
|
|
21
|
+
relativeEffectsPath,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
|
|
3
|
+
import { getOwnerAndRepoFromUrl } from "./getOwnerAndRepoFromUrl";
|
|
4
|
+
|
|
5
|
+
export interface RepoDetails {
|
|
6
|
+
branch: string;
|
|
7
|
+
remoteUrl: string;
|
|
8
|
+
blobUrl: string;
|
|
9
|
+
commitUrl: string;
|
|
10
|
+
topLevelPath: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getRepoDetails(): RepoDetails | undefined {
|
|
14
|
+
try {
|
|
15
|
+
const topLevelPathOutput = execSync(`git rev-parse --show-toplevel`);
|
|
16
|
+
const topLevelPath = topLevelPathOutput.toString().trim();
|
|
17
|
+
|
|
18
|
+
const remoteUrlOutput = execSync(`git remote get-url origin`);
|
|
19
|
+
const remoteUrl = remoteUrlOutput.toString().trim();
|
|
20
|
+
|
|
21
|
+
const branchOutput = execSync(`git rev-parse --abbrev-ref HEAD`);
|
|
22
|
+
const branch = branchOutput.toString().trim();
|
|
23
|
+
|
|
24
|
+
if (!remoteUrl || !branch) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { owner, repo } = getOwnerAndRepoFromUrl(remoteUrl);
|
|
29
|
+
|
|
30
|
+
if (!owner || !repo) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let blobUrl;
|
|
35
|
+
let commitUrl;
|
|
36
|
+
|
|
37
|
+
if (remoteUrl.indexOf("github.com") > -1) {
|
|
38
|
+
blobUrl = `https://github.com/${owner}/${repo}/blob/${branch}/{{blobPath}}`;
|
|
39
|
+
commitUrl = `https://github.com/${owner}/${repo}/commit/{{hash}}`;
|
|
40
|
+
} else if (remoteUrl.indexOf("bitbucket.org") > -1) {
|
|
41
|
+
blobUrl = `https://bitbucket.org/${owner}/${repo}/src/${branch}/{{blobPath}}`;
|
|
42
|
+
commitUrl = `https://bitbucket.org/${owner}/${repo}/commits/{{hash}}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!blobUrl || !commitUrl) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
branch,
|
|
51
|
+
remoteUrl,
|
|
52
|
+
blobUrl,
|
|
53
|
+
commitUrl,
|
|
54
|
+
topLevelPath,
|
|
55
|
+
};
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error(e);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Plugin } from "../cli";
|
|
2
|
+
import { exportCatalog } from "./exportCatalog";
|
|
3
|
+
import { serveCatalog } from "./serveCatalog";
|
|
4
|
+
import { Dependencies } from "../dependencies";
|
|
5
|
+
|
|
6
|
+
export const catalogPlugin: Plugin = {
|
|
7
|
+
command: "catalog",
|
|
8
|
+
handler: async function (options) {
|
|
9
|
+
const { rootDirectoryPath, projectConfig, datasource, parsed } = options;
|
|
10
|
+
const deps: Dependencies = { rootDirectoryPath, projectConfig, datasource, options: parsed };
|
|
11
|
+
|
|
12
|
+
const allowedSubcommands = ["export", "serve"];
|
|
13
|
+
const subcommand = parsed._[1];
|
|
14
|
+
|
|
15
|
+
if (!allowedSubcommands.includes(subcommand)) {
|
|
16
|
+
console.log("Please specify a subcommand: `export` or `serve`");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// export
|
|
21
|
+
if (subcommand === "export") {
|
|
22
|
+
return await exportCatalog(deps);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// serve
|
|
26
|
+
if (subcommand === "serve") {
|
|
27
|
+
return await serveCatalog(deps);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
examples: [
|
|
31
|
+
{
|
|
32
|
+
command: "catalog export",
|
|
33
|
+
description: "export catalog of all entities",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
command: "catalog serve",
|
|
37
|
+
description: "serve catalog of all entities",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as http from "http";
|
|
4
|
+
|
|
5
|
+
import { Dependencies } from "../dependencies";
|
|
6
|
+
|
|
7
|
+
export function serveCatalog(deps: Dependencies) {
|
|
8
|
+
const { projectConfig, options } = deps;
|
|
9
|
+
|
|
10
|
+
const port = options.p || 3000;
|
|
11
|
+
|
|
12
|
+
http
|
|
13
|
+
.createServer(function (request, response) {
|
|
14
|
+
const requestedUrl = request.url;
|
|
15
|
+
const filePath =
|
|
16
|
+
requestedUrl === "/"
|
|
17
|
+
? path.join(projectConfig.catalogExportDirectoryPath, "index.html")
|
|
18
|
+
: path.join(projectConfig.catalogExportDirectoryPath, requestedUrl as string);
|
|
19
|
+
|
|
20
|
+
console.log("requesting: " + filePath + "");
|
|
21
|
+
|
|
22
|
+
const extname = path.extname(filePath);
|
|
23
|
+
let contentType = "text/html";
|
|
24
|
+
switch (extname) {
|
|
25
|
+
case ".js":
|
|
26
|
+
contentType = "text/javascript";
|
|
27
|
+
break;
|
|
28
|
+
case ".css":
|
|
29
|
+
contentType = "text/css";
|
|
30
|
+
break;
|
|
31
|
+
case ".json":
|
|
32
|
+
contentType = "application/json";
|
|
33
|
+
break;
|
|
34
|
+
case ".png":
|
|
35
|
+
contentType = "image/png";
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fs.readFile(filePath, function (error, content) {
|
|
40
|
+
if (error) {
|
|
41
|
+
if (error.code == "ENOENT") {
|
|
42
|
+
response.writeHead(404, { "Content-Type": "text/html" });
|
|
43
|
+
response.end("404 Not Found", "utf-8");
|
|
44
|
+
} else {
|
|
45
|
+
response.writeHead(500);
|
|
46
|
+
response.end("Error 500: " + error.code);
|
|
47
|
+
response.end();
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
response.writeHead(200, { "Content-Type": contentType });
|
|
51
|
+
response.end(content, "utf-8");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
})
|
|
55
|
+
.listen(port);
|
|
56
|
+
|
|
57
|
+
console.log(`Server running at http://127.0.0.1:${port}/`);
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
package/src/cli/plugins.ts
CHANGED
|
@@ -5,9 +5,16 @@ import { lintPlugin } from "../linter";
|
|
|
5
5
|
import { buildPlugin } from "../builder";
|
|
6
6
|
import { testPlugin } from "../tester";
|
|
7
7
|
import { initPlugin } from "../init";
|
|
8
|
+
import { catalogPlugin } from "../catalog";
|
|
8
9
|
|
|
9
10
|
export const commonPlugins: Plugin[] = [];
|
|
10
11
|
|
|
11
12
|
export const nonProjectPlugins: Plugin[] = [initPlugin];
|
|
12
13
|
|
|
13
|
-
export const projectBasedPlugins: Plugin[] = [
|
|
14
|
+
export const projectBasedPlugins: Plugin[] = [
|
|
15
|
+
configPlugin,
|
|
16
|
+
lintPlugin,
|
|
17
|
+
buildPlugin,
|
|
18
|
+
testPlugin,
|
|
19
|
+
catalogPlugin,
|
|
20
|
+
];
|
|
@@ -13,7 +13,7 @@ export const TESTS_DIRECTORY_NAME = "tests";
|
|
|
13
13
|
export const SYSTEM_DIRECTORY_NAME = ".eventvisor";
|
|
14
14
|
export const DATAFILES_DIRECTORY_NAME = "datafiles";
|
|
15
15
|
export const DATAFILE_NAME_PATTERN = "eventvisor-%s.json";
|
|
16
|
-
export const
|
|
16
|
+
export const CATALOG_EXPORT_DIRECTORY_NAME = "out";
|
|
17
17
|
|
|
18
18
|
export const CONFIG_MODULE_NAME = "eventvisor.config.js";
|
|
19
19
|
export const ROOT_DIR_PLACEHOLDER = "<rootDir>";
|
|
@@ -34,6 +34,7 @@ export interface ProjectConfig {
|
|
|
34
34
|
testsDirectoryPath: string;
|
|
35
35
|
datafilesDirectoryPath: string;
|
|
36
36
|
systemDirectoryPath: string;
|
|
37
|
+
catalogExportDirectoryPath: string;
|
|
37
38
|
datafileNamePattern: string;
|
|
38
39
|
|
|
39
40
|
tags: string[];
|
|
@@ -68,6 +69,7 @@ export function getProjectConfig(rootDirectoryPath: string): ProjectConfig {
|
|
|
68
69
|
datafilesDirectoryPath: path.join(rootDirectoryPath, DATAFILES_DIRECTORY_NAME),
|
|
69
70
|
datafileNamePattern: DATAFILE_NAME_PATTERN,
|
|
70
71
|
systemDirectoryPath: path.join(rootDirectoryPath, SYSTEM_DIRECTORY_NAME),
|
|
72
|
+
catalogExportDirectoryPath: path.join(rootDirectoryPath, CATALOG_EXPORT_DIRECTORY_NAME),
|
|
71
73
|
|
|
72
74
|
plugins: [],
|
|
73
75
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DatafileContent, EntityType } from "@eventvisor/types";
|
|
1
|
+
import type { DatafileContent, EntityType, HistoryEntry, Commit } from "@eventvisor/types";
|
|
2
2
|
|
|
3
3
|
export interface DatafileOptions {
|
|
4
4
|
tag: string;
|
|
@@ -20,4 +20,8 @@ export abstract class Adapter {
|
|
|
20
20
|
// revision
|
|
21
21
|
abstract readRevision(): Promise<string>;
|
|
22
22
|
abstract writeRevision(revision: string): Promise<void>;
|
|
23
|
+
|
|
24
|
+
// history
|
|
25
|
+
abstract listHistoryEntries(entityType?: EntityType, entityKey?: string): Promise<HistoryEntry[]>;
|
|
26
|
+
abstract readCommit(commit: string, entityType?: EntityType, entityKey?: string): Promise<Commit>;
|
|
23
27
|
}
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
Test,
|
|
11
11
|
TestName,
|
|
12
12
|
DatafileContent,
|
|
13
|
+
EntityType,
|
|
13
14
|
} from "@eventvisor/types";
|
|
14
15
|
|
|
15
16
|
import { ProjectConfig, CustomParser } from "../config";
|
|
@@ -161,4 +162,13 @@ export class Datasource {
|
|
|
161
162
|
getTestSpecName(testName: TestName) {
|
|
162
163
|
return `${testName}.${this.getExtension()}`;
|
|
163
164
|
}
|
|
165
|
+
|
|
166
|
+
// history
|
|
167
|
+
listHistoryEntries(entityType?: EntityType, entityKey?: string) {
|
|
168
|
+
return this.adapter.listHistoryEntries(entityType, entityKey);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
readCommit(commitHash: string, entityType?: EntityType, entityKey?: string) {
|
|
172
|
+
return this.adapter.readCommit(commitHash, entityType, entityKey);
|
|
173
|
+
}
|
|
164
174
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
+
import { execSync, spawn } from "child_process";
|
|
3
4
|
|
|
4
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
DatafileContent,
|
|
7
|
+
EntityType,
|
|
8
|
+
HistoryEntry,
|
|
9
|
+
HistoryEntity,
|
|
10
|
+
Commit,
|
|
11
|
+
} from "@eventvisor/types";
|
|
5
12
|
|
|
6
13
|
import { Adapter, DatafileOptions } from "./adapter";
|
|
7
14
|
import { ProjectConfig, CustomParser } from "../config";
|
|
15
|
+
import { getCommit } from "../utils/git";
|
|
8
16
|
|
|
9
17
|
export function getRevisionFilePath(projectConfig: ProjectConfig): string {
|
|
10
18
|
return path.join(projectConfig.systemDirectoryPath, `REVISION`);
|
|
@@ -203,4 +211,150 @@ export class FilesystemAdapter extends Adapter {
|
|
|
203
211
|
const shortPath = outputFilePath.replace(root + path.sep, "");
|
|
204
212
|
console.log(` Datafile generated: ${shortPath}`);
|
|
205
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* History
|
|
217
|
+
*/
|
|
218
|
+
async getRawHistory(pathPatterns: string[]): Promise<string> {
|
|
219
|
+
const gitPaths = pathPatterns.join(" ");
|
|
220
|
+
|
|
221
|
+
const logCommand = `git log --name-only --pretty=format:"%h|%an|%aI" --relative --no-merges -- ${gitPaths}`;
|
|
222
|
+
const fullCommand = `(cd ${this.rootDirectoryPath} && ${logCommand})`;
|
|
223
|
+
|
|
224
|
+
return new Promise(function (resolve, reject) {
|
|
225
|
+
const child = spawn(fullCommand, { shell: true });
|
|
226
|
+
let result = "";
|
|
227
|
+
|
|
228
|
+
child.stdout.on("data", function (data) {
|
|
229
|
+
result += data.toString();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
child.stderr.on("data", function (data) {
|
|
233
|
+
console.error(data.toString());
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
child.on("close", function (code) {
|
|
237
|
+
if (code === 0) {
|
|
238
|
+
resolve(result);
|
|
239
|
+
} else {
|
|
240
|
+
reject(code);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
getPathPatterns(entityType?: EntityType, entityKey?: string): string[] {
|
|
247
|
+
let pathPatterns: string[] = [];
|
|
248
|
+
|
|
249
|
+
if (entityType && entityKey) {
|
|
250
|
+
pathPatterns = [this.getEntityPath(entityType, entityKey)];
|
|
251
|
+
} else if (entityType) {
|
|
252
|
+
if (entityType === "attribute") {
|
|
253
|
+
pathPatterns = [this.config.attributesDirectoryPath];
|
|
254
|
+
} else if (entityType === "event") {
|
|
255
|
+
pathPatterns = [this.config.eventsDirectoryPath];
|
|
256
|
+
} else if (entityType === "destination") {
|
|
257
|
+
pathPatterns = [this.config.destinationsDirectoryPath];
|
|
258
|
+
} else if (entityType === "effect") {
|
|
259
|
+
pathPatterns = [this.config.effectsDirectoryPath];
|
|
260
|
+
} else if (entityType === "test") {
|
|
261
|
+
pathPatterns = [this.config.testsDirectoryPath];
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
pathPatterns = [
|
|
265
|
+
this.config.eventsDirectoryPath,
|
|
266
|
+
this.config.attributesDirectoryPath,
|
|
267
|
+
this.config.destinationsDirectoryPath,
|
|
268
|
+
this.config.effectsDirectoryPath,
|
|
269
|
+
this.config.testsDirectoryPath,
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return pathPatterns.map((p) => p.replace((this.rootDirectoryPath as string) + path.sep, ""));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async listHistoryEntries(entityType?: EntityType, entityKey?: string): Promise<HistoryEntry[]> {
|
|
277
|
+
const pathPatterns = this.getPathPatterns(entityType, entityKey);
|
|
278
|
+
const rawHistory = await this.getRawHistory(pathPatterns);
|
|
279
|
+
|
|
280
|
+
const fullHistory: HistoryEntry[] = [];
|
|
281
|
+
const blocks = rawHistory.split("\n\n");
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
284
|
+
const block = blocks[i];
|
|
285
|
+
|
|
286
|
+
if (block.length === 0) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const lines = block.split("\n");
|
|
291
|
+
|
|
292
|
+
const commitLine = lines[0];
|
|
293
|
+
const [commitHash, author, timestamp] = commitLine.split("|");
|
|
294
|
+
|
|
295
|
+
const entities: HistoryEntity[] = [];
|
|
296
|
+
|
|
297
|
+
const filePathLines = lines.slice(1);
|
|
298
|
+
for (let j = 0; j < filePathLines.length; j++) {
|
|
299
|
+
const relativePath = filePathLines[j];
|
|
300
|
+
const absolutePath = path.join(this.rootDirectoryPath as string, relativePath);
|
|
301
|
+
const fileName = absolutePath.split(path.sep).pop() as string;
|
|
302
|
+
const relativeDir = path.dirname(absolutePath);
|
|
303
|
+
|
|
304
|
+
const key = fileName.replace("." + this.parser.extension, "");
|
|
305
|
+
|
|
306
|
+
let type: EntityType = "attribute";
|
|
307
|
+
if (relativeDir === this.config.attributesDirectoryPath) {
|
|
308
|
+
type = "attribute";
|
|
309
|
+
} else if (relativeDir === this.config.eventsDirectoryPath) {
|
|
310
|
+
type = "event";
|
|
311
|
+
} else if (relativeDir === this.config.destinationsDirectoryPath) {
|
|
312
|
+
type = "destination";
|
|
313
|
+
} else if (relativeDir === this.config.effectsDirectoryPath) {
|
|
314
|
+
type = "effect";
|
|
315
|
+
} else if (relativeDir === this.config.testsDirectoryPath) {
|
|
316
|
+
type = "test";
|
|
317
|
+
} else {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
entities.push({
|
|
322
|
+
type,
|
|
323
|
+
key,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (entities.length === 0) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
fullHistory.push({
|
|
332
|
+
commit: commitHash,
|
|
333
|
+
author,
|
|
334
|
+
timestamp,
|
|
335
|
+
entities,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return fullHistory;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async readCommit(
|
|
343
|
+
commitHash: string,
|
|
344
|
+
entityType?: EntityType,
|
|
345
|
+
entityKey?: string,
|
|
346
|
+
): Promise<Commit> {
|
|
347
|
+
const pathPatterns = this.getPathPatterns(entityType, entityKey);
|
|
348
|
+
const gitPaths = pathPatterns.join(" ");
|
|
349
|
+
const logCommand = `git show ${commitHash} --relative -- ${gitPaths}`;
|
|
350
|
+
const fullCommand = `(cd ${this.rootDirectoryPath} && ${logCommand})`;
|
|
351
|
+
|
|
352
|
+
const gitShowOutput = execSync(fullCommand, { encoding: "utf8" }).toString();
|
|
353
|
+
const commit = getCommit(gitShowOutput, {
|
|
354
|
+
rootDirectoryPath: this.rootDirectoryPath as string,
|
|
355
|
+
projectConfig: this.config,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return commit;
|
|
359
|
+
}
|
|
206
360
|
}
|
package/src/utils/git.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
|
|
3
|
+
import type { Commit, EntityDiff, EntityType } from "@eventvisor/types";
|
|
4
|
+
|
|
5
|
+
import { CustomParser, ProjectConfig } from "../config";
|
|
6
|
+
|
|
7
|
+
function parseGitCommitShowOutput(gitShowOutput: string) {
|
|
8
|
+
const result = {
|
|
9
|
+
hash: "",
|
|
10
|
+
author: "",
|
|
11
|
+
date: "",
|
|
12
|
+
message: "",
|
|
13
|
+
diffs: {},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const lines = gitShowOutput.split("\n");
|
|
17
|
+
let currentFile = "";
|
|
18
|
+
let parsingDiffs = false;
|
|
19
|
+
let parsingMessage = false;
|
|
20
|
+
|
|
21
|
+
lines.forEach((line) => {
|
|
22
|
+
if (line.startsWith("commit ")) {
|
|
23
|
+
result.hash = line.replace("commit ", "").trim();
|
|
24
|
+
} else if (line.startsWith("Author:")) {
|
|
25
|
+
result.author = line.replace("Author:", "").trim();
|
|
26
|
+
parsingMessage = false;
|
|
27
|
+
} else if (line.startsWith("Date:")) {
|
|
28
|
+
result.date = line.replace("Date:", "").trim();
|
|
29
|
+
parsingMessage = true; // Start parsing commit message
|
|
30
|
+
} else if (line.startsWith("diff --git")) {
|
|
31
|
+
if (currentFile) {
|
|
32
|
+
// Finish capturing the previous file's diff
|
|
33
|
+
parsingDiffs = false;
|
|
34
|
+
}
|
|
35
|
+
currentFile = line.split(" ")[2].substring(2);
|
|
36
|
+
result.diffs[currentFile] = line + "\n"; // Include the diff --git line
|
|
37
|
+
parsingDiffs = true;
|
|
38
|
+
parsingMessage = false; // Stop parsing commit message
|
|
39
|
+
} else if (parsingDiffs) {
|
|
40
|
+
result.diffs[currentFile] += line + "\n";
|
|
41
|
+
} else if (parsingMessage && line.trim() !== "") {
|
|
42
|
+
result.message += line.trim() + "\n"; // Capture commit message
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function analyzeFileChange(diff) {
|
|
50
|
+
let status = "updated"; // Default to 'updated'
|
|
51
|
+
|
|
52
|
+
// Check for file creation or deletion
|
|
53
|
+
if (diff.includes("new file mode")) {
|
|
54
|
+
status = "created";
|
|
55
|
+
} else if (diff.includes("deleted file mode")) {
|
|
56
|
+
status = "deleted";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return status;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getCommit(
|
|
63
|
+
gitShowOutput: string,
|
|
64
|
+
options: { rootDirectoryPath: string; projectConfig: ProjectConfig },
|
|
65
|
+
): Commit {
|
|
66
|
+
const parsed = parseGitCommitShowOutput(gitShowOutput);
|
|
67
|
+
const { rootDirectoryPath, projectConfig } = options;
|
|
68
|
+
|
|
69
|
+
const commit: Commit = {
|
|
70
|
+
hash: parsed.hash,
|
|
71
|
+
author: parsed.author,
|
|
72
|
+
timestamp: parsed.date,
|
|
73
|
+
entities: [],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
Object.keys(parsed.diffs).forEach((file) => {
|
|
77
|
+
const diff = parsed.diffs[file];
|
|
78
|
+
const status = analyzeFileChange(diff);
|
|
79
|
+
|
|
80
|
+
const absolutePath = path.join(rootDirectoryPath, file);
|
|
81
|
+
const relativeDir = path.dirname(absolutePath);
|
|
82
|
+
|
|
83
|
+
// get entity type
|
|
84
|
+
let type: EntityType = "attribute";
|
|
85
|
+
if (relativeDir === projectConfig.attributesDirectoryPath) {
|
|
86
|
+
type = "attribute";
|
|
87
|
+
} else if (relativeDir === projectConfig.eventsDirectoryPath) {
|
|
88
|
+
type = "event";
|
|
89
|
+
} else if (relativeDir === projectConfig.destinationsDirectoryPath) {
|
|
90
|
+
type = "destination";
|
|
91
|
+
} else if (relativeDir === projectConfig.effectsDirectoryPath) {
|
|
92
|
+
type = "effect";
|
|
93
|
+
} else if (relativeDir === projectConfig.testsDirectoryPath) {
|
|
94
|
+
type = "test";
|
|
95
|
+
} else {
|
|
96
|
+
// unknown type
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// get entity key
|
|
101
|
+
const fileName = absolutePath.split(path.sep).pop() as string;
|
|
102
|
+
const extensionWithDot = "." + (projectConfig.parser as CustomParser).extension;
|
|
103
|
+
|
|
104
|
+
if (!fileName.endsWith(extensionWithDot)) {
|
|
105
|
+
// unknown extension
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const key = fileName.replace(extensionWithDot, "");
|
|
110
|
+
|
|
111
|
+
const entityDiff: EntityDiff = {
|
|
112
|
+
type,
|
|
113
|
+
key,
|
|
114
|
+
content: diff,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (status === "created") {
|
|
118
|
+
entityDiff.created = true;
|
|
119
|
+
} else if (status === "deleted") {
|
|
120
|
+
entityDiff.deleted = true;
|
|
121
|
+
} else {
|
|
122
|
+
entityDiff.updated = true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
commit.entities.push(entityDiff);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return commit;
|
|
129
|
+
}
|