@dev-blinq/cucumber_client 1.0.1216-dev → 1.0.1216-stage

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 (40) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +220 -0
  2. package/bin/assets/preload/recorderv3.js +74 -4
  3. package/bin/assets/preload/unique_locators.js +24 -3
  4. package/bin/assets/scripts/aria_snapshot.js +235 -0
  5. package/bin/assets/scripts/dom_attr.js +372 -0
  6. package/bin/assets/scripts/dom_element.js +0 -0
  7. package/bin/assets/scripts/dom_parent.js +185 -0
  8. package/bin/assets/scripts/event_utils.js +105 -0
  9. package/bin/assets/scripts/pw.js +7886 -0
  10. package/bin/assets/scripts/recorder.js +1147 -0
  11. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  12. package/bin/assets/scripts/unique_locators.js +841 -0
  13. package/bin/assets/scripts/yaml.js +4770 -0
  14. package/bin/assets/templates/_hooks_template.txt +37 -0
  15. package/bin/assets/templates/page_template.txt +2 -16
  16. package/bin/assets/templates/utils_template.txt +48 -63
  17. package/bin/client/apiTest/apiTest.js +6 -0
  18. package/bin/client/cli_helpers.js +11 -13
  19. package/bin/client/code_cleanup/utils.js +42 -14
  20. package/bin/client/code_gen/code_inversion.js +92 -11
  21. package/bin/client/code_gen/index.js +3 -0
  22. package/bin/client/code_gen/page_reflection.js +37 -20
  23. package/bin/client/code_gen/playwright_codeget.js +170 -33
  24. package/bin/client/cucumber/feature.js +85 -27
  25. package/bin/client/cucumber/steps_definitions.js +85 -77
  26. package/bin/client/local_agent.js +7 -3
  27. package/bin/client/project.js +7 -1
  28. package/bin/client/recorderv3/bvt_recorder.js +278 -79
  29. package/bin/client/recorderv3/implemented_steps.js +75 -14
  30. package/bin/client/recorderv3/index.js +49 -7
  31. package/bin/client/recorderv3/network.js +299 -0
  32. package/bin/client/recorderv3/step_runner.js +181 -12
  33. package/bin/client/recorderv3/step_utils.js +157 -6
  34. package/bin/client/recorderv3/update_feature.js +58 -30
  35. package/bin/client/recording.js +7 -0
  36. package/bin/client/run_cucumber.js +15 -2
  37. package/bin/client/scenario_report.js +18 -6
  38. package/bin/client/test_scenario.js +0 -1
  39. package/bin/index.js +1 -0
  40. package/package.json +15 -8
@@ -1,6 +1,5 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "fs";
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
2
2
  import path from "path";
3
- import fs from "fs";
4
3
  import { generatePageName } from "../code_gen/playwright_codeget.js";
