@testspectra/cli 1.0.17 → 1.0.18

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.
@@ -4,41 +4,152 @@ import { fileURLToPath } from "url";
4
4
  import { ConfigLoader } from "../config/loader.js";
5
5
  import { TypeGenerator } from "../types/generator.js";
6
6
  import inquirer from "inquirer";
7
+ function parsePnpmWorkspaceGlobs(content) {
8
+ const lines = content.split("\n");
9
+ const globs = [];
10
+ let inPackages = false;
11
+ for (const line of lines) {
12
+ const trimmed = line.trim();
13
+ if (trimmed.startsWith("packages:")) {
14
+ inPackages = true;
15
+ continue;
16
+ }
17
+ if (inPackages) {
18
+ if (trimmed.startsWith("-")) {
19
+ const item = trimmed.replace(/^-\s*['"]?/, "").replace(/['"]?\s*$/, "");
20
+ if (item)
21
+ globs.push(item);
22
+ }
23
+ else if (trimmed && !trimmed.startsWith("#")) {
24
+ break;
25
+ }
26
+ }
27
+ }
28
+ return globs;
29
+ }
30
+ function scanDirectoriesForProjects(cwd, patterns) {
31
+ const foundProjects = [];
32
+ const visited = new Set();
33
+ function search(dir, currentDepth, maxDepth) {
34
+ if (currentDepth > maxDepth || !fs.existsSync(dir))
35
+ return;
36
+ const base = path.basename(dir);
37
+ if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git" || base === "shared") {
38
+ return;
39
+ }
40
+ const hasPkg = fs.existsSync(path.join(dir, "package.json"));
41
+ const hasNx = fs.existsSync(path.join(dir, "project.json"));
42
+ if ((hasPkg || hasNx) && dir !== cwd) {
43
+ const rel = path.relative(cwd, dir);
44
+ if (!visited.has(rel)) {
45
+ visited.add(rel);
46
+ let name = path.basename(dir);
47
+ if (hasPkg) {
48
+ try {
49
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
50
+ if (pkg.name)
51
+ name = pkg.name;
52
+ }
53
+ catch { }
54
+ }
55
+ else if (hasNx) {
56
+ try {
57
+ const nxJson = JSON.parse(fs.readFileSync(path.join(dir, "project.json"), "utf-8"));
58
+ if (nxJson.name)
59
+ name = nxJson.name;
60
+ }
61
+ catch { }
62
+ }
63
+ foundProjects.push({
64
+ name,
65
+ relPath: rel,
66
+ absPath: dir,
67
+ hasNxProject: hasNx,
68
+ });
69
+ }
70
+ return; // don't recurse into nested sub-packages unless needed
71
+ }
72
+ try {
73
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
74
+ for (const entry of entries) {
75
+ if (entry.isDirectory()) {
76
+ search(path.join(dir, entry.name), currentDepth + 1, maxDepth);
77
+ }
78
+ }
79
+ }
80
+ catch { }
81
+ }
82
+ // If patterns exist from pnpm-workspace.yaml, search those roots
83
+ if (patterns.length > 0) {
84
+ for (const pat of patterns) {
85
+ const cleanPat = pat.replace(/\/\*\*?$/, "").replace(/\/\*$/, "");
86
+ const searchRoot = path.join(cwd, cleanPat);
87
+ if (fs.existsSync(searchRoot)) {
88
+ const stat = fs.statSync(searchRoot);
89
+ if (stat.isDirectory()) {
90
+ const entries = fs.readdirSync(searchRoot, { withFileTypes: true });
91
+ for (const e of entries) {
92
+ if (e.isDirectory()) {
93
+ search(path.join(searchRoot, e.name), 1, 3);
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ // Fallback search in standard directories if nothing found
101
+ if (foundProjects.length === 0) {
102
+ for (const fallback of ["modules", "packages", "apps", "libs", "features"]) {
103
+ const fbPath = path.join(cwd, fallback);
104
+ if (fs.existsSync(fbPath)) {
105
+ search(fbPath, 1, 3);
106
+ }
107
+ }
108
+ }
109
+ return foundProjects;
110
+ }
7
111
  export async function initCommand(options = {}) {
8
112
  const cwd = process.cwd();
9
113
  const existingConfig = ConfigLoader.findConfigFile(cwd);
10
- if (existingConfig && !options.force) {
114
+ if (existingConfig && existingConfig === path.join(cwd, path.basename(existingConfig)) && !options.force) {
11
115
  console.log(`\x1b[33m[TestSpectra]\x1b[0m Config already exists at ${path.basename(existingConfig)}. Use --force to overwrite.`);
12
116
  return;
13
117
  }
14
- // Interactive Prompt if template is not specified
15
- let selectedTemplate = options.template;
16
- if (!selectedTemplate) {
17
- // Detect if we are inside an Nx workspace
18
- let isNx = false;
19
- let checkNx = cwd;
20
- while (checkNx !== path.dirname(checkNx)) {
21
- if (fs.existsSync(path.join(checkNx, "nx.json"))) {
22
- isNx = true;
23
- break;
24
- }
25
- checkNx = path.dirname(checkNx);
118
+ // Detect monorepo environment
119
+ let isMonorepo = false;
120
+ let pnpmPatterns = [];
121
+ const pnpmWorkspacePath = path.join(cwd, "pnpm-workspace.yaml");
122
+ const nxJsonPath = path.join(cwd, "nx.json");
123
+ if (fs.existsSync(pnpmWorkspacePath)) {
124
+ isMonorepo = true;
125
+ try {
126
+ const content = fs.readFileSync(pnpmWorkspacePath, "utf-8");
127
+ pnpmPatterns = parsePnpmWorkspaceGlobs(content);
26
128
  }
129
+ catch { }
130
+ }
131
+ else if (fs.existsSync(nxJsonPath)) {
132
+ isMonorepo = true;
133
+ }
134
+ const detectedProjects = isMonorepo ? scanDirectoriesForProjects(cwd, pnpmPatterns) : [];
135
+ let selectedTemplate = options.template;
136
+ if (!selectedTemplate && !options.force) {
137
+ const choices = [
138
+ { name: "Standard (Standalone or single project)", value: "default" },
139
+ { name: "Nx Monorepo (Interactive multi-module feature discovery & centralized config)", value: "nx" },
140
+ ];
27
141
  const answers = await inquirer.prompt([
28
142
  {
29
143
  type: "list",
30
144
  name: "template",
31
- message: "Select TestSpectra project template:",
32
- choices: [
33
- { name: "Standard (Standalone or standard monorepo)", value: "default" },
34
- { name: "Nx Monorepo (Includes project.json with Nx targets & affected support)", value: "nx" },
35
- ],
36
- default: isNx ? "nx" : "default",
145
+ message: "Select TestSpectra setup mode:",
146
+ choices,
147
+ default: isMonorepo ? "nx" : "default",
37
148
  },
38
149
  ]);
39
150
  selectedTemplate = answers.template;
40
151
  }
41
- const templateName = selectedTemplate || "default";
152
+ const templateName = selectedTemplate || (isMonorepo ? "nx" : "default");
42
153
  // 1. Resolve template directory
43
154
  const __filename = fileURLToPath(import.meta.url);
44
155
  const __dirname = path.dirname(__filename);
@@ -52,8 +163,8 @@ export async function initCommand(options = {}) {
52
163
  if (!fs.existsSync(templateDir)) {
53
164
  throw new Error(`[TestSpectra] Scaffold template directory not found at: ${templateDir}`);
54
165
  }
55
- // 2. Dynamically resolve CLI version
56
- let cliVersion = "^1.0.6";
166
+ // 2. Resolve CLI dependency version
167
+ let cliVersion = "^1.0.17";
57
168
  try {
58
169
  const cliPackageJsonPath = path.resolve(__dirname, "../../package.json");
59
170
  if (fs.existsSync(cliPackageJsonPath)) {
@@ -64,7 +175,6 @@ export async function initCommand(options = {}) {
64
175
  }
65
176
  }
66
177
  catch { }
67
- // Check if cwd is inside the testspectra monorepo with a pnpm-workspace.yaml
68
178
  let isInternalWorkspace = false;
69
179
  let cur = cwd;
70
180
  while (cur !== path.dirname(cur)) {
@@ -81,9 +191,8 @@ export async function initCommand(options = {}) {
81
191
  cur = path.dirname(cur);
82
192
  }
83
193
  const cliDepVersion = isInternalWorkspace ? "workspace:*" : cliVersion;
84
- const projectName = path.basename(cwd);
85
- // 3. Recursive copy function with variable replacement
86
- function copyRecursive(src, dest) {
194
+ // Helper: Copy directory contents recursively
195
+ function copyRecursive(src, dest, replacements = {}) {
87
196
  const base = path.basename(src);
88
197
  if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git") {
89
198
  return;
@@ -95,41 +204,161 @@ export async function initCommand(options = {}) {
95
204
  }
96
205
  const entries = fs.readdirSync(src);
97
206
  for (const entry of entries) {
98
- copyRecursive(path.join(src, entry), path.join(dest, entry));
207
+ copyRecursive(path.join(src, entry), path.join(dest, entry), replacements);
99
208
  }
100
209
  }
101
210
  else {
102
- // If destination exists and force is not set, skip
103
211
  if (fs.existsSync(dest) && !options.force) {
104
212
  return;
105
213
  }
106
214
  let content = fs.readFileSync(src, "utf-8");
107
- if (src.endsWith("package.json")) {
215
+ for (const [k, v] of Object.entries(replacements)) {
216
+ content = content.replaceAll(k, v);
217
+ }
218
+ if (dest.endsWith("package.json")) {
108
219
  try {
109
- const pkgObj = JSON.parse(content);
110
- if (src === path.join(templateDir, "package.json")) {
111
- pkgObj.name = projectName;
112
- }
113
- if (pkgObj.devDependencies && pkgObj.devDependencies["@testspectra/cli"]) {
114
- pkgObj.devDependencies["@testspectra/cli"] = cliDepVersion;
220
+ const pkg = JSON.parse(content);
221
+ if (pkg.devDependencies && pkg.devDependencies["@testspectra/cli"]) {
222
+ pkg.devDependencies["@testspectra/cli"] = cliDepVersion;
115
223
  }
116
- if (pkgObj.dependencies && pkgObj.dependencies["@testspectra/cli"]) {
117
- pkgObj.dependencies["@testspectra/cli"] = cliDepVersion;
224
+ if (pkg.dependencies && pkg.dependencies["@testspectra/cli"]) {
225
+ pkg.dependencies["@testspectra/cli"] = cliDepVersion;
118
226
  }
119
- content = JSON.stringify(pkgObj, null, 2);
227
+ content = JSON.stringify(pkg, null, 2);
120
228
  }
121
229
  catch { }
122
230
  }
123
- else if (src.endsWith("project.json")) {
124
- content = content
125
- .replace(/\{\{PROJECT_NAME\}\}/g, projectName)
126
- .replace(/\{\{PROJECT_DIR\}\}/g, path.relative(cur, cwd) || ".");
127
- }
128
231
  fs.writeFileSync(dest, content, "utf-8");
129
232
  }
130
233
  }
234
+ // --- MONOREPO INTERACTIVE FLOW ---
235
+ if (templateName === "nx" && (detectedProjects.length > 0 || isMonorepo)) {
236
+ console.log(`\n\x1b[36m[TestSpectra Monorepo Discovery]\x1b[0m`);
237
+ console.log(`Found ${detectedProjects.length} workspace feature project(s).`);
238
+ let chosenProjects = [];
239
+ if (detectedProjects.length > 0) {
240
+ const projectAnswers = await inquirer.prompt([
241
+ {
242
+ type: "checkbox",
243
+ name: "selectedProjects",
244
+ message: "Select workspace features where TestSpectra should be initialized:",
245
+ choices: detectedProjects.map((p) => ({
246
+ name: `${p.relPath} (${p.name})${p.hasNxProject ? " [Nx project.json]" : ""}`,
247
+ value: p.relPath,
248
+ checked: true,
249
+ })),
250
+ },
251
+ ]);
252
+ chosenProjects = projectAnswers.selectedProjects;
253
+ }
254
+ const folderAnswers = await inquirer.prompt([
255
+ {
256
+ type: "input",
257
+ name: "e2eFolderName",
258
+ message: "Enter E2E test folder name for each selected feature:",
259
+ default: "e2e",
260
+ },
261
+ {
262
+ type: "input",
263
+ name: "sharedTestingPath",
264
+ message: "Enter path for Shared Testing library (POMs, Steps, Actions, Fixtures):",
265
+ default: "shared/testing",
266
+ },
267
+ ]);
268
+ const e2eFolderName = folderAnswers.e2eFolderName.trim() || "e2e";
269
+ const sharedTestingRelPath = folderAnswers.sharedTestingPath.trim() || "shared/testing";
270
+ const sharedTestingAbsPath = path.join(cwd, sharedTestingRelPath);
271
+ // 1. Centralized Root Config
272
+ const rootConfigSrc = path.join(templateDir, "spectra.config.ts");
273
+ const rootConfigDest = path.join(cwd, "spectra.config.ts");
274
+ if (fs.existsSync(rootConfigSrc) && (!fs.existsSync(rootConfigDest) || options.force)) {
275
+ fs.copyFileSync(rootConfigSrc, rootConfigDest);
276
+ console.log(`\x1b[32m[TestSpectra]\x1b[0m Created centralized configuration at ./spectra.config.ts`);
277
+ }
278
+ // 2. Centralized Root .testspectra folder for cache/logs
279
+ const rootTestDataDir = path.join(cwd, ".testspectra");
280
+ if (!fs.existsSync(rootTestDataDir)) {
281
+ fs.mkdirSync(rootTestDataDir, { recursive: true });
282
+ }
283
+ // 3. Centralized Shared Testing Library
284
+ const sharedTestingSrc = path.join(templateDir, "shared/testing");
285
+ if (fs.existsSync(sharedTestingSrc)) {
286
+ copyRecursive(sharedTestingSrc, sharedTestingAbsPath);
287
+ console.log(`\x1b[32m[TestSpectra]\x1b[0m Initialized shared testing library at ./${sharedTestingRelPath}`);
288
+ }
289
+ // 4. Distribute E2E Folders to Selected Feature Modules
290
+ const sampleE2eSrc = path.join(templateDir, "modules/auth/e2e");
291
+ const featureE2eDirs = [];
292
+ for (const projRel of chosenProjects) {
293
+ const targetE2eDir = path.join(cwd, projRel, e2eFolderName);
294
+ const featureName = path.basename(projRel);
295
+ const e2eProjectName = `${featureName}-${e2eFolderName}`;
296
+ copyRecursive(sampleE2eSrc, targetE2eDir, {
297
+ "auth-e2e": e2eProjectName,
298
+ "modules/auth/e2e": path.relative(cwd, targetE2eDir).replace(/\\/g, "/"),
299
+ "scope:auth": `scope:${featureName}`,
300
+ });
301
+ // Adjust project.json
302
+ const projJsonPath = path.join(targetE2eDir, "project.json");
303
+ if (fs.existsSync(projJsonPath)) {
304
+ try {
305
+ const pObj = JSON.parse(fs.readFileSync(projJsonPath, "utf-8"));
306
+ pObj.name = e2eProjectName;
307
+ pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g, "/");
308
+ pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, "/");
309
+ pObj.targets["type-check"].options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, "/");
310
+ pObj.tags = [`scope:${featureName}`, "type:e2e"];
311
+ fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), "utf-8");
312
+ }
313
+ catch { }
314
+ }
315
+ // Ensure no local spectra.config.ts (centralized in root)
316
+ const localConfig = path.join(targetE2eDir, "spectra.config.ts");
317
+ if (fs.existsSync(localConfig))
318
+ fs.unlinkSync(localConfig);
319
+ featureE2eDirs.push(targetE2eDir);
320
+ console.log(`\x1b[32m[TestSpectra]\x1b[0m Initialized ${e2eProjectName} in ./${path.relative(cwd, targetE2eDir)}`);
321
+ }
322
+ // 5. Update Root Solution tsconfig.json references
323
+ const rootTsConfigPath = path.join(cwd, "tsconfig.json");
324
+ const references = [];
325
+ for (const e2eDir of featureE2eDirs) {
326
+ references.push({ path: `./${path.relative(cwd, e2eDir).replace(/\\/g, "/")}` });
327
+ }
328
+ references.push({ path: `./${sharedTestingRelPath.replace(/\\/g, "/")}` });
329
+ let rootTsConfig = { files: [], references: [] };
330
+ if (fs.existsSync(rootTsConfigPath)) {
331
+ try {
332
+ rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath, "utf-8"));
333
+ if (!rootTsConfig.references)
334
+ rootTsConfig.references = [];
335
+ for (const ref of references) {
336
+ if (!rootTsConfig.references.some((r) => r.path === ref.path)) {
337
+ rootTsConfig.references.push(ref);
338
+ }
339
+ }
340
+ }
341
+ catch { }
342
+ }
343
+ else {
344
+ rootTsConfig = { files: [], references };
345
+ }
346
+ fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2), "utf-8");
347
+ // 6. Generate Ambient Types for Shared + All E2E Feature Directories
348
+ TypeGenerator.writeDeclarationFiles(cwd);
349
+ for (const e2eDir of featureE2eDirs) {
350
+ TypeGenerator.writeDeclarationFiles(e2eDir);
351
+ }
352
+ console.log(`\n\x1b[32m[TestSpectra]\x1b[0m Enterprise Nx Monorepo initialized successfully!`);
353
+ console.log(`\x1b[36m- Centralized Configuration:\x1b[0m ./spectra.config.ts`);
354
+ console.log(`\x1b[36m- Centralized App Data/Cache:\x1b[0m ./.testspectra/`);
355
+ console.log(`\x1b[36m- Shared Testing Library:\x1b[0m ./${sharedTestingRelPath}`);
356
+ console.log(`\x1b[36m- Initialized Features (${featureE2eDirs.length}):\x1b[0m\n ${featureE2eDirs.map((d) => path.relative(cwd, d)).join("\n ")}`);
357
+ return;
358
+ }
359
+ // --- STANDALONE / DEFAULT PROJECT FLOW ---
360
+ const projectName = path.basename(cwd);
131
361
  copyRecursive(templateDir, cwd);
132
- // 4. Pre-configure pnpm allowBuilds for standalone projects
133
362
  if (!isInternalWorkspace) {
134
363
  const pnpmWorkspacePath = path.join(cwd, "pnpm-workspace.yaml");
135
364
  if (!fs.existsSync(pnpmWorkspacePath) || options.force) {
@@ -137,21 +366,7 @@ export async function initCommand(options = {}) {
137
366
  fs.writeFileSync(pnpmWorkspacePath, pnpmWsContent, "utf-8");
138
367
  }
139
368
  }
140
- // 5. Generate ambient declaration files
141
369
  TypeGenerator.writeDeclarationFiles(cwd);
142
- // If subprojects exist (e.g. in Nx monorepo template), generate types for them as well
143
- const modulesDir = path.join(cwd, "modules");
144
- if (fs.existsSync(modulesDir)) {
145
- const mods = fs.readdirSync(modulesDir, { withFileTypes: true });
146
- for (const m of mods) {
147
- if (m.isDirectory()) {
148
- const e2eDir = path.join(modulesDir, m.name, "e2e");
149
- if (fs.existsSync(e2eDir)) {
150
- TypeGenerator.writeDeclarationFiles(e2eDir);
151
- }
152
- }
153
- }
154
- }
155
370
  console.log(`\x1b[32m[TestSpectra]\x1b[0m Initialized project from template successfully!`);
156
371
  console.log(`\x1b[32m[TestSpectra]\x1b[0m Generated ambient multi-platform types in .testspectra/types/`);
157
372
  }
@@ -13,7 +13,17 @@ export async function runCommand(specPath, options = {}) {
13
13
  config.webConfig.headlessMode = options.headless;
14
14
  }
15
15
  const platform = options.target || "web";
16
- const appDataPath = options.workdir ? path.resolve(options.workdir) : path.join(cwd, ".testspectra");
16
+ // Find workspace root for centralized .testspectra
17
+ let workspaceRoot = cwd;
18
+ let cur = cwd;
19
+ while (cur !== path.dirname(cur)) {
20
+ if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cur, "nx.json")) || fs.existsSync(path.join(cur, "spectra.config.ts")) || fs.existsSync(path.join(cur, "testspectra.config.ts"))) {
21
+ workspaceRoot = cur;
22
+ break;
23
+ }
24
+ cur = path.dirname(cur);
25
+ }
26
+ const appDataPath = options.workdir ? path.resolve(options.workdir) : path.join(workspaceRoot, ".testspectra");
17
27
  if (!fs.existsSync(appDataPath)) {
18
28
  fs.mkdirSync(appDataPath, { recursive: true });
19
29
  }
@@ -11,11 +11,18 @@ export class ConfigLoader {
11
11
  ".testspectrarc.json",
12
12
  ];
13
13
  static findConfigFile(cwd = process.cwd()) {
14
- for (const filename of this.CONFIG_FILE_NAMES) {
15
- const fullPath = path.join(cwd, filename);
16
- if (fs.existsSync(fullPath)) {
17
- return fullPath;
14
+ let cur = path.resolve(cwd);
15
+ while (true) {
16
+ for (const filename of this.CONFIG_FILE_NAMES) {
17
+ const fullPath = path.join(cur, filename);
18
+ if (fs.existsSync(fullPath)) {
19
+ return fullPath;
20
+ }
18
21
  }
22
+ const parent = path.dirname(cur);
23
+ if (parent === cur)
24
+ break;
25
+ cur = parent;
19
26
  }
20
27
  return null;
21
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/cli",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,61 +0,0 @@
1
- import { defineConfig } from "@testspectra/cli";
2
-
3
- export default defineConfig({
4
- webConfig: {
5
- baseUrl: "https://the-internet.herokuapp.com",
6
- maxConcurrentSessions: "1",
7
- headlessMode: true,
8
- implicitWait: "5000",
9
- pageLoadTimeout: "30000",
10
- scriptTimeout: "30000",
11
- parallelizationMode: "testcase",
12
- },
13
- browsers: [
14
- {
15
- id: "chrome-desktop",
16
- type: "chrome",
17
- mobileEmulation: false,
18
- },
19
- ],
20
- androidConfig: {
21
- appiumServer: "http://127.0.0.1:4723",
22
- platformName: "Android",
23
- platformVersion: "13",
24
- deviceName: "emulator-5554",
25
- automationName: "UiAutomator2",
26
- appPackage: "",
27
- appActivity: "",
28
- autoGrantPermissions: true,
29
- noReset: false,
30
- implicitWait: "10000",
31
- parallelizationMode: "suite",
32
- },
33
- iosConfig: {
34
- appiumServer: "http://127.0.0.1:4723",
35
- platformName: "iOS",
36
- platformVersion: "16.0",
37
- deviceName: "iPhone 14",
38
- automationName: "XCUITest",
39
- bundleId: "",
40
- udid: "auto",
41
- xcodeOrgId: "",
42
- xcodeSigningId: "iPhone Developer",
43
- autoAcceptAlerts: true,
44
- noReset: false,
45
- implicitWait: "10000",
46
- parallelizationMode: "suite",
47
- },
48
- loadConfig: {
49
- virtualUsers: "10",
50
- duration: "1m",
51
- },
52
- loadStages: [],
53
- thresholds: [],
54
- executionConfig: {
55
- networkMonitoringEnabled: true,
56
- fastResponseTime: "200",
57
- normalResponseTime: "1000",
58
- monitoredDomains: [],
59
- environmentVariables: [],
60
- },
61
- });