create-appraisejs 0.1.9 → 0.1.10-alpha.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 (159) hide show
  1. package/README.md +24 -17
  2. package/dist/cli.d.ts +2 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.e2e.test.js +11 -8
  5. package/dist/cli.e2e.test.js.map +1 -1
  6. package/dist/cli.js +32 -48
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +5 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/config.test.js +9 -5
  12. package/dist/config.test.js.map +1 -1
  13. package/dist/copy-template.d.ts +1 -1
  14. package/dist/copy-template.d.ts.map +1 -1
  15. package/dist/copy-template.js +7 -3
  16. package/dist/copy-template.js.map +1 -1
  17. package/dist/copy-template.test.js +14 -9
  18. package/dist/copy-template.test.js.map +1 -1
  19. package/dist/create-project.d.ts +23 -0
  20. package/dist/create-project.d.ts.map +1 -0
  21. package/dist/create-project.js +58 -0
  22. package/dist/create-project.js.map +1 -0
  23. package/dist/create-project.test.d.ts +2 -0
  24. package/dist/create-project.test.d.ts.map +1 -0
  25. package/dist/create-project.test.js +80 -0
  26. package/dist/create-project.test.js.map +1 -0
  27. package/dist/install.d.ts +8 -4
  28. package/dist/install.d.ts.map +1 -1
  29. package/dist/install.js +22 -72
  30. package/dist/install.js.map +1 -1
  31. package/dist/install.test.js +26 -10
  32. package/dist/install.test.js.map +1 -1
  33. package/dist/package-manager.d.ts +11 -0
  34. package/dist/package-manager.d.ts.map +1 -0
  35. package/dist/package-manager.js +47 -0
  36. package/dist/package-manager.js.map +1 -0
  37. package/dist/package-manager.test.d.ts +2 -0
  38. package/dist/package-manager.test.d.ts.map +1 -0
  39. package/dist/package-manager.test.js +51 -0
  40. package/dist/package-manager.test.js.map +1 -0
  41. package/dist/prepare-template-utils.d.ts +10 -0
  42. package/dist/prepare-template-utils.d.ts.map +1 -0
  43. package/dist/prepare-template-utils.js +53 -0
  44. package/dist/prepare-template-utils.js.map +1 -0
  45. package/dist/prepare-template-utils.test.d.ts +2 -0
  46. package/dist/prepare-template-utils.test.d.ts.map +1 -0
  47. package/dist/prepare-template-utils.test.js +67 -0
  48. package/dist/prepare-template-utils.test.js.map +1 -0
  49. package/dist/prompts.d.ts +2 -0
  50. package/dist/prompts.d.ts.map +1 -1
  51. package/dist/prompts.js +11 -3
  52. package/dist/prompts.js.map +1 -1
  53. package/dist/prompts.test.js +17 -7
  54. package/dist/prompts.test.js.map +1 -1
  55. package/dist/template-sync-utils.test.d.ts +2 -0
  56. package/dist/template-sync-utils.test.d.ts.map +1 -0
  57. package/dist/template-sync-utils.test.js +41 -0
  58. package/dist/template-sync-utils.test.js.map +1 -0
  59. package/package.json +3 -2
  60. package/templates/default/.appraise-template-meta.json +5 -0
  61. package/templates/default/.env.example +1 -1
  62. package/templates/default/.vscode/settings.json +10 -3
  63. package/templates/default/README.md +27 -25
  64. package/templates/default/automation/features/base/login.feature +15 -0
  65. package/templates/default/automation/locators/base/home.json +3 -0
  66. package/templates/default/automation/locators/base/login.json +6 -0
  67. package/templates/default/automation/locators/base/test.json +1 -0
  68. package/templates/default/automation/mapping/locator-map.json +14 -0
  69. package/templates/default/{src/tests → automation}/steps/actions/click.step.ts +1 -4
  70. package/templates/default/{src/tests → automation}/steps/actions/hover.step.ts +1 -4
  71. package/templates/default/{src/tests → automation}/steps/actions/input.step.ts +1 -4
  72. package/templates/default/{src/tests → automation}/steps/actions/navigation.step.ts +1 -3
  73. package/templates/default/{src/tests → automation}/steps/actions/random_data.step.ts +1 -3
  74. package/templates/default/{src/tests → automation}/steps/actions/store.step.ts +1 -4
  75. package/templates/default/automation/steps/actions/wait.step.ts +91 -0
  76. package/templates/default/{src/tests → automation}/steps/validations/active_state_assertion.step.ts +1 -4
  77. package/templates/default/{src/tests → automation}/steps/validations/navigation_assertion.step.ts +1 -2
  78. package/templates/default/{src/tests → automation}/steps/validations/text_assertion.step.ts +1 -4
  79. package/templates/default/{src/tests → automation}/steps/validations/visibility_assertion.step.ts +1 -4
  80. package/templates/default/cucumber.mjs +6 -6
  81. package/templates/default/eslint.config.mjs +5 -4
  82. package/templates/default/package-lock.json +322 -485
  83. package/templates/default/package.json +12 -6
  84. package/templates/default/packages/cucumber-runtime/package.json +13 -0
  85. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -0
  86. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -0
  87. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -0
  88. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -0
  89. package/templates/default/{src/tests/hooks → packages/cucumber-runtime/src}/hooks.ts +17 -32
  90. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -0
  91. package/templates/default/{src/tests/utils → packages/cucumber-runtime/src}/locator.util.ts +50 -64
  92. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -0
  93. package/templates/default/packages/cucumber-runtime/src/paths.ts +33 -0
  94. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -0
  95. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -0
  96. package/templates/default/{src/tests/config/executor → packages/cucumber-runtime/src}/world.ts +4 -1
  97. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -0
  98. package/templates/default/scripts/setup-env.ts +4 -4
  99. package/templates/default/scripts/sync-appraise-base-template.ts +124 -105
  100. package/templates/default/scripts/sync-environments.ts +8 -5
  101. package/templates/default/scripts/sync-locator-groups.ts +7 -10
  102. package/templates/default/scripts/sync-locators.ts +5 -9
  103. package/templates/default/scripts/sync-modules.ts +9 -17
  104. package/templates/default/scripts/sync-tags.ts +2 -2
  105. package/templates/default/scripts/sync-template-step-groups.ts +16 -6
  106. package/templates/default/scripts/sync-template-steps.ts +16 -5
  107. package/templates/default/scripts/sync-test-cases.ts +6 -3
  108. package/templates/default/scripts/sync-test-suites.ts +7 -4
  109. package/templates/default/src/actions/environments/environment-actions.ts +6 -23
  110. package/templates/default/src/actions/locator/locator-actions.ts +36 -93
  111. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +24 -78
  112. package/templates/default/src/actions/modules/module-actions.ts +4 -2
  113. package/templates/default/src/actions/tags/tag-actions.ts +4 -1
  114. package/templates/default/src/actions/template-step/template-step-actions.ts +10 -101
  115. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +31 -130
  116. package/templates/default/src/actions/test-case/test-case-actions.ts +31 -94
  117. package/templates/default/src/actions/test-run/test-run-actions.ts +11 -13
  118. package/templates/default/src/actions/test-suite/test-suite-actions.ts +29 -82
  119. package/templates/default/src/app/(base)/locator-groups/page.tsx +1 -3
  120. package/templates/default/src/app/(base)/reports/page.tsx +1 -1
  121. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +2 -2
  122. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +1 -1
  123. package/templates/default/src/app/(base)/tags/page.tsx +2 -2
  124. package/templates/default/src/app/(base)/template-steps/page.tsx +1 -2
  125. package/templates/default/src/app/(base)/test-runs/page.tsx +2 -2
  126. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +2 -1
  127. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +2 -1
  128. package/templates/default/src/app/page.tsx +4 -5
  129. package/templates/default/src/components/diagram/dynamic-parameters.tsx +76 -40
  130. package/templates/default/src/components/diagram/options-header-node.tsx +1 -1
  131. package/templates/default/src/components/ui/data-table.tsx +33 -39
  132. package/templates/default/src/lib/automation/paths.ts +181 -0
  133. package/templates/default/src/lib/automation/projection-service.ts +230 -0
  134. package/templates/default/src/lib/environment-file-utils.ts +14 -51
  135. package/templates/default/src/lib/executor/local-executor-adapter.ts +101 -0
  136. package/templates/default/src/lib/executor/types.ts +24 -0
  137. package/templates/default/src/lib/feature-file-generator.ts +22 -112
  138. package/templates/default/src/lib/locator-group-file-utils.ts +57 -120
  139. package/templates/default/src/lib/process/task-spawner.ts +236 -0
  140. package/templates/default/src/lib/template-sync-utils.d.ts +7 -0
  141. package/templates/default/src/lib/template-sync-utils.d.ts.map +1 -0
  142. package/templates/default/src/lib/template-sync-utils.js +47 -0
  143. package/templates/default/src/lib/template-sync-utils.js.map +1 -0
  144. package/templates/default/src/lib/template-sync-utils.ts +63 -0
  145. package/templates/default/src/lib/test-run/process-manager.ts +9 -87
  146. package/templates/default/src/lib/test-run/test-run-executor.ts +7 -136
  147. package/templates/default/src/lib/test-run/winston-logger.ts +6 -35
  148. package/templates/default/src/lib/utils/template-step-file-generator.ts +22 -85
  149. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +7 -22
  150. package/templates/default/public/favicon.ico +0 -0
  151. package/templates/default/src/tests/executor.ts +0 -80
  152. package/templates/default/src/tests/mapping/locator-map.json +0 -1
  153. package/templates/default/src/tests/steps/actions/wait.step.ts +0 -107
  154. package/templates/default/src/tests/support/parameter-types.ts +0 -12
  155. package/templates/default/src/tests/utils/cache.util.ts +0 -260
  156. package/templates/default/src/tests/utils/cli.util.ts +0 -177
  157. package/templates/default/src/tests/utils/environment.util.ts +0 -65
  158. package/templates/default/src/tests/utils/random-data.util.ts +0 -45
  159. package/templates/default/src/tests/utils/spawner.util.ts +0 -617
