@featurevisor/core 0.51.2 → 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 (54) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +19 -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/linter/conditionSchema.js +27 -5
  12. package/lib/linter/conditionSchema.js.map +1 -1
  13. package/lib/site/exportSite.d.ts +2 -0
  14. package/lib/site/exportSite.js +34 -0
  15. package/lib/site/exportSite.js.map +1 -0
  16. package/lib/site/generateHistory.d.ts +3 -0
  17. package/lib/site/generateHistory.js +76 -0
  18. package/lib/site/generateHistory.js.map +1 -0
  19. package/lib/site/generateSiteSearchIndex.d.ts +4 -0
  20. package/lib/site/generateSiteSearchIndex.js +141 -0
  21. package/lib/site/generateSiteSearchIndex.js.map +1 -0
  22. package/lib/site/getLastModifiedFromHistory.d.ts +2 -0
  23. package/lib/site/getLastModifiedFromHistory.js +19 -0
  24. package/lib/site/getLastModifiedFromHistory.js.map +1 -0
  25. package/lib/site/getOwnerAndRepoFromUrl.d.ts +4 -0
  26. package/lib/site/getOwnerAndRepoFromUrl.js +21 -0
  27. package/lib/site/getOwnerAndRepoFromUrl.js.map +1 -0
  28. package/lib/site/getRelativePaths.d.ts +6 -0
  29. package/lib/site/getRelativePaths.js +16 -0
  30. package/lib/site/getRelativePaths.js.map +1 -0
  31. package/lib/site/getRepoDetails.d.ts +8 -0
  32. package/lib/site/getRepoDetails.js +49 -0
  33. package/lib/site/getRepoDetails.js.map +1 -0
  34. package/lib/site/index.d.ts +2 -0
  35. package/lib/site/index.js +19 -0
  36. package/lib/site/index.js.map +1 -0
  37. package/lib/site/serveSite.d.ts +2 -0
  38. package/lib/site/serveSite.js +55 -0
  39. package/lib/site/serveSite.js.map +1 -0
  40. package/package.json +7 -7
  41. package/src/linter/conditionSchema.ts +39 -10
  42. package/src/site/exportSite.ts +53 -0
  43. package/src/site/generateHistory.ts +101 -0
  44. package/src/site/generateSiteSearchIndex.ts +203 -0
  45. package/src/site/getLastModifiedFromHistory.ts +21 -0
  46. package/src/site/getOwnerAndRepoFromUrl.ts +17 -0
  47. package/src/site/getRelativePaths.ts +24 -0
  48. package/src/site/getRepoDetails.ts +62 -0
  49. package/src/site/index.ts +2 -0
  50. package/src/site/serveSite.ts +60 -0
  51. package/lib/site.d.ts +0 -16
  52. package/lib/site.js +0 -368
  53. package/lib/site.js.map +0 -1
  54. package/src/site.ts +0 -515
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOwnerAndRepoFromUrl = void 0;
4
+ function getOwnerAndRepoFromUrl(url) {
5
+ var owner;
6
+ var repo;
7
+ if (url.startsWith("https://")) {
8
+ var parts = url.split("/");
9
+ repo = parts.pop().replace(".git", "");
10
+ owner = parts.pop();
11
+ }
12
+ else if (url.startsWith("git@")) {
13
+ var urlParts = url.split(":");
14
+ var parts = urlParts[1].split("/");
15
+ repo = parts.pop().replace(".git", "");
16
+ owner = parts.pop();
17
+ }
18
+ return { owner: owner, repo: repo };
19
+ }
20
+ exports.getOwnerAndRepoFromUrl = getOwnerAndRepoFromUrl;
21
+ //# sourceMappingURL=getOwnerAndRepoFromUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getOwnerAndRepoFromUrl.js","sourceRoot":"","sources":["../../src/site/getOwnerAndRepoFromUrl.ts"],"names":[],"mappings":";;;AAAA,SAAgB,sBAAsB,CAAC,GAAW;IAChD,IAAI,KAAK,CAAC;IACV,IAAI,IAAI,CAAC;IAET,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;QAC9B,IAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,GAAI,KAAK,CAAC,GAAG,EAAa,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnD,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;KACrB;SAAM,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;QACjC,IAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChC,IAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,GAAI,KAAK,CAAC,GAAG,EAAa,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnD,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;KACrB;IAED,OAAO,EAAE,KAAK,OAAA,EAAE,IAAI,MAAA,EAAE,CAAC;AACzB,CAAC;AAhBD,wDAgBC"}
@@ -0,0 +1,6 @@
1
+ import { ProjectConfig } from "../config";
2
+ export declare function getRelativePaths(rootDirectoryPath: any, projectConfig: ProjectConfig): {
3
+ relativeFeaturesPath: string;
4
+ relativeSegmentsPath: string;
5
+ relativeAttributesPath: string;
6
+ };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRelativePaths = void 0;
4
+ var path = require("path");
5
+ function getRelativePaths(rootDirectoryPath, projectConfig) {
6
+ var relativeFeaturesPath = path.relative(rootDirectoryPath, projectConfig.featuresDirectoryPath);
7
+ var relativeSegmentsPath = path.relative(rootDirectoryPath, projectConfig.segmentsDirectoryPath);
8
+ var relativeAttributesPath = path.relative(rootDirectoryPath, projectConfig.attributesDirectoryPath);
9
+ return {
10
+ relativeFeaturesPath: relativeFeaturesPath,
11
+ relativeSegmentsPath: relativeSegmentsPath,
12
+ relativeAttributesPath: relativeAttributesPath,
13
+ };
14
+ }
15
+ exports.getRelativePaths = getRelativePaths;
16
+ //# sourceMappingURL=getRelativePaths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRelativePaths.js","sourceRoot":"","sources":["../../src/site/getRelativePaths.ts"],"names":[],"mappings":";;;AAAA,2BAA6B;AAI7B,SAAgB,gBAAgB,CAAC,iBAAiB,EAAE,aAA4B;IAC9E,IAAM,oBAAoB,GAAG,IAAI,CAAC,QAAQ,CACxC,iBAAiB,EACjB,aAAa,CAAC,qBAAqB,CACpC,CAAC;IACF,IAAM,oBAAoB,GAAG,IAAI,CAAC,QAAQ,CACxC,iBAAiB,EACjB,aAAa,CAAC,qBAAqB,CACpC,CAAC;IACF,IAAM,sBAAsB,GAAG,IAAI,CAAC,QAAQ,CAC1C,iBAAiB,EACjB,aAAa,CAAC,uBAAuB,CACtC,CAAC;IAEF,OAAO;QACL,oBAAoB,sBAAA;QACpB,oBAAoB,sBAAA;QACpB,sBAAsB,wBAAA;KACvB,CAAC;AACJ,CAAC;AAnBD,4CAmBC"}
@@ -0,0 +1,8 @@
1
+ export interface RepoDetails {
2
+ branch: string;
3
+ remoteUrl: string;
4
+ blobUrl: string;
5
+ commitUrl: string;
6
+ topLevelPath: string;
7
+ }
8
+ export declare function getRepoDetails(): RepoDetails | undefined;
@@ -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.51.2",
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",
@@ -9,9 +9,9 @@
9
9
  "dist": "echo 'Nothing to dist here'",
