@featurevisor/core 0.52.0 → 0.52.1

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 (51) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +8 -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/src/builder/allocator.ts.html +1 -1
  9. package/coverage/lcov-report/src/builder/index.html +1 -1
  10. package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
  11. package/lib/site/exportSite.d.ts +2 -0
  12. package/lib/site/exportSite.js +34 -0
  13. package/lib/site/exportSite.js.map +1 -0
  14. package/lib/site/generateHistory.d.ts +3 -0
  15. package/lib/site/generateHistory.js +76 -0
  16. package/lib/site/generateHistory.js.map +1 -0
  17. package/lib/site/generateSiteSearchIndex.d.ts +4 -0
  18. package/lib/site/generateSiteSearchIndex.js +141 -0
  19. package/lib/site/generateSiteSearchIndex.js.map +1 -0
  20. package/lib/site/getLastModifiedFromHistory.d.ts +2 -0
  21. package/lib/site/getLastModifiedFromHistory.js +19 -0
  22. package/lib/site/getLastModifiedFromHistory.js.map +1 -0
  23. package/lib/site/getOwnerAndRepoFromUrl.d.ts +4 -0
  24. package/lib/site/getOwnerAndRepoFromUrl.js +21 -0
  25. package/lib/site/getOwnerAndRepoFromUrl.js.map +1 -0
  26. package/lib/site/getRelativePaths.d.ts +6 -0
  27. package/lib/site/getRelativePaths.js +16 -0
  28. package/lib/site/getRelativePaths.js.map +1 -0
  29. package/lib/site/getRepoDetails.d.ts +8 -0
  30. package/lib/site/getRepoDetails.js +49 -0
  31. package/lib/site/getRepoDetails.js.map +1 -0
  32. package/lib/site/index.d.ts +2 -0
  33. package/lib/site/index.js +19 -0
  34. package/lib/site/index.js.map +1 -0
  35. package/lib/site/serveSite.d.ts +2 -0
  36. package/lib/site/serveSite.js +55 -0
  37. package/lib/site/serveSite.js.map +1 -0
  38. package/package.json +2 -2
  39. package/src/site/exportSite.ts +53 -0
  40. package/src/site/generateHistory.ts +101 -0
  41. package/src/site/generateSiteSearchIndex.ts +203 -0
  42. package/src/site/getLastModifiedFromHistory.ts +21 -0
  43. package/src/site/getOwnerAndRepoFromUrl.ts +17 -0
  44. package/src/site/getRelativePaths.ts +24 -0
  45. package/src/site/getRepoDetails.ts +62 -0
  46. package/src/site/index.ts +2 -0
  47. package/src/site/serveSite.ts +60 -0
  48. package/lib/site.d.ts +0 -16
  49. package/lib/site.js +0 -368
  50. package/lib/site.js.map +0 -1
  51. package/src/site.ts +0 -515
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRepoDetails = void 0;
4
+ var child_process_1 = require("child_process");
5
+ var getOwnerAndRepoFromUrl_1 = require("./getOwnerAndRepoFromUrl");
6
+ function getRepoDetails() {
7
+ try {
8
+ var topLevelPathOutput = (0, child_process_1.execSync)("git rev-parse --show-toplevel");
9
+ var topLevelPath = topLevelPathOutput.toString().trim();
10
+ var remoteUrlOutput = (0, child_process_1.execSync)("git remote get-url origin");
11
+ var remoteUrl = remoteUrlOutput.toString().trim();
12
+ var branchOutput = (0, child_process_1.execSync)("git rev-parse --abbrev-ref HEAD");
13
+ var branch = branchOutput.toString().trim();
14
+ if (!remoteUrl || !branch) {
15
+ return;
16
+ }
17
+ var _a = (0, getOwnerAndRepoFromUrl_1.getOwnerAndRepoFromUrl)(remoteUrl), owner = _a.owner, repo = _a.repo;
18
+ if (!owner || !repo) {
19
+ return;
20
+ }
21
+ var blobUrl = void 0;
22
+ var commitUrl = void 0;
23
+ if (remoteUrl.indexOf("github.com") > -1) {
24
+ blobUrl = "https://github.com/".concat(owner, "/").concat(repo, "/blob/").concat(branch, "/{{blobPath}}");
25
+ commitUrl = "https://github.com/".concat(owner, "/").concat(repo, "/commit/{{hash}}");
26
+ }
27
+ else if (remoteUrl.indexOf("bitbucket.org") > -1) {
28
+ blobUrl = "https://bitbucket.org/".concat(owner, "/").concat(repo, "/src/").concat(branch, "/{{blobPath}}");
29
+ commitUrl = "https://bitbucket.org/".concat(owner, "/").concat(repo, "/commits/{{hash}}");
30
+ }
31
+ if (!blobUrl || !commitUrl) {
32
+ return;
33
+ }
34
+ return {
35
+ branch: branch,
36
+ remoteUrl: remoteUrl,
37
+ blobUrl: blobUrl,
38
+ commitUrl: commitUrl,
39
+ topLevelPath: topLevelPath,
40
+ };
41
+ }
42
+ catch (e) {
43
+ console.error(e);
44
+ return;
45
+ }
46
+ return;
47
+ }
48
+ exports.getRepoDetails = getRepoDetails;
49
+ //# sourceMappingURL=getRepoDetails.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRepoDetails.js","sourceRoot":"","sources":["../../src/site/getRepoDetails.ts"],"names":[],"mappings":";;;AAAA,+CAAyC;AAEzC,mEAAkE;AAUlE,SAAgB,cAAc;IAC5B,IAAI;QACF,IAAM,kBAAkB,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,CAAC,CAAC;QACrE,IAAM,YAAY,GAAG,kBAAkB,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QAE1D,IAAM,eAAe,GAAG,IAAA,wBAAQ,EAAC,2BAA2B,CAAC,CAAC;QAC9D,IAAM,SAAS,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QAEpD,IAAM,YAAY,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,CAAC,CAAC;QACjE,IAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QAE9C,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE;YACzB,OAAO;SACR;QAEK,IAAA,KAAkB,IAAA,+CAAsB,EAAC,SAAS,CAAC,EAAjD,KAAK,WAAA,EAAE,IAAI,UAAsC,CAAC;QAE1D,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE;YACnB,OAAO;SACR;QAED,IAAI,OAAO,SAAA,CAAC;QACZ,IAAI,SAAS,SAAA,CAAC;QAEd,IAAI,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE;YACxC,OAAO,GAAG,6BAAsB,KAAK,cAAI,IAAI,mBAAS,MAAM,kBAAe,CAAC;YAC5E,SAAS,GAAG,6BAAsB,KAAK,cAAI,IAAI,qBAAkB,CAAC;SACnE;aAAM,IAAI,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE;YAClD,OAAO,GAAG,gCAAyB,KAAK,cAAI,IAAI,kBAAQ,MAAM,kBAAe,CAAC;YAC9E,SAAS,GAAG,gCAAyB,KAAK,cAAI,IAAI,sBAAmB,CAAC;SACvE;QAED,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS,EAAE;YAC1B,OAAO;SACR;QAED,OAAO;YACL,MAAM,QAAA;YACN,SAAS,WAAA;YACT,OAAO,SAAA;YACP,SAAS,WAAA;YACT,YAAY,cAAA;SACb,CAAC;KACH;IAAC,OAAO,CAAC,EAAE;QACV,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACjB,OAAO;KACR;IAED,OAAO;AACT,CAAC;AAjDD,wCAiDC"}
@@ -0,0 +1,2 @@
1
+ export * from "./exportSite";
2
+ export * from "./serveSite";
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./exportSite"), exports);
18
+ __exportStar(require("./serveSite"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/site/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,8CAA4B"}
@@ -0,0 +1,2 @@
1
+ import { ProjectConfig } from "../config";
2
+ export declare function serveSite(rootDirectoryPath: string, projectConfig: ProjectConfig, options?: any): void;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serveSite = void 0;
4
+ var fs = require("fs");
5
+ var path = require("path");
6
+ var http = require("http");
7
+ function serveSite(rootDirectoryPath, projectConfig, options) {
8
+ if (options === void 0) { options = {}; }
9
+ var port = options.p || 3000;
10
+ http
11
+ .createServer(function (request, response) {
12
+ var requestedUrl = request.url;
13
+ var filePath = requestedUrl === "/"
14
+ ? path.join(projectConfig.siteExportDirectoryPath, "index.html")
15
+ : path.join(projectConfig.siteExportDirectoryPath, requestedUrl);
16
+ console.log("requesting: " + filePath + "");
17
+ var extname = path.extname(filePath);
18
+ var contentType = "text/html";
19
+ switch (extname) {
20
+ case ".js":
21
+ contentType = "text/javascript";
22
+ break;
23
+ case ".css":
24
+ contentType = "text/css";
25
+ break;
26
+ case ".json":
27
+ contentType = "application/json";
28
+ break;
29
+ case ".png":
30
+ contentType = "image/png";
31
+ break;
32
+ }
33
+ fs.readFile(filePath, function (error, content) {
34
+ if (error) {
35
+ if (error.code == "ENOENT") {
36
+ response.writeHead(404, { "Content-Type": "text/html" });
37
+ response.end("404 Not Found", "utf-8");
38
+ }
39
+ else {
40
+ response.writeHead(500);
41
+ response.end("Error 500: " + error.code);
42
+ response.end();
43
+ }
44
+ }
45
+ else {
46
+ response.writeHead(200, { "Content-Type": contentType });
47
+ response.end(content, "utf-8");
48
+ }
49
+ });
50
+ })
51
+ .listen(port);
52
+ console.log("Server running at http://127.0.0.1:".concat(port, "/"));
53
+ }
54
+ exports.serveSite = serveSite;
55
+ //# sourceMappingURL=serveSite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serveSite.js","sourceRoot":"","sources":["../../src/site/serveSite.ts"],"names":[],"mappings":";;;AAAA,uBAAyB;AACzB,2BAA6B;AAC7B,2BAA6B;AAI7B,SAAgB,SAAS,CACvB,iBAAyB,EACzB,aAA4B,EAC5B,OAAiB;IAAjB,wBAAA,EAAA,YAAiB;IAEjB,IAAM,IAAI,GAAG,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC;IAE/B,IAAI;SACD,YAAY,CAAC,UAAU,OAAO,EAAE,QAAQ;QACvC,IAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC;QACjC,IAAM,QAAQ,GACZ,YAAY,KAAK,GAAG;YAClB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,uBAAuB,EAAE,YAAY,CAAC;YAChE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,uBAAuB,EAAE,YAAsB,CAAC,CAAC;QAE/E,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,QAAQ,GAAG,EAAE,CAAC,CAAC;QAE5C,IAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,WAAW,GAAG,WAAW,CAAC;QAC9B,QAAQ,OAAO,EAAE;YACf,KAAK,KAAK;gBACR,WAAW,GAAG,iBAAiB,CAAC;gBAChC,MAAM;YACR,KAAK,MAAM;gBACT,WAAW,GAAG,UAAU,CAAC;gBACzB,MAAM;YACR,KAAK,OAAO;gBACV,WAAW,GAAG,kBAAkB,CAAC;gBACjC,MAAM;YACR,KAAK,MAAM;gBACT,WAAW,GAAG,WAAW,CAAC;gBAC1B,MAAM;SACT;QAED,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,KAAK,EAAE,OAAO;YAC5C,IAAI,KAAK,EAAE;gBACT,IAAI,KAAK,CAAC,IAAI,IAAI,QAAQ,EAAE;oBAC1B,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;oBACzD,QAAQ,CAAC,GAAG,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;iBACxC;qBAAM;oBACL,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACxB,QAAQ,CAAC,GAAG,CAAC,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;oBACzC,QAAQ,CAAC,GAAG,EAAE,CAAC;iBAChB;aACF;iBAAM;gBACL,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;gBACzD,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;aAChC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;SACD,MAAM,CAAC,IAAI,CAAC,CAAC;IAEhB,OAAO,CAAC,GAAG,CAAC,6CAAsC,IAAI,MAAG,CAAC,CAAC;AAC7D,CAAC;AArDD,8BAqDC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featurevisor/core",
3
- "version": "0.52.0",
3
+ "version": "0.52.1",
4
4
  "description": "Core package of Featurevisor for Node.js usage",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -57,5 +57,5 @@
57
57
  "@types/js-yaml": "^4.0.5",
58
58
  "@types/tar": "^6.1.4"
59
59
  },
60
- "gitHead": "9bc6c82fa09d23dd9486c44b51474758c3e8c8bb"
60
+ "gitHead": "2ab10538e5f287087fe0892c3a383476dbe6df86"
61
61
  }