@@ -2,44 +2,29 @@
2
2
 
3
3
  import prisma from '@/config/db-config'
4
4
  import { locatorGroupSchema } from '@/constants/form-opts/locator-group-form-opts'
5
+ import { automationProjectionService } from '@/lib/automation/projection-service'
6
+ import { getLocatorGroupFilePath, readLocatorGroupFile } from '@/lib/locator-group-file-utils'
5
7
  import { ActionResponse } from '@/types/form/actionHandler'
6
8
  import { revalidatePath } from 'next/cache'
7
9
  import { z } from 'zod'
8
10
  import { Prisma } from '@prisma/client'
9
- import {
10
- createOrUpdateLocatorGroupFile,
11
- deleteLocatorGroupFile,
12
- renameLocatorGroupFile,
13
- moveLocatorGroupFile,
14
- createEmptyLocatorGroupFile,
15
- readLocatorGroupFile,
16
- updateLocatorMapFile,
17
- removeLocatorMapEntry,
18
- } from '@/lib/locator-group-file-utils'
19
-
20
- // Common include pattern for locator groups
11
+
21
12
  const locatorGroupInclude = {
22
13
  module: {
23
14
  select: { name: true },
24
15
  },
25
16
  } as const
26
17
 
27
- /**
28
- * Check if a locator group name already exists
29
- */
30
18
  async function checkUniqueName(name: string, excludeId?: string): Promise<boolean> {
31
19
  const existing = await prisma.locatorGroup.findFirst({
32
20
  where: {
33
- name: name,
21
+ name,
34
22
  ...(excludeId && { id: { not: excludeId } }),
35
23
  },
36
24
  })
37
25
  return !!existing
38
26
  }
