@qulib/core 0.1.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.
Files changed (99) hide show
  1. package/README.md +146 -0
  2. package/bin/qulib.js +17 -0
  3. package/dist/adapters/adapter-factory.d.ts +5 -0
  4. package/dist/adapters/adapter-factory.d.ts.map +1 -0
  5. package/dist/adapters/adapter-factory.js +21 -0
  6. package/dist/adapters/adapter.interface.d.ts +7 -0
  7. package/dist/adapters/adapter.interface.d.ts.map +1 -0
  8. package/dist/adapters/adapter.interface.js +1 -0
  9. package/dist/adapters/api-adapter.d.ts +8 -0
  10. package/dist/adapters/api-adapter.d.ts.map +1 -0
  11. package/dist/adapters/api-adapter.js +9 -0
  12. package/dist/adapters/cypress-component-adapter.d.ts +8 -0
  13. package/dist/adapters/cypress-component-adapter.d.ts.map +1 -0
  14. package/dist/adapters/cypress-component-adapter.js +9 -0
  15. package/dist/adapters/cypress-e2e-adapter.d.ts +8 -0
  16. package/dist/adapters/cypress-e2e-adapter.d.ts.map +1 -0
  17. package/dist/adapters/cypress-e2e-adapter.js +9 -0
  18. package/dist/adapters/playwright-adapter.d.ts +8 -0
  19. package/dist/adapters/playwright-adapter.d.ts.map +1 -0
  20. package/dist/adapters/playwright-adapter.js +9 -0
  21. package/dist/analyze.d.ts +20 -0
  22. package/dist/analyze.d.ts.map +1 -0
  23. package/dist/analyze.js +21 -0
  24. package/dist/cli/index.d.ts +3 -0
  25. package/dist/cli/index.d.ts.map +1 -0
  26. package/dist/cli/index.js +102 -0
  27. package/dist/harness/decision-logger.d.ts +7 -0
  28. package/dist/harness/decision-logger.d.ts.map +1 -0
  29. package/dist/harness/decision-logger.js +68 -0
  30. package/dist/harness/run-options.d.ts +6 -0
  31. package/dist/harness/run-options.d.ts.map +1 -0
  32. package/dist/harness/run-options.js +1 -0
  33. package/dist/harness/state-manager.d.ts +6 -0
  34. package/dist/harness/state-manager.d.ts.map +1 -0
  35. package/dist/harness/state-manager.js +64 -0
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +1 -0
  39. package/dist/llm/context-builder.d.ts +3 -0
  40. package/dist/llm/context-builder.d.ts.map +1 -0
  41. package/dist/llm/context-builder.js +33 -0
  42. package/dist/llm/provider.d.ts +4 -0
  43. package/dist/llm/provider.d.ts.map +1 -0
  44. package/dist/llm/provider.js +56 -0
  45. package/dist/phases/act.d.ts +5 -0
  46. package/dist/phases/act.d.ts.map +1 -0
  47. package/dist/phases/act.js +41 -0
  48. package/dist/phases/observe.d.ts +10 -0
  49. package/dist/phases/observe.d.ts.map +1 -0
  50. package/dist/phases/observe.js +48 -0
  51. package/dist/phases/think.d.ts +6 -0
  52. package/dist/phases/think.d.ts.map +1 -0
  53. package/dist/phases/think.js +85 -0
  54. package/dist/reporters/json-reporter.d.ts +3 -0
  55. package/dist/reporters/json-reporter.d.ts.map +1 -0
  56. package/dist/reporters/json-reporter.js +8 -0
  57. package/dist/reporters/markdown-reporter.d.ts +3 -0
  58. package/dist/reporters/markdown-reporter.d.ts.map +1 -0
  59. package/dist/reporters/markdown-reporter.js +42 -0
  60. package/dist/schemas/config.schema.d.ts +327 -0
  61. package/dist/schemas/config.schema.d.ts.map +1 -0
  62. package/dist/schemas/config.schema.js +39 -0
  63. package/dist/schemas/decision-log.schema.d.ts +22 -0
  64. package/dist/schemas/decision-log.schema.d.ts.map +1 -0
  65. package/dist/schemas/decision-log.schema.js +8 -0
  66. package/dist/schemas/gap-analysis.schema.d.ts +363 -0
  67. package/dist/schemas/gap-analysis.schema.d.ts.map +1 -0
  68. package/dist/schemas/gap-analysis.schema.js +60 -0
  69. package/dist/schemas/index.d.ts +6 -0
  70. package/dist/schemas/index.d.ts.map +1 -0
  71. package/dist/schemas/index.js +5 -0
  72. package/dist/schemas/repo-analysis.schema.d.ts +165 -0
  73. package/dist/schemas/repo-analysis.schema.d.ts.map +1 -0
  74. package/dist/schemas/repo-analysis.schema.js +29 -0
  75. package/dist/schemas/route-inventory.schema.d.ts +241 -0
  76. package/dist/schemas/route-inventory.schema.d.ts.map +1 -0
  77. package/dist/schemas/route-inventory.schema.js +30 -0
  78. package/dist/tools/auth.d.ts +4 -0
  79. package/dist/tools/auth.d.ts.map +1 -0
  80. package/dist/tools/auth.js +35 -0
  81. package/dist/tools/cypress-explorer.d.ts +7 -0
  82. package/dist/tools/cypress-explorer.d.ts.map +1 -0
  83. package/dist/tools/cypress-explorer.js +5 -0
  84. package/dist/tools/explorer-factory.d.ts +4 -0
  85. package/dist/tools/explorer-factory.d.ts.map +1 -0
  86. package/dist/tools/explorer-factory.js +12 -0
  87. package/dist/tools/explorer.interface.d.ts +6 -0
  88. package/dist/tools/explorer.interface.d.ts.map +1 -0
  89. package/dist/tools/explorer.interface.js +1 -0
  90. package/dist/tools/gap-engine.d.ts +6 -0
  91. package/dist/tools/gap-engine.d.ts.map +1 -0
  92. package/dist/tools/gap-engine.js +101 -0
  93. package/dist/tools/playwright-explorer.d.ts +7 -0
  94. package/dist/tools/playwright-explorer.d.ts.map +1 -0
  95. package/dist/tools/playwright-explorer.js +150 -0
  96. package/dist/tools/repo-scanner.d.ts +3 -0
  97. package/dist/tools/repo-scanner.d.ts.map +1 -0
  98. package/dist/tools/repo-scanner.js +147 -0
  99. package/package.json +54 -0
