create-appraisejs 0.1.8 → 0.1.10-alpha.0
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.
- package/README.md +24 -17
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.e2e.test.js +11 -8
- package/dist/cli.e2e.test.js.map +1 -1
- package/dist/cli.js +32 -48
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +9 -5
- package/dist/config.test.js.map +1 -1
- package/dist/copy-template.d.ts +1 -1
- package/dist/copy-template.d.ts.map +1 -1
- package/dist/copy-template.js +7 -3
- package/dist/copy-template.js.map +1 -1
- package/dist/copy-template.test.js +14 -9
- package/dist/copy-template.test.js.map +1 -1
- package/dist/create-project.d.ts +23 -0
- package/dist/create-project.d.ts.map +1 -0
- package/dist/create-project.js +58 -0
- package/dist/create-project.js.map +1 -0
- package/dist/create-project.test.d.ts +2 -0
- package/dist/create-project.test.d.ts.map +1 -0
- package/dist/create-project.test.js +80 -0
- package/dist/create-project.test.js.map +1 -0
- package/dist/install.d.ts +8 -4
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +22 -72
- package/dist/install.js.map +1 -1
- package/dist/install.test.js +26 -10
- package/dist/install.test.js.map +1 -1
- package/dist/package-manager.d.ts +11 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +47 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/package-manager.test.d.ts +2 -0
- package/dist/package-manager.test.d.ts.map +1 -0
- package/dist/package-manager.test.js +51 -0
- package/dist/package-manager.test.js.map +1 -0
- package/dist/prepare-template-utils.d.ts +10 -0
- package/dist/prepare-template-utils.d.ts.map +1 -0
- package/dist/prepare-template-utils.js +53 -0
- package/dist/prepare-template-utils.js.map +1 -0
- package/dist/prepare-template-utils.test.d.ts +2 -0
- package/dist/prepare-template-utils.test.d.ts.map +1 -0
- package/dist/prepare-template-utils.test.js +67 -0
- package/dist/prepare-template-utils.test.js.map +1 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +11 -3
- package/dist/prompts.js.map +1 -1
- package/dist/prompts.test.js +17 -7
- package/dist/prompts.test.js.map +1 -1
- package/dist/template-sync-utils.test.d.ts +2 -0
- package/dist/template-sync-utils.test.d.ts.map +1 -0
- package/dist/template-sync-utils.test.js +41 -0
- package/dist/template-sync-utils.test.js.map +1 -0
- package/package.json +3 -2
- package/templates/default/.appraise-template-meta.json +5 -0
- package/templates/default/.env.example +1 -1
- package/templates/default/.vscode/settings.json +10 -3
- package/templates/default/README.md +27 -25
- package/templates/default/automation/features/base/login.feature +15 -0
- package/templates/default/automation/locators/base/home.json +3 -0
- package/templates/default/automation/locators/base/login.json +6 -0
- package/templates/default/automation/locators/base/test.json +1 -0
- package/templates/default/automation/mapping/locator-map.json +14 -0
- package/templates/default/{src/tests → automation}/steps/actions/click.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/actions/hover.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/actions/input.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/actions/navigation.step.ts +1 -3
- package/templates/default/{src/tests → automation}/steps/actions/random_data.step.ts +1 -3
- package/templates/default/{src/tests → automation}/steps/actions/store.step.ts +1 -4
- package/templates/default/automation/steps/actions/wait.step.ts +91 -0
- package/templates/default/{src/tests → automation}/steps/validations/active_state_assertion.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/validations/navigation_assertion.step.ts +1 -2
- package/templates/default/{src/tests → automation}/steps/validations/text_assertion.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/validations/visibility_assertion.step.ts +1 -4
- package/templates/default/cucumber.mjs +6 -6
- package/templates/default/eslint.config.mjs +5 -4
- package/templates/default/package-lock.json +322 -485
- package/templates/default/package.json +11 -6
- package/templates/default/packages/cucumber-runtime/package.json +13 -0
- package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -0
- package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -0
- package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -0
- package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -0
- package/templates/default/{src/tests/hooks → packages/cucumber-runtime/src}/hooks.ts +17 -32
- package/templates/default/packages/cucumber-runtime/src/index.ts +17 -0
- package/templates/default/{src/tests/utils → packages/cucumber-runtime/src}/locator.util.ts +50 -64
- package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -0
- package/templates/default/packages/cucumber-runtime/src/paths.ts +33 -0
- package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -0
- package/templates/default/packages/cucumber-runtime/src/types.ts +13 -0
- package/templates/default/{src/tests/config/executor → packages/cucumber-runtime/src}/world.ts +4 -1
- package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -0
- package/templates/default/scripts/setup-env.ts +4 -4
- package/templates/default/scripts/sync-appraise-base-template.ts +123 -105
- package/templates/default/scripts/sync-environments.ts +8 -5
- package/templates/default/scripts/sync-locator-groups.ts +7 -10
- package/templates/default/scripts/sync-locators.ts +5 -9
- package/templates/default/scripts/sync-modules.ts +9 -17
- package/templates/default/scripts/sync-tags.ts +2 -2
- package/templates/default/scripts/sync-template-step-groups.ts +16 -6
- package/templates/default/scripts/sync-template-steps.ts +16 -5
- package/templates/default/scripts/sync-test-cases.ts +6 -3
- package/templates/default/scripts/sync-test-suites.ts +7 -4
- package/templates/default/src/actions/environments/environment-actions.ts +6 -23
- package/templates/default/src/actions/locator/locator-actions.ts +36 -93
- package/templates/default/src/actions/locator-groups/locator-group-actions.ts +24 -78
- package/templates/default/src/actions/modules/module-actions.ts +4 -2
- package/templates/default/src/actions/tags/tag-actions.ts +4 -1
- package/templates/default/src/actions/template-step/template-step-actions.ts +10 -101
- package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +31 -130
- package/templates/default/src/actions/test-case/test-case-actions.ts +31 -94
- package/templates/default/src/actions/test-run/test-run-actions.ts +11 -13
- package/templates/default/src/actions/test-suite/test-suite-actions.ts +29 -82
- package/templates/default/src/app/(base)/locator-groups/page.tsx +1 -3
- package/templates/default/src/app/(base)/reports/page.tsx +1 -1
- package/templates/default/src/app/(base)/reports/test-cases/page.tsx +2 -2
- package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +1 -1
- package/templates/default/src/app/(base)/tags/page.tsx +2 -2
- package/templates/default/src/app/(base)/template-steps/page.tsx +1 -2
- package/templates/default/src/app/(base)/test-runs/page.tsx +2 -2
- package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +2 -1
- package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +2 -1
- package/templates/default/src/app/page.tsx +4 -5
- package/templates/default/src/components/diagram/dynamic-parameters.tsx +76 -40
- package/templates/default/src/components/diagram/options-header-node.tsx +1 -1
- package/templates/default/src/components/ui/data-table.tsx +33 -39
- package/templates/default/src/lib/automation/paths.ts +181 -0
- package/templates/default/src/lib/automation/projection-service.ts +230 -0
- package/templates/default/src/lib/environment-file-utils.ts +14 -51
- package/templates/default/src/lib/executor/local-executor-adapter.ts +101 -0
- package/templates/default/src/lib/executor/types.ts +24 -0
- package/templates/default/src/lib/feature-file-generator.ts +22 -112
- package/templates/default/src/lib/locator-group-file-utils.ts +57 -120
- package/templates/default/src/lib/process/task-spawner.ts +236 -0
- package/templates/default/src/lib/template-sync-utils.d.ts +7 -0
- package/templates/default/src/lib/template-sync-utils.d.ts.map +1 -0
- package/templates/default/src/lib/template-sync-utils.js +47 -0
- package/templates/default/src/lib/template-sync-utils.js.map +1 -0
- package/templates/default/src/lib/template-sync-utils.ts +63 -0
- package/templates/default/src/lib/test-run/process-manager.ts +9 -87
- package/templates/default/src/lib/test-run/test-run-executor.ts +7 -136
- package/templates/default/src/lib/test-run/winston-logger.ts +6 -35
- package/templates/default/src/lib/utils/template-step-file-generator.ts +22 -85
- package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +7 -22
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/src/tests/executor.ts +0 -80
- package/templates/default/src/tests/mapping/locator-map.json +0 -1
- package/templates/default/src/tests/steps/actions/wait.step.ts +0 -107
- package/templates/default/src/tests/support/parameter-types.ts +0 -12
- package/templates/default/src/tests/utils/cache.util.ts +0 -260
- package/templates/default/src/tests/utils/cli.util.ts +0 -177
- package/templates/default/src/tests/utils/environment.util.ts +0 -65
- package/templates/default/src/tests/utils/random-data.util.ts +0 -45
- package/templates/default/src/tests/utils/spawner.util.ts +0 -617
|
@@ -2,35 +2,36 @@ import { promises as fs } from 'fs'
|
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import prisma from '@/config/db-config'
|
|
4
4
|
import { buildModulePath } from '@/lib/path-helpers/module-path'
|
|
5
|
+
import {
|
|
6
|
+
ensureAutomationWorkspaceReady,
|
|
7
|
+
getAutomationLocatorsDir,
|
|
8
|
+
getAutomationMappingDir,
|
|
9
|
+
} from '@/lib/automation/paths'
|
|
5
10
|
|
|
6
|
-
/**
|
|
7
|
-
* Gets the file path for a locator group based on its module hierarchy
|
|
8
|
-
*/
|
|
9
11
|
export async function getLocatorGroupFilePath(locatorGroupId: string): Promise<string | null> {
|
|
10
12
|
try {
|
|
13
|
+
await ensureAutomationWorkspaceReady()
|
|
11
14
|
const locatorGroup = await prisma.locatorGroup.findUnique({
|
|
12
15
|
where: { id: locatorGroupId },
|
|
13
16
|
include: { module: true },
|
|
14
17
|
})
|
|
15
18
|
|
|
16
|
-
if (!locatorGroup)
|
|
19
|
+
if (!locatorGroup) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
const allModules = await prisma.module.findMany()
|
|
19
24
|
const modulePath = buildModulePath(allModules, locatorGroup.module)
|
|
20
|
-
|
|
21
25
|
const sanitizedPath = modulePath.replace(/^\//, '').replace(/\//g, path.sep)
|
|
22
26
|
const fileName = `${locatorGroup.name}.json`
|
|
23
27
|
|
|
24
|
-
return path.join(
|
|
28
|
+
return path.join(getAutomationLocatorsDir(), sanitizedPath, fileName)
|
|
25
29
|
} catch (error) {
|
|
26
30
|
console.error('Error getting locator group file path:', error)
|
|
27
31
|
return null
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
/**
|
|
32
|
-
* Generates JSON content for a locator group from its locators
|
|
33
|
-
*/
|
|
34
35
|
export async function generateLocatorGroupContent(locatorGroupId: string): Promise<Record<string, string>> {
|
|
35
36
|
try {
|
|
36
37
|
const locatorGroup = await prisma.locatorGroup.findUnique({
|
|
@@ -42,7 +43,9 @@ export async function generateLocatorGroupContent(locatorGroupId: string): Promi
|
|
|
42
43
|
},
|
|
43
44
|
})
|
|
44
45
|
|
|
45
|
-
if (!locatorGroup)
|
|
46
|
+
if (!locatorGroup) {
|
|
47
|
+
return {}
|
|
48
|
+
}
|
|
46
49
|
|
|
47
50
|
return Object.fromEntries(locatorGroup.locators.map(locator => [locator.name, locator.value]))
|
|
48
51
|
} catch (error) {
|
|
@@ -51,29 +54,21 @@ export async function generateLocatorGroupContent(locatorGroupId: string): Promi
|
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
/**
|
|
55
|
-
* Ensures a directory exists, creating it if necessary
|
|
56
|
-
*/
|
|
57
57
|
export async function ensureDirectoryExists(filePath: string): Promise<void> {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await fs.access(dir)
|
|
61
|
-
} catch {
|
|
62
|
-
await fs.mkdir(dir, { recursive: true })
|
|
63
|
-
}
|
|
58
|
+
await ensureAutomationWorkspaceReady()
|
|
59
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
/**
|
|
67
|
-
* Creates or updates a locator group JSON file
|
|
68
|
-
*/
|
|
69
62
|
export async function createOrUpdateLocatorGroupFile(locatorGroupId: string): Promise<boolean> {
|
|
70
63
|
try {
|
|
64
|
+
await ensureAutomationWorkspaceReady()
|
|
71
65
|
const filePath = await getLocatorGroupFilePath(locatorGroupId)
|
|
72
|
-
if (!filePath)
|
|
66
|
+
if (!filePath) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
73
69
|
|
|
74
70
|
await ensureDirectoryExists(filePath)
|
|
75
71
|
const content = await generateLocatorGroupContent(locatorGroupId)
|
|
76
|
-
|
|
77
72
|
await fs.writeFile(filePath, JSON.stringify(content, null, 2))
|
|
78
73
|
return true
|
|
79
74
|
} catch (error) {
|
|
@@ -82,19 +77,18 @@ export async function createOrUpdateLocatorGroupFile(locatorGroupId: string): Pr
|
|
|
82
77
|
}
|
|
83
78
|
}
|
|
84
79
|
|
|
85
|
-
|
|
86
|
-
* Deletes a locator group JSON file and cleans up empty directories
|
|
87
|
-
*/
|
|
88
|
-
export async function deleteLocatorGroupFile(locatorGroupId: string): Promise<boolean> {
|
|
80
|
+
export async function deleteLocatorGroupFile(locatorGroupId: string, filePathOverride?: string): Promise<boolean> {
|
|
89
81
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
await ensureAutomationWorkspaceReady()
|
|
83
|
+
const filePath = filePathOverride ?? (await getLocatorGroupFilePath(locatorGroupId))
|
|
84
|
+
if (!filePath) {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
92
87
|
|
|
93
|
-
// Check if file exists before trying to delete
|
|
94
88
|
try {
|
|
95
89
|
await fs.access(filePath)
|
|
96
90
|
} catch {
|
|
97
|
-
return true
|
|
91
|
+
return true
|
|
98
92
|
}
|
|
99
93
|
|
|
100
94
|
await fs.unlink(filePath)
|
|
@@ -106,39 +100,26 @@ export async function deleteLocatorGroupFile(locatorGroupId: string): Promise<bo
|
|
|
106
100
|
}
|
|
107
101
|
}
|
|
108
102
|
|
|
109
|
-
/**
|
|
110
|
-
* Renames a locator group file when the name changes
|
|
111
|
-
*/
|
|
112
103
|
export async function renameLocatorGroupFile(
|
|
113
|
-
|
|
104
|
+
locatorGroupId: string,
|
|
114
105
|
newName: string,
|
|
115
106
|
oldName?: string,
|
|
116
107
|
): Promise<boolean> {
|
|
117
108
|
try {
|
|
118
|
-
|
|
119
|
-
const currentFilePath = await getLocatorGroupFilePath(
|
|
120
|
-
if (!currentFilePath)
|
|
121
|
-
|
|
122
|
-
// If oldName is provided, construct the old file path manually
|
|
123
|
-
// Otherwise, try to get it from the current path (fallback)
|
|
124
|
-
let oldFilePath: string
|
|
125
|
-
if (oldName) {
|
|
126
|
-
oldFilePath = path.join(path.dirname(currentFilePath), `${oldName}.json`)
|
|
127
|
-
} else {
|
|
128
|
-
oldFilePath = currentFilePath
|
|
109
|
+
await ensureAutomationWorkspaceReady()
|
|
110
|
+
const currentFilePath = await getLocatorGroupFilePath(locatorGroupId)
|
|
111
|
+
if (!currentFilePath) {
|
|
112
|
+
return false
|
|
129
113
|
}
|
|
130
114
|
|
|
115
|
+
const oldFilePath = oldName ? path.join(path.dirname(currentFilePath), `${oldName}.json`) : currentFilePath
|
|
131
116
|
const newFilePath = path.join(path.dirname(currentFilePath), `${newName}.json`)
|
|
132
117
|
|
|
133
118
|
try {
|
|
134
119
|
await fs.access(oldFilePath)
|
|
135
|
-
console.log('oldFilePath exists:', oldFilePath)
|
|
136
120
|
await fs.rename(oldFilePath, newFilePath)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
console.log('File not found at old path, creating new one:', error)
|
|
140
|
-
// File doesn't exist, create new one
|
|
141
|
-
return await createOrUpdateLocatorGroupFile(oldLocatorGroupId)
|
|
121
|
+
} catch {
|
|
122
|
+
return createOrUpdateLocatorGroupFile(locatorGroupId)
|
|
142
123
|
}
|
|
143
124
|
|
|
144
125
|
return true
|
|
@@ -148,27 +129,25 @@ export async function renameLocatorGroupFile(
|
|
|
148
129
|
}
|
|
149
130
|
}
|
|
150
131
|
|
|
151
|
-
|
|
152
|
-
* Moves a locator group file when the module changes
|
|
153
|
-
*/
|
|
154
|
-
export async function moveLocatorGroupFile(locatorGroupId: string): Promise<boolean> {
|
|
132
|
+
export async function moveLocatorGroupFile(locatorGroupId: string, previousFilePath?: string): Promise<boolean> {
|
|
155
133
|
try {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
134
|
+
await ensureAutomationWorkspaceReady()
|
|
135
|
+
if (previousFilePath) {
|
|
136
|
+
await deleteLocatorGroupFile(locatorGroupId, previousFilePath)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return createOrUpdateLocatorGroupFile(locatorGroupId)
|
|
159
140
|
} catch (error) {
|
|
160
141
|
console.error('Error moving locator group file:', error)
|
|
161
142
|
return false
|
|
162
143
|
}
|
|
163
144
|
}
|
|
164
145
|
|
|
165
|
-
/**
|
|
166
|
-
* Cleans up empty directories recursively
|
|
167
|
-
*/
|
|
168
146
|
async function cleanupEmptyDirectories(filePath: string): Promise<void> {
|
|
169
147
|
let currentDir = path.dirname(filePath)
|
|
148
|
+
const locatorsRoot = getAutomationLocatorsDir()
|
|
170
149
|
|
|
171
|
-
while (currentDir !==
|
|
150
|
+
while (currentDir.startsWith(locatorsRoot) && currentDir !== locatorsRoot && currentDir !== path.dirname(currentDir)) {
|
|
172
151
|
try {
|
|
173
152
|
const files = await fs.readdir(currentDir)
|
|
174
153
|
if (files.length === 0) {
|
|
@@ -183,13 +162,13 @@ async function cleanupEmptyDirectories(filePath: string): Promise<void> {
|
|
|
183
162
|
}
|
|
184
163
|
}
|
|
185
164
|
|
|
186
|
-
/**
|
|
187
|
-
* Creates an empty JSON file for a new locator group
|
|
188
|
-
*/
|
|
189
165
|
export async function createEmptyLocatorGroupFile(locatorGroupId: string): Promise<boolean> {
|
|
190
166
|
try {
|
|
167
|
+
await ensureAutomationWorkspaceReady()
|
|
191
168
|
const filePath = await getLocatorGroupFilePath(locatorGroupId)
|
|
192
|
-
if (!filePath)
|
|
169
|
+
if (!filePath) {
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
193
172
|
|
|
194
173
|
await ensureDirectoryExists(filePath)
|
|
195
174
|
await fs.writeFile(filePath, JSON.stringify({}, null, 2))
|
|
@@ -200,46 +179,31 @@ export async function createEmptyLocatorGroupFile(locatorGroupId: string): Promi
|
|
|
200
179
|
}
|
|
201
180
|
}
|
|
202
181
|
|
|
203
|
-
/**
|
|
204
|
-
* Reads and parses the content of a locator group file
|
|
205
|
-
*/
|
|
206
182
|
export async function readLocatorGroupFile(
|
|
207
183
|
locatorGroupId: string,
|
|
208
184
|
): Promise<{ filePath: string; content: Record<string, string> } | null> {
|
|
209
185
|
try {
|
|
186
|
+
await ensureAutomationWorkspaceReady()
|
|
210
187
|
const filePath = await getLocatorGroupFilePath(locatorGroupId)
|
|
211
|
-
if (!filePath)
|
|
188
|
+
if (!filePath) {
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
212
191
|
|
|
213
192
|
const fileContent = await fs.readFile(filePath, 'utf-8')
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return { filePath, content: jsonContent }
|
|
193
|
+
return { filePath, content: JSON.parse(fileContent) }
|
|
217
194
|
} catch (error) {
|
|
218
195
|
console.error('Error reading locator group file:', error)
|
|
219
196
|
return null
|
|
220
197
|
}
|
|
221
198
|
}
|
|
222
199
|
|
|
223
|
-
/**
|
|
224
|
-
* Updates the locator map file with locator group information
|
|
225
|
-
* Overload for updating existing entries (4 parameters)
|
|
226
|
-
*/
|
|
227
200
|
export async function updateLocatorMapFile(
|
|
228
201
|
currentLocatorGroupRoute: string,
|
|
229
202
|
newLocatorGroupRoute: string,
|
|
230
203
|
currentLocatorGroupName: string,
|
|
231
204
|
newLocatorGroupName: string,
|
|
232
205
|
): Promise<boolean>
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Updates the locator map file with locator group information
|
|
236
|
-
* Overload for adding new entries (2 parameters)
|
|
237
|
-
*/
|
|
238
206
|
export async function updateLocatorMapFile(newLocatorGroupName: string, newLocatorGroupRoute: string): Promise<boolean>
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Implementation of updateLocatorMapFile with proper overload handling
|
|
242
|
-
*/
|
|
243
207
|
export async function updateLocatorMapFile(
|
|
244
208
|
param1: string,
|
|
245
209
|
param2: string,
|
|
@@ -247,54 +211,43 @@ export async function updateLocatorMapFile(
|
|
|
247
211
|
param4?: string,
|
|
248
212
|
): Promise<boolean> {
|
|
249
213
|
try {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// Ensure the mapping directory exists
|
|
214
|
+
await ensureAutomationWorkspaceReady()
|
|
215
|
+
const locatorMapPath = path.join(getAutomationMappingDir(), 'locator-map.json')
|
|
253
216
|
await ensureDirectoryExists(locatorMapPath)
|
|
254
217
|
|
|
255
218
|
let locatorMap: Array<{ name: string; path: string }> = []
|
|
256
219
|
|
|
257
|
-
// Read existing locator map or create empty array
|
|
258
220
|
try {
|
|
259
221
|
const fileContent = await fs.readFile(locatorMapPath, 'utf-8')
|
|
260
222
|
locatorMap = JSON.parse(fileContent)
|
|
261
223
|
} catch {
|
|
262
|
-
// File doesn't exist, start with empty array
|
|
263
224
|
locatorMap = []
|
|
264
225
|
}
|
|
265
226
|
|
|
266
|
-
// Determine if this is a 2-param call (new entry) or 4-param call (update)
|
|
267
227
|
const isNewEntry = param3 === undefined && param4 === undefined
|
|
268
228
|
|
|
269
229
|
if (isNewEntry) {
|
|
270
|
-
// 2 params: newLocatorGroupName, newLocatorGroupRoute
|
|
271
230
|
const name = param1
|
|
272
231
|
const route = param2
|
|
273
|
-
|
|
274
|
-
// Check for uniqueness
|
|
275
232
|
const existingEntry = locatorMap.find(entry => entry.name === name)
|
|
276
233
|
if (existingEntry) {
|
|
277
234
|
console.error(`Locator group with name "${name}" already exists in locator map`)
|
|
278
235
|
return false
|
|
279
236
|
}
|
|
280
237
|
|
|
281
|
-
// Add new entry
|
|
282
238
|
locatorMap.push({ name, path: route })
|
|
283
239
|
} else {
|
|
284
|
-
// 4 params: update existing entry
|
|
285
240
|
const currentLocatorGroupRoute = param1
|
|
286
241
|
const newLocatorGroupRoute = param2
|
|
287
242
|
const currentLocatorGroupName = param3!
|
|
288
243
|
const newLocatorGroupName = param4!
|
|
289
244
|
|
|
290
|
-
// Find the entry to update
|
|
291
245
|
const entryIndex = locatorMap.findIndex(entry => entry.name === currentLocatorGroupName)
|
|
292
246
|
if (entryIndex === -1) {
|
|
293
247
|
console.error(`Locator group with name "${currentLocatorGroupName}" not found in locator map`)
|
|
294
248
|
return false
|
|
295
249
|
}
|
|
296
250
|
|
|
297
|
-
// Check if new name is unique (if name is changing)
|
|
298
251
|
if (currentLocatorGroupName !== newLocatorGroupName) {
|
|
299
252
|
const existingEntry = locatorMap.find(entry => entry.name === newLocatorGroupName)
|
|
300
253
|
if (existingEntry) {
|
|
@@ -303,15 +256,12 @@ export async function updateLocatorMapFile(
|
|
|
303
256
|
}
|
|
304
257
|
}
|
|
305
258
|
|
|
306
|
-
// Update the entry
|
|
307
259
|
const updatedEntry = { ...locatorMap[entryIndex] }
|
|
308
260
|
|
|
309
|
-
// Update name if it changed
|
|
310
261
|
if (currentLocatorGroupName !== newLocatorGroupName) {
|
|
311
262
|
updatedEntry.name = newLocatorGroupName
|
|
312
263
|
}
|
|
313
264
|
|
|
314
|
-
// Update path if it changed
|
|
315
265
|
if (currentLocatorGroupRoute !== newLocatorGroupRoute) {
|
|
316
266
|
updatedEntry.path = newLocatorGroupRoute
|
|
317
267
|
}
|
|
@@ -319,7 +269,6 @@ export async function updateLocatorMapFile(
|
|
|
319
269
|
locatorMap[entryIndex] = updatedEntry
|
|
320
270
|
}
|
|
321
271
|
|
|
322
|
-
// Write the updated locator map back to file
|
|
323
272
|
await fs.writeFile(locatorMapPath, JSON.stringify(locatorMap, null, 2))
|
|
324
273
|
return true
|
|
325
274
|
} catch (error) {
|
|
@@ -328,40 +277,28 @@ export async function updateLocatorMapFile(
|
|
|
328
277
|
}
|
|
329
278
|
}
|
|
330
279
|
|
|
331
|
-
/**
|
|
332
|
-
* Removes locator group entries from the locator map file
|
|
333
|
-
* @param locatorGroupNames - Array of locator group names to remove
|
|
334
|
-
*/
|
|
335
280
|
export async function removeLocatorMapEntry(locatorGroupNames: string[]): Promise<boolean> {
|
|
336
281
|
try {
|
|
337
|
-
|
|
282
|
+
await ensureAutomationWorkspaceReady()
|
|
283
|
+
const locatorMapPath = path.join(getAutomationMappingDir(), 'locator-map.json')
|
|
338
284
|
|
|
339
|
-
// Check if file exists
|
|
340
285
|
try {
|
|
341
286
|
await fs.access(locatorMapPath)
|
|
342
287
|
} catch {
|
|
343
|
-
// File doesn't exist, nothing to remove
|
|
344
288
|
return true
|
|
345
289
|
}
|
|
346
290
|
|
|
347
|
-
// Read existing locator map
|
|
348
291
|
const fileContent = await fs.readFile(locatorMapPath, 'utf-8')
|
|
349
292
|
let locatorMap: Array<{ name: string; path: string }> = JSON.parse(fileContent)
|
|
350
293
|
|
|
351
|
-
// Filter out the entries to be removed
|
|
352
294
|
const originalLength = locatorMap.length
|
|
353
295
|
locatorMap = locatorMap.filter(entry => !locatorGroupNames.includes(entry.name))
|
|
354
296
|
|
|
355
|
-
|
|
356
|
-
const removedCount = originalLength - locatorMap.length
|
|
357
|
-
if (removedCount === 0) {
|
|
358
|
-
console.log('No matching locator group entries found in locator map')
|
|
297
|
+
if (originalLength === locatorMap.length) {
|
|
359
298
|
return true
|
|
360
299
|
}
|
|
361
300
|
|
|
362
|
-
// Write the updated locator map back to file
|
|
363
301
|
await fs.writeFile(locatorMapPath, JSON.stringify(locatorMap, null, 2))
|
|
364
|
-
console.log(`Removed ${removedCount} locator group entry(ies) from locator map`)
|
|
365
302
|
return true
|
|
366
303
|
} catch (error) {
|
|
367
304
|
console.error('Error removing locator map entries:', error)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { execa, type Options as ExecaOptions } from 'execa'
|
|
2
|
+
import type { ChildProcess } from 'child_process'
|
|
3
|
+
import { EventEmitter } from 'events'
|
|
4
|
+
|
|
5
|
+
export interface SpawnerOptions extends ExecaOptions {
|
|
6
|
+
streamLogs?: boolean
|
|
7
|
+
prefixLogs?: boolean
|
|
8
|
+
logPrefix?: string
|
|
9
|
+
captureOutput?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SpawnedProcess {
|
|
13
|
+
process: ChildProcess
|
|
14
|
+
pid: number | undefined
|
|
15
|
+
name: string
|
|
16
|
+
output: {
|
|
17
|
+
stdout: string[]
|
|
18
|
+
stderr: string[]
|
|
19
|
+
}
|
|
20
|
+
isRunning: boolean
|
|
21
|
+
exitCode: number | null
|
|
22
|
+
startTime: Date
|
|
23
|
+
endTime: Date | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class TaskSpawner extends EventEmitter {
|
|
27
|
+
private processes: Map<string, SpawnedProcess> = new Map()
|
|
28
|
+
private processCounter = 0
|
|
29
|
+
private outputBuffers: Map<string, { stdout: string; stderr: string }> = new Map()
|
|
30
|
+
|
|
31
|
+
async spawn(command: string, args: string[] = [], options: SpawnerOptions = {}): Promise<SpawnedProcess> {
|
|
32
|
+
const { streamLogs = true, prefixLogs = true, logPrefix, captureOutput = false, ...spawnOptions } = options
|
|
33
|
+
|
|
34
|
+
const processName = logPrefix || `${command}_${++this.processCounter}`
|
|
35
|
+
const spawnedProcess: SpawnedProcess = {
|
|
36
|
+
process: null as unknown as ChildProcess,
|
|
37
|
+
pid: undefined,
|
|
38
|
+
name: processName,
|
|
39
|
+
output: {
|
|
40
|
+
stdout: [],
|
|
41
|
+
stderr: [],
|
|
42
|
+
},
|
|
43
|
+
isRunning: false,
|
|
44
|
+
exitCode: null,
|
|
45
|
+
startTime: new Date(),
|
|
46
|
+
endTime: null,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const stdioConfig = captureOutput ? 'pipe' : streamLogs ? 'inherit' : 'pipe'
|
|
50
|
+
const childProcess = execa(command, args, {
|
|
51
|
+
stdio: stdioConfig,
|
|
52
|
+
...spawnOptions,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
spawnedProcess.process = childProcess
|
|
56
|
+
spawnedProcess.pid = childProcess.pid
|
|
57
|
+
spawnedProcess.isRunning = true
|
|
58
|
+
|
|
59
|
+
this.processes.set(processName, spawnedProcess)
|
|
60
|
+
this.outputBuffers.set(processName, { stdout: '', stderr: '' })
|
|
61
|
+
|
|
62
|
+
this.setupProcessListeners(spawnedProcess, {
|
|
63
|
+
streamLogs,
|
|
64
|
+
prefixLogs,
|
|
65
|
+
captureOutput,
|
|
66
|
+
stdioConfig,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
this.emit('spawn', spawnedProcess)
|
|
70
|
+
return spawnedProcess
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
killProcess(processName: string, signal: NodeJS.Signals = 'SIGTERM'): boolean {
|
|
74
|
+
const spawnedProcess = this.processes.get(processName)
|
|
75
|
+
if (!spawnedProcess || !spawnedProcess.isRunning) {
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
spawnedProcess.process.kill(signal)
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async waitForProcess(processName: string): Promise<number | null> {
|
|
84
|
+
const spawnedProcess = this.processes.get(processName)
|
|
85
|
+
if (!spawnedProcess) {
|
|
86
|
+
throw new Error(`Process '${processName}' not found`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return new Promise(resolve => {
|
|
90
|
+
if (!spawnedProcess.isRunning) {
|
|
91
|
+
resolve(spawnedProcess.exitCode)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
spawnedProcess.process.on('exit', (code: number | null) => resolve(code))
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getProcess(processName: string): SpawnedProcess | undefined {
|
|
100
|
+
return this.processes.get(processName)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private processBufferedOutput(
|
|
104
|
+
processName: string,
|
|
105
|
+
stream: 'stdout' | 'stderr',
|
|
106
|
+
streamLogs: boolean,
|
|
107
|
+
prefixLogs: boolean,
|
|
108
|
+
captureOutput: boolean,
|
|
109
|
+
spawnedProcess: SpawnedProcess,
|
|
110
|
+
): void {
|
|
111
|
+
const buffer = this.outputBuffers.get(processName)
|
|
112
|
+
if (!buffer) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const lines = buffer[stream].split('\n')
|
|
117
|
+
buffer[stream] = lines.pop() || ''
|
|
118
|
+
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
if (captureOutput) {
|
|
121
|
+
spawnedProcess.output[stream].push(`${line}\n`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (streamLogs) {
|
|
125
|
+
const prefix = prefixLogs ? `[${processName}] ` : ''
|
|
126
|
+
if (stream === 'stdout') {
|
|
127
|
+
console.log(`${prefix}${line}`)
|
|
128
|
+
} else {
|
|
129
|
+
console.error(`${prefix}${line}`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.emit(stream, { processName, data: `${line}\n` })
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private setupProcessListeners(
|
|
138
|
+
spawnedProcess: SpawnedProcess,
|
|
139
|
+
options: {
|
|
140
|
+
streamLogs: boolean
|
|
141
|
+
prefixLogs: boolean
|
|
142
|
+
captureOutput: boolean
|
|
143
|
+
stdioConfig: string | string[]
|
|
144
|
+
},
|
|
145
|
+
): void {
|
|
146
|
+
const { streamLogs, prefixLogs, captureOutput, stdioConfig } = options
|
|
147
|
+
const { process: childProcess, name } = spawnedProcess
|
|
148
|
+
|
|
149
|
+
if (stdioConfig === 'pipe') {
|
|
150
|
+
childProcess.stdout?.on('data', (data: Buffer) => {
|
|
151
|
+
const buffer = this.outputBuffers.get(name)
|
|
152
|
+
if (!buffer) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
buffer.stdout += data.toString()
|
|
157
|
+
this.processBufferedOutput(name, 'stdout', streamLogs, prefixLogs, captureOutput, spawnedProcess)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
childProcess.stderr?.on('data', (data: Buffer) => {
|
|
161
|
+
const buffer = this.outputBuffers.get(name)
|
|
162
|
+
if (!buffer) {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
buffer.stderr += data.toString()
|
|
167
|
+
this.processBufferedOutput(name, 'stderr', streamLogs, prefixLogs, captureOutput, spawnedProcess)
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
childProcess.on('exit', (code: number | null) => {
|
|
172
|
+
if (stdioConfig === 'pipe') {
|
|
173
|
+
const buffer = this.outputBuffers.get(name)
|
|
174
|
+
if (buffer) {
|
|
175
|
+
if (buffer.stdout) {
|
|
176
|
+
if (captureOutput) {
|
|
177
|
+
spawnedProcess.output.stdout.push(buffer.stdout)
|
|
178
|
+
}
|
|
179
|
+
if (streamLogs) {
|
|
180
|
+
const prefix = prefixLogs ? `[${name}] ` : ''
|
|
181
|
+
console.log(`${prefix}${buffer.stdout}`)
|
|
182
|
+
}
|
|
183
|
+
this.emit('stdout', { processName: name, data: buffer.stdout })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (buffer.stderr) {
|
|
187
|
+
if (captureOutput) {
|
|
188
|
+
spawnedProcess.output.stderr.push(buffer.stderr)
|
|
189
|
+
}
|
|
190
|
+
if (streamLogs) {
|
|
191
|
+
const prefix = prefixLogs ? `[${name}] ` : ''
|
|
192
|
+
console.error(`${prefix}${buffer.stderr}`)
|
|
193
|
+
}
|
|
194
|
+
this.emit('stderr', { processName: name, data: buffer.stderr })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.outputBuffers.delete(name)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
spawnedProcess.isRunning = false
|
|
202
|
+
spawnedProcess.exitCode = code
|
|
203
|
+
spawnedProcess.endTime = new Date()
|
|
204
|
+
this.emit('exit', { processName: name, code })
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
childProcess.on('error', (error: Error) => {
|
|
208
|
+
spawnedProcess.isRunning = false
|
|
209
|
+
spawnedProcess.endTime = new Date()
|
|
210
|
+
|
|
211
|
+
if (streamLogs) {
|
|
212
|
+
const prefix = prefixLogs ? `[${name}] ` : ''
|
|
213
|
+
console.error(`${prefix}ERROR: ${error.message}`)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.emit('error', { processName: name, error })
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const globalForTaskSpawner = global as unknown as {
|
|
222
|
+
taskSpawner: TaskSpawner | undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const taskSpawner = globalForTaskSpawner.taskSpawner ?? new TaskSpawner()
|
|
226
|
+
|
|
227
|
+
if (!globalForTaskSpawner.taskSpawner) {
|
|
228
|
+
globalForTaskSpawner.taskSpawner = taskSpawner
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const spawnTask = (command: string, args: string[] = [], options: SpawnerOptions = {}) =>
|
|
232
|
+
taskSpawner.spawn(command, args, options)
|
|
233
|
+
|
|
234
|
+
export const killTask = (processName: string, signal?: NodeJS.Signals) => taskSpawner.killProcess(processName, signal)
|
|
235
|
+
|
|
236
|
+
export const waitForTask = (processName: string) => taskSpawner.waitForProcess(processName)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function shouldExcludeTemplatePath(relativePath: string): boolean;
|
|
2
|
+
export declare function shouldBackfillLegacyEnvironmentConfig(targetHasEnvironmentsFile: boolean, legacyEnvironmentsDirExists: boolean): boolean;
|
|
3
|
+
export declare function getAutomationFeaturesDir(baseDir: string): string;
|
|
4
|
+
export declare function getAutomationLocatorsDir(baseDir: string): string;
|
|
5
|
+
export declare function getAutomationLocatorMapPath(baseDir: string): string;
|
|
6
|
+
export declare function extractModulePathFromAutomationFile(filePath: string, baseDir: string, automationSubdir: 'features' | 'locators'): string;
|
|
7
|
+
//# sourceMappingURL=template-sync-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-sync-utils.d.ts","sourceRoot":"","sources":["template-sync-utils.ts"],"names":[],"mappings":"AAUA,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAgBvE;AAED,wBAAgB,qCAAqC,CACnD,yBAAyB,EAAE,OAAO,EAClC,2BAA2B,EAAE,OAAO,GACnC,OAAO,CAET;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,UAAU,GAAG,UAAU,GACxC,MAAM,CAWR"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { join, relative } from 'path';
|
|
2
|
+
const EXCLUDED_DIRS = new Set(['node_modules', '.next', '.git', 'dist']);
|
|
3
|
+
const EXCLUDED_EXTENSIONS = new Set(['.db', '.sqlite', '.sqlite3', '.tsbuildinfo']);
|
|
4
|
+
const EXCLUDED_PATH_PREFIXES = ['automation/reports/'];
|
|
5
|
+
function toPosixPath(value) {
|
|
6
|
+
return value.replace(/\\/g, '/');
|
|
7
|
+
}
|
|
8
|
+
export function shouldExcludeTemplatePath(relativePath) {
|
|
9
|
+
const normalizedPath = toPosixPath(relativePath);
|
|
10
|
+
const parts = normalizedPath.split('/');
|
|
11
|
+
if (parts.some(part => EXCLUDED_DIRS.has(part)))
|
|
12
|
+
return true;
|
|
13
|
+
if (EXCLUDED_PATH_PREFIXES.some(prefix => normalizedPath.startsWith(prefix)))
|
|
14
|
+
return true;
|
|
15
|
+
const ext = normalizedPath.endsWith('.sqlite3')
|
|
16
|
+
? '.sqlite3'
|
|
17
|
+
: normalizedPath.endsWith('.sqlite')
|
|
18
|
+
? '.sqlite'
|
|
19
|
+
: normalizedPath.endsWith('.tsbuildinfo')
|
|
20
|
+
? '.tsbuildinfo'
|
|
21
|
+
: normalizedPath.slice(normalizedPath.lastIndexOf('.'));
|
|
22
|
+
return EXCLUDED_EXTENSIONS.has(ext);
|
|
23
|
+
}
|
|
24
|
+
export function shouldBackfillLegacyEnvironmentConfig(targetHasEnvironmentsFile, legacyEnvironmentsDirExists) {
|
|
25
|
+
return !targetHasEnvironmentsFile && legacyEnvironmentsDirExists;
|
|
26
|
+
}
|
|
27
|
+
export function getAutomationFeaturesDir(baseDir) {
|
|
28
|
+
return join(baseDir, 'automation', 'features');
|
|
29
|
+
}
|
|
30
|
+
export function getAutomationLocatorsDir(baseDir) {
|
|
31
|
+
return join(baseDir, 'automation', 'locators');
|
|
32
|
+
}
|
|
33
|
+
export function getAutomationLocatorMapPath(baseDir) {
|
|
34
|
+
return join(baseDir, 'automation', 'mapping', 'locator-map.json');
|
|
35
|
+
}
|
|
36
|
+
export function extractModulePathFromAutomationFile(filePath, baseDir, automationSubdir) {
|
|
37
|
+
const automationBaseDir = automationSubdir === 'features' ? getAutomationFeaturesDir(baseDir) : getAutomationLocatorsDir(baseDir);
|
|
38
|
+
const normalizedBaseDir = toPosixPath(automationBaseDir).replace(/\/$/, '');
|
|
39
|
+
const normalizedFilePath = toPosixPath(filePath);
|
|
40
|
+
const relativePath = normalizedFilePath.startsWith(`${normalizedBaseDir}/`)
|
|
41
|
+
? normalizedFilePath.slice(normalizedBaseDir.length + 1)
|
|
42
|
+
: toPosixPath(relative(automationBaseDir, filePath));
|
|
43
|
+
const pathParts = relativePath.split('/').filter(part => part && part !== '');
|
|
44
|
+
const moduleParts = pathParts.slice(0, -1);
|
|
45
|
+
return moduleParts.length > 0 ? `/${moduleParts.join('/')}` : '/';
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=template-sync-utils.js.map
|