39
27
 
40
- /**
41
- * Get all locator groups
42
- */
43
28
  export async function getAllLocatorGroupsAction(): Promise<ActionResponse> {
44
29
  try {
45
30
  const locatorGroups = await prisma.locatorGroup.findMany({
@@ -58,9 +43,6 @@ export async function getAllLocatorGroupsAction(): Promise<ActionResponse> {
58
43
  }
59
44
  }
60
45
 
61
- /**
62
- * Get a locator group by ID
63
- */
64
46
  export async function getLocatorGroupByIdAction(id: string): Promise<ActionResponse> {
65
47
  try {
66
48
  const locatorGroup = await prisma.locatorGroup.findUnique({
@@ -80,15 +62,11 @@ export async function getLocatorGroupByIdAction(id: string): Promise<ActionRespo
80
62
  }
81
63
  }
82
64
 
83
- /**
84
- * Create a new locator group
85
- */
86
65
  export async function createLocatorGroupAction(
87
66
  _prev: unknown,
88
67
  value: z.infer<typeof locatorGroupSchema>,
89
68
  ): Promise<ActionResponse> {
90
69
  try {
91
- // Check if name already exists
92
70
  const nameExists = await checkUniqueName(value.name)
93
71
  if (nameExists) {
94
72
  return {
@@ -96,6 +74,7 @@ export async function createLocatorGroupAction(
96
74
  error: 'A locator group with this name already exists. Please choose a different name.',
97
75
  }
98
76
  }
77
+
99
78
  const locatorGroup = await prisma.locatorGroup.create({
100
79
  data: {
101
80
  name: value.name,
@@ -107,9 +86,8 @@ export async function createLocatorGroupAction(
107
86
  },
108
87
  })
109
88
 
110
- // Create empty JSON file initially
111
- await createEmptyLocatorGroupFile(locatorGroup.id)
112
- await updateLocatorMapFile(value.name, value.route ?? '/')
89
+ await automationProjectionService.createEmptyLocatorGroup(locatorGroup.id)
90
+ await automationProjectionService.syncLocatorMap(value.name, value.route ?? '/')
113
91
 
114
92
  revalidatePath('/locator-groups')
115
93
  return {
@@ -118,7 +96,6 @@ export async function createLocatorGroupAction(
118
96
  message: 'Locator group created successfully',
119
97
  }
120
98
  } catch (error) {
121
- // Handle Prisma unique constraint error
122
99
  if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
123
100
  return {
124
101
  status: 400,
@@ -132,16 +109,12 @@ export async function createLocatorGroupAction(
132
109
  }
133
110
  }
134
111
 
135
- /**
136
- * Update an existing locator group
137
- */
138
112
  export async function updateLocatorGroupAction(
139
113
  _prev: unknown,
140
114
  value: z.infer<typeof locatorGroupSchema>,
141
115
  id?: string,
142
116
  ): Promise<ActionResponse> {
143
117
  try {
144
- // Get current state to detect changes
145
118
  const currentLocatorGroup = await prisma.locatorGroup.findUnique({
146
119
  where: { id },
147
120
  include: { module: true },
@@ -154,7 +127,6 @@ export async function updateLocatorGroupAction(
154
127
  }
155
128
  }
156
129
 
157
- // Check if name already exists (only if name is changing)
158
130
  if (currentLocatorGroup.name !== value.name) {
159
131
  const nameExists = await checkUniqueName(value.name, id)
160
132
  if (nameExists) {
@@ -165,7 +137,8 @@ export async function updateLocatorGroupAction(
165
137
  }
166
138
  }
167
139
 
168
- // Update the locator group
140
+ const previousFilePath = await getLocatorGroupFilePath(id!)
141
+
169
142
  const updatedLocatorGroup = await prisma.locatorGroup.update({
170
143
  where: { id },
171
144
  data: {
@@ -179,28 +152,27 @@ export async function updateLocatorGroupAction(
179
152
  include: locatorGroupInclude,
180
153
  })
181
154
 
182
- // Handle file operations based on changes
183
155
  const nameChanged = currentLocatorGroup.name !== value.name
184
156
  const moduleChanged = currentLocatorGroup.moduleId !== value.moduleId
185
157
  const routeChanged = currentLocatorGroup.route !== value.route
186
158
 
187
- if (nameChanged && moduleChanged) {
188
- // Both changed - move the file (this will handle both changes)
189
- await moveLocatorGroupFile(id!)
159
+ if (moduleChanged) {
160
+ await automationProjectionService.moveLocatorGroup(id!, previousFilePath ?? undefined)
190
161
  } else if (nameChanged) {
191
- // Only name changed - rename the file
192
- await renameLocatorGroupFile(id!, value.name, currentLocatorGroup.name)
193
- } else if (moduleChanged) {
194
- // Only module changed - move the file
195
- await moveLocatorGroupFile(id!)
162
+ await automationProjectionService.renameLocatorGroup(id!, value.name, currentLocatorGroup.name)
196
163
  } else {
197
- // No structural changes - just update content
198
- await createOrUpdateLocatorGroupFile(id!)
164
+ await automationProjectionService.syncLocatorGroup(id!)
199
165
  }
200
166
 
201
167
  if (routeChanged || nameChanged) {
202
- await updateLocatorMapFile(currentLocatorGroup.route, value.route ?? '/', currentLocatorGroup.name, value.name)
168
+ await automationProjectionService.syncLocatorMap(
169
+ currentLocatorGroup.route,
170
+ value.route ?? '/',
171
+ currentLocatorGroup.name,
172
+ value.name,
173
+ )
203
174
  }
175
+
204
176
  revalidatePath('/locator-groups')
205
177
  return {
206
178
  status: 200,
@@ -208,7 +180,6 @@ export async function updateLocatorGroupAction(
208
180
  message: 'Locator group updated successfully',
209
181
  }
210
182
  } catch (error) {
211
- // Handle Prisma unique constraint error
212
183
  if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
213
184
  return {
214
185
  status: 400,
@@ -222,26 +193,16 @@ export async function updateLocatorGroupAction(
222
193
  }
223
194
  }
224
195
 
225
- /**
226
- * Delete locator groups
227
- */
228
196
  export async function deleteLocatorGroupAction(ids: string[]): Promise<ActionResponse> {
229
197
  try {
230
- // Get locator group names before deletion for locator map cleanup
231
198
  const locatorGroupsToDelete = await prisma.locatorGroup.findMany({
232
199
  where: { id: { in: ids } },
233
200
  select: { name: true },
234
201
  })
235
202
 
236
- const locatorGroupNames = locatorGroupsToDelete.map(group => group.name)
237
-
238
- // Remove entries from locator map
239
- await removeLocatorMapEntry(locatorGroupNames)
203
+ await automationProjectionService.deleteLocatorMapEntries(locatorGroupsToDelete.map(group => group.name))
204
+ await Promise.all(ids.map(id => automationProjectionService.deleteLocatorGroup(id)))
240
205
 
241
- // Delete JSON files first
242
- await Promise.all(ids.map(id => deleteLocatorGroupFile(id)))
243
-
244
- // Delete the locator groups (locators will be deleted via cascade)
245
206
  await prisma.locatorGroup.deleteMany({
246
207
  where: { id: { in: ids } },
247
208
  })
@@ -260,9 +221,6 @@ export async function deleteLocatorGroupAction(ids: string[]): Promise<ActionRes
260
221
  }
261
222
  }
262
223
 
263
- /**
264
- * Get the content of a specific locator group file
265
- */
266
224
  export async function getLocatorGroupFileContentAction(locatorGroupId: string): Promise<ActionResponse> {
267
225
  try {
268
226
  const fileData = await readLocatorGroupFile(locatorGroupId)
@@ -286,9 +244,6 @@ export async function getLocatorGroupFileContentAction(locatorGroupId: string):
286
244
  }
287
245
  }
288
246
 
289
- /**
290
- * Check if a locator group name is unique
291
- */
292
247
  export async function checkLocatorGroupNameUniqueAction(name: string, excludeId?: string): Promise<ActionResponse> {
293
248
  try {
294
249
  const nameExists = await checkUniqueName(name, excludeId)
@@ -304,23 +259,14 @@ export async function checkLocatorGroupNameUniqueAction(name: string, excludeId?
304
259
  }
305
260
  }
306
261
 
307
- /**
308
- * Regenerate all locator group files from database
309
- */
310
262
  export async function regenerateAllLocatorGroupFilesAction(): Promise<ActionResponse> {
311
263
  try {
312
264
  const locatorGroups = await prisma.locatorGroup.findMany({
313
- include: {
314
- module: true,
315
- locators: {
316
- select: { name: true, value: true },
317
- },
318
- },
265
+ select: { id: true },
319
266
  })
320
267
 
321
- // Process all files in parallel for better performance
322
268
  const results = await Promise.allSettled(
323
- locatorGroups.map(locatorGroup => createOrUpdateLocatorGroupFile(locatorGroup.id)),
269
+ locatorGroups.map(locatorGroup => automationProjectionService.syncLocatorGroup(locatorGroup.id)),
324
270
  )
325
271
 
326
272
  const successCount = results.filter(result => result.status === 'fulfilled' && result.value).length
@@ -2,6 +2,7 @@
2
2
 
3
3
  import prisma from '@/config/db-config'
4
4
  import { moduleSchema, ROOT_MODULE_UUID } from '@/constants/form-opts/module-form-opts'
5
+ import { automationProjectionService } from '@/lib/automation/projection-service'
5
6
  import { ActionResponse } from '@/types/form/actionHandler'
6
7
  import { revalidatePath } from 'next/cache'
7
8
  import { z } from 'zod'
@@ -36,6 +37,7 @@ export async function deleteModuleAction(ids: string[]): Promise<ActionResponse>
36
37
  id: { in: ids },
37
38
  },
38
39
  })
40
+ await automationProjectionService.regenerateAllPathDependentArtifacts()
39
41
  revalidatePath('/modules')
40
42
  return {
41
43
  status: 200,
@@ -53,7 +55,6 @@ export async function createModuleAction(_prev: unknown, value: z.infer<typeof m
53
55
  try {
54
56
  moduleSchema.parse(value)
55
57
 
56
- // Convert the special root UUID to null for database storage
57
58
  const moduleData = {
58
59
  ...value,
59
60
  parentId: value.parentId === ROOT_MODULE_UUID ? null : value.parentId,
@@ -62,6 +63,7 @@ export async function createModuleAction(_prev: unknown, value: z.infer<typeof m
62
63
  const newModule = await prisma.module.create({
63
64
  data: moduleData,
64
65
  })
66
+ await automationProjectionService.regenerateAllPathDependentArtifacts()
65
67
  revalidatePath('/modules')
66
68
  return {
67
69
  status: 200,
@@ -108,7 +110,6 @@ export async function updateModuleAction(
108
110
  try {
109
111
  moduleSchema.parse(value)
110
112
 
111
- // Convert the special root UUID to null for database storage
112
113
  const moduleData = {
113
114
  ...value,
114
115
  parentId: value.parentId === ROOT_MODULE_UUID ? null : value.parentId,
@@ -118,6 +119,7 @@ export async function updateModuleAction(
118
119
  where: { id },
119
120
  data: moduleData,
120
121
  })
122
+ await automationProjectionService.regenerateAllPathDependentArtifacts()
121
123
  revalidatePath('/modules')
122
124
  return {
123
125
  status: 200,
@@ -2,6 +2,7 @@
2
2
 
3
3
  import prisma from '@/config/db-config'
4
4
  import { tagSchema } from '@/constants/form-opts/tag-form-opts'
5
+ import { automationProjectionService } from '@/lib/automation/projection-service'
5
6
  import { ActionResponse } from '@/types/form/actionHandler'
6
7
  import { TagType } from '@prisma/client'
7
8
  import { revalidatePath } from 'next/cache'
@@ -29,7 +30,7 @@ export async function getAllTagsAction(): Promise<ActionResponse> {
29
30
  export async function deleteTagAction(ids: string[]): Promise<ActionResponse> {
30
31
  try {
31
32
  await prisma.tag.deleteMany({ where: { id: { in: ids } } })
32
-
33
+ await automationProjectionService.regenerateAllFeatures()
33
34
  revalidatePath('/tags')
34
35
 
35
36
  return {
@@ -50,6 +51,7 @@ export async function createTagAction(_prev: unknown, value: z.infer<typeof tagS
50
51
  data: value,
51
52
  })
52
53
 
54
+ await automationProjectionService.regenerateAllFeatures()
53
55
  revalidatePath('/tags')
54
56
 
55
57
  return {
@@ -88,6 +90,7 @@ export async function updateTagAction(
88
90
  try {
89
91
  const updatedTag = await prisma.tag.update({ where: { id }, data: value })
90
92
 
93
+ await automationProjectionService.regenerateAllFeatures()
91
94
  revalidatePath('/tags')
92
95
 
93
96
  return {
@@ -2,37 +2,11 @@
2
2
 
3
3
  import prisma from '@/config/db-config'
4
4
  import { templateStepSchema } from '@/constants/form-opts/template-test-step-form-opts'
5
+ import { automationProjectionService } from '@/lib/automation/projection-service'
5
6
  import { ActionResponse } from '@/types/form/actionHandler'
6
7
  import { StepParameterType, TemplateStepIcon, TemplateStepType } from '@prisma/client'
7
8
  import { revalidatePath } from 'next/cache'
8
9
  import { z } from 'zod'
9
- import {
10
- addTemplateStepToFile,
11
- removeTemplateStepFromFile,
12
- updateTemplateStepInFile,
13
- } from '@/lib/utils/template-step-file-manager-intelligent'
14
-
15
- // TemplateStepGroupType helper - will be available from @prisma/client after migration
16
- type TemplateStepGroupType = 'ACTION' | 'VALIDATION'
17
-
18
- // Type helper to safely extract type from Prisma templateStepGroup records
19
- type TemplateStepGroupWithType = {
20
- id: string
21
- name: string
22
- description: string | null
23
- type?: TemplateStepGroupType
24
- createdAt: Date
25
- updatedAt: Date
26
- }
27
-
28
- function getGroupTypeFromRelation(group: unknown): TemplateStepGroupType {
29
- const groupWithType = group as TemplateStepGroupWithType
30
- const type = groupWithType.type
31
- if (type === 'VALIDATION' || type === 'ACTION') {
32
- return type
33
- }
34
- return 'ACTION' // default
35
- }
36
10
 
37
11
  export async function getAllTemplateStepsAction(): Promise<ActionResponse> {
38
12
  try {
@@ -61,19 +35,14 @@ export async function getAllTemplateStepsAction(): Promise<ActionResponse> {
61
35
 
62
36
  export async function deleteTemplateStepAction(templateStepIds: string[]): Promise<ActionResponse> {
63
37
  try {
64
- // Get the template steps with their group info before deletion
65
38
  const stepsToDelete = await prisma.templateStep.findMany({
66
39
  where: { id: { in: templateStepIds } },
67
- include: {
68
- templateStepGroup: true,
40
+ select: {
41
+ templateStepGroupId: true,
69
42
  },
70
43
  })
71
44
 
72
- // Delete in order: child records first. TemplateTestCaseStepParameter and
73
- // TestCaseStepParameter must be removed before TemplateTestCaseStep/TestCaseStep
74
- // (which are cascade-deleted from TemplateStep).
75
45
  await prisma.$transaction(async tx => {
76
- // Delete TemplateTestCaseStepParameter records first
77
46
  await tx.templateTestCaseStepParameter.deleteMany({
78
47
  where: {
79
48
  templateTestCaseStep: {
@@ -82,7 +51,6 @@ export async function deleteTemplateStepAction(templateStepIds: string[]): Promi
82
51
  },
83
52
  })
84
53
 
85
- // Delete TestCaseStepParameter records
86
54
  await tx.testCaseStepParameter.deleteMany({
87
55
  where: {
88
56
  testCaseStep: {
@@ -91,14 +59,12 @@ export async function deleteTemplateStepAction(templateStepIds: string[]): Promi
91
59
  },
92
60
  })
93
61
 
94
- // Delete the template step parameters
95
62
  await tx.templateStepParameter.deleteMany({
96
63
  where: {
97
64
  templateStepId: { in: templateStepIds },
98
65
  },
99
66
  })
100
67
 
101
- // Delete the template steps (this will cascade delete TemplateTestCaseStep and TestCaseStep)
102
68
  await tx.templateStep.deleteMany({
103
69
  where: {
104
70
  id: { in: templateStepIds },
@@ -106,18 +72,8 @@ export async function deleteTemplateStepAction(templateStepIds: string[]): Promi
106
72
  })
107
73
  })
108
74
 
109
- // Remove the deleted steps from their respective group files
110
- for (const step of stepsToDelete) {
111
- if (step.templateStepGroup) {
112
- try {
113
- const groupType = getGroupTypeFromRelation(step.templateStepGroup)
114
- await removeTemplateStepFromFile(step.templateStepGroup.name, step, groupType)
115
- } catch (fileError) {
116
- console.error(`Failed to remove step from file for group "${step.templateStepGroup.name}":`, fileError)
117
- // Don't fail the entire operation if file update fails
118
- }
119
- }
120
- }
75
+ const affectedGroupIds = [...new Set(stepsToDelete.map(step => step.templateStepGroupId))]
76
+ await Promise.all(affectedGroupIds.map(groupId => automationProjectionService.syncTemplateStepGroup(groupId)))
121
77
 
122
78
  revalidatePath('/template-steps')
123
79
  return {
@@ -137,7 +93,6 @@ export async function createTemplateStepAction(
137
93
  value: z.infer<typeof templateStepSchema>,
138
94
  ): Promise<ActionResponse> {
139
95
  try {
140
- // First, try to create the template step
141
96
  const newTemplateStep = await prisma.templateStep.create({
142
97
  data: {
143
98
  name: value.name,
@@ -159,21 +114,9 @@ export async function createTemplateStepAction(
159
114
  },
160
115
  },
161
116
  },
162
- include: {
163
- templateStepGroup: true,
164
- },
165
117
  })
166
118
 
167
- // If database creation succeeds, add the step to the group's file
168
- if (newTemplateStep.templateStepGroup) {
169
- try {
170
- const groupType = getGroupTypeFromRelation(newTemplateStep.templateStepGroup)
171
- await addTemplateStepToFile(newTemplateStep.templateStepGroup.name, newTemplateStep, groupType)
172
- } catch (fileError) {
173
- console.error(`Failed to add step to file after creating template step:`, fileError)
174
- // Don't fail the entire operation if file update fails
175
- }
176
- }
119
+ await automationProjectionService.syncTemplateStepGroup(newTemplateStep.templateStepGroupId)
177
120
 
178
121
  revalidatePath('/template-steps')
179
122
 
@@ -203,11 +146,10 @@ export async function updateTemplateStepAction(
203
146
  }
204
147
  }
205
148
 
206
- // Get the current template step to check if group changed
207
149
  const currentStep = await prisma.templateStep.findUnique({
208
150
  where: { id },
209
- include: {
210
- templateStepGroup: true,
151
+ select: {
152
+ templateStepGroupId: true,
211
153
  },
212
154
  })
213
155
 
@@ -218,10 +160,6 @@ export async function updateTemplateStepAction(
218
160
  }
219
161
  }
220
162
 
221
- // Check if the group changed
222
- const groupChanged = currentStep.templateStepGroupId !== value.templateStepGroupId
223
-
224
- // Update the template step
225
163
  const updatedTemplateStep = await prisma.templateStep.update({
226
164
  where: { id },
227
165
  data: {
@@ -247,39 +185,10 @@ export async function updateTemplateStepAction(
247
185
  },
248
186
  },
249
187
  },
250
- include: {
251
- templateStepGroup: true,
252
- },
253
188
  })
254
189
 
255
- // Update files for affected groups
256
- try {
257
- if (groupChanged) {
258
- // If group changed, remove from old group and add to new group
259
- if (currentStep.templateStepGroup) {
260
- const oldGroupType = getGroupTypeFromRelation(currentStep.templateStepGroup)
261
- await removeTemplateStepFromFile(currentStep.templateStepGroup.name, currentStep, oldGroupType)
262
- }
263
- if (updatedTemplateStep.templateStepGroup) {
264
- const newGroupType = getGroupTypeFromRelation(updatedTemplateStep.templateStepGroup)
265
- await addTemplateStepToFile(updatedTemplateStep.templateStepGroup.name, updatedTemplateStep, newGroupType)
266
- }
267
- } else {
268
- // If group didn't change, just update the step in the current group
269
- if (updatedTemplateStep.templateStepGroup) {
270
- const groupType = getGroupTypeFromRelation(updatedTemplateStep.templateStepGroup)
271
- await updateTemplateStepInFile(
272
- updatedTemplateStep.templateStepGroup.name,
273
- updatedTemplateStep,
274
- groupType,
275
- currentStep,
276
- )
277
- }
278
- }
279
- } catch (fileError) {
280
- console.error(`Failed to update file after updating template step:`, fileError)
281
- // Don't fail the entire operation if file update fails
282
- }
190
+ const affectedGroupIds = new Set([currentStep.templateStepGroupId, updatedTemplateStep.templateStepGroupId])
191
+ await Promise.all(Array.from(affectedGroupIds).map(groupId => automationProjectionService.syncTemplateStepGroup(groupId)))
283
192
 
284
193
  revalidatePath('/template-steps')
285
194
  return {