@trackunit/iris-app-e2e 1.8.89 → 1.8.91

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 (42) hide show
  1. package/CHANGELOG.md +2511 -0
  2. package/generators.json +12 -0
  3. package/package.json +19 -14
  4. package/src/commands/defaultCommands.js +135 -0
  5. package/src/commands/defaultCommands.js.map +1 -0
  6. package/src/generators/e2e-configuration/README.md +79 -0
  7. package/src/generators/e2e-configuration/files/root/cypress/e2e/app.cy.ts__tmpl__ +18 -0
  8. package/src/generators/e2e-configuration/files/root/cypress/fixtures/auth.json__tmpl__ +4 -0
  9. package/src/generators/e2e-configuration/files/root/cypress/support/commands.ts__tmpl__ +3 -0
  10. package/src/generators/e2e-configuration/files/root/cypress/support/e2e.ts__tmpl__ +18 -0
  11. package/src/generators/e2e-configuration/files/root/cypress/tsconfig.json__tmpl__ +17 -0
  12. package/src/generators/e2e-configuration/files/root/cypress.config.ts__tmpl__ +25 -0
  13. package/src/generators/e2e-configuration/generator.js +238 -0
  14. package/src/generators/e2e-configuration/generator.js.map +1 -0
  15. package/src/generators/e2e-configuration/schema.json +20 -0
  16. package/src/index.js +16 -0
  17. package/src/index.js.map +1 -0
  18. package/src/plugins/createLogFile.js +42 -0
  19. package/src/plugins/createLogFile.js.map +1 -0
  20. package/src/plugins/defaultPlugins.js +142 -0
  21. package/src/plugins/defaultPlugins.js.map +1 -0
  22. package/src/plugins/nxPreset.js +41 -0
  23. package/src/plugins/nxPreset.js.map +1 -0
  24. package/src/plugins/writeFileWithPrettier.js +35 -0
  25. package/src/plugins/writeFileWithPrettier.js.map +1 -0
  26. package/src/setup/defaultE2ESetup.js +45 -0
  27. package/src/setup/defaultE2ESetup.js.map +1 -0
  28. package/src/setup/setupHarRecording.js +24 -0
  29. package/src/setup/setupHarRecording.js.map +1 -0
  30. package/src/support.js +13 -0
  31. package/src/support.js.map +1 -0
  32. package/src/utils/Codeowner.js +136 -0
  33. package/src/utils/Codeowner.js.map +1 -0
  34. package/src/utils/fileNameBuilder.js +38 -0
  35. package/src/utils/fileNameBuilder.js.map +1 -0
  36. package/src/utils/fileUpdater.js +37 -0
  37. package/src/utils/fileUpdater.js.map +1 -0
  38. package/index.cjs.default.js +0 -1
  39. package/index.cjs.js +0 -422
  40. package/index.cjs.mjs +0 -2
  41. package/index.d.ts +0 -1
  42. package/index.esm.js +0 -398
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCodeowner = exports.toShortTeamName = exports.parseCodeowners = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs_1 = tslib_1.__importDefault(require("fs"));
6
+ const path_1 = tslib_1.__importDefault(require("path"));
7
+ /**
8
+ * Parses lines from a CODEOWNERS‐style file into a mapping of patterns → owners.
9
+ * Skips blank lines and comments (lines beginning with `#`).
10
+ *
11
+ * @param {string[]} lines - Each element is a line from your CODEOWNERS file.
12
+ * @returns {Record<string, string>} An object whose keys are normalized path patterns (no leading/trailing slashes)
13
+ * and whose values are the owner (e.g. a GitHub team handle).
14
+ * @example
15
+ * ```ts
16
+ * const lines = [
17
+ * "# Our CODEOWNERS",
18
+ * "/src @team/backend",
19
+ * "README.md @team/docs",
20
+ * "", // blank lines ignored
21
+ * "# end of file"
22
+ * ];
23
+ * const owners = parseCodeowners(lines);
24
+ * // → { "src": "@team/backend", "README.md": "@team/docs" }
25
+ * ```
26
+ */
27
+ const parseCodeowners = (lines) => {
28
+ const patterns = {};
29
+ for (const line of lines) {
30
+ const trimmed = line.trim();
31
+ if (!trimmed || trimmed.startsWith("#")) {
32
+ continue;
33
+ }
34
+ const [pattern, owner] = trimmed.split(/\s+/, 2);
35
+ if (pattern && owner) {
36
+ const normalizedPattern = pattern.replace(/^\/+|\/+$/g, "");
37
+ patterns[normalizedPattern] = owner;
38
+ }
39
+ }
40
+ return patterns;
41
+ };
42
+ exports.parseCodeowners = parseCodeowners;
43
+ /**
44
+ * Converts a full team handle (potentially namespaced and with platform suffix)
45
+ * into its “short” team name.
46
+ *
47
+ * - Strips any leading `@org/` prefix
48
+ * - Removes `-be` or `-fe` suffix if present
49
+ *
50
+ * @param {string} teamName - e.g. `"@trackunit/backend-be"` or `"@trackunit/frontend-fe"`
51
+ * @returns {string} e.g. `"backend"` or `"frontend"`
52
+ * @example
53
+ * ```ts
54
+ * toShortTeamName("@trackunit/backend-be"); // → "backend"
55
+ * toShortTeamName("@trackunit/frontend-fe"); // → "frontend"
56
+ * toShortTeamName("infra"); // → "infra"
57
+ * ```
58
+ */
59
+ const toShortTeamName = (teamName) => {
60
+ if (!teamName) {
61
+ return undefined;
62
+ }
63
+ let shortName = teamName;
64
+ if (teamName.startsWith("@")) {
65
+ shortName = shortName.slice(shortName.indexOf("/") + 1);
66
+ }
67
+ if (shortName.endsWith("-be") || shortName.endsWith("-fe")) {
68
+ shortName = shortName.slice(0, shortName.lastIndexOf("-"));
69
+ }
70
+ return shortName;
71
+ };
72
+ exports.toShortTeamName = toShortTeamName;
73
+ /**
74
+ * Recursively looks up the CODEOWNER for a given file or directory path
75
+ * by reading your workspace’s CODEOWNERS file.
76
+ *
77
+ * Walks up the directory tree until it finds a matching pattern. If no
78
+ * deeper match is found but there is a `"."` entry, returns that.
79
+ *
80
+ * @param {string} currentPath - Absolute path to the file/directory you’re querying.
81
+ * @param {string} workspaceRoot - Absolute path to your repo/workspace root.
82
+ * @param {string} [codeownersFileName="TEAM_CODEOWNERS"] - Filename to read at the root.
83
+ * @returns {string|undefined} The owner handle (e.g. `"@team/backend"`) or `undefined` if none found.
84
+ * @example
85
+ * ```ts
86
+ * // Suppose your repo root has a TEAM_CODEOWNERS file containing:
87
+ * // src @team/backend
88
+ * // src/utils @team/utils
89
+ * // . @team/root
90
+ *
91
+ * const owner1 = getCodeowner(
92
+ * "/Users/alice/project/src/utils/helpers.ts",
93
+ * "/Users/alice/project"
94
+ * );
95
+ * // → "@team/utils"
96
+ *
97
+ * const owner2 = getCodeowner(
98
+ * "/Users/alice/project/other/file.txt",
99
+ * "/Users/alice/project"
100
+ * );
101
+ * // → "@team/root" (falls back to the "." entry)
102
+ * ```
103
+ */
104
+ const getCodeowner = (currentPath, workspaceRoot, codeownersFileName = "TEAM_CODEOWNERS") => {
105
+ if (!workspaceRoot) {
106
+ return undefined;
107
+ }
108
+ const codeownersPath = path_1.default.join(workspaceRoot, codeownersFileName);
109
+ if (!fs_1.default.existsSync(codeownersPath)) {
110
+ return undefined;
111
+ }
112
+ const codeownersLines = fs_1.default.readFileSync(codeownersPath, "utf8").split("\n");
113
+ const codeowners = (0, exports.parseCodeowners)(codeownersLines);
114
+ let relPath = path_1.default
115
+ .relative(workspaceRoot, currentPath)
116
+ .replace(/\\/g, "/")
117
+ .replace(/^\/+|\/+$/g, "");
118
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
119
+ while (true) {
120
+ const codeowner = codeowners[relPath];
121
+ if (codeowner) {
122
+ return codeowner;
123
+ }
124
+ const parent = path_1.default.posix.dirname(relPath);
125
+ if ((parent === relPath || parent === ".") && codeowners["."]) {
126
+ return codeowners["."];
127
+ }
128
+ if (parent === relPath || parent === ".") {
129
+ break;
130
+ }
131
+ relPath = parent;
132
+ }
133
+ return undefined;
134
+ };
135
+ exports.getCodeowner = getCodeowner;
136
+ //# sourceMappingURL=Codeowner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Codeowner.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-e2e/src/utils/Codeowner.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\n/**\n * Parses lines from a CODEOWNERS‐style file into a mapping of patterns → owners.\n * Skips blank lines and comments (lines beginning with `#`).\n *\n * @param {string[]} lines - Each element is a line from your CODEOWNERS file.\n * @returns {Record<string, string>} An object whose keys are normalized path patterns (no leading/trailing slashes)\n * and whose values are the owner (e.g. a GitHub team handle).\n * @example\n * ```ts\n * const lines = [\n * \"# Our CODEOWNERS\",\n * \"/src @team/backend\",\n * \"README.md @team/docs\",\n * \"\", // blank lines ignored\n * \"# end of file\"\n * ];\n * const owners = parseCodeowners(lines);\n * // → { \"src\": \"@team/backend\", \"README.md\": \"@team/docs\" }\n * ```\n */\nexport const parseCodeowners = (lines: Array<string>): Record<string, string> => {\n const patterns: Record<string, string> = {};\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue;\n }\n const [pattern, owner] = trimmed.split(/\\s+/, 2);\n if (pattern && owner) {\n const normalizedPattern = pattern.replace(/^\\/+|\\/+$/g, \"\");\n patterns[normalizedPattern] = owner;\n }\n }\n return patterns;\n};\n\n/**\n * Converts a full team handle (potentially namespaced and with platform suffix)\n * into its “short” team name.\n *\n * - Strips any leading `@org/` prefix\n * - Removes `-be` or `-fe` suffix if present\n *\n * @param {string} teamName - e.g. `\"@trackunit/backend-be\"` or `\"@trackunit/frontend-fe\"`\n * @returns {string} e.g. `\"backend\"` or `\"frontend\"`\n * @example\n * ```ts\n * toShortTeamName(\"@trackunit/backend-be\"); // → \"backend\"\n * toShortTeamName(\"@trackunit/frontend-fe\"); // → \"frontend\"\n * toShortTeamName(\"infra\"); // → \"infra\"\n * ```\n */\nexport const toShortTeamName = (teamName?: string): string | undefined => {\n if (!teamName) {\n return undefined;\n }\n let shortName = teamName;\n if (teamName.startsWith(\"@\")) {\n shortName = shortName.slice(shortName.indexOf(\"/\") + 1);\n }\n if (shortName.endsWith(\"-be\") || shortName.endsWith(\"-fe\")) {\n shortName = shortName.slice(0, shortName.lastIndexOf(\"-\"));\n }\n return shortName;\n};\n\n/**\n * Recursively looks up the CODEOWNER for a given file or directory path\n * by reading your workspace’s CODEOWNERS file.\n *\n * Walks up the directory tree until it finds a matching pattern. If no\n * deeper match is found but there is a `\".\"` entry, returns that.\n *\n * @param {string} currentPath - Absolute path to the file/directory you’re querying.\n * @param {string} workspaceRoot - Absolute path to your repo/workspace root.\n * @param {string} [codeownersFileName=\"TEAM_CODEOWNERS\"] - Filename to read at the root.\n * @returns {string|undefined} The owner handle (e.g. `\"@team/backend\"`) or `undefined` if none found.\n * @example\n * ```ts\n * // Suppose your repo root has a TEAM_CODEOWNERS file containing:\n * // src @team/backend\n * // src/utils @team/utils\n * // . @team/root\n *\n * const owner1 = getCodeowner(\n * \"/Users/alice/project/src/utils/helpers.ts\",\n * \"/Users/alice/project\"\n * );\n * // → \"@team/utils\"\n *\n * const owner2 = getCodeowner(\n * \"/Users/alice/project/other/file.txt\",\n * \"/Users/alice/project\"\n * );\n * // → \"@team/root\" (falls back to the \".\" entry)\n * ```\n */\nexport const getCodeowner = (\n currentPath: string,\n workspaceRoot: string,\n codeownersFileName = \"TEAM_CODEOWNERS\"\n): string | undefined => {\n if (!workspaceRoot) {\n return undefined;\n }\n\n const codeownersPath = path.join(workspaceRoot, codeownersFileName);\n if (!fs.existsSync(codeownersPath)) {\n return undefined;\n }\n\n const codeownersLines = fs.readFileSync(codeownersPath, \"utf8\").split(\"\\n\");\n const codeowners = parseCodeowners(codeownersLines);\n\n let relPath = path\n .relative(workspaceRoot, currentPath)\n .replace(/\\\\/g, \"/\")\n .replace(/^\\/+|\\/+$/g, \"\");\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const codeowner = codeowners[relPath];\n if (codeowner) {\n return codeowner;\n }\n\n const parent = path.posix.dirname(relPath);\n if ((parent === relPath || parent === \".\") && codeowners[\".\"]) {\n return codeowners[\".\"];\n }\n if (parent === relPath || parent === \".\") {\n break;\n }\n relPath = parent;\n }\n\n return undefined;\n};\n"]}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeForFilename = sanitizeForFilename;
4
+ exports.fileNameBuilder = fileNameBuilder;
5
+ /**
6
+ * Sanitizes a string to be safe for use in filenames.
7
+ * Removes special characters and truncates if too long.
8
+ *
9
+ * @param str - The string to sanitize (e.g., test suite or test name)
10
+ * @returns {string} Sanitized string safe for filesystem use
11
+ */
12
+ function sanitizeForFilename(str) {
13
+ return str
14
+ .replace(/[^a-zA-Z0-9-_\s\[\]\(\)]/g, "") // Remove special chars - with exceptions
15
+ .replace(/-+/g, "-") // Collapse multiple hyphens into one
16
+ .replace(/\s+/g, " ") // Collapse multiple spaces into one
17
+ .replace(/^-|-$/g, ""); // Trim leading/trailing hyphens
18
+ }
19
+ /**
20
+ * Builds a standardized filename for test artifacts (e.g., HAR files, screenshots).
21
+ * Combines test name, state, and attempt number into a readable filename.
22
+ *
23
+ * @param testName - The name of the test (will be sanitized)
24
+ * @param state - The test state (e.g., 'passed', 'failed')
25
+ * @param currentRetry - The current retry attempt number (0-based)
26
+ * @returns {string} A sanitized filename in the format: "testName (state) (attempt N)"
27
+ * @example
28
+ * fileNameBuilder("Login Flow", "failed", 0)
29
+ * // Returns: "Login Flow (failed) (attempt 1)"
30
+ * @example
31
+ * fileNameBuilder("Test: with -> special chars", "passed", 2)
32
+ * // Returns: "Test with - special chars (passed) (attempt 3)"
33
+ */
34
+ function fileNameBuilder(testName, state, currentRetry) {
35
+ const fileName = `${testName} ${currentRetry !== undefined ? `(Attempt ${currentRetry + 1})` : ""} (${state.toLowerCase()})`;
36
+ return sanitizeForFilename(fileName);
37
+ }
38
+ //# sourceMappingURL=fileNameBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileNameBuilder.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-e2e/src/utils/fileNameBuilder.ts"],"names":[],"mappings":";;AAOA,kDAMC;AAiBD,0CAGC;AAjCD;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,GAAW;IAC7C,OAAO,GAAG;SACP,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC,yCAAyC;SAClF,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,qCAAqC;SACzD,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,oCAAoC;SACzD,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,gCAAgC;AAC5D,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,eAAe,CAAC,QAAgB,EAAE,KAAa,EAAE,YAAqB;IACpF,MAAM,QAAQ,GAAG,GAAG,QAAQ,IAAI,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,YAAY,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC;IAC7H,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AACvC,CAAC","sourcesContent":["/**\n * Sanitizes a string to be safe for use in filenames.\n * Removes special characters and truncates if too long.\n *\n * @param str - The string to sanitize (e.g., test suite or test name)\n * @returns {string} Sanitized string safe for filesystem use\n */\nexport function sanitizeForFilename(str: string): string {\n return str\n .replace(/[^a-zA-Z0-9-_\\s\\[\\]\\(\\)]/g, \"\") // Remove special chars - with exceptions\n .replace(/-+/g, \"-\") // Collapse multiple hyphens into one\n .replace(/\\s+/g, \" \") // Collapse multiple spaces into one\n .replace(/^-|-$/g, \"\"); // Trim leading/trailing hyphens\n}\n\n/**\n * Builds a standardized filename for test artifacts (e.g., HAR files, screenshots).\n * Combines test name, state, and attempt number into a readable filename.\n *\n * @param testName - The name of the test (will be sanitized)\n * @param state - The test state (e.g., 'passed', 'failed')\n * @param currentRetry - The current retry attempt number (0-based)\n * @returns {string} A sanitized filename in the format: \"testName (state) (attempt N)\"\n * @example\n * fileNameBuilder(\"Login Flow\", \"failed\", 0)\n * // Returns: \"Login Flow (failed) (attempt 1)\"\n * @example\n * fileNameBuilder(\"Test: with -> special chars\", \"passed\", 2)\n * // Returns: \"Test with - special chars (passed) (attempt 3)\"\n */\nexport function fileNameBuilder(testName: string, state: string, currentRetry?: number): string {\n const fileName = `${testName} ${currentRetry !== undefined ? `(Attempt ${currentRetry + 1})` : \"\"} (${state.toLowerCase()})`;\n return sanitizeForFilename(fileName);\n}\n"]}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteFileInTree = exports.updateFileInTree = void 0;
4
+ /**
5
+ *
6
+ * @param tree File system tree
7
+ * @param path Path to source file in the Tree
8
+ * @param updater Function that maps the current file content to a new to be written to the Tree
9
+ */
10
+ const updateFileInTree = (tree, path, updater) => {
11
+ if (!tree.exists(path)) {
12
+ throw new Error("File not found: " + path);
13
+ }
14
+ const fileContent = tree.read(path, "utf-8");
15
+ if (!fileContent) {
16
+ throw new Error("File is empty: " + path);
17
+ }
18
+ const result = updater(fileContent);
19
+ if (result !== fileContent) {
20
+ tree.write(path, result);
21
+ }
22
+ };
23
+ exports.updateFileInTree = updateFileInTree;
24
+ /**
25
+ *
26
+ * @param tree File system tree
27
+ * @param path Path to source file in the Tree
28
+ */
29
+ const deleteFileInTree = (tree, path) => {
30
+ if (!tree.exists(path)) {
31
+ // File doesn't exist, nothing to delete - this is fine
32
+ return;
33
+ }
34
+ tree.delete(path);
35
+ };
36
+ exports.deleteFileInTree = deleteFileInTree;
37
+ //# sourceMappingURL=fileUpdater.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileUpdater.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-e2e/src/utils/fileUpdater.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACI,MAAM,gBAAgB,GAAG,CAAC,IAAU,EAAE,IAAY,EAAE,OAAwC,EAAE,EAAE;IACrG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEpC,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC,CAAC;AAdW,QAAA,gBAAgB,oBAc3B;AAEF;;;;GAIG;AACI,MAAM,gBAAgB,GAAG,CAAC,IAAU,EAAE,IAAY,EAAE,EAAE;IAC3D,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,uDAAuD;QACvD,OAAO;IACT,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC,CAAC;AAPW,QAAA,gBAAgB,oBAO3B","sourcesContent":["import { Tree } from \"@nx/devkit\";\n\n/**\n *\n * @param tree File system tree\n * @param path Path to source file in the Tree\n * @param updater Function that maps the current file content to a new to be written to the Tree\n */\nexport const updateFileInTree = (tree: Tree, path: string, updater: (fileContent: string) => string) => {\n if (!tree.exists(path)) {\n throw new Error(\"File not found: \" + path);\n }\n\n const fileContent = tree.read(path, \"utf-8\");\n if (!fileContent) {\n throw new Error(\"File is empty: \" + path);\n }\n const result = updater(fileContent);\n\n if (result !== fileContent) {\n tree.write(path, result);\n }\n};\n\n/**\n *\n * @param tree File system tree\n * @param path Path to source file in the Tree\n */\nexport const deleteFileInTree = (tree: Tree, path: string) => {\n if (!tree.exists(path)) {\n // File doesn't exist, nothing to delete - this is fine\n return;\n }\n\n tree.delete(path);\n};\n"]}
@@ -1 +0,0 @@
1
- exports._default = require('./index.cjs.js').default;
package/index.cjs.js DELETED
@@ -1,422 +0,0 @@
1
- 'use strict';
2
-
3
- var fs = require('fs');
4
- var path = require('path');
5
- var crypto = require('crypto');
6
- var nodeXlsx = require('node-xlsx');
7
- var cypressPreset = require('@nx/cypress/plugins/cypress-preset');
8
- var nxTsconfigPaths_plugin = require('@nx/vite/plugins/nx-tsconfig-paths.plugin');
9
-
10
- function _interopNamespaceDefault(e) {
11
- var n = Object.create(null);
12
- if (e) {
13
- Object.keys(e).forEach(function (k) {
14
- if (k !== 'default') {
15
- var d = Object.getOwnPropertyDescriptor(e, k);
16
- Object.defineProperty(n, k, d.get ? d : {
17
- enumerable: true,
18
- get: function () { return e[k]; }
19
- });
20
- }
21
- });
22
- }
23
- n.default = e;
24
- return Object.freeze(n);
25
- }
26
-
27
- var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
28
-
29
- /* eslint-disable no-console */
30
- /**
31
- * Writes a file with Prettier formatting applied.
32
- * Automatically detects parser based on file extension.
33
- */
34
- const writeFileWithPrettier = async (nxRoot, filePath, content, writeOptions = { encoding: "utf-8" }, writer) => {
35
- const prettierConfigPath = path__namespace.join(nxRoot, ".prettierrc");
36
- const options = await writer
37
- .resolveConfig(prettierConfigPath)
38
- .catch(error => console.log("Prettier config error: ", error));
39
- if (!options) {
40
- throw new Error("Could not find prettier config");
41
- }
42
- if (filePath.endsWith("json")) {
43
- options.parser = "json";
44
- }
45
- else {
46
- options.parser = "typescript";
47
- }
48
- try {
49
- const prettySrc = await writer.format(content, options);
50
- fs.writeFileSync(filePath, prettySrc, writeOptions);
51
- }
52
- catch (error) {
53
- console.error("Error in prettier.format:", error);
54
- }
55
- };
56
-
57
- const isNetworkCall = (log) => {
58
- return log.type === "cy:fetch" || log.type === "cy:request" || log.type === "cy:response" || log.type === "cy:xrh";
59
- };
60
- /**
61
- * Creates log files for Cypress test runs.
62
- * Generates separate files for all logs, errors, and network errors.
63
- */
64
- function createLogFile(nxRoot, logsPath, fileNameWithoutExtension, logs, logWriter) {
65
- if (!fs.existsSync(logsPath)) {
66
- fs.mkdirSync(logsPath, { recursive: true });
67
- }
68
- const logFilePath = path.join(logsPath, fileNameWithoutExtension);
69
- writeFileWithPrettier(nxRoot, logFilePath + "-all.json", JSON.stringify(logs), { flag: "a" }, logWriter);
70
- const errorCmds = logs.filter(log => log.severity === "error" &&
71
- // This seems to be when apollo has cancelled requests it marks it as failed to fetch only on cypress ?!??
72
- // might be fixed by https://github.com/Trackunit/manager/pull/12917
73
- !log.message.includes("TypeError: Failed to fetch"));
74
- if (errorCmds.length > 0) {
75
- writeFileWithPrettier(nxRoot, logFilePath + "-errors.json", JSON.stringify(errorCmds), {
76
- flag: "a",
77
- }, logWriter);
78
- }
79
- const networkErrorsCmds = logs.filter(log => isNetworkCall(log) &&
80
- !log.message.includes("Status: 200") &&
81
- log.severity !== "success" &&
82
- !log.message.includes("sentry.io/api/") &&
83
- // This seems to be when apollo has cancelled requests it marks it as failed to fetch only on cypress ?!??
84
- !log.message.includes("TypeError: Failed to fetch"));
85
- if (networkErrorsCmds.length > 0) {
86
- writeFileWithPrettier(nxRoot, logFilePath + "-network-errors.json", JSON.stringify(networkErrorsCmds), {
87
- flag: "a",
88
- }, logWriter);
89
- }
90
- }
91
-
92
- /**
93
- * Parses lines from a CODEOWNERS‐style file into a mapping of patterns → owners.
94
- * Skips blank lines and comments (lines beginning with `#`).
95
- *
96
- * @param {string[]} lines - Each element is a line from your CODEOWNERS file.
97
- * @returns {Record<string, string>} An object whose keys are normalized path patterns (no leading/trailing slashes)
98
- * and whose values are the owner (e.g. a GitHub team handle).
99
- * @example
100
- * ```ts
101
- * const lines = [
102
- * "# Our CODEOWNERS",
103
- * "/src @team/backend",
104
- * "README.md @team/docs",
105
- * "", // blank lines ignored
106
- * "# end of file"
107
- * ];
108
- * const owners = parseCodeowners(lines);
109
- * // → { "src": "@team/backend", "README.md": "@team/docs" }
110
- * ```
111
- */
112
- const parseCodeowners = (lines) => {
113
- const patterns = {};
114
- for (const line of lines) {
115
- const trimmed = line.trim();
116
- if (!trimmed || trimmed.startsWith("#")) {
117
- continue;
118
- }
119
- const [pattern, owner] = trimmed.split(/\s+/, 2);
120
- if (pattern && owner) {
121
- const normalizedPattern = pattern.replace(/^\/+|\/+$/g, "");
122
- patterns[normalizedPattern] = owner;
123
- }
124
- }
125
- return patterns;
126
- };
127
- /**
128
- * Converts a full team handle (potentially namespaced and with platform suffix)
129
- * into its “short” team name.
130
- *
131
- * - Strips any leading `@org/` prefix
132
- * - Removes `-be` or `-fe` suffix if present
133
- *
134
- * @param {string} teamName - e.g. `"@trackunit/backend-be"` or `"@trackunit/frontend-fe"`
135
- * @returns {string} e.g. `"backend"` or `"frontend"`
136
- * @example
137
- * ```ts
138
- * toShortTeamName("@trackunit/backend-be"); // → "backend"
139
- * toShortTeamName("@trackunit/frontend-fe"); // → "frontend"
140
- * toShortTeamName("infra"); // → "infra"
141
- * ```
142
- */
143
- const toShortTeamName = (teamName) => {
144
- if (!teamName) {
145
- return undefined;
146
- }
147
- let shortName = teamName;
148
- if (teamName.startsWith("@")) {
149
- shortName = shortName.slice(shortName.indexOf("/") + 1);
150
- }
151
- if (shortName.endsWith("-be") || shortName.endsWith("-fe")) {
152
- shortName = shortName.slice(0, shortName.lastIndexOf("-"));
153
- }
154
- return shortName;
155
- };
156
- /**
157
- * Recursively looks up the CODEOWNER for a given file or directory path
158
- * by reading your workspace’s CODEOWNERS file.
159
- *
160
- * Walks up the directory tree until it finds a matching pattern. If no
161
- * deeper match is found but there is a `"."` entry, returns that.
162
- *
163
- * @param {string} currentPath - Absolute path to the file/directory you’re querying.
164
- * @param {string} workspaceRoot - Absolute path to your repo/workspace root.
165
- * @param {string} [codeownersFileName="TEAM_CODEOWNERS"] - Filename to read at the root.
166
- * @returns {string|undefined} The owner handle (e.g. `"@team/backend"`) or `undefined` if none found.
167
- * @example
168
- * ```ts
169
- * // Suppose your repo root has a TEAM_CODEOWNERS file containing:
170
- * // src @team/backend
171
- * // src/utils @team/utils
172
- * // . @team/root
173
- *
174
- * const owner1 = getCodeowner(
175
- * "/Users/alice/project/src/utils/helpers.ts",
176
- * "/Users/alice/project"
177
- * );
178
- * // → "@team/utils"
179
- *
180
- * const owner2 = getCodeowner(
181
- * "/Users/alice/project/other/file.txt",
182
- * "/Users/alice/project"
183
- * );
184
- * // → "@team/root" (falls back to the "." entry)
185
- * ```
186
- */
187
- const getCodeowner = (currentPath, workspaceRoot, codeownersFileName = "TEAM_CODEOWNERS") => {
188
- if (!workspaceRoot) {
189
- return undefined;
190
- }
191
- const codeownersPath = path.join(workspaceRoot, codeownersFileName);
192
- if (!fs.existsSync(codeownersPath)) {
193
- return undefined;
194
- }
195
- const codeownersLines = fs.readFileSync(codeownersPath, "utf8").split("\n");
196
- const codeowners = parseCodeowners(codeownersLines);
197
- let relPath = path
198
- .relative(workspaceRoot, currentPath)
199
- .replace(/\\/g, "/")
200
- .replace(/^\/+|\/+$/g, "");
201
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
202
- while (true) {
203
- const codeowner = codeowners[relPath];
204
- if (codeowner) {
205
- return codeowner;
206
- }
207
- const parent = path.posix.dirname(relPath);
208
- if ((parent === relPath || parent === ".") && codeowners["."]) {
209
- return codeowners["."];
210
- }
211
- if (parent === relPath || parent === ".") {
212
- break;
213
- }
214
- relPath = parent;
215
- }
216
- return undefined;
217
- };
218
-
219
- /**
220
- * Sanitizes a string to be safe for use in filenames.
221
- * Removes special characters and truncates if too long.
222
- *
223
- * @param str - The string to sanitize (e.g., test suite or test name)
224
- * @returns {string} Sanitized string safe for filesystem use
225
- */
226
- function sanitizeForFilename(str) {
227
- return str
228
- .replace(/[^a-zA-Z0-9-_\s\[\]\(\)]/g, "") // Remove special chars - with exceptions
229
- .replace(/-+/g, "-") // Collapse multiple hyphens into one
230
- .replace(/\s+/g, " ") // Collapse multiple spaces into one
231
- .replace(/^-|-$/g, ""); // Trim leading/trailing hyphens
232
- }
233
- /**
234
- * Builds a standardized filename for test artifacts (e.g., HAR files, screenshots).
235
- * Combines test name, state, and attempt number into a readable filename.
236
- *
237
- * @param testName - The name of the test (will be sanitized)
238
- * @param state - The test state (e.g., 'passed', 'failed')
239
- * @param currentRetry - The current retry attempt number (0-based)
240
- * @returns {string} A sanitized filename in the format: "testName (state) (attempt N)"
241
- * @example
242
- * fileNameBuilder("Login Flow", "failed", 0)
243
- * // Returns: "Login Flow (failed) (attempt 1)"
244
- * @example
245
- * fileNameBuilder("Test: with -> special chars", "passed", 2)
246
- * // Returns: "Test with - special chars (passed) (attempt 3)"
247
- */
248
- function fileNameBuilder(testName, state, currentRetry) {
249
- const fileName = `${testName} ${""} (${state.toLowerCase()})`;
250
- return sanitizeForFilename(fileName);
251
- }
252
-
253
- /**
254
- * Utility function to find NX workspace root by looking for nx.json or workspace.json.
255
- * This is more reliable than hardcoded relative paths and works from any directory.
256
- *
257
- * @param startDir Starting directory for the search (defaults to current working directory)
258
- * @returns {string} Absolute path to the workspace root
259
- * @throws Error if workspace root cannot be found
260
- */
261
- function findWorkspaceRoot(startDir = process.cwd()) {
262
- let currentDir = startDir;
263
- while (currentDir !== path.dirname(currentDir)) {
264
- if (fs.existsSync(path.join(currentDir, "nx.json")) || fs.existsSync(path.join(currentDir, "workspace.json"))) {
265
- return currentDir;
266
- }
267
- currentDir = path.dirname(currentDir);
268
- }
269
- throw new Error("Could not find NX workspace root (nx.json or workspace.json not found)");
270
- }
271
- /**
272
- * Creates default Cypress configuration for E2E testing.
273
- * Supports both legacy string parameter (dirname) and new options object for backward compatibility.
274
- */
275
- const defaultCypressConfig = optionsOrDirname => {
276
- // Support both old API (string/undefined) and new API (object) for backward compatibility
277
- const options = typeof optionsOrDirname === "object" ? optionsOrDirname : {};
278
- const { nxRoot: providedNxRoot, outputDirOverride, projectConfig = {}, behaviorConfig = {}, pluginConfig = {}, } = options;
279
- // Use NX workspace detection for reliable root finding
280
- const nxRoot = providedNxRoot ?? findWorkspaceRoot();
281
- // For output path calculation, determine the relative path from caller to workspace root
282
- const callerDirname = typeof optionsOrDirname === "string" ? optionsOrDirname : process.cwd();
283
- const relativePath = path.relative(nxRoot, callerDirname);
284
- const dotsToNxRoot = relativePath
285
- .split(path.sep)
286
- .map(_ => "..")
287
- .join("/");
288
- const envOutputDirOverride = process.env.NX_E2E_OUTPUT_DIR || outputDirOverride;
289
- // Function to build output paths that respects the override
290
- const buildOutputPath = (subPath) => {
291
- if (envOutputDirOverride) {
292
- return path.join(dotsToNxRoot, envOutputDirOverride, subPath);
293
- }
294
- return `${dotsToNxRoot}/dist/cypress/${relativePath}/${subPath}`;
295
- };
296
- return {
297
- projectId: projectConfig.projectId ?? process.env.CYPRESS_PROJECT_ID,
298
- defaultCommandTimeout: behaviorConfig.defaultCommandTimeout ?? 20000,
299
- execTimeout: behaviorConfig.execTimeout ?? 300000,
300
- taskTimeout: behaviorConfig.taskTimeout ?? 35000,
301
- pageLoadTimeout: behaviorConfig.pageLoadTimeout ?? 35000,
302
- // setting to undefined makes no effect on the baseUrl so child projects can override it
303
- baseUrl: process.env.NX_FEATURE_BRANCH_BASE_URL ?? undefined,
304
- // This avoid issues with SRI hashes changing
305
- modifyObstructiveCode: false,
306
- requestTimeout: behaviorConfig.requestTimeout ?? 25000,
307
- responseTimeout: behaviorConfig.responseTimeout ?? 150000,
308
- retries: behaviorConfig.retries ?? {
309
- runMode: 3,
310
- openMode: 0,
311
- },
312
- fixturesFolder: projectConfig.fixturesFolder ?? "./src/fixtures",
313
- downloadsFolder: pluginConfig.outputPath ?? buildOutputPath("downloads"),
314
- logsFolder: pluginConfig.logsFolder ?? buildOutputPath("logs"),
315
- chromeWebSecurity: false,
316
- reporter: "junit",
317
- reporterOptions: {
318
- mochaFile: buildOutputPath("results-[hash].xml"),
319
- },
320
- specPattern: projectConfig.specPattern ?? "src/e2e/**/*.e2e.{js,jsx,ts,tsx}",
321
- supportFile: projectConfig.supportFile ?? "src/support/e2e.ts",
322
- nxRoot,
323
- fileServerFolder: ".",
324
- video: true,
325
- trashAssetsBeforeRuns: false, // Don't delete videos from previous test runs (important for sequential runs)
326
- videosFolder: pluginConfig.videosFolder ?? buildOutputPath("videos"),
327
- screenshotsFolder: pluginConfig.screenshotsFolder ?? buildOutputPath("screenshots"),
328
- env: {
329
- hars_folders: pluginConfig.harFolder ?? buildOutputPath("hars"),
330
- CYPRESS_RUN_UNIQUE_ID: crypto.randomUUID(),
331
- },
332
- };
333
- };
334
- /**
335
- * Sets up Cypress plugins for logging, tasks, and HAR generation.
336
- * Configures terminal reporting, XLSX parsing, and HTTP archive recording.
337
- */
338
- const setupPlugins = (on, config, logWriter, installHarGenerator) => {
339
- /* ---- BEGIN: Logging setup ---- */
340
- // Read options https://github.com/archfz/cypress-terminal-report
341
- const options = {
342
- printLogsToFile: "always", // Ensures logs are always printed to a file
343
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
344
- collectTestLogs: (context, logs) => {
345
- const testName = fileNameBuilder(context.test, context.state);
346
- createLogFile(config.nxRoot, config.logsFolder, testName, logs, logWriter);
347
- },
348
- };
349
- // eslint-disable-next-line @typescript-eslint/no-require-imports
350
- require("cypress-terminal-report/src/installLogsPrinter")(on, options);
351
- /* ---- END: Logging setup ---- */
352
- /* ---- BEGIN: Task setup ---- */
353
- on("task", {
354
- parseXlsx(filePath) {
355
- return new Promise((resolve, reject) => {
356
- try {
357
- const jsonData = nodeXlsx.parse(fs.readFileSync(filePath));
358
- resolve(jsonData);
359
- }
360
- catch (e) {
361
- reject(e);
362
- }
363
- });
364
- },
365
- fileExists(filename) {
366
- return fs.existsSync(filename);
367
- },
368
- });
369
- /* ---- END: Task setup ---- */
370
- /* ---- BEGIN: codeowner setup ---- */
371
- const codeowner = toShortTeamName(getCodeowner(config.projectRoot, config.nxRoot));
372
- config.env.codeowner = codeowner;
373
- /* ---- END: codeowner setup ---- */
374
- /* ---- BEGIN: HAR setup ---- */
375
- // Installing the HAR geneartor should happen last according to the documentation
376
- // https://github.com/NeuraLegion/cypress-har-generator?tab=readme-ov-file#setting-up-the-plugin
377
- installHarGenerator(on);
378
- /* ---- END: HAR setup ---- */
379
- return config;
380
- };
381
-
382
- /**
383
- * Creates the standard NX E2E preset configuration for Cypress.
384
- * This wraps the @nx/cypress preset with our default Vite configuration.
385
- *
386
- * @param filename - Pass `__filename` from the calling cypress.config.ts
387
- * @example
388
- * ```ts
389
- * import { createNxPreset, CypressPluginConfig, defaultCypressConfig, setupPlugins } from "@trackunit/iris-app-e2e";
390
- * import { defineConfig } from "cypress";
391
- *
392
- * const nxPreset = createNxPreset(__filename);
393
- *
394
- * export default defineConfig({
395
- * chromeWebSecurity: false,
396
- * e2e: {
397
- * ...nxPreset,
398
- * ...defaultCypressConfig(__dirname),
399
- * async setupNodeEvents(on, config: CypressPluginConfig) {
400
- * await nxPreset.setupNodeEvents?.(on, config);
401
- * return setupPlugins(on, config, formatter, installHarGenerator);
402
- * },
403
- * },
404
- * });
405
- * ```
406
- */
407
- function createNxPreset(filename) {
408
- return cypressPreset.nxE2EPreset(filename, {
409
- bundler: "vite",
410
- cypressDir: "cypress",
411
- viteConfigOverrides: {
412
- configFile: false,
413
- plugins: [nxTsconfigPaths_plugin.nxViteTsPaths()],
414
- },
415
- });
416
- }
417
-
418
- exports.createLogFile = createLogFile;
419
- exports.createNxPreset = createNxPreset;
420
- exports.defaultCypressConfig = defaultCypressConfig;
421
- exports.setupPlugins = setupPlugins;
422
- exports.writeFileWithPrettier = writeFileWithPrettier;
package/index.cjs.mjs DELETED
@@ -1,2 +0,0 @@
1
- export * from './index.cjs.js';
2
- export { _default as default } from './index.cjs.default.js';