@featurevisor/core 0.61.0 → 0.62.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.
Files changed (46) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/coverage/clover.xml +2 -2
  4. package/coverage/lcov-report/index.html +1 -1
  5. package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
  6. package/coverage/lcov-report/lib/builder/index.html +1 -1
  7. package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
  8. package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
  9. package/coverage/lcov-report/lib/tester/index.html +1 -1
  10. package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
  11. package/coverage/lcov-report/src/builder/index.html +1 -1
  12. package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
  13. package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
  14. package/coverage/lcov-report/src/tester/index.html +1 -1
  15. package/lib/datasource/adapter.d.ts +3 -2
  16. package/lib/datasource/adapter.js.map +1 -1
  17. package/lib/datasource/datasource.d.ts +5 -2
  18. package/lib/datasource/datasource.js +10 -2
  19. package/lib/datasource/datasource.js.map +1 -1
  20. package/lib/datasource/filesystemAdapter.d.ts +11 -3
  21. package/lib/datasource/filesystemAdapter.js +127 -1
  22. package/lib/datasource/filesystemAdapter.js.map +1 -1
  23. package/lib/site/exportSite.js +4 -3
  24. package/lib/site/exportSite.js.map +1 -1
  25. package/lib/site/generateHistory.d.ts +1 -1
  26. package/lib/site/generateHistory.js +58 -66
  27. package/lib/site/generateHistory.js.map +1 -1
  28. package/lib/{utils.js → utils/extractKeys.js} +1 -1
  29. package/lib/utils/extractKeys.js.map +1 -0
  30. package/lib/utils/git.d.ts +6 -0
  31. package/lib/utils/git.js +121 -0
  32. package/lib/utils/git.js.map +1 -0
  33. package/lib/utils/index.d.ts +2 -0
  34. package/lib/utils/index.js +19 -0
  35. package/lib/utils/index.js.map +1 -0
  36. package/package.json +5 -5
  37. package/src/datasource/adapter.ts +17 -3
  38. package/src/datasource/datasource.ts +12 -2
  39. package/src/datasource/filesystemAdapter.ts +144 -4
  40. package/src/site/exportSite.ts +1 -3
  41. package/src/site/generateHistory.ts +3 -77
  42. package/src/utils/git.ts +129 -0
  43. package/src/utils/index.ts +2 -0
  44. package/lib/utils.js.map +0 -1
  45. /package/lib/{utils.d.ts → utils/extractKeys.d.ts} +0 -0
  46. /package/src/{utils.ts → utils/extractKeys.ts} +0 -0
@@ -1,12 +1,25 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
+ import { execSync } from "child_process";
3
4
 
4
5
  import * as mkdirp from "mkdirp";
5
6
 
6
- import { ExistingState, EnvironmentKey, DatafileContent } from "@featurevisor/types";
7
-
8
- import { Adapter, EntityType, DatafileOptions } from "./adapter";
7
+ import {
8
+ ExistingState,
9
+ EnvironmentKey,
10
+ DatafileContent,
11
+ EntityType,
12
+ HistoryEntry,
13
+ Commit,
14
+ CommitHash,
15
+ HistoryEntity,
16
+ } from "@featurevisor/types";
17
+
18
+ import { Adapter, DatafileOptions } from "./adapter";
9
19
  import { ProjectConfig, CustomParser } from "../config";
20
+ import { getCommit } from "../utils/git";
21
+
22
+ const commitRegex = /^commit (\w+)\nAuthor: (.+) <(.+)>\nDate: (.+)\n\n(.+)/gm; // eslint-disable-line
10
23
 