@@ -0,0 +1,53 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ import * as mkdirp from "mkdirp";
5
+
6
+ import { ProjectConfig } from "../config";
7
+
8
+ import { generateHistory } from "./generateHistory";
9
+ import { getRepoDetails } from "./getRepoDetails";
10
+ import { generateSiteSearchIndex } from "./generateSiteSearchIndex";
11
+
12
+ export function exportSite(rootDirectoryPath: string, projectConfig: ProjectConfig) {
13
+ const hasError = false;
14
+
15
+ mkdirp.sync(projectConfig.siteExportDirectoryPath);
16
+
17
+ const sitePackagePath = path.dirname(require.resolve("@featurevisor/site/package.json"));
18
+
19
+ // copy site dist
20
+ const siteDistPath = path.join(sitePackagePath, "dist");
21
+ fs.cpSync(siteDistPath, projectConfig.siteExportDirectoryPath, { recursive: true });
22
+
23
+ const sitePublicPath = path.join(sitePackagePath, "public");
24
+ fs.cpSync(sitePublicPath, projectConfig.siteExportDirectoryPath, { recursive: true });
25
+
26
+ console.log("Site dist copied to:", projectConfig.siteExportDirectoryPath);
27
+
28
+ // generate history
29
+ const fullHistory = generateHistory(rootDirectoryPath, projectConfig);
30
+
31
+ // site search index
32
+ const repoDetails = getRepoDetails();
33
+ const searchIndex = generateSiteSearchIndex(
34
+ rootDirectoryPath,
35
+ projectConfig,
36
+ fullHistory,
37
+ repoDetails,
38
+ );
39
+ const searchIndexFilePath = path.join(projectConfig.siteExportDirectoryPath, "search-index.json");
40
+ fs.writeFileSync(searchIndexFilePath, JSON.stringify(searchIndex));
41
+ console.log(`Site search index written at: ${searchIndexFilePath}`);
42
+
43
+ // copy datafiles
44
+ fs.cpSync(
45
+ projectConfig.outputDirectoryPath,
46
+ path.join(projectConfig.siteExportDirectoryPath, "datafiles"),
47
+ { recursive: true },
48
+ );
49
+
50
+ // @TODO: replace placeoholders in index.html
51
+
52
+ return hasError;
53
+ }
@@ -0,0 +1,101 @@
1
+ import * as path from "path";
2
+ import * as fs from "fs";
3
+ import { execSync } from "child_process";
4
+
5
+ import { HistoryEntry } from "@featurevisor/types";
6
+
7
+ import { ProjectConfig } from "../config";
8
+
9
+ import { getRelativePaths } from "./getRelativePaths";
10
+
11
+ export function generateHistory(rootDirectoryPath, projectConfig: ProjectConfig): HistoryEntry[] {
12
+ try {
13
+ // raw history
14
+ const rawHistoryFilePath = path.join(projectConfig.siteExportDirectoryPath, "history-raw.txt");
15
+
16
+ const { relativeFeaturesPath, relativeSegmentsPath, relativeAttributesPath } = getRelativePaths(
17
+ rootDirectoryPath,
18
+ projectConfig,
19
+ );
20
+
21
+ const separator = "|";
22
+
23
+ const cmd = `git log --name-only --pretty=format:"%h${separator}%an${separator}%aI" --no-merges --relative -- ${relativeFeaturesPath} ${relativeSegmentsPath} ${relativeAttributesPath} > ${rawHistoryFilePath}`;
24
+ execSync(cmd);
25
+
26
+ console.log(`History (raw) generated at: ${rawHistoryFilePath}`);
27
+
28
+ // structured history
29
+ const rawHistory = fs.readFileSync(rawHistoryFilePath, "utf8");
30
+
31
+ const fullHistory: HistoryEntry[] = [];
32
+
33
+ let entry: HistoryEntry = {
34
+ commit: "",
35
+ author: "",
36
+ timestamp: "",
37
+ entities: [],
38
+ };
39
+
40
+ rawHistory.split("\n").forEach((line, index) => {
41
+ if (index === 0 && line.length === 0) {
42
+ // no history found
43
+ return;
44
+ }
45
+
46
+ if (index > 0 && line.length === 0) {
47
+ // commit finished
48
+ fullHistory.push(entry);
49
+
50
+ return;
51
+ }
52
+
53
+ if (line.indexOf(separator) > -1) {
54
+ // commit line
55
+ const parts = line.split("|");
56
+
57
+ entry = {
58
+ commit: parts[0],
59
+ author: parts[1],
60
+ timestamp: parts[2],
61
+ entities: [],
62
+ };
63
+ } else {
64
+ // file line
65
+ const lineSplit = line.split(path.sep);
66
+ const fileName = lineSplit.pop() as string;
67
+ const relativeDir = lineSplit.join(path.sep);
68
+
69
+ const key = fileName.replace("." + projectConfig.parser, "");
70
+
71
+ let type = "feature" as "attribute" | "segment" | "feature";
72
+
73
+ if (relativeDir === relativeSegmentsPath) {
74
+ type = "segment";
75
+ } else if (relativeDir === relativeAttributesPath) {
76
+ type = "attribute";
77
+ }
78
+
79
+ entry.entities.push({
80
+ type,
81
+ key,
82
+ });
83
+ }
84
+ });
85
+
86
+ const fullHistoryFilePath = path.join(
87
+ projectConfig.siteExportDirectoryPath,
88
+ "history-full.json",
89
+ );
90
+ fs.writeFileSync(fullHistoryFilePath, JSON.stringify(fullHistory));
91
+ console.log(`History (full) generated at: ${fullHistoryFilePath}`);
92
+
93
+ return fullHistory;
94
+ } catch (error) {
95
+ console.error(
96
+ `Error when generating history from git: ${error.status}\n${error.stderr.toString()}`,
97
+ );
98
+
99
+ return [];
100
+ }
101
+ }
@@ -0,0 +1,203 @@
1
+ import {
2
+ HistoryEntry,
3
+ SearchIndex,
4
+ AttributeKey,
5
+ FeatureKey,
6
+ SegmentKey,
7
+ Condition,
8
+ } from "@featurevisor/types";
9
+
10
+ import { Datasource } from "../datasource";
11
+ import { ProjectConfig } from "../config";
12
+ import { extractAttributeKeysFromConditions, extractSegmentKeysFromGroupSegments } from "../utils";
13
+
14
+ import { getRelativePaths } from "./getRelativePaths";
15
+ import { getLastModifiedFromHistory } from "./getLastModifiedFromHistory";
16
+ import { RepoDetails } from "./getRepoDetails";
17
+
18
+ export function generateSiteSearchIndex(
19
+ rootDirectoryPath: string,
20
+ projectConfig: ProjectConfig,
21
+ fullHistory: HistoryEntry[],
22
+ repoDetails: RepoDetails | undefined,
23
+ ): SearchIndex {
24
+ const result: SearchIndex = {
25
+ links: undefined,
26
+ entities: {
27
+ attributes: [],
28
+ segments: [],
29
+ features: [],
30
+ },
31
+ };
32
+ const datasource = new Datasource(projectConfig);
33
+
34
+ /**
35
+ * Links
36
+ */
37
+ if (repoDetails) {
38
+ const { relativeAttributesPath, relativeSegmentsPath, relativeFeaturesPath } = getRelativePaths(
39
+ rootDirectoryPath,
40
+ projectConfig,
41
+ );
42
+
43
+ let prefix = "";
44
+ if (repoDetails.topLevelPath !== rootDirectoryPath) {
45
+ prefix = rootDirectoryPath.replace(repoDetails.topLevelPath + "/", "") + "/";
46
+ }
47
+
48
+ result.links = {
49
+ attribute: repoDetails.blobUrl.replace(
50
+ "{{blobPath}}",
51
+ prefix + relativeAttributesPath + "/{{key}}." + datasource.getExtension(),
52
+ ),
53
+ segment: repoDetails.blobUrl.replace(
54
+ "{{blobPath}}",
55
+ prefix + relativeSegmentsPath + "/{{key}}." + datasource.getExtension(),
56
+ ),
57
+ feature: repoDetails.blobUrl.replace(
58
+ "{{blobPath}}",
59
+ prefix + relativeFeaturesPath + "/{{key}}." + datasource.getExtension(),
60
+ ),
61
+ commit: repoDetails.commitUrl,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Entities
67
+ */
68
+ // usage
69
+ const attributesUsedInFeatures: {
70
+ [key: AttributeKey]: Set<FeatureKey>;
71
+ } = {};
72
+ const attributesUsedInSegments: {
73
+ [key: AttributeKey]: Set<SegmentKey>;
74
+ } = {};
75
+ const segmentsUsedInFeatures: {
76
+ [key: SegmentKey]: Set<FeatureKey>;
77
+ } = {};
78
+
79
+ // features
80
+ const featureFiles = datasource.listFeatures();
81
+ featureFiles.forEach((entityName) => {
82
+ const parsed = datasource.readFeature(entityName);
83
+
84
+ if (Array.isArray(parsed.variations)) {
85
+ parsed.variations.forEach((variation) => {
86
+ if (!variation.variables) {
87
+ return;
88
+ }
89
+
90
+ variation.variables.forEach((v) => {
91
+ if (v.overrides) {
92
+ v.overrides.forEach((o) => {
93
+ if (o.conditions) {
94
+ extractAttributeKeysFromConditions(o.conditions).forEach((attributeKey) => {
95
+ if (!attributesUsedInFeatures[attributeKey]) {
96
+ attributesUsedInFeatures[attributeKey] = new Set();
97
+ }
98
+
99
+ attributesUsedInFeatures[attributeKey].add(entityName);
100
+ });
101
+ }
102
+
103
+ if (o.segments && o.segments !== "*") {
104
+ extractSegmentKeysFromGroupSegments(o.segments).forEach((segmentKey) => {
105
+ if (!segmentsUsedInFeatures[segmentKey]) {
106
+ segmentsUsedInFeatures[segmentKey] = new Set();
107
+ }
108
+
109
+ segmentsUsedInFeatures[segmentKey].add(entityName);
110
+ });
111
+ }
112
+ });
113
+ }
114
+ });
115
+ });
116
+ }
117
+
118
+ Object.keys(parsed.environments).forEach((environmentKey) => {
119
+ const env = parsed.environments[environmentKey];
120
+
121
+ env.rules.forEach((rule) => {
122
+ if (rule.segments && rule.segments !== "*") {
123
+ extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) => {
124
+ if (!segmentsUsedInFeatures[segmentKey]) {
125
+ segmentsUsedInFeatures[segmentKey] = new Set();
126
+ }
127
+
128
+ segmentsUsedInFeatures[segmentKey].add(entityName);
129
+ });
130
+ }
131
+ });
132
+
133
+ if (env.force) {
134
+ env.force.forEach((force) => {
135
+ if (force.segments && force.segments !== "*") {
136
+ extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) => {
137
+ if (!segmentsUsedInFeatures[segmentKey]) {
138
+ segmentsUsedInFeatures[segmentKey] = new Set();
139
+ }
140
+
141
+ segmentsUsedInFeatures[segmentKey].add(entityName);
142
+ });
143
+ }
144
+
145
+ if (force.conditions) {
146
+ extractAttributeKeysFromConditions(force.conditions).forEach((attributeKey) => {
147
+ if (!attributesUsedInFeatures[attributeKey]) {
148
+ attributesUsedInFeatures[attributeKey] = new Set();
149
+ }
150
+
151
+ attributesUsedInFeatures[attributeKey].add(entityName);
152
+ });
153
+ }
154
+ });
155
+ }
156
+ });
157
+
158
+ result.entities.features.push({
159
+ ...parsed,
160
+ key: entityName,
161
+ lastModified: getLastModifiedFromHistory(fullHistory, "feature", entityName),
162
+ });
163
+ });
164
+
165
+ // segments
166
+ const segmentFiles = datasource.listSegments();
167
+ segmentFiles.forEach((entityName) => {
168
+ const parsed = datasource.readSegment(entityName);
169
+
170
+ extractAttributeKeysFromConditions(parsed.conditions as Condition | Condition[]).forEach(
171
+ (attributeKey) => {
172
+ if (!attributesUsedInSegments[attributeKey]) {
173
+ attributesUsedInSegments[attributeKey] = new Set();
174
+ }
175
+
176
+ attributesUsedInSegments[attributeKey].add(entityName);
177
+ },
178
+ );
179
+
180
+ result.entities.segments.push({
181
+ ...parsed,
182
+ key: entityName,
183
+ lastModified: getLastModifiedFromHistory(fullHistory, "segment", entityName),
184
+ usedInFeatures: Array.from(segmentsUsedInFeatures[entityName] || []),
185
+ });
186
+ });
187
+
188
+ // attributes
189
+ const attributeFiles = datasource.listAttributes();
190
+ attributeFiles.forEach((entityName) => {
191
+ const parsed = datasource.readAttribute(entityName);
192
+
193
+ result.entities.attributes.push({
194
+ ...parsed,
195
+ key: entityName,
196
+ lastModified: getLastModifiedFromHistory(fullHistory, "attribute", entityName),
197
+ usedInFeatures: Array.from(attributesUsedInFeatures[entityName] || []),
198
+ usedInSegments: Array.from(attributesUsedInSegments[entityName] || []),
199
+ });
200
+ });
201
+
202
+ return result;
203
+ }
@@ -0,0 +1,21 @@
1
+ import { HistoryEntry, LastModified } from "@featurevisor/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,24 @@
1
+ import * as path from "path";
2
+
3
+ import { ProjectConfig } from "../config";
4
+
5
+ export function getRelativePaths(rootDirectoryPath, projectConfig: ProjectConfig) {
6
+ const relativeFeaturesPath = path.relative(
7
+ rootDirectoryPath,
8
+ projectConfig.featuresDirectoryPath,
9
+ );
10
+ const relativeSegmentsPath = path.relative(
11
+ rootDirectoryPath,
12
+ projectConfig.segmentsDirectoryPath,
13
+ );
14
+ const relativeAttributesPath = path.relative(
15
+ rootDirectoryPath,
16
+ projectConfig.attributesDirectoryPath,
17
+ );
18
+
19
+ return {
20
+ relativeFeaturesPath,
21
+ relativeSegmentsPath,
22
+ relativeAttributesPath,
23
+ };
24
+ }
@@ -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,2 @@
1
+ export * from "./exportSite";
2
+ export * from "./serveSite";