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
@@ -0,0 +1,35 @@
1
+ import { faker } from '@faker-js/faker'
2
+
3
+ export enum RandomDataType {
4
+ FULL_NAME = 'fullName',
5
+ FIRST_NAME = 'firstName',
6
+ LAST_NAME = 'lastName',
7
+ EMAIL = 'email',
8
+ PASSWORD = 'password',
9
+ PHONE = 'phone',
10
+ ADDRESS = 'address',
11
+ UNIQUE_TEXT = 'uniqueText',
12
+ }
13
+
14
+ export function generateRandomData(randomDataType: RandomDataType): string {
15
+ switch (randomDataType) {
16
+ case RandomDataType.FULL_NAME:
17
+ return faker.person.fullName()
18
+ case RandomDataType.FIRST_NAME:
19
+ return faker.person.firstName()
20
+ case RandomDataType.LAST_NAME:
21
+ return faker.person.lastName()
22
+ case RandomDataType.EMAIL:
23
+ return faker.internet.email()
24
+ case RandomDataType.PASSWORD:
25
+ return faker.internet.password()
26
+ case RandomDataType.PHONE:
27
+ return faker.phone.number()
28
+ case RandomDataType.ADDRESS:
29
+ return faker.location.streetAddress()
30
+ case RandomDataType.UNIQUE_TEXT:
31
+ return faker.string.uuid()
32
+ default:
33
+ throw new Error(`Invalid random data type: ${randomDataType}`)
34
+ }
35
+ }
@@ -0,0 +1,13 @@
1
+ export type BrowserName = 'chromium' | 'firefox' | 'webkit'
2
+
3
+ export type CSSSelector = string
4
+ export type XPathSelector = `/${string}` | `//${string}`
5
+ export type SelectorName = string
6
+ export type Selector = CSSSelector | XPathSelector
7
+ export type Locator = Record<string, Selector>
8
+ export type LocatorMap = {
9
+ name: string
10
+ path: string
11
+ }
12
+
13
+ export type LocatorCollection = Record<string, Locator>
@@ -9,12 +9,14 @@ export interface ScenarioData {
9
9
  token?: string
10
10
  vars: Record<string, unknown>
11
11
  }
12
+
12
13
  export class CustomWorld extends World {
13
14
  context!: BrowserContext
14
15
  page!: Page
15
16
  data: ScenarioData = {
16
17
  vars: {},
17
18
  }
19
+
18
20
  constructor(options: IWorldOptions) {
19
21
  super(options)
20
22
  }
@@ -27,6 +29,7 @@ export class CustomWorld extends World {
27
29
  if (!(key in this.data.vars)) {
28
30
  throw new Error(`Variable ${key} not found`)
29
31
  }
32
+
30
33
  return this.data.vars[key] as T
31
34
  }
32
35
 
@@ -36,6 +39,6 @@ export class CustomWorld extends World {
36
39
  }
37
40
 
38
41
  setWorldConstructor(CustomWorld)
39
-
40
42
  chai.use(chaiAsPromised)
43
+
41
44
  export const expect = chai.expect
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "noEmit": false,
7
+ "declaration": true,
8
+ "emitDeclarationOnly": false
9
+ },
10
+ "include": ["src/**/*.ts"]
11
+ }
@@ -4,16 +4,16 @@ import fs from 'fs'
4
4
  import path from 'path'
5
5
 
6
6
  const envContent = `# Database configuration for local development
7
- DATABASE_URL="file:./prisma/dev.db"
7
+ DATABASE_URL="file:./dev.db"
8
8
  `
9
9
 
10
10
  const envPath = path.join(process.cwd(), '.env')
11
11
 
12
12
  if (!fs.existsSync(envPath)) {
13
13
  fs.writeFileSync(envPath, envContent)
14
- console.log(' Created .env file with SQLite configuration')
14
+ console.log('? Created .env file with SQLite configuration')
15
15
  } else {
16
- console.log('ℹ️ .env file already exists, skipping creation')
16
+ console.log('?? .env file already exists, skipping creation')
17
17
  }