11
24
  export function getExistingStateFilePath(
12
25
  projectConfig: ProjectConfig,
@@ -18,7 +31,7 @@ export function getExistingStateFilePath(
18
31
  export class FilesystemAdapter extends Adapter {
19
32
  private parser: CustomParser;
20
33
 
21
- constructor(private config: ProjectConfig) {
34
+ constructor(private config: ProjectConfig, private rootDirectoryPath?: string) {
22
35
  super();
23
36
 
24
37
  this.parser = config.parser as CustomParser;
@@ -161,4 +174,131 @@ export class FilesystemAdapter extends Adapter {
161
174
  const shortPath = outputFilePath.replace(root + path.sep, "");
162
175
  console.log(` Datafile generated: ${shortPath}`);
163
176
  }
177
+
178
+ /**
179
+ * History
180
+ */
181
+ getRawHistory(pathPatterns: string[]) {
182
+ const gitPaths = pathPatterns.join(" ");
183
+
184
+ const logCommand = `git log --name-only --pretty=format:"%h|%an|%aI" --relative --no-merges -- ${gitPaths}`;
185
+ const fullCommand = `(cd ${this.rootDirectoryPath} && ${logCommand})`;
186
+
187
+ return execSync(fullCommand, { encoding: "utf8" }).toString();
188
+ }
189
+
190
+ getPathPatterns(entityType?: EntityType, entityKey?: string): string[] {
191
+ let pathPatterns: string[] = [];
192
+
193
+ if (entityType && entityKey) {
194
+ pathPatterns = [this.getEntityPath(entityType, entityKey)];
195
+ } else if (entityType) {
196
+ if (entityType === "attribute") {
197
+ pathPatterns = [this.config.attributesDirectoryPath];
198
+ } else if (entityType === "segment") {
199
+ pathPatterns = [this.config.segmentsDirectoryPath];
200
+ } else if (entityType === "feature") {
201
+ pathPatterns = [this.config.featuresDirectoryPath];
202
+ } else if (entityType === "group") {
203
+ pathPatterns = [this.config.groupsDirectoryPath];
204
+ } else if (entityType === "test") {
205
+ pathPatterns = [this.config.testsDirectoryPath];
206
+ }
207
+ } else {
208
+ pathPatterns = [
209
+ this.config.featuresDirectoryPath,
210
+ this.config.attributesDirectoryPath,
211
+ this.config.segmentsDirectoryPath,
212
+ this.config.groupsDirectoryPath,
213
+ this.config.testsDirectoryPath,
214
+ ];
215
+ }
216
+
217
+ return pathPatterns.map((p) => p.replace((this.rootDirectoryPath as string) + path.sep, ""));
218
+ }
219
+
220
+ async listHistoryEntries(entityType?: EntityType, entityKey?: string): Promise<HistoryEntry[]> {
221
+ const pathPatterns = this.getPathPatterns(entityType, entityKey);
222
+ const rawHistory = this.getRawHistory(pathPatterns);
223
+
224
+ const fullHistory: HistoryEntry[] = [];
225
+ const blocks = rawHistory.split("\n\n");
226
+
227
+ for (let i = 0; i < blocks.length; i++) {
228
+ const block = blocks[i];
229
+
230
+ if (block.length === 0) {
231
+ continue;
232
+ }
233
+
234
+ const lines = block.split("\n");
235
+
236
+ const commitLine = lines[0];
237
+ const [commitHash, author, timestamp] = commitLine.split("|");
238
+
239
+ const entities: HistoryEntity[] = [];
240
+
241
+ const filePathLines = lines.slice(1);
242
+ for (let j = 0; j < filePathLines.length; j++) {
243
+ const relativePath = filePathLines[j];
244
+ const absolutePath = path.join(this.rootDirectoryPath as string, relativePath);
245
+ const fileName = absolutePath.split(path.sep).pop() as string;
246
+ const relativeDir = path.dirname(absolutePath);
247
+
248
+ const key = fileName.replace("." + this.parser.extension, "");
249
+
250
+ let type: EntityType = "attribute";
251
+ if (relativeDir === this.config.attributesDirectoryPath) {
252
+ type = "attribute";
253
+ } else if (relativeDir === this.config.segmentsDirectoryPath) {
254
+ type = "segment";
255
+ } else if (relativeDir === this.config.featuresDirectoryPath) {
256
+ type = "feature";
257
+ } else if (relativeDir === this.config.groupsDirectoryPath) {
258
+ type = "group";
259
+ } else if (relativeDir === this.config.testsDirectoryPath) {
260
+ type = "test";
261
+ } else {
262
+ continue;
263
+ }
264
+
265
+ entities.push({
266
+ type,
267
+ key,
268
+ });
269
+ }
270
+
271
+ if (entities.length === 0) {
272
+ continue;
273
+ }
274
+
275
+ fullHistory.push({
276
+ commit: commitHash,
277
+ author,
278
+ timestamp,
279
+ entities,
280
+ });
281
+ }
282
+
283
+ return fullHistory;
284
+ }
285
+
286
+ async readCommit(
287
+ commitHash: CommitHash,
288
+ entityType?: EntityType,
289
+ entityKey?: string,
290
+ ): Promise<Commit> {
291
+ const pathPatterns = this.getPathPatterns(entityType, entityKey);
292
+ const gitPaths = pathPatterns.join(" ");
293
+ const logCommand = `git show ${commitHash} --relative -- ${gitPaths}`;
294
+ const fullCommand = `(cd ${this.rootDirectoryPath} && ${logCommand})`;
295
+
296
+ const gitShowOutput = execSync(fullCommand, { encoding: "utf8" }).toString();
297
+ const commit = getCommit(gitShowOutput, {
298
+ rootDirectoryPath: this.rootDirectoryPath as string,
299
+ projectConfig: this.config,
300
+ });
301
+
302
+ return commit;
303
+ }
164
304
  }
@@ -24,7 +24,7 @@ export async function exportSite(deps: Dependencies) {
24
24
  console.log("Site dist copied to:", projectConfig.siteExportDirectoryPath);
25
25
 
26
26
  // generate history
27
- const fullHistory = generateHistory(deps);
27
+ const fullHistory = await generateHistory(deps);
28
28
 
29
29
  // site search index
30
30
  const repoDetails = getRepoDetails();
@@ -40,7 +40,5 @@ export async function exportSite(deps: Dependencies) {
40
40
  { recursive: true },
41
41
  );
42
42
 
43
- // @TODO: replace placeoholders in index.html
44
-
45
43
  return hasError;
46
44
  }
@@ -1,88 +1,14 @@
1
1
  import * as path from "path";
2
2
  import * as fs from "fs";
3
- import { execSync } from "child_process";
4
3
 
5
4
  import { HistoryEntry } from "@featurevisor/types";
6
-
7
- import { getRelativePaths } from "./getRelativePaths";
8
5
  import { Dependencies } from "../dependencies";
9
6
 
10
- export function generateHistory(deps: Dependencies): HistoryEntry[] {
11
- const { rootDirectoryPath, projectConfig, datasource } = deps;
7
+ export async function generateHistory(deps: Dependencies): Promise<HistoryEntry[]> {
8
+ const { projectConfig, datasource } = deps;
12
9
 
13
10
  try {
14
- // raw history
15
- const rawHistoryFilePath = path.join(projectConfig.siteExportDirectoryPath, "history-raw.txt");
16
-
17
- const { relativeFeaturesPath, relativeSegmentsPath, relativeAttributesPath } = getRelativePaths(
18
- rootDirectoryPath,
19
- projectConfig,
20
- );
21
-
22
- const separator = "|";
23
-
24
- const cmd = `git log --name-only --pretty=format:"%h${separator}%an${separator}%aI" --no-merges --relative -- ${relativeFeaturesPath} ${relativeSegmentsPath} ${relativeAttributesPath} > ${rawHistoryFilePath}`;
25
- execSync(cmd);
26
-
27
- console.log(`History (raw) generated at: ${rawHistoryFilePath}`);
28
-
29
- // structured history
30
- const rawHistory = fs.readFileSync(rawHistoryFilePath, "utf8");
31
-
32
- const fullHistory: HistoryEntry[] = [];
33
-
34
- let entry: HistoryEntry = {
35
- commit: "",
36
- author: "",
37
- timestamp: "",
38
- entities: [],
39
- };
40
-
41
- rawHistory.split("\n").forEach((line, index) => {
42
- if (index === 0 && line.length === 0) {
43
- // no history found
44
- return;
45
- }
46
-
47
- if (index > 0 && line.length === 0) {
48
- // commit finished
49
- fullHistory.push(entry);
50
-
51
- return;
52
- }
53
-
54
- if (line.indexOf(separator) > -1) {
55
- // commit line
56
- const parts = line.split("|");
57
-
58
- entry = {
59
- commit: parts[0],
60
- author: parts[1],
61
- timestamp: parts[2],
62
- entities: [],
63
- };
64
- } else {
65
- // file line
66
- const lineSplit = line.split(path.sep);
67
- const fileName = lineSplit.pop() as string;
68
- const relativeDir = lineSplit.join(path.sep);
69
-
70
- const key = fileName.replace("." + datasource.getExtension(), "");
71
-
72
- let type = "feature" as "attribute" | "segment" | "feature";
73
-
74
- if (relativeDir === relativeSegmentsPath) {
75
- type = "segment";
76
- } else if (relativeDir === relativeAttributesPath) {
77
- type = "attribute";
78
- }
79
-
80
- entry.entities.push({
81
- type,
82
- key,
83
- });
84
- }
85
- });
11
+ const fullHistory = await datasource.listHistoryEntries();
86
12
 
87
13
  const fullHistoryFilePath = path.join(
88
14
  projectConfig.siteExportDirectoryPath,
@@ -0,0 +1,129 @@
1
+ import * as path from "path";
2
+
3
+ import { Commit, EntityDiff, EntityType } from "@featurevisor/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.segmentsDirectoryPath) {
88
+ type = "segment";
89
+ } else if (relativeDir === projectConfig.featuresDirectoryPath) {
90
+ type = "feature";
91
+ } else if (relativeDir === projectConfig.groupsDirectoryPath) {
92
+ type = "group";
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
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./extractKeys";
2
+ export * from "./git";
package/lib/utils.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAEA,SAAgB,mCAAmC,CACjD,QAAuC;IAEvC,IAAM,MAAM,GAAG,IAAI,GAAG,EAAc,CAAC;IAErC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;QAC3B,QAAQ,CAAC,OAAO,CAAC,UAAC,OAAO;YACvB,mCAAmC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,UAAC,UAAU;gBAC9D,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;KACJ;IAED,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE;QAChC,IAAI,KAAK,IAAI,QAAQ,EAAE;YACrB,mCAAmC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAC,UAAU;gBACnE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;SACJ;QAED,IAAI,IAAI,IAAI,QAAQ,EAAE;YACpB,mCAAmC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,UAAC,UAAU;gBAClE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;SACJ;QAED,IAAI,KAAK,IAAI,QAAQ,EAAE;YACrB,mCAAmC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAC,UAAU;gBACnE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;SACJ;KACF;IAED,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE;QAChC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;KACtB;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAtCD,kFAsCC;AAED,SAAgB,kCAAkC,CAChD,UAAmC;IAEnC,IAAM,MAAM,GAAG,IAAI,GAAG,EAAgB,CAAC;IAEvC,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;QAC7B,UAAU,CAAC,OAAO,CAAC,UAAC,SAAS;YAC3B,kCAAkC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,UAAC,YAAY;gBACjE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;KACJ;IAED,IAAI,WAAW,IAAI,UAAU,EAAE;QAC7B,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;KAClC;IAED,IAAI,KAAK,IAAI,UAAU,EAAE;QACvB,kCAAkC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAC,YAAY;YACtE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;KACJ;IAED,IAAI,IAAI,IAAI,UAAU,EAAE;QACtB,kCAAkC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,UAAC,YAAY;YACrE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;KACJ;IAED,IAAI,KAAK,IAAI,UAAU,EAAE;QACvB,kCAAkC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAC,YAAY;YACtE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;KACJ;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AApCD,gFAoCC"}
File without changes
File without changes