@@ -0,0 +1,147 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { relative, basename } from 'node:path';
3
+ import glob from 'fast-glob';
4
+ import { RepoAnalysisSchema } from '../schemas/repo-analysis.schema.js';
5
+ const IGNORE_PATTERNS = ['**/node_modules/**', '**/.next/**', '**/dist/**', '**/build/**'];
6
+ function toPosix(path) {
7
+ return path.split('\\').join('/');
8
+ }
9
+ function normalizeRoutePath(path) {
10
+ const normalized = `/${path}`.replace(/\/+/g, '/').replace(/\/$/, '');
11
+ return normalized === '' ? '/' : normalized;
12
+ }
13
+ function detectTestType(filePath, content) {
14
+ const normalizedPath = toPosix(filePath);
15
+ if (normalizedPath.includes('cypress/e2e'))
16
+ return 'cypress-e2e';
17
+ if (normalizedPath.includes('cypress/component'))
18
+ return 'cypress-component';
19
+ if ((normalizedPath.includes('playwright') || normalizedPath.includes('e2e')) &&
20
+ normalizedPath.endsWith('.spec.ts')) {
21
+ return 'playwright';
22
+ }
23
+ if (/\bfrom\s+['"]vitest['"]|\brequire\(['"]vitest['"]\)/.test(content))
24
+ return 'vitest';
25
+ if (/\bfrom\s+['"]jest['"]|\brequire\(['"]jest['"]\)/.test(content))
26
+ return 'jest';
27
+ return 'other';
28
+ }
29
+ function extractCoveredPaths(content) {
30
+ const matches = [...content.matchAll(/['"`](\/[a-zA-Z0-9\/_\-\[\]]+)['"`]/g)].map((m) => m[1]);
31
+ return [...new Set(matches)];
32
+ }
33
+ export async function scanRepo(repoPath) {
34
+ const routes = [];
35
+ const appRouterFiles = await glob(['app/**/page.tsx', 'app/**/page.ts'], {
36
+ cwd: repoPath,
37
+ onlyFiles: true,
38
+ absolute: true,
39
+ ignore: IGNORE_PATTERNS,
40
+ });
41
+ for (const file of appRouterFiles) {
42
+ const rel = toPosix(relative(repoPath, file));
43
+ const routeSegment = rel.replace(/^app\//, '').replace(/\/page\.tsx?$/, '');
44
+ const routePath = normalizeRoutePath(routeSegment);
45
+ routes.push({ path: routePath, file: rel, method: 'GET' });
46
+ }
47
+ const pagesRouterFiles = await glob(['pages/**/*.tsx', 'pages/**/*.ts'], {
48
+ cwd: repoPath,
49
+ onlyFiles: true,
50
+ absolute: true,
51
+ ignore: IGNORE_PATTERNS,
52
+ });
53
+ for (const file of pagesRouterFiles) {
54
+ const rel = toPosix(relative(repoPath, file));
55
+ const name = basename(rel);
56
+ if (name.startsWith('_'))
57
+ continue;
58
+ const routeSegment = rel.replace(/^pages\//, '').replace(/\.tsx?$/, '');
59
+ const routePath = routeSegment === 'index'
60
+ ? '/'
61
+ : normalizeRoutePath(routeSegment.replace(/\/index$/, ''));
62
+ routes.push({ path: routePath, file: rel, method: 'GET' });
63
+ }
64
+ const expressFiles = await glob(['src/**/*.ts', 'src/**/*.js'], {
65
+ cwd: repoPath,
66
+ onlyFiles: true,
67
+ absolute: true,
68
+ ignore: IGNORE_PATTERNS,
69
+ });
70
+ for (const file of expressFiles) {
71
+ const rel = toPosix(relative(repoPath, file));
72
+ const content = await readFile(file, 'utf8');
73
+ const routeRegex = /router\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)/gi;
74
+ for (const match of content.matchAll(routeRegex)) {
75
+ const method = match[1]?.toUpperCase();
76
+ const routePath = normalizeRoutePath(match[2] ?? '/');
77
+ routes.push({ path: routePath, file: rel, method });
78
+ }
79
+ }
80
+ const testFilePaths = await glob([
81
+ '**/*.spec.ts',
82
+ '**/*.test.ts',
83
+ '**/*.spec.tsx',
84
+ '**/*.test.tsx',
85
+ '**/cypress/e2e/**/*.ts',
86
+ '**/cypress/e2e/**/*.cy.ts',
87
+ ], {
88
+ cwd: repoPath,
89
+ onlyFiles: true,
90
+ absolute: true,
91
+ ignore: IGNORE_PATTERNS,
92
+ });
93
+ const testFiles = [];
94
+ for (const file of [...new Set(testFilePaths)]) {
95
+ const rel = toPosix(relative(repoPath, file));
96
+ const content = await readFile(file, 'utf8');
97
+ testFiles.push({
98
+ file: rel,
99
+ type: detectTestType(rel, content),
100
+ coveredPaths: extractCoveredPaths(content),
101
+ });
102
+ }
103
+ const cypressRoot = await glob(['cypress'], { cwd: repoPath, onlyDirectories: true, absolute: false, deep: 1 });
104
+ const e2eFolder = await glob(['cypress/e2e'], { cwd: repoPath, onlyDirectories: true, absolute: false, deep: 1 });
105
+ const componentFolder = await glob(['cypress/component'], { cwd: repoPath, onlyDirectories: true, absolute: false, deep: 1 });
106
+ const fixturesFolder = await glob(['cypress/fixtures'], { cwd: repoPath, onlyDirectories: true, absolute: false, deep: 1 });
107
+ const supportFolder = await glob(['cypress/support'], { cwd: repoPath, onlyDirectories: true, absolute: false, deep: 1 });
108
+ const commandsFile = await glob(['cypress/support/commands.ts'], { cwd: repoPath, onlyFiles: true, absolute: false });
109
+ const existingE2eFiles = await glob(['cypress/e2e/**/*.cy.ts'], { cwd: repoPath, onlyFiles: true, absolute: false });
110
+ const existingComponentFiles = await glob(['cypress/component/**/*.cy.tsx'], {
111
+ cwd: repoPath,
112
+ onlyFiles: true,
113
+ absolute: false,
114
+ });
115
+ const tsxFiles = await glob(['**/*.tsx'], {
116
+ cwd: repoPath,
117
+ onlyFiles: true,
118
+ absolute: true,
119
+ ignore: [...IGNORE_PATTERNS, '**/*.spec.tsx'],
120
+ });
121
+ const missingTestIds = [];
122
+ for (const file of tsxFiles) {
123
+ const rel = toPosix(relative(repoPath, file));
124
+ const content = await readFile(file, 'utf8');
125
+ const hasInteractive = content.includes('<button') || content.includes('<input') || content.includes('<a ');
126
+ if (hasInteractive && !content.includes('data-testid')) {
127
+ missingTestIds.push(rel);
128
+ }
129
+ }
130
+ return RepoAnalysisSchema.parse({
131
+ scannedAt: new Date().toISOString(),
132
+ repoPath,
133
+ routes,
134
+ testFiles,
135
+ missingTestIds: [...new Set(missingTestIds)],
136
+ cypressStructure: {
137
+ detected: cypressRoot.length > 0,
138
+ e2eFolder: e2eFolder[0],
139
+ componentFolder: componentFolder[0],
140
+ fixturesFolder: fixturesFolder[0],
141
+ supportFolder: supportFolder[0],
142
+ hasCommandsFile: commandsFile.length > 0,
143
+ existingE2eFiles,
144
+ existingComponentFiles,
145
+ },
146
+ });
147
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@qulib/core",
3
+ "version": "0.1.0",
4
+ "description": "Qulib — analyze deployed web apps for honest quality gaps (CLI + programmatic API)",
5
+ "license": "MIT",
6
+ "author": "Tapesh Nagarwal",
7
+ "homepage": "https://github.com/TapeshN/qulib#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/TapeshN/qulib.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/TapeshN/qulib/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/index.js",
25
+ "types": "./dist/index.d.ts"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "bin",
31
+ "README.md"
32
+ ],
33
+ "bin": {
34
+ "qulib": "./bin/qulib.js"
35
+ },
36
+ "scripts": {
37
+ "dev": "tsx src/cli/index.ts",
38
+ "analyze": "tsx src/cli/index.ts analyze",
39
+ "clean": "tsx src/cli/index.ts clean",
40
+ "build": "tsc"
41
+ },
42
+ "dependencies": {
43
+ "@axe-core/playwright": "^4.9.0",
44
+ "@playwright/test": "^1.44.0",
45
+ "commander": "^12.1.0",
46
+ "fast-glob": "^3.3.2",
47
+ "zod": "^3.23.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.0.0",
51
+ "tsx": "^4.11.0",
52
+ "typescript": "^5.4.0"
53
+ }
54
+ }