@toolstackhq/create-qa-patterns 1.0.14 → 1.0.15

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 (123) hide show
  1. package/README.md +5 -0
  2. package/index.js +252 -1076
  3. package/lib/args.js +139 -0
  4. package/lib/constants.js +115 -0
  5. package/lib/interactive.js +131 -0
  6. package/lib/local-env.js +65 -0
  7. package/lib/metadata.js +329 -0
  8. package/lib/output.js +326 -0
  9. package/lib/prereqs.js +72 -0
  10. package/lib/scaffold.js +120 -0
  11. package/lib/templates.js +40 -0
  12. package/package.json +5 -3
  13. package/templates/cypress-template/.env.example +2 -2
  14. package/templates/cypress-template/.github/workflows/cypress-tests.yml +2 -2
  15. package/templates/cypress-template/README.md +10 -6
  16. package/templates/cypress-template/allurerc.mjs +1 -1
  17. package/templates/cypress-template/config/environments.ts +13 -11
  18. package/templates/cypress-template/config/runtime-config.ts +17 -12
  19. package/templates/cypress-template/config/secret-manager.ts +1 -1
  20. package/templates/cypress-template/config/test-env.ts +3 -3
  21. package/templates/cypress-template/cypress/e2e/ui-journey.cy.ts +12 -10
  22. package/templates/cypress-template/cypress/support/app-config.ts +5 -5
  23. package/templates/cypress-template/cypress/support/commands.ts +7 -7
  24. package/templates/cypress-template/cypress/support/data/data-factory.ts +6 -4
  25. package/templates/cypress-template/cypress/support/data/id-generator.ts +1 -1
  26. package/templates/cypress-template/cypress/support/data/seeded-faker.ts +2 -2
  27. package/templates/cypress-template/cypress/support/e2e.ts +2 -2
  28. package/templates/cypress-template/cypress/support/pages/login-page.ts +4 -4
  29. package/templates/cypress-template/cypress/support/pages/people-page.ts +10 -10
  30. package/templates/cypress-template/cypress.config.ts +9 -9
  31. package/templates/cypress-template/demo-apps/ui-demo-app/public/styles.css +1 -1
  32. package/templates/cypress-template/demo-apps/ui-demo-app/src/server.js +44 -41
  33. package/templates/cypress-template/demo-apps/ui-demo-app/src/store.js +31 -3
  34. package/templates/cypress-template/demo-apps/ui-demo-app/src/templates.js +5 -5
  35. package/templates/cypress-template/eslint.config.mjs +53 -45
  36. package/templates/cypress-template/package.json +6 -5
  37. package/templates/cypress-template/scripts/ensure-local-env.mjs +36 -0
  38. package/templates/cypress-template/scripts/generate-allure-report.mjs +16 -10
  39. package/templates/cypress-template/scripts/run-cypress.mjs +33 -24
  40. package/templates/cypress-template/scripts/run-tests.sh +1 -0
  41. package/templates/cypress-template/tsconfig.json +7 -1
  42. package/templates/playwright-template/.env.example +6 -6
  43. package/templates/playwright-template/.github/workflows/playwright-tests.yml +14 -5
  44. package/templates/playwright-template/README.md +6 -5
  45. package/templates/playwright-template/allurerc.mjs +1 -1
  46. package/templates/playwright-template/components/flash-message.ts +2 -2
  47. package/templates/playwright-template/config/environments.ts +16 -14
  48. package/templates/playwright-template/config/runtime-config.ts +17 -12
  49. package/templates/playwright-template/config/secret-manager.ts +1 -1
  50. package/templates/playwright-template/config/test-env.ts +3 -3
  51. package/templates/playwright-template/data/factories/data-factory.ts +6 -4
  52. package/templates/playwright-template/data/generators/id-generator.ts +1 -1
  53. package/templates/playwright-template/data/generators/seeded-faker.ts +2 -2
  54. package/templates/playwright-template/demo-apps/api-demo-server/src/server.js +9 -9
  55. package/templates/playwright-template/demo-apps/api-demo-server/src/store.js +1 -1
  56. package/templates/playwright-template/demo-apps/ui-demo-app/public/styles.css +1 -1
  57. package/templates/playwright-template/demo-apps/ui-demo-app/src/server.js +44 -41
  58. package/templates/playwright-template/demo-apps/ui-demo-app/src/store.js +31 -3
  59. package/templates/playwright-template/demo-apps/ui-demo-app/src/templates.js +5 -5
  60. package/templates/playwright-template/eslint.config.mjs +40 -40
  61. package/templates/playwright-template/fixtures/test-fixtures.ts +27 -12
  62. package/templates/playwright-template/lint/architecture-plugin.cjs +36 -31
  63. package/templates/playwright-template/package.json +7 -6
  64. package/templates/playwright-template/pages/base-page.ts +4 -4
  65. package/templates/playwright-template/pages/login-page.ts +9 -9
  66. package/templates/playwright-template/pages/people-page.ts +21 -17
  67. package/templates/playwright-template/playwright.config.ts +22 -19
  68. package/templates/playwright-template/reporters/structured-reporter.ts +11 -8
  69. package/templates/playwright-template/scripts/ensure-local-env.mjs +37 -0
  70. package/templates/playwright-template/scripts/generate-allure-report.mjs +16 -10
  71. package/templates/playwright-template/scripts/run-tests.sh +1 -0
  72. package/templates/playwright-template/tests/api-people.spec.ts +8 -6
  73. package/templates/playwright-template/tests/ui-journey.spec.ts +13 -8
  74. package/templates/playwright-template/tsconfig.json +3 -11
  75. package/templates/playwright-template/utils/logger.ts +12 -8
  76. package/templates/playwright-template/utils/test-step.ts +5 -5
  77. package/templates/wdio-template/.env.example +14 -0
  78. package/templates/wdio-template/.github/workflows/wdio-tests.yml +46 -0
  79. package/templates/wdio-template/README.md +241 -0
  80. package/templates/wdio-template/allurerc.mjs +10 -0
  81. package/templates/wdio-template/components/README.md +5 -0
  82. package/templates/wdio-template/components/flash-message.ts +16 -0
  83. package/templates/wdio-template/config/README.md +5 -0
  84. package/templates/wdio-template/config/environments.ts +40 -0
  85. package/templates/wdio-template/config/runtime-config.ts +53 -0
  86. package/templates/wdio-template/config/secret-manager.ts +29 -0
  87. package/templates/wdio-template/config/test-env.ts +9 -0
  88. package/templates/wdio-template/data/README.md +9 -0
  89. package/templates/wdio-template/data/factories/README.md +6 -0
  90. package/templates/wdio-template/data/factories/data-factory.ts +36 -0
  91. package/templates/wdio-template/data/generators/README.md +5 -0
  92. package/templates/wdio-template/data/generators/id-generator.ts +18 -0
  93. package/templates/wdio-template/data/generators/seeded-faker.ts +14 -0
  94. package/templates/wdio-template/demo-apps/ui-demo-app/public/styles.css +120 -0
  95. package/templates/wdio-template/demo-apps/ui-demo-app/src/server.js +152 -0
  96. package/templates/wdio-template/demo-apps/ui-demo-app/src/store.js +71 -0
  97. package/templates/wdio-template/demo-apps/ui-demo-app/src/templates.js +121 -0
  98. package/templates/wdio-template/eslint.config.mjs +86 -0
  99. package/templates/wdio-template/lint/architecture-plugin.cjs +123 -0
  100. package/templates/wdio-template/package-lock.json +11058 -0
  101. package/templates/wdio-template/package.json +44 -0
  102. package/templates/wdio-template/pages/README.md +6 -0
  103. package/templates/wdio-template/pages/base-page.ts +15 -0
  104. package/templates/wdio-template/pages/login-page.ts +27 -0
  105. package/templates/wdio-template/pages/people-page.ts +54 -0
  106. package/templates/wdio-template/reporters/README.md +5 -0
  107. package/templates/wdio-template/reporters/structured-reporter.ts +78 -0
  108. package/templates/wdio-template/scripts/README.md +5 -0
  109. package/templates/wdio-template/scripts/ensure-local-env.mjs +36 -0
  110. package/templates/wdio-template/scripts/generate-allure-report.mjs +72 -0
  111. package/templates/wdio-template/scripts/run-tests.sh +7 -0
  112. package/templates/wdio-template/scripts/run-wdio.mjs +114 -0
  113. package/templates/wdio-template/tests/README.md +7 -0
  114. package/templates/wdio-template/tests/ui-journey.spec.ts +52 -0
  115. package/templates/wdio-template/tsconfig.json +22 -0
  116. package/templates/wdio-template/utils/README.md +5 -0
  117. package/templates/wdio-template/utils/logger.ts +60 -0
  118. package/templates/wdio-template/utils/test-step.ts +20 -0
  119. package/templates/wdio-template/wdio.conf.ts +58 -0
  120. package/tests/args.test.js +58 -0
  121. package/tests/local-env.test.js +70 -0
  122. package/tests/metadata.test.js +147 -0
  123. package/tests/templates.test.js +44 -0