10
10
  "build": "npm run transpile",
11
11
  "test": "jest --config jest.config.js --verbose --coverage",
12
- "format": "prettier . --check --cache --log-level=warn",
12
+ "format": "prettier . --check --cache --loglevel=warn",
13
13
  "lint": "eslint . --cache",
14
- "format:fix": "prettier . --write --cache --log-level=warn",
14
+ "format:fix": "prettier . --write --cache --loglevel=warn",
15
15
  "lint:fix": "eslint . --fix --cache"
16
16
  },
17
17
  "author": {
@@ -44,9 +44,9 @@
44
44
  },
45
45
  "license": "MIT",
46
46
  "dependencies": {
47
- "@featurevisor/sdk": "^0.51.0",
48
- "@featurevisor/site": "^0.51.0",
49
- "@featurevisor/types": "^0.51.0",
47
+ "@featurevisor/sdk": "^0.52.0",
48
+ "@featurevisor/site": "^0.52.0",
49
+ "@featurevisor/types": "^0.52.0",
50
50
  "axios": "^1.3.4",
51
51
  "joi": "^17.8.3",
52
52
  "js-yaml": "^4.1.0",
@@ -57,5 +57,5 @@
57
57
  "@types/js-yaml": "^4.0.5",
58
58
  "@types/tar": "^6.1.4"
59
59
  },
60
- "gitHead": "ce2a43a27cfe221668ccc73f0399ff2a1f9371a9"
60
+ "gitHead": "2ab10538e5f287087fe0892c3a383476dbe6df86"
61
61
  }
@@ -44,16 +44,45 @@ export function getConditionsJoiSchema(
44
44
  "notIn",
45
45
  )
46
46
  .required(),
47
- value: Joi.alternatives()
48
- .try(
49
- // @TODO: make them more specific
50
- Joi.string(),
51
- Joi.number(),
52
- Joi.boolean(),
53
- Joi.date(),
54
- Joi.array().items(Joi.string()),
55
- )
56
- .required(),
47
+ value: Joi.when("operator", {
48
+ is: Joi.string().valid("equals", "notEquals"),
49
+ then: Joi.alternatives()
50
+ .try(Joi.string(), Joi.number(), Joi.boolean(), Joi.date())
51
+ .allow(null)
52
+ .required(),
53
+ })
54
+ .when("operator", {
55
+ is: Joi.string().valid(
56
+ "greaterThan",
57
+ "greaterThanOrEquals",
58
+ "lessThan",
59
+ "lessThanOrEquals",
60
+ ),
61
+ then: Joi.number().required(),
62
+ })
63
+ .when("operator", {
64
+ is: Joi.string().valid("contains", "notContains", "startsWith", "endsWith"),
65
+ then: Joi.string().required(),
66
+ })
67
+ .when("operator", {
68
+ is: Joi.string().valid(
69
+ "semverEquals",
70
+ "semverNotEquals",
71
+ "semverGreaterThan",
72
+ "semverGreaterThanOrEquals",
73
+ "semverLessThan",
74
+ "semverLessThanOrEquals",
75
+ ),
76
+ then: Joi.string().required(),
77
+ })
78
+ .when("operator", {
79
+ is: Joi.string().valid("before", "after"),
80
+ then: Joi.alternatives(Joi.date(), Joi.string()).required(),
81
+ })
82
+ .when("operator", {
83
+ is: Joi.string().valid("in", "notIn"),
84
+ then: Joi.array().items(Joi.string()).required(),
85
+ }),
57
86
  });
58
87
 
59
88
  const andOrNotConditionJoiSchema = Joi.alternatives()
@@ -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
+ }