5
4
  import {
6
5
  executeStep,
@@ -9,27 +8,36 @@ import {
9
8
  getUtilsCodePage,
10
9
  loadStepDefinitions,
11
10
  saveRecording,
11
+ saveRoutes,
12
12
  } from "./step_utils.js";
13
13
  import { escapeString, getExamplesContent } from "./update_feature.js";
14
+ import fs from "fs";
14
15
  import { locateDefinitionPath } from "../cucumber/steps_definitions.js";
16
+ import { tmpdir } from "os";
15
17
 
16
18
  // let copiedCodeToTemp = false;
17
19
  async function withAbort(fn, signal) {
18
20
  if (!signal) {
19
21
  return await fn();
20
22
  }
23
+ return new Promise((resolve, reject) => {
24
+ const abortHandler = () => reject(new Error("Aborted"));
25
+ signal.addEventListener("abort", abortHandler, { once: true });
21
26
 
22
- const abortPromise = new Promise((_, reject) => {
23
- signal.addEventListener("abort", () => reject(new Error("Aborted")), { once: true });
27
+ fn()
28
+ .then(resolve)
29
+ .catch(reject)
30
+ .finally(() => {
31
+ signal.removeEventListener("abort", abortHandler);
32
+ });
24
33
  });
25
-
26
- return await Promise.race([fn(), abortPromise]);
27
34
  }
28
35
  export class BVTStepRunner {
29
36
  #currentStepController;
30
37
  #port;
31
- constructor({ projectDir }) {
38
+ constructor({ projectDir, sendExecutionStatus }) {
32
39
  this.projectDir = projectDir;
40
+ this.sendExecutionStatus = sendExecutionStatus;
33
41
  }
34
42
  setRemoteDebugPort(port) {
35
43
  this.#port = port;
@@ -56,11 +64,12 @@ export class BVTStepRunner {
56
64
  // copiedCodeToTemp = true;
57
65
  }
58
66
 
59
- async writeTempFeatureFile({ step, parametersMap, tempFolderPath }) {
67
+ async writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags }) {
60
68
  const tFilePath = path.join(tempFolderPath, "__temp.feature");
61
69
  // console.log(tFilePath);
62
70
  let tFileContent = `# temp feature file
63
71
  Feature: Temp feature
72
+ ${tags ? tags.join(" ") : ""}
64
73
  Scenario Outline: Temp Scenario
65
74
  Given ${escapeString(step.text)}
66
75
  `;
@@ -68,7 +77,136 @@ export class BVTStepRunner {
68
77
  writeFileSync(tFilePath, tFileContent);
69
78
  return tFilePath;
70
79
  }
71
- async runStep({ step, parametersMap, envPath }, bvtContext, options) {
80
+
81
+ executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText, config }, options) => {
82
+ const { skipAfter = true, skipBefore = true } = options || {};
83
+ const environment = {
84
+ ...process.env,
85
+ };
86
+
87
+ try {
88
+ const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
89
+ const { runConfiguration } = await loadConfiguration(
90
+ {
91
+ provided: {
92
+ name: [scenario],
93
+ paths: [feature_file_path],
94
+ import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
95
+ // format: ["bvt"],
96
+ },
97
+ },
98
+ { cwd: process.cwd(), env: environment }
99
+ );
100
+ // const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
101
+ // console.log("Files found:", files);
102
+ const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
103
+ // console.log("found ", support.stepDefinitions.length, "step definitions");
104
+ // support.stepDefinitions.map((step) => {
105
+ // console.log("step", step.pattern);
106
+ // });
107
+
108
+ support.afterTestRunHookDefinitions = [];
109
+ if (skipAfter) {
110
+ // ignore afterAll/after hooks
111
+ support.afterTestCaseHookDefinitions = [];
112
+ }
113
+ if (skipBefore && !config.legacySyntax) {
114
+ // ignore beforeAll/before hooks
115
+ support.beforeTestCaseHookDefinitions = support.beforeTestCaseHookDefinitions.filter((hook) => {
116
+ return hook.uri.endsWith("utils.mjs");
117
+ });
118
+ }
119
+ support.beforeTestRunHookDefinitions = [];
120
+
121
+ let errorMesssage = null;
122
+ let info = null;
123
+ let errInfo = null;
124
+ const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
125
+ if (message.testStepFinished) {
126
+ const { testStepFinished } = message;
127
+ const { testStepResult } = testStepFinished;
128
+ if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
129
+ if (!errorMesssage) {
130
+ errorMesssage = testStepResult.message;
131
+ if (info) {
132
+ errInfo = info;
133
+ }
134
+ }
135
+ }
136
+ if (testStepResult.status === "UNDEFINED") {
137
+ if (!errorMesssage) {
138
+ errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
139
+ if (info) {
140
+ errInfo = info;
141
+ }
142
+ }
143
+ }
144
+ }
145
+ if (message.attachment) {
146
+ const attachment = message.attachment;
147
+ if (attachment.mediaType === "application/json" && attachment.body) {
148
+ const body = JSON.parse(attachment.body);
149
+ info = body.info;
150
+ const result = body.result;
151
+
152
+ if (result.status === "PASSED") {
153
+ this.sendExecutionStatus({
154
+ type: "cmdExecutionSuccess",
155
+ cmdId: body.cmdId,
156
+ selectedStrategy: info?.selectedStrategy,
157
+ });
158
+ } else {
159
+ this.sendExecutionStatus({
160
+ type: "cmdExecutionError",
161
+ cmdId: body.cmdId,
162
+ error: {
163
+ message: result.message,
164
+ info,
165
+ },
166
+ });
167
+ }
168
+ } else if (attachment.mediaType === "application/json+intercept-results" && attachment.body) {
169
+ const body = JSON.parse(attachment.body);
170
+ if (body) {
171
+ this.sendExecutionStatus({
172
+ type: "interceptResults",
173
+ interceptResults: body,
174
+ });
175
+ }
176
+ }
177
+ }
178
+ });
179
+ if (errorMesssage) {
180
+ const bvtError = new Error(errorMesssage);
181
+ Object.assign(bvtError, { info: errInfo });
182
+ throw bvtError;
183
+ }
184
+
185
+ return {
186
+ result,
187
+ info,
188
+ };
189
+ } catch (error) {
190
+ console.error("Error running cucumber-js", error);
191
+ throw error;
192
+ }
193
+ };
194
+
195
+ async runStep({ step, parametersMap, envPath, tags, config }, bvtContext, options) {
196
+ let cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
197
+ if (bvtContext.web) {
198
+ bvtContext.web.getCmdId = () => {
199
+ if (cmdIDs.length === 0) {
200
+ cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
201
+ }
202
+ const cId = cmdIDs.shift();
203
+ this.sendExecutionStatus({
204
+ type: "cmdExecutionStart",
205
+ cmdId: cId,
206
+ });
207
+ return cId;
208
+ };
209
+ }
72
210
  let codePage; // = getCodePage();
73
211
  // const tempFolderPath = process.env.tempFeaturesFolderPath;
74
212
  const __temp_features_FolderName = "__temp_features" + Math.random().toString(36).substring(2, 7);
@@ -108,19 +246,49 @@ export class BVTStepRunner {
108
246
  if (!codePage) {
109
247
  codePage = getUtilsCodePage(this.projectDir);
110
248
  }
249
+ } else {
250
+ let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
251
+ if (process.env.TEMP_RUN === "true") {
252
+ // console.log("Save routes in temp folder for running:", routesPath);
253
+ if (existsSync(routesPath)) {
254
+ console.log("Removing existing temp_routes_folder:", routesPath);
255
+ rmSync(routesPath, { recursive: true });
256
+ }
257
+ mkdirSync(routesPath, { recursive: true });
258
+ console.log("Created temp_routes_folder:", routesPath);
259
+ saveRoutes({ step, folderPath: routesPath });
260
+ } else {
261
+ console.log("Saving routes in project directory:", this.projectDir);
262
+ if (existsSync(routesPath)) {
263
+ // remove the folder
264
+ try {
265
+ rmSync(routesPath, { recursive: true });
266
+ console.log("Removed temp_routes_folder:", routesPath);
267
+ } catch (error) {
268
+ console.error("Error removing temp_routes folder", error);
269
+ }
270
+ }
271
+ routesPath = path.join(this.projectDir, "data", "routes");
272
+ console.log("Saving routes to:", routesPath);
273
+ if (!existsSync(routesPath)) {
274
+ mkdirSync(routesPath, { recursive: true });
275
+ }
276
+ saveRoutes({ step, folderPath: routesPath });
277
+ }
111
278
  }
112
- const feature_file_path = await this.writeTempFeatureFile({ step, parametersMap, tempFolderPath });
279
+ const feature_file_path = await this.writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags });
113
280
  // console.log({ feature_file_path, step_text: step.text });
114
281
 
115
282
  const stepExecController = new AbortController();
116
283
  this.#currentStepController = stepExecController;
117
- await withAbort(async () => {
118
- await stepsDefinitions.executeStepRemote(
284
+ const { result, info } = await withAbort(async () => {
285
+ return await this.executeStepRemote(
119
286
  {
120
287
  feature_file_path,
121
288
  tempFolderPath,
122
289
  stepText: step.text,
123
290
  scenario: "Temp Scenario",
291
+ config,
124
292
  },
125
293
  options
126
294
  );
@@ -130,5 +298,6 @@ export class BVTStepRunner {
130
298
  fs.rmSync(tempFolderPath, { recursive: true });
131
299
  }
132
300
  });
301
+ return { result, info };
133
302
  }
134
303
  }
@@ -1,14 +1,16 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
2
  import path from "path";
3
3
  import url from "url";
4
4
  import logger from "../../logger.js";
5
- import { CodePage } from "../code_gen/page_reflection.js";
5
+ import { CodePage, getAiConfig } from "../code_gen/page_reflection.js";
6
6
  import { generateCode, generatePageName } from "../code_gen/playwright_codeget.js";
7
7
  import { invertCodeToCommand } from "../code_gen/code_inversion.js";
8
8
  import { Step } from "../cucumber/feature.js";
9
9
  import { locateDefinitionPath, StepsDefinitions } from "../cucumber/steps_definitions.js";
10
10
  import { Recording } from "../recording.js";
11
11
  import { generateApiCode } from "../code_gen/api_codegen.js";
12
+ import { tmpdir } from "os";
13
+ import { createHash } from "crypto";
12
14
 
13
15
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
14
16
 
@@ -69,19 +71,74 @@ function makeStepTextUnique(step, stepsDefinitions) {
69
71
  }
70
72
 
71
73
  export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
72
- // console.log("saveRecording", step.text);
74
+ let routesPath = path.join(tmpdir(), "blinq_temp_routes");
75
+
76
+ if (process.env.TEMP_RUN) {
77
+ if (existsSync(routesPath)) {
78
+ rmSync(routesPath, { recursive: true });
79
+ }
80
+ mkdirSync(routesPath, { recursive: true });
81
+ saveRoutes({ step, folderPath: routesPath });
82
+ } else {
83
+ if (existsSync(routesPath)) {
84
+ // remove the folder
85
+ try {
86
+ rmSync(routesPath, { recursive: true });
87
+ console.log("Removed temp_routes_folder:", routesPath);
88
+ } catch (error) {
89
+ console.error("Error removing temp_routes folder", error);
90
+ }
91
+ routesPath = path.join(projectDir, "data", "routes");
92
+ if (!existsSync(routesPath)) {
93
+ mkdirSync(routesPath, { recursive: true });
94
+ }
95
+ saveRoutes({ step, folderPath: routesPath });
96
+ }
97
+ }
98
+
73
99
  if (step.isImplementedWhileRecording && !process.env.TEMP_RUN) {
74
100
  return;
75
101
  }
102
+
76
103
  if (step.isImplemented && step.shouldOverride) {
77
104
  let stepDef = stepsDefinitions.findMatchingStep(step.text);
78
105
  codePage = getCodePage(stepDef.file);
79
106
  } else {
80
107
  const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
108
+
81
109
  if (isUtilStep) {
82
110
  return;
83
111
  }
84
112
  }
113
+
114
+ if (process.env.TEMP_RUN === "true") {
115
+ console.log("Save routes in temp folder for running:", routesPath);
116
+ if (existsSync(routesPath)) {
117
+ console.log("Removing existing temp_routes_folder:", routesPath);
118
+ rmSync(routesPath, { recursive: true });
119
+ }
120
+ mkdirSync(routesPath, { recursive: true });
121
+ console.log("Created temp_routes_folder:", routesPath);
122
+ saveRoutes({ step, folderPath: routesPath });
123
+ } else {
124
+ console.log("Saving routes in project directory:", projectDir);
125
+ if (existsSync(routesPath)) {
126
+ // remove the folder
127
+ try {
128
+ rmSync(routesPath, { recursive: true });
129
+ console.log("Removed temp_routes_folder:", routesPath);
130
+ } catch (error) {
131
+ console.error("Error removing temp_routes folder", error);
132
+ }
133
+ }
134
+ routesPath = path.join(projectDir, "data", "routes");
135
+ console.log("Saving routes to:", routesPath);
136
+ if (!existsSync(routesPath)) {
137
+ mkdirSync(routesPath, { recursive: true });
138
+ }
139
+ saveRoutes({ step, folderPath: routesPath });
140
+ }
141
+
85
142
  cucumberStep.text = step.text;
86
143
  const recording = new Recording();
87
144
  const steps = step.commands;
@@ -108,6 +165,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
108
165
  isStaticToken,
109
166
  status,
110
167
  } = step.commands[0].value;
168
+
111
169
  const result = await generateApiCode(
112
170
  {
113
171
  url,
@@ -132,6 +190,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
132
190
  step.keyword,
133
191
  stepsDefinitions
134
192
  );
193
+
135
194
  if (!step.isImplemented) {
136
195
  stepsDefinitions.addStep({
137
196
  name: step.text,
@@ -139,6 +198,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
139
198
  source: "recorder",
140
199
  });
141
200
  }
201
+
142
202
  cucumberStep.methodName = result.methodName;
143
203
  return result.codePage;
144
204
  } else {
@@ -166,7 +226,13 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
166
226
  path
167
227
  );
168
228
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
169
- const stepResult = codePage.addCucumberStep(keyword, cucumberStep.getTemplate(), methodName, steps.length);
229
+ const stepResult = codePage.addCucumberStep(
230
+ keyword,
231
+ cucumberStep.getTemplate(),
232
+ methodName,
233
+ steps.length,
234
+ step.finalTimeout
235
+ );
170
236
 
171
237
  if (!step.isImplemented) {
172
238
  stepsDefinitions.addStep({
@@ -187,7 +253,17 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
187
253
 
188
254
  const getLocatorsJson = (file) => {
189
255
  if (!file) return {};
190
- const locatorsFilePath = file.replace(".mjs", ".json");
256
+ let locatorsFilePath = file.replace(".mjs", ".json");
257
+ const originLocatorsFilePath = locatorsFilePath;
258
+ const config = getAiConfig();
259
+ if (config && config.locatorsMetadataDir) {
260
+ // if config.locatorsMetadataDir is set, use it to create the file path
261
+ locatorsFilePath = path.join(config.locatorsMetadataDir, path.basename(locatorsFilePath));
262
+ if (!existsSync(locatorsFilePath)) {
263
+ // if the file does not exist in the config directory, use the original path
264
+ locatorsFilePath = originLocatorsFilePath;
265
+ }
266
+ }
191
267
  if (!existsSync(locatorsFilePath)) {
192
268
  return {};
193
269
  }
@@ -296,6 +372,12 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
296
372
  const utilsTemplateFilePath = path.join(__dirname, "../../assets", "templates", "utils_template.txt");
297
373
  const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
298
374
  writeFileSync(utilsFilePath, utilsContent, "utf8");
375
+ const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
376
+ if (existsSync(hooksTemplateFilePath)) {
377
+ const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
378
+ const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
379
+ writeFileSync(hooksFilePath, hooksContent, "utf8");
380
+ }
299
381
  const steps = scenario.steps;
300
382
 
301
383
  const stepsDefinitions = new StepsDefinitions(projectDir);
@@ -311,6 +393,34 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
311
393
  }
312
394
  }
313
395
  if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
396
+ let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
397
+ if (process.env.TEMP_RUN === "true") {
398
+ console.log("Save routes in temp folder for running:", routesPath);
399
+ if (existsSync(routesPath)) {
400
+ console.log("Removing existing temp_routes_folder:", routesPath);
401
+ rmSync(routesPath, { recursive: true });
402
+ }
403
+ mkdirSync(routesPath, { recursive: true });
404
+ console.log("Created temp_routes_folder:", routesPath);
405
+ saveRoutes({ step, folderPath: routesPath });
406
+ } else {
407
+ console.log("Saving routes in project directory:", projectDir);
408
+ if (existsSync(routesPath)) {
409
+ // remove the folder
410
+ try {
411
+ rmSync(routesPath, { recursive: true });
412
+ console.log("Removed temp_routes_folder:", routesPath);
413
+ } catch (error) {
414
+ console.error("Error removing temp_routes folder", error);
415
+ }
416
+ }
417
+ routesPath = path.join(projectDir, "data", "routes");
418
+ console.log("Saving routes to:", routesPath);
419
+ if (!existsSync(routesPath)) {
420
+ mkdirSync(routesPath, { recursive: true });
421
+ }
422
+ saveRoutes({ step, folderPath: routesPath });
423
+ }
314
424
  continue;
315
425
  }
316
426
  const cucumberStep = getCucumberStep({ step });
@@ -318,7 +428,6 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
318
428
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
319
429
  // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
320
430
  let codePage = getCodePage(stepDefsFilePath);
321
-
322
431
  codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
323
432
  if (!codePage) {
324
433
  continue;
@@ -330,3 +439,45 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
330
439
  }
331
440
  writeFileSync(utilsFilePath, utilsContent, "utf8");
332
441
  }
442
+
443
+ export function saveRoutes({ step, folderPath }) {
444
+ const routeItems = step.routeItems;
445
+ if (!routeItems || routeItems.length === 0) {
446
+ return;
447
+ }
448
+ const cucumberStep = getCucumberStep({ step });
449
+ const template = cucumberStep.getTemplate();
450
+ const stepNameHash = createHash("sha256").update(template).digest("hex");
451
+ console.log("Saving routes for step:", step.text, "with hash:", stepNameHash);
452
+
453
+ const routeItemsWithFilters = routeItems.map((routeItem) => {
454
+ const oldFilters = routeItem.filters;
455
+ const queryParamsObject = {};
456
+ oldFilters.queryParams.forEach((queryParam) => {
457
+ queryParamsObject[queryParam.key] = queryParam.value;
458
+ });
459
+ const newFilters = { path: oldFilters.path, method: oldFilters.method, queryParams: queryParamsObject };
460
+ return {
461
+ ...routeItem,
462
+ filters: newFilters,
463
+ };
464
+ });
465
+
466
+ const routesFilePath = path.join(folderPath, stepNameHash + ".json");
467
+ console.log("Routes file path:", routesFilePath);
468
+ const routesData = {
469
+ template,
470
+ routes: routeItemsWithFilters,
471
+ };
472
+ console.log("Routes data to save:", routesData);
473
+
474
+ if (!existsSync(folderPath)) {
475
+ mkdirSync(folderPath, { recursive: true });
476
+ }
477
+ try {
478
+ writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
479
+ console.log("Saved routes to", routesFilePath);
480
+ } catch (error) {
481
+ console.error("Failed to save routes to", routesFilePath, "Error:", error);
482
+ }
483
+ }