package/lib/args.js ADDED
@@ -0,0 +1,139 @@
1
+ const path = require('node:path');
2
+
3
+ function parseCliOptions(args, { resolveTemplate, supportedTemplateIds }) {
4
+ const options = {
5
+ yes: false,
6
+ noInstall: false,
7
+ noSetup: false,
8
+ noTest: false,
9
+ safe: false,
10
+ templateName: null,
11
+ positionalArgs: []
12
+ };
13
+
14
+ for (let index = 0; index < args.length; index += 1) {
15
+ const arg = args[index];
16
+
17
+ switch (arg) {
18
+ case '--yes':
19
+ options.yes = true;
20
+ break;
21
+ case '--no-install':
22
+ options.noInstall = true;
23
+ break;
24
+ case '--no-setup':
25
+ options.noSetup = true;
26
+ break;
27
+ case '--no-test':
28
+ options.noTest = true;
29
+ break;
30
+ case '--safe':
31
+ options.safe = true;
32
+ break;
33
+ case '--template': {
34
+ const templateValue = args[index + 1];
35
+ if (!templateValue) {
36
+ throw new Error('Missing value for --template.');
37
+ }
38
+
39
+ const templateName = resolveTemplate(templateValue);
40
+ if (!templateName) {
41
+ throw new Error(
42
+ `Unsupported template "${templateValue}". Supported templates: ${supportedTemplateIds.join(', ')}.`
43
+ );
44
+ }
45
+
46
+ options.templateName = templateName;
47
+ index += 1;
48
+ break;
49
+ }
50
+ default:
51
+ options.positionalArgs.push(arg);
52
+ break;
53
+ }
54
+ }
55
+
56
+ return options;
57
+ }
58
+
59
+ function resolveNonInteractiveArgs(args, options) {
60
+ const {
61
+ templateName,
62
+ resolveTemplate,
63
+ supportedTemplateIds,
64
+ defaultTemplate
65
+ } = options;
66
+
67
+ if (templateName) {
68
+ if (args.length > 1) {
69
+ throw new Error(
70
+ 'Too many arguments. Run `create-qa-patterns --help` for usage.'
71
+ );
72
+ }
73
+
74
+ if (args.length === 0) {
75
+ return {
76
+ templateName,
77
+ targetDirectory: process.cwd(),
78
+ generatedInCurrentDirectory: true
79
+ };
80
+ }
81
+
82
+ return {
83
+ templateName,
84
+ targetDirectory: path.resolve(process.cwd(), args[0]),
85
+ generatedInCurrentDirectory: false
86
+ };
87
+ }
88
+
89
+ if (args.length === 0) {
90
+ return {
91
+ templateName: defaultTemplate,
92
+ targetDirectory: process.cwd(),
93
+ generatedInCurrentDirectory: true
94
+ };
95
+ }
96
+
97
+ if (args.length === 1) {
98
+ const explicitTemplate = resolveTemplate(args[0]);
99
+
100
+ if (explicitTemplate) {
101
+ return {
102
+ templateName: explicitTemplate,
103
+ targetDirectory: process.cwd(),
104
+ generatedInCurrentDirectory: true
105
+ };
106
+ }
107
+
108
+ return {
109
+ templateName: defaultTemplate,
110
+ targetDirectory: path.resolve(process.cwd(), args[0]),
111
+ generatedInCurrentDirectory: false
112
+ };
113
+ }
114
+
115
+ if (args.length === 2) {
116
+ const explicitTemplate = resolveTemplate(args[0]);
117
+
118
+ if (!explicitTemplate) {
119
+ throw new Error(
120
+ `Unsupported template "${args[0]}". Supported templates: ${supportedTemplateIds.join(', ')}.`
121
+ );
122
+ }
123
+
124
+ return {
125
+ templateName: explicitTemplate,
126
+ targetDirectory: path.resolve(process.cwd(), args[1]),
127
+ generatedInCurrentDirectory: false
128
+ };
129
+ }
130
+
131
+ throw new Error(
132
+ 'Too many arguments. Run `create-qa-patterns --help` for usage.'
133
+ );
134
+ }
135
+
136
+ module.exports = {
137
+ parseCliOptions,
138
+ resolveNonInteractiveArgs
139
+ };
@@ -0,0 +1,115 @@
1
+ const DEFAULT_TEMPLATE = 'playwright-template';
2
+ const CLI_PACKAGE_VERSION = require('../package.json').version;
3
+ const METADATA_FILENAME = '.qa-patterns.json';
4
+ const MIN_NODE_VERSION = {
5
+ major: 18,
6
+ minor: 18,
7
+ patch: 0
8
+ };
9
+
10
+ const DEFAULT_GITIGNORE = `node_modules/
11
+
12
+ .env
13
+ .env.*
14
+ !.env.example
15
+
16
+ .DS_Store
17
+ *.log
18
+ *.tgz
19
+ .idea/
20
+ .vscode/
21
+ .nyc_output/
22
+ coverage/
23
+ dist/
24
+ build/
25
+ tmp/
26
+ temp/
27
+ downloads/
28
+ cypress.env.json
29
+ reports/
30
+ cypress/screenshots/
31
+ cypress/videos/
32
+ reports/screenshots/
33
+ reports/videos/
34
+ allure-results/
35
+ allure-report/
36
+ test-results/
37
+ playwright-report/
38
+ `;
39
+
40
+ const MANAGED_FILE_PATTERNS = {
41
+ common: [
42
+ '.env.example',
43
+ '.gitignore',
44
+ 'package.json',
45
+ 'package-lock.json',
46
+ 'tsconfig.json',
47
+ 'eslint.config.mjs',
48
+ 'allurerc.mjs',
49
+ 'config/**',
50
+ 'scripts/**',
51
+ '.github/**'
52
+ ],
53
+ 'playwright-template': [
54
+ 'playwright.config.ts',
55
+ 'docker/**',
56
+ 'lint/**',
57
+ 'reporters/**',
58
+ 'utils/logger.ts',
59
+ 'utils/test-step.ts'
60
+ ],
61
+ 'cypress-template': ['cypress.config.ts'],
62
+ 'wdio-template': [
63
+ 'wdio.conf.ts',
64
+ 'lint/**',
65
+ 'reporters/**',
66
+ 'utils/logger.ts',
67
+ 'utils/test-step.ts'
68
+ ]
69
+ };
70
+
71
+ const TEMPLATE_CATALOG = [
72
+ {
73
+ id: DEFAULT_TEMPLATE,
74
+ aliases: ['playwright', 'pw'],
75
+ label: 'Playwright Template',
76
+ description:
77
+ 'TypeScript starter with page objects, fixtures, multi-environment config, reporting, linting, CI and Docker.',
78
+ defaultPackageName: 'playwright-template',
79
+ demoAppsManagedByTemplate: true,
80
+ setup: {
81
+ availability: 'npx',
82
+ prompt: 'Run npx playwright install now?',
83
+ summaryLabel: 'Playwright browser install',
84
+ nextStep: 'npx playwright install'
85
+ }
86
+ },
87
+ {
88
+ id: 'cypress-template',
89
+ aliases: ['cypress', 'cy'],
90
+ label: 'Cypress Template',
91
+ description:
92
+ 'TypeScript starter with Cypress e2e specs, custom commands, page modules, env-based config, CI, and a bundled demo app.',
93
+ defaultPackageName: 'cypress-template',
94
+ demoAppsManagedByTemplate: true
95
+ },
96
+ {
97
+ id: 'wdio-template',
98
+ aliases: ['webdriverio', 'wdio'],
99
+ label: 'WebdriverIO Template',
100
+ description:
101
+ 'TypeScript starter with WebdriverIO + Mocha, page objects, env-based config, CI, and a bundled demo app.',
102
+ defaultPackageName: 'wdio-template',
103
+ demoAppsManagedByTemplate: true
104
+ }
105
+ ];
106
+
107
+ module.exports = {
108
+ CLI_PACKAGE_VERSION,
109
+ DEFAULT_GITIGNORE,
110
+ DEFAULT_TEMPLATE,
111
+ MANAGED_FILE_PATTERNS,
112
+ METADATA_FILENAME,
113
+ MIN_NODE_VERSION,
114
+ TEMPLATE_CATALOG
115
+ };
@@ -0,0 +1,131 @@
1
+ const readline = require('node:readline');
2
+
3
+ function createLineInterface() {
4
+ return readline.createInterface({
5
+ input: process.stdin,
6
+ output: process.stdout
7
+ });
8
+ }
9
+
10
+ function askQuestion(prompt) {
11
+ const lineInterface = createLineInterface();
12
+
13
+ return new Promise((resolve) => {
14
+ lineInterface.question(prompt, (answer) => {
15
+ lineInterface.close();
16
+ resolve(answer.trim());
17
+ });
18
+ });
19
+ }
20
+
21
+ async function askYesNo(prompt, defaultValue = true) {
22
+ const suffix = defaultValue ? ' [Y/n] ' : ' [y/N] ';
23
+
24
+ while (true) {
25
+ const answer = (await askQuestion(`${prompt}${suffix}`)).toLowerCase();
26
+
27
+ if (!answer) {
28
+ return defaultValue;
29
+ }
30
+
31
+ if (['y', 'yes'].includes(answer)) {
32
+ return true;
33
+ }
34
+
35
+ if (['n', 'no'].includes(answer)) {
36
+ return false;
37
+ }
38
+
39
+ process.stdout.write('Please answer yes or no.\n');
40
+ }
41
+ }
42
+
43
+ async function selectTemplateInteractively(templates) {
44
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
45
+ return null;
46
+ }
47
+
48
+ readline.emitKeypressEvents(process.stdin);
49
+
50
+ if (typeof process.stdin.setRawMode === 'function') {
51
+ process.stdin.setRawMode(true);
52
+ }
53
+
54
+ let selectedIndex = 0;
55
+ let renderedLines = 0;
56
+
57
+ const render = () => {
58
+ if (renderedLines > 0) {
59
+ readline.moveCursor(process.stdout, 0, -renderedLines);
60
+ readline.clearScreenDown(process.stdout);
61
+ }
62
+
63
+ const lines = [
64
+ 'Select a template',
65
+ 'Use ↑/↓ to choose and press Enter to continue.',
66
+ ''
67
+ ];
68
+
69
+ for (let index = 0; index < templates.length; index += 1) {
70
+ const template = templates[index];
71
+ const marker = index === selectedIndex ? '>' : ' ';
72
+ lines.push(`${marker} ${template.label}`);
73
+ lines.push(` ${template.description}`);
74
+ lines.push('');
75
+ }
76
+
77
+ renderedLines = lines.length;
78
+ process.stdout.write(`${lines.join('\n')}\n`);
79
+ };
80
+
81
+ render();
82
+
83
+ return new Promise((resolve) => {
84
+ const handleKeypress = (_, key) => {
85
+ if (!key) {
86
+ return;
87
+ }
88
+
89
+ if (key.name === 'up') {
90
+ selectedIndex =
91
+ (selectedIndex - 1 + templates.length) % templates.length;
92
+ render();
93
+ return;
94
+ }
95
+
96
+ if (key.name === 'down') {
97
+ selectedIndex = (selectedIndex + 1) % templates.length;
98
+ render();
99
+ return;
100
+ }
101
+
102
+ if (key.name === 'return') {
103
+ process.stdin.off('keypress', handleKeypress);
104
+ if (typeof process.stdin.setRawMode === 'function') {
105
+ process.stdin.setRawMode(false);
106
+ }
107
+ readline.clearScreenDown(process.stdout);
108
+ process.stdout.write(`Selected: ${templates[selectedIndex].label}\n\n`);
109
+ resolve(templates[selectedIndex].id);
110
+ return;
111
+ }
112
+
113
+ if (key.ctrl && key.name === 'c') {
114
+ process.stdin.off('keypress', handleKeypress);
115
+ if (typeof process.stdin.setRawMode === 'function') {
116
+ process.stdin.setRawMode(false);
117
+ }
118
+ process.stdout.write('\n');
119
+ process.exit(1);
120
+ }
121
+ };
122
+
123
+ process.stdin.on('keypress', handleKeypress);
124
+ });
125
+ }
126
+
127
+ module.exports = {
128
+ askQuestion,
129
+ askYesNo,
130
+ selectTemplateInteractively
131
+ };
@@ -0,0 +1,65 @@
1
+ const crypto = require('node:crypto');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ function createLocalCredentials(targetDirectory) {
6
+ const projectSlug = path
7
+ .basename(targetDirectory)
8
+ .toLowerCase()
9
+ .replace(/[^a-z0-9]+/g, '-')
10
+ .replace(/^-+|-+$/g, '')
11
+ .slice(0, 12);
12
+ const username = `${projectSlug || 'local'}-${crypto.randomBytes(3).toString('hex')}`;
13
+ const password = `${crypto.randomBytes(9).toString('base64url')}A1!`;
14
+
15
+ return {
16
+ username,
17
+ password
18
+ };
19
+ }
20
+
21
+ function renderLocalEnv(templateId, credentials) {
22
+ const common = [
23
+ 'TEST_ENV=dev',
24
+ 'TEST_RUN_ID=local',
25
+ 'DEV_UI_BASE_URL=http://127.0.0.1:3000'
26
+ ];
27
+
28
+ if (templateId === 'playwright-template') {
29
+ common.push('DEV_API_BASE_URL=http://127.0.0.1:3001');
30
+ }
31
+
32
+ common.push(
33
+ `DEV_APP_USERNAME=${credentials.username}`,
34
+ `DEV_APP_PASSWORD=${credentials.password}`,
35
+ `UI_DEMO_USERNAME=${credentials.username}`,
36
+ `UI_DEMO_PASSWORD=${credentials.password}`
37
+ );
38
+
39
+ return `${common.join('\n')}\n`;
40
+ }
41
+
42
+ function writeGeneratedLocalEnv(targetDirectory, templateId, credentials) {
43
+ const envPath = path.join(targetDirectory, '.env');
44
+
45
+ if (fs.existsSync(envPath)) {
46
+ return {
47
+ created: false,
48
+ envPath,
49
+ credentials
50
+ };
51
+ }
52
+
53
+ fs.writeFileSync(envPath, renderLocalEnv(templateId, credentials), 'utf8');
54
+ return {
55
+ created: true,
56
+ envPath,
57
+ credentials
58
+ };
59
+ }
60
+
61
+ module.exports = {
62
+ createLocalCredentials,
63
+ renderLocalEnv,
64
+ writeGeneratedLocalEnv
65
+ };