18
18
 
19
- console.log('🎉 Environment setup complete!')
19
+ console.log('?? Environment setup complete!')
@@ -1,103 +1,113 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Sync the default template from the base app (repo root).
4
- * Copies src/, prisma/, public/, scripts/, and root config files into templates/default/,
5
- * preserves template-only README.md and appraisejs.config.json, and merges package.json scripts.
4
+ * Copies src/, automation/, prisma/, public/, scripts/, the cucumber runtime package,
5
+ * and root config files into templates/default/ while preserving template-only files.
6
6
  *
7
7
  * Usage: npx tsx scripts/sync-appraise-base-template.ts
8
8
  * Or: npm run sync-template
9
9
  */
10
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs'
11
+ import { dirname, join } from 'path'
12
+ import { fileURLToPath } from 'url'
13
+ import { shouldBackfillLegacyEnvironmentConfig, shouldExcludeTemplatePath } from '../src/lib/template-sync-utils'
10
14
 
11
- import { cpSync, readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from 'fs';
12
- import { dirname, join } from 'path';
13
- import { fileURLToPath } from 'url';
14
-
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
16
- const repoRoot = join(__dirname, '..');
17
- const target = join(repoRoot, 'templates', 'default');
18
-
19
- const EXCLUDED_DIRS = new Set(['node_modules', '.next', '.git']);
20
- const EXCLUDED_EXTENSIONS = new Set(['.db', '.sqlite', '.sqlite3']);
21
- const EXCLUDED_TEST_DATA_PREFIXES = [
22
- 'tests/features/',
23
- 'tests/config/environments/',
24
- 'tests/locators/',
25
- 'tests/reports/',
26
- ];
15
+ const __dirname = dirname(fileURLToPath(import.meta.url))
16
+ const repoRoot = join(__dirname, '..')
17
+ const target = join(repoRoot, 'templates', 'default')
27
18
 
28
19
  function shouldExclude(relativePath: string): boolean {
29
- const parts = relativePath.split(/[/\\]/);
30
- if (parts.some((p) => EXCLUDED_DIRS.has(p))) return true;
31
- if (EXCLUDED_TEST_DATA_PREFIXES.some((prefix) => relativePath.startsWith(prefix))) return true;
32
- const ext = relativePath.endsWith('.sqlite3')
33
- ? '.sqlite3'
34
- : relativePath.endsWith('.sqlite')
35
- ? '.sqlite'
36
- : relativePath.slice(relativePath.lastIndexOf('.'));
37
- if (EXCLUDED_EXTENSIONS.has(ext)) return true;
38
- return false;
20
+ return shouldExcludeTemplatePath(relativePath)
39
21
  }
40
22
 
41
- function copyDirWithFilter(src: string, dest: string): void {
42
- cpSync(src, dest, {
43
- recursive: true,
44
- force: true,
45
- filter: (source: string) => {
46
- const rel = source.slice(src.length).replace(/^[/\\]/, '') || '';
47
- return !shouldExclude(rel);
48
- },
49
- });
23
+ function copyDirWithFilter(src: string, dest: string, base = src): void {
24
+ if (!existsSync(src)) return
25
+
26
+ mkdirSync(dest, { recursive: true })
27
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
28
+ const srcPath = join(src, entry.name)
29
+ const relativePath = srcPath.slice(base.length + 1).replace(/\\/g, '/')
30
+ if (shouldExclude(relativePath)) continue
31
+
32
+ const destPath = join(dest, entry.name)
33
+ if (entry.isDirectory()) {
34
+ copyDirWithFilter(srcPath, destPath, base)
35
+ continue
36
+ }
37
+
38
+ cpSync(srcPath, destPath, { force: true })
39
+ }
50
40
  }
51
41
 
52
42
  function copyFile(src: string, dest: string): void {
53
- mkdirSync(dirname(dest), { recursive: true });
54
- cpSync(src, dest, { force: true });
43
+ mkdirSync(dirname(dest), { recursive: true })
44
+ cpSync(src, dest, { force: true })
55
45
  }
56
46
 
57
- // 1. Preserve template-only files
58
- const readmePath = join(target, 'README.md');
59
- const appraisejsConfigPath = join(target, 'appraisejs.config.json');
60
- const savedReadme = existsSync(readmePath) ? readFileSync(readmePath, 'utf8') : null;
61
- const savedAppraisejsConfig = existsSync(appraisejsConfigPath)
62
- ? readFileSync(appraisejsConfigPath, 'utf8')
63
- : null;
64
-
65
- // 2. Copy directories
66
- console.log('Copying src/...');
67
- copyDirWithFilter(join(repoRoot, 'src'), join(target, 'src'));
68
-
69
- // Clear test data dirs and write empty configs (no project-specific features/envs/locators/reports)
70
- const dirsToClear = [
71
- join(target, 'src', 'tests', 'features'),
72
- join(target, 'src', 'tests', 'locators'),
73
- join(target, 'src', 'tests', 'reports'),
74
- ];
75
- for (const dir of dirsToClear) {
76
- if (existsSync(dir)) {
77
- rmSync(dir, { recursive: true });
47
+ function resetAutomationReports(templateRoot: string): void {
48
+ const reportsRoot = join(templateRoot, 'automation', 'reports')
49
+ rmSync(reportsRoot, { recursive: true, force: true })
50
+ mkdirSync(join(reportsRoot, 'logs'), { recursive: true })
51
+ mkdirSync(join(reportsRoot, 'traces'), { recursive: true })
52
+ }
53
+
54
+ function syncLegacyEnvironmentConfig(): void {
55
+ const legacyEnvironmentsDir = join(repoRoot, 'src', 'tests', 'config', 'environments')
56
+ const targetEnvironmentsDir = join(target, 'automation', 'config', 'environments')
57
+ const targetEnvironmentsFile = join(targetEnvironmentsDir, 'environments.json')
58
+
59
+ if (!shouldBackfillLegacyEnvironmentConfig(existsSync(targetEnvironmentsFile), existsSync(legacyEnvironmentsDir))) {
60
+ return
78
61
  }
62
+
63
+ mkdirSync(targetEnvironmentsDir, { recursive: true })
64
+ cpSync(legacyEnvironmentsDir, targetEnvironmentsDir, { recursive: true, force: true })
65
+ console.log('Backfilled automation/config/environments from legacy src/tests config.')
79
66
  }
80
- const testDirs = [
81
- join(target, 'src', 'tests', 'features'),
82
- join(target, 'src', 'tests', 'config', 'environments'),
83
- join(target, 'src', 'tests', 'locators'),
84
- join(target, 'src', 'tests', 'reports'),
85
- join(target, 'src', 'tests', 'mapping'),
86
- ];
87
- for (const dir of testDirs) {
88
- mkdirSync(dir, { recursive: true });
67
+
68
+ function syncCucumberRuntimePackage(): void {
69
+ const runtimeTarget = join(target, 'packages', 'cucumber-runtime')
70
+ rmSync(runtimeTarget, { recursive: true, force: true })
71
+ mkdirSync(runtimeTarget, { recursive: true })
72
+
73
+ copyFile(join(repoRoot, 'packages', 'cucumber-runtime', 'package.json'), join(runtimeTarget, 'package.json'))
74
+ copyFile(join(repoRoot, 'packages', 'cucumber-runtime', 'tsconfig.json'), join(runtimeTarget, 'tsconfig.json'))
75
+ copyDirWithFilter(join(repoRoot, 'packages', 'cucumber-runtime', 'src'), join(runtimeTarget, 'src'))
89
76
  }
90
- writeFileSync(join(target, 'src', 'tests', 'config', 'environments', 'environments.json'), JSON.stringify({}) + '\n');
91
- writeFileSync(join(target, 'src', 'tests', 'mapping', 'locator-map.json'), JSON.stringify([]) + '\n');
92
77
 
93
- console.log('Copying prisma/...');
94
- copyDirWithFilter(join(repoRoot, 'prisma'), join(target, 'prisma'));
95
- console.log('Copying public/...');
96
- copyDirWithFilter(join(repoRoot, 'public'), join(target, 'public'));
97
- console.log('Copying scripts/...');
98
- copyDirWithFilter(join(repoRoot, 'scripts'), join(target, 'scripts'));
78
+ const readmePath = join(target, 'README.md')
79
+ const appraisejsConfigPath = join(target, 'appraisejs.config.json')
80
+ const savedReadme = existsSync(readmePath) ? readFileSync(readmePath, 'utf8') : null
81
+ const savedAppraisejsConfig = existsSync(appraisejsConfigPath)
82
+ ? readFileSync(appraisejsConfigPath, 'utf8')
83
+ : null
84
+
85
+ rmSync(target, { recursive: true, force: true })
86
+ mkdirSync(target, { recursive: true })
87
+
88
+ console.log('Copying src/...')
89
+ copyDirWithFilter(join(repoRoot, 'src'), join(target, 'src'))
90
+
91
+ console.log('Copying automation/...')
92
+ copyDirWithFilter(join(repoRoot, 'automation'), join(target, 'automation'))
93
+ syncLegacyEnvironmentConfig()
94
+ resetAutomationReports(target)
95
+
96
+ console.log('Copying cucumber runtime package...')
97
+ syncCucumberRuntimePackage()
98
+
99
+ console.log('Copying prisma/...')
100
+ copyDirWithFilter(join(repoRoot, 'prisma'), join(target, 'prisma'))
101
+ console.log('Copying public/...')
102
+ copyDirWithFilter(join(repoRoot, 'public'), join(target, 'public'))
103
+ console.log('Copying scripts/...')
104
+ copyDirWithFilter(join(repoRoot, 'scripts'), join(target, 'scripts'))
105
+
106
+ const legacyTestsRoot = join(target, 'src', 'tests')
107
+ if (existsSync(legacyTestsRoot)) {
108
+ rmSync(legacyTestsRoot, { recursive: true, force: true })
109
+ }
99
110
 
100
- // 3. Copy root config files and package lock files
101
111
  const configFiles = [
102
112
  '.gitignore',
103
113
  'eslint.config.mjs',
@@ -106,51 +116,60 @@ const configFiles = [
106
116
  'postcss.config.mjs',
107
117
  'components.json',
108
118
  'next.config.ts',
119
+ 'next-env.d.ts',
109
120
  '.env.example',
110
121
  'package-lock.json',
111
122
  'yarn.lock',
112
123
  'pnpm-lock.yaml',
113
- ];
114
- console.log('Copying config files...');
124
+ 'bun.lockb',
125
+ ]
115
126
  for (const name of configFiles) {
116
- const src = join(repoRoot, name);
127
+ const src = join(repoRoot, name)
117
128
  if (existsSync(src)) {
118
- copyFile(src, join(target, name));
129
+ copyFile(src, join(target, name))
119
130
  }
120
131
  }
121
132
 
122
- // Copy cucumber.mjs from repo root (required for running tests)
123
- const cucumberSource = join(repoRoot, 'cucumber.mjs');
133
+ const cucumberSource = join(repoRoot, 'cucumber.mjs')
124
134
  if (existsSync(cucumberSource)) {
125
- copyFile(cucumberSource, join(target, 'cucumber.mjs'));
126
- console.log('Synced cucumber.mjs to template');
135
+ copyFile(cucumberSource, join(target, 'cucumber.mjs'))
136
+ console.log('Synced cucumber.mjs to template')
127
137
  }
128
138
 
129
- // Copy .vscode folder from repo root
130
- const vscodeSource = join(repoRoot, '.vscode');
139
+ const vscodeSource = join(repoRoot, '.vscode')
131
140
  if (existsSync(vscodeSource)) {
132
- cpSync(vscodeSource, join(target, '.vscode'), { recursive: true, force: true });
133
- console.log('Synced .vscode to template');
141
+ cpSync(vscodeSource, join(target, '.vscode'), { recursive: true, force: true })
142
+ console.log('Synced .vscode to template')
134
143
  }
135
144
 
136
- // 4. package.json: base + template-only scripts
137
145
  const rootPkg = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')) as {
138
- scripts: Record<string, string>;
139
- [key: string]: unknown;
140
- };
141
- rootPkg.scripts['appraisejs:setup'] = 'npm run setup';
142
- rootPkg.scripts['appraisejs:sync'] = 'npm run sync-all';
143
- writeFileSync(join(target, 'package.json'), JSON.stringify(rootPkg, null, 2) + '\n');
144
- console.log('Wrote package.json with appraisejs:setup and appraisejs:sync.');
145
-
146
- // 5. Restore template-only files
146
+ scripts: Record<string, string>
147
+ [key: string]: unknown
148
+ }
149
+ rootPkg.scripts = {
150
+ ...rootPkg.scripts,
151
+ build: 'npm run build:local',
152
+ 'build:local': 'npm run generate-db-client && npm run build:cucumber-runtime && next build',
153
+ start: 'next start',
154
+ 'generate-db-client': 'npx prisma generate --schema prisma/schema.prisma',
155
+ 'migrate-db': 'npx prisma migrate deploy',
156
+ 'install-playwright': 'npx playwright install',
157
+ setup: 'npm run install-dependencies && npm run setup:db && npm run build:local',
158
+ 'setup:db': 'npm run setup-env && npm run generate-db-client && npm run migrate-db && npm run sync-all',
159
+ 'setup:full': 'npm run install-dependencies && npm run setup:db && npm run build:local',
160
+ 'appraisejs:setup': 'npm run setup',
161
+ 'appraisejs:sync': 'npm run sync-all',
162
+ }
163
+ writeFileSync(join(target, 'package.json'), JSON.stringify(rootPkg, null, 2) + '\n')
164
+ console.log('Wrote template package.json with production-first scaffold scripts.')
165
+
147
166
  if (savedReadme) {
148
- writeFileSync(readmePath, savedReadme);
149
- console.log('Restored README.md');
167
+ writeFileSync(readmePath, savedReadme)
168
+ console.log('Restored README.md')
150
169
  }
151
170
  if (savedAppraisejsConfig) {
152
- writeFileSync(appraisejsConfigPath, savedAppraisejsConfig);
153
- console.log('Restored appraisejs.config.json');
171
+ writeFileSync(appraisejsConfigPath, savedAppraisejsConfig)
172
+ console.log('Restored appraisejs.config.json')
154
173
  }
155
174
 
156
- console.log('Synced base app to templates/default.');
175
+ console.log('Synced base app to templates/default with starter automation assets.')
@@ -9,8 +9,8 @@
9
9
  */
10
10
 
11
11
  import { promises as fs } from 'fs'
12
- import { join } from 'path'
13
12
  import prisma from '../src/config/db-config'
13
+ import { ensureAutomationWorkspaceReady, getAutomationEnvironmentsDir } from '../src/lib/automation/paths'
14
14
 
15
15
  interface EnvironmentConfig {
16
16
  baseUrl: string
@@ -43,8 +43,8 @@ interface SyncResult {
43
43
  /**
44
44
  * Reads and parses the environments.json file
45
45
  */
46
- async function readEnvironmentsFromFile(baseDir: string): Promise<Record<string, EnvironmentConfig>> {
47
- const filePath = join(baseDir, 'src', 'tests', 'config', 'environments', 'environments.json')
46
+ async function readEnvironmentsFromFile(): Promise<Record<string, EnvironmentConfig>> {
47
+ const filePath = `${getAutomationEnvironmentsDir()}/environments.json`
48
48
 
49
49
  try {
50
50
  await fs.access(filePath)
@@ -285,11 +285,11 @@ async function main() {
285
285
  console.log('🔄 Starting environments sync...')
286
286
  console.log('This will scan environments.json and sync environments to database.\n')
287
287
 
288
- const baseDir = process.cwd()
288
+ await ensureAutomationWorkspaceReady()
289
289
 
290
290
  // Read environments from file
291
291
  console.log('📁 Reading environments.json...')
292
- const jsonContent = await readEnvironmentsFromFile(baseDir)
292
+ const jsonContent = await readEnvironmentsFromFile()
293
293
  const environmentKeys = Object.keys(jsonContent)
294
294
  console.log(` Found ${environmentKeys.length} environment(s): ${environmentKeys.join(', ') || 'none'}`)
295
295
 
@@ -321,3 +321,6 @@ async function main() {
321
321
 
322
322
  main()
323
323
 
324
+
325
+
326
+
@@ -14,6 +14,7 @@ import { join } from 'path'
14
14
  import { glob } from 'glob'
15
15
  import prisma from '../src/config/db-config'
16
16
  import { findModuleByPath, buildModuleHierarchy } from '../src/lib/module-hierarchy-builder'
17
+ import { extractModulePathFromAutomationFile, getAutomationLocatorMapPath } from '../src/lib/template-sync-utils'
17
18
 
18
19
  /**
19
20
  * Represents a locator group from the filesystem
@@ -52,7 +53,7 @@ interface SyncResult {
52
53
  * Reads and parses the locator-map.json file
53
54
  */
54
55
  async function readLocatorMap(baseDir: string): Promise<LocatorMapEntry[]> {
55
- const locatorMapPath = join(baseDir, 'src', 'tests', 'mapping', 'locator-map.json')
56
+ const locatorMapPath = getAutomationLocatorMapPath(baseDir)
56
57
 
57
58
  try {
58
59
  await fs.access(locatorMapPath)
@@ -82,7 +83,7 @@ async function readLocatorMap(baseDir: string): Promise<LocatorMapEntry[]> {
82
83
  * Scans the locators directory to find all locator group files
83
84
  */
84
85
  async function scanLocatorGroupFiles(baseDir: string): Promise<string[]> {
85
- const pattern = 'src/tests/locators/**/*.json'
86
+ const pattern = 'automation/locators/**/*.json'
86
87
 
87
88
  try {
88
89
  const files = await glob(pattern, {
@@ -96,15 +97,11 @@ async function scanLocatorGroupFiles(baseDir: string): Promise<string[]> {
96
97
 
97
98
  /**
98
99
  * Extracts module path from locator file path
99
- * Example: src/tests/locators/home/home.json -> /home
100
- * Example: src/tests/locators/users/admins/directors.json -> /users/admins
100
+ * Example: automation/locators/home/home.json -> /home
101
+ * Example: automation/locators/users/admins/directors.json -> /users/admins
101
102
  */
102
103
  function extractModulePathFromLocatorFile(filePath: string, baseDir: string): string {
103
- const testsDir = join(baseDir, 'src', 'tests')
104
- const relativePath = filePath.replace(testsDir, '').replace(/\\/g, '/')
105
- const pathParts = relativePath.split('/').filter(p => p && p !== 'locators')
106
- const moduleParts = pathParts.slice(0, -1) // Remove filename
107
- return moduleParts.length > 0 ? '/' + moduleParts.join('/') : '/'
104
+ return extractModulePathFromAutomationFile(filePath, baseDir, 'locators')
108
105
  }
109
106
 
110
107
  /**
@@ -374,7 +371,7 @@ async function main() {
374
371
  console.log(` Found ${locatorMap.length} entry(ies) in locator map`)
375
372
 
376
373
  // Build locator groups from filesystem
377
- console.log('\n📁 Scanning src/tests/locators directory...')
374
+ console.log('\n📁 Scanning automation/locators directory...')
378
375
  const locatorGroups = await buildLocatorGroupsFromFS(baseDir, locatorMap)
379
376
  result.locatorGroupsScanned = locatorGroups.length
380
377
  console.log(` Found ${locatorGroups.length} locator group(s) in filesystem`)
@@ -15,6 +15,7 @@ import { glob } from 'glob'
15
15
  import prisma from '../src/config/db-config'
16
16
  import { buildModuleHierarchy } from '../src/lib/module-hierarchy-builder'
17
17
  import { getLocatorGroupFilePath } from '../src/lib/locator-group-file-utils'
18
+ import { extractModulePathFromAutomationFile } from '../src/lib/template-sync-utils'
18
19
 
19
20
  interface SyncResult {
20
21
  locatorsScanned: number
@@ -34,7 +35,7 @@ interface SyncResult {
34
35
  * Scans locator directory for all JSON files
35
36
  */
36
37
  async function scanLocatorFiles(baseDir: string): Promise<string[]> {
37
- const pattern = 'src/tests/locators/**/*.json'
38
+ const pattern = 'automation/locators/**/*.json'
38
39
  try {
39
40
  const files = await glob(pattern, {
40
41
  cwd: baseDir,
@@ -47,14 +48,10 @@ async function scanLocatorFiles(baseDir: string): Promise<string[]> {
47
48
 
48
49
  /**
49
50
  * Extracts module path from locator file path
50
- * Example: src/tests/locators/users/admins/directors/directors.json -> /users/admins/directors
51
+ * Example: automation/locators/users/admins/directors/directors.json -> /users/admins/directors
51
52
  */
52
53
  function extractModulePathFromLocatorFile(filePath: string, baseDir: string): string {
53
- const testsDir = join(baseDir, 'src', 'tests')
54
- const relativePath = filePath.replace(testsDir, '').replace(/\\/g, '/')
55
- const pathParts = relativePath.split('/').filter(p => p && p !== 'locators')
56
- const moduleParts = pathParts.slice(0, -1) // Remove filename
57
- return moduleParts.length > 0 ? '/' + moduleParts.join('/') : '/'
54
+ return extractModulePathFromAutomationFile(filePath, baseDir, 'locators')
58
55
  }
59
56
 
60
57
  /**
@@ -368,7 +365,7 @@ async function main() {
368
365
  const baseDir = process.cwd()
369
366
 
370
367
  // Scan locator files
371
- console.log('📁 Scanning src/tests/locators...')
368
+ console.log('📁 Scanning automation/locators...')
372
369
  const files = await scanLocatorFiles(baseDir)
373
370
  console.log(` Found ${files.length} locator file(s)`)
374
371
 
@@ -399,4 +396,3 @@ async function main() {
399
396
  }
400
397
 
401
398
  main()
402
-
@@ -13,6 +13,7 @@ import { buildModuleHierarchy, findModuleByPath, getAllModulesWithPaths } from '
13
13
  import { join } from 'path'
14
14
  import { glob } from 'glob'
15
15
  import prisma from '../src/config/db-config'
16
+ import { extractModulePathFromAutomationFile } from '../src/lib/template-sync-utils'
16
17
 
17
18
  interface SyncResult {
18
19
  modulesScanned: number
@@ -33,7 +34,7 @@ async function scanLocatorDirectories(baseDir: string): Promise<string[]> {
33
34
 
34
35
  try {
35
36
  // Get all JSON files in locators directory
36
- const pattern = 'src/tests/locators/**/*.json'
37
+ const pattern = 'automation/locators/**/*.json'
37
38
  const files = await glob(pattern, {
38
39
  cwd: baseDir,
39
40
  })
@@ -61,7 +62,7 @@ async function scanFeatureDirectories(baseDir: string): Promise<string[]> {
61
62
 
62
63
  try {
63
64
  // Get all feature files
64
- const pattern = 'src/tests/features/**/*.feature'
65
+ const pattern = 'automation/features/**/*.feature'
65
66
  const files = await glob(pattern, {
66
67
  cwd: baseDir,
67
68
  })
@@ -83,26 +84,18 @@ async function scanFeatureDirectories(baseDir: string): Promise<string[]> {
83
84
 
84
85
  /**
85
86
  * Extracts module path from locator file path
86
- * Example: src/tests/locators/home/home.json -> /home
87
+ * Example: automation/locators/home/home.json -> /home
87
88
  */
88
89
  function extractModulePathFromLocatorFile(filePath: string, baseDir: string): string {
89
- const testsDir = join(baseDir, 'src', 'tests')
90
- const relativePath = filePath.replace(testsDir, '').replace(/\\/g, '/')
91
- const pathParts = relativePath.split('/').filter(p => p && p !== 'locators')
92
- const moduleParts = pathParts.slice(0, -1) // Remove filename
93
- return moduleParts.length > 0 ? '/' + moduleParts.join('/') : '/'
90
+ return extractModulePathFromAutomationFile(filePath, baseDir, 'locators')
94
91
  }
95
92
 
96
93
  /**
97
94
  * Extracts module path from feature file path
98
- * Example: src/tests/features/login/demo.feature -> /login
95
+ * Example: automation/features/login/demo.feature -> /login
99
96
  */
100
97
  function extractModulePathFromFeatureFile(filePath: string, baseDir: string): string {
101
- const featuresBaseDir = join(baseDir, 'src', 'tests', 'features')
102
- const relativePath = filePath.replace(featuresBaseDir, '').replace(/\\/g, '/')
103
- const pathParts = relativePath.split('/').filter(part => part && part !== '')
104
- const moduleParts = pathParts.slice(0, -1) // Remove filename
105
- return moduleParts.length > 0 ? '/' + moduleParts.join('/') : '/'
98
+ return extractModulePathFromAutomationFile(filePath, baseDir, 'features')
106
99
  }
107
100
 
108
101
  /**
@@ -301,11 +294,11 @@ async function main() {
301
294
  const baseDir = process.cwd()
302
295
 
303
296
  // Scan directories
304
- console.log('📁 Scanning src/tests/locators...')
297
+ console.log('📁 Scanning automation/locators...')
305
298
  const locatorModulePaths = await scanLocatorDirectories(baseDir)
306
299
  console.log(` Found ${locatorModulePaths.length} module path(s): ${locatorModulePaths.join(', ') || 'none'}`)
307
300
 
308
- console.log('\n📁 Scanning src/tests/features...')
301
+ console.log('\n📁 Scanning automation/features...')
309
302
  const featureModulePaths = await scanFeatureDirectories(baseDir)
310
303
  console.log(` Found ${featureModulePaths.length} module path(s): ${featureModulePaths.join(', ') || 'none'}`)
311
304
 
@@ -346,4 +339,3 @@ async function main() {
346
339
  }
347
340
 
348
341
  main()
349
-
@@ -235,10 +235,10 @@ async function main() {
235
235
  console.log('Filesystem is the source of truth - tags in DB but not in FS will be deleted.\n')
236
236
 
237
237
  const baseDir = process.cwd()
238
- const featuresDir = join(baseDir, 'src', 'tests', 'features')
238
+ const featuresDir = join(baseDir, 'automation', 'features')
239
239
 
240
240
  // Scan feature files
241
- console.log('📁 Scanning feature files in src/tests/features...')
241
+ console.log('📁 Scanning feature files in automation/features...')
242
242
  const parsedFeatures = await scanFeatureFiles(featuresDir)
243
243
  console.log(` Found ${parsedFeatures.length} feature file(s)`)
244
244