@qulib/core 0.2.1 → 0.3.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 +45 -3
- package/dist/analyze.d.ts +16 -4
- package/dist/analyze.d.ts.map +1 -1
- package/dist/analyze.js +98 -38
- package/dist/cli/cost-doctor.d.ts +2 -0
- package/dist/cli/cost-doctor.d.ts.map +1 -0
- package/dist/cli/cost-doctor.js +72 -0
- package/dist/cli/index.js +61 -0
- package/dist/harness/progress-log.d.ts +7 -0
- package/dist/harness/progress-log.d.ts.map +1 -0
- package/dist/harness/progress-log.js +1 -0
- package/dist/harness/run-options.d.ts +2 -0
- package/dist/harness/run-options.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/llm/content-hash.d.ts +2 -0
- package/dist/llm/content-hash.d.ts.map +1 -0
- package/dist/llm/content-hash.js +4 -0
- package/dist/llm/context-builder.js +1 -1
- package/dist/llm/cost-intelligence.d.ts +29 -0
- package/dist/llm/cost-intelligence.d.ts.map +1 -0
- package/dist/llm/cost-intelligence.js +153 -0
- package/dist/llm/provider.d.ts +11 -1
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +43 -4
- package/dist/phases/act.d.ts.map +1 -1
- package/dist/phases/act.js +4 -1
- package/dist/phases/observe.js +1 -1
- package/dist/phases/think-finalize.d.ts +6 -0
- package/dist/phases/think-finalize.d.ts.map +1 -0
- package/dist/phases/think-finalize.js +164 -0
- package/dist/phases/think.d.ts +2 -0
- package/dist/phases/think.d.ts.map +1 -1
- package/dist/phases/think.js +16 -65
- package/dist/reporters/markdown-reporter.d.ts.map +1 -1
- package/dist/reporters/markdown-reporter.js +23 -3
- package/dist/schemas/config.schema.d.ts +364 -0
- package/dist/schemas/config.schema.d.ts.map +1 -1
- package/dist/schemas/config.schema.js +55 -1
- package/dist/schemas/cost-intelligence.schema.d.ts +229 -0
- package/dist/schemas/cost-intelligence.schema.d.ts.map +1 -0
- package/dist/schemas/cost-intelligence.schema.js +41 -0
- package/dist/schemas/decision-log.schema.d.ts +2 -2
- package/dist/schemas/gap-analysis.schema.d.ts +288 -49
- package/dist/schemas/gap-analysis.schema.d.ts.map +1 -1
- package/dist/schemas/gap-analysis.schema.js +7 -3
- package/dist/schemas/index.d.ts +3 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +3 -1
- package/dist/schemas/public-surface.schema.d.ts +268 -0
- package/dist/schemas/public-surface.schema.d.ts.map +1 -0
- package/dist/schemas/public-surface.schema.js +15 -0
- package/dist/schemas/repo-analysis.schema.d.ts +6 -6
- package/dist/tools/auth-block-gap.d.ts +3 -0
- package/dist/tools/auth-block-gap.d.ts.map +1 -0
- package/dist/tools/auth-block-gap.js +19 -0
- package/dist/tools/auth-detector.d.ts +2 -1
- package/dist/tools/auth-detector.d.ts.map +1 -1
- package/dist/tools/auth-detector.js +28 -3
- package/dist/tools/auth-explorer.d.ts +4 -0
- package/dist/tools/auth-explorer.d.ts.map +1 -0
- package/dist/tools/auth-explorer.js +346 -0
- package/dist/tools/auth-surface-analyzer.d.ts +4 -0
- package/dist/tools/auth-surface-analyzer.d.ts.map +1 -0
- package/dist/tools/auth-surface-analyzer.js +154 -0
- package/dist/tools/cypress-explorer.d.ts +2 -1
- package/dist/tools/cypress-explorer.d.ts.map +1 -1
- package/dist/tools/cypress-explorer.js +1 -1
- package/dist/tools/explorer.interface.d.ts +2 -1
- package/dist/tools/explorer.interface.d.ts.map +1 -1
- package/dist/tools/gap-engine.d.ts +3 -1
- package/dist/tools/gap-engine.d.ts.map +1 -1
- package/dist/tools/gap-engine.js +39 -12
- package/dist/tools/oauth-providers.d.ts +7 -0
- package/dist/tools/oauth-providers.d.ts.map +1 -0
- package/dist/tools/oauth-providers.js +21 -0
- package/dist/tools/playwright-explorer.d.ts +2 -1
- package/dist/tools/playwright-explorer.d.ts.map +1 -1
- package/dist/tools/playwright-explorer.js +21 -3
- package/dist/tools/public-surface.d.ts +5 -0
- package/dist/tools/public-surface.d.ts.map +1 -0
- package/dist/tools/public-surface.js +13 -0
- package/dist/tools/user-providers.d.ts +15 -0
- package/dist/tools/user-providers.d.ts.map +1 -0
- package/dist/tools/user-providers.js +62 -0
- package/package.json +6 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const BUILT_IN_OAUTH_PROVIDERS = [
|
|
2
|
+
{ id: 'github', label: 'GitHub', patterns: [/\bgithub\b/i] },
|
|
3
|
+
{ id: 'google', label: 'Google', patterns: [/\bgoogle\b/i, /accounts\.google\.com/i] },
|
|
4
|
+
{ id: 'microsoft', label: 'Microsoft', patterns: [/microsoft/i, /login\.microsoftonline\.com/i] },
|
|
5
|
+
{ id: 'apple', label: 'Apple', patterns: [/sign in with apple/i, /\bapple id\b/i] },
|
|
6
|
+
{ id: 'facebook', label: 'Facebook', patterns: [/facebook/i] },
|
|
7
|
+
{ id: 'twitter', label: 'Twitter/X', patterns: [/twitter\.com/i, /\bsign in with x\b/i] },
|
|
8
|
+
{ id: 'linkedin', label: 'LinkedIn', patterns: [/linkedin/i] },
|
|
9
|
+
{ id: 'auth0', label: 'Auth0', patterns: [/auth0/i] },
|
|
10
|
+
{ id: 'okta', label: 'Okta', patterns: [/\bokta\b/i] },
|
|
11
|
+
{ id: 'onelogin', label: 'OneLogin', patterns: [/onelogin/i] },
|
|
12
|
+
{ id: 'duo', label: 'Duo Security', patterns: [/duo security/i] },
|
|
13
|
+
{ id: 'ping', label: 'Ping Identity', patterns: [/pingidentity/i, /pingone/i] },
|
|
14
|
+
{ id: 'workday', label: 'Workday', patterns: [/workday/i] },
|
|
15
|
+
{ id: 'saml', label: 'SAML SSO', patterns: [/\bsaml\b/i] },
|
|
16
|
+
{ id: 'clever', label: 'Clever', patterns: [/\bclever\b/i, /clever\.com/i] },
|
|
17
|
+
{ id: 'classlink', label: 'ClassLink', patterns: [/classlink/i] },
|
|
18
|
+
{ id: 'schoology', label: 'Schoology', patterns: [/schoology/i] },
|
|
19
|
+
{ id: 'canvas', label: 'Canvas (Instructure)', patterns: [/\bcanvas lms\b/i, /instructure/i] },
|
|
20
|
+
{ id: 'blackboard', label: 'Blackboard', patterns: [/blackboard/i] },
|
|
21
|
+
];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AppExplorer } from './explorer.interface.js';
|
|
2
2
|
import { type RouteInventory } from '../schemas/route-inventory.schema.js';
|
|
3
3
|
import type { HarnessConfig } from '../schemas/config.schema.js';
|
|
4
|
+
import type { RunArtifactsOptions } from '../harness/run-options.js';
|
|
4
5
|
export declare class PlaywrightExplorer implements AppExplorer {
|
|
5
|
-
explore(baseUrl: string, config: HarnessConfig): Promise<RouteInventory>;
|
|
6
|
+
explore(baseUrl: string, config: HarnessConfig, artifacts?: RunArtifactsOptions): Promise<RouteInventory>;
|
|
6
7
|
}
|
|
7
8
|
//# sourceMappingURL=playwright-explorer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-explorer.d.ts","sourceRoot":"","sources":["../../src/tools/playwright-explorer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAwB,KAAK,cAAc,EAAc,MAAM,sCAAsC,CAAC;AAC7G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"playwright-explorer.d.ts","sourceRoot":"","sources":["../../src/tools/playwright-explorer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAwB,KAAK,cAAc,EAAc,MAAM,sCAAsC,CAAC;AAC7G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAoBrE,qBAAa,kBAAmB,YAAW,WAAW;IAC9C,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;CAsKhH"}
|
|
@@ -15,8 +15,12 @@ function isInternalHref(href, baseUrlStr) {
|
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
function debugMode() {
|
|
19
|
+
return process.env.QULIB_DEBUG === '1';
|
|
20
|
+
}
|
|
18
21
|
export class PlaywrightExplorer {
|
|
19
|
-
async explore(baseUrl, config) {
|
|
22
|
+
async explore(baseUrl, config, artifacts) {
|
|
23
|
+
const progress = artifacts?.progressLog;
|
|
20
24
|
const browser = await launchBrowser();
|
|
21
25
|
let context;
|
|
22
26
|
try {
|
|
@@ -28,7 +32,10 @@ export class PlaywrightExplorer {
|
|
|
28
32
|
}
|
|
29
33
|
if (config.auth) {
|
|
30
34
|
const label = config.auth.type === 'form-login' ? config.auth.credentials.username : 'storage-state';
|
|
31
|
-
|
|
35
|
+
progress?.info(`Authenticated context: ${label}`);
|
|
36
|
+
if (!progress) {
|
|
37
|
+
process.stderr.write(`[qulib] authenticated as ${label}\n`);
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
const visited = new Set();
|
|
34
41
|
const queue = [baseUrl];
|
|
@@ -56,10 +63,15 @@ export class PlaywrightExplorer {
|
|
|
56
63
|
}
|
|
57
64
|
});
|
|
58
65
|
try {
|
|
59
|
-
await page.goto(url, {
|
|
66
|
+
const navResponse = await page.goto(url, {
|
|
60
67
|
timeout: config.timeoutMs,
|
|
61
68
|
waitUntil: 'domcontentloaded',
|
|
62
69
|
});
|
|
70
|
+
const httpStatus = navResponse?.status() ?? 0;
|
|
71
|
+
if (debugMode()) {
|
|
72
|
+
const html = await page.content();
|
|
73
|
+
progress?.debug(`page HTML byteLength=${Buffer.byteLength(html, 'utf8')} url=${normalized}`);
|
|
74
|
+
}
|
|
63
75
|
const pageTitle = await page.title();
|
|
64
76
|
const formCount = await page.locator('form').count();
|
|
65
77
|
const buttonLabels = await page.locator('button').allInnerTexts();
|
|
@@ -92,6 +104,9 @@ export class PlaywrightExplorer {
|
|
|
92
104
|
const axeResults = await new AxeBuilder({ page })
|
|
93
105
|
.withTags(['wcag2a', 'wcag2aa'])
|
|
94
106
|
.analyze();
|
|
107
|
+
if (debugMode()) {
|
|
108
|
+
progress?.debug(`raw axe violations (pre-map) count=${axeResults.violations.length} json=${JSON.stringify(axeResults.violations)}`);
|
|
109
|
+
}
|
|
95
110
|
a11yViolations = axeResults.violations.map((v) => ({
|
|
96
111
|
id: v.id,
|
|
97
112
|
impact: v.impact ?? 'unknown',
|
|
@@ -103,6 +118,7 @@ export class PlaywrightExplorer {
|
|
|
103
118
|
consoleErrors.push(`axe-core failure: ${String(err)}`);
|
|
104
119
|
}
|
|
105
120
|
const path = new URL(url).pathname || '/';
|
|
121
|
+
progress?.info(`Crawled ${normalized} status=${httpStatus} a11yViolations=${a11yViolations.length}`);
|
|
106
122
|
routes.push({
|
|
107
123
|
path,
|
|
108
124
|
pageTitle,
|
|
@@ -112,6 +128,7 @@ export class PlaywrightExplorer {
|
|
|
112
128
|
consoleErrors,
|
|
113
129
|
brokenLinks,
|
|
114
130
|
a11yViolations,
|
|
131
|
+
statusCode: httpStatus,
|
|
115
132
|
});
|
|
116
133
|
}
|
|
117
134
|
catch (err) {
|
|
@@ -123,6 +140,7 @@ export class PlaywrightExplorer {
|
|
|
123
140
|
return url;
|
|
124
141
|
}
|
|
125
142
|
})();
|
|
143
|
+
progress?.info(`Crawled ${normalized} status=error a11yViolations=0 err=${String(err).slice(0, 120)}`);
|
|
126
144
|
routes.push({
|
|
127
145
|
path,
|
|
128
146
|
pageTitle: '',
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RouteInventory } from '../schemas/route-inventory.schema.js';
|
|
2
|
+
import type { Gap } from '../schemas/gap-analysis.schema.js';
|
|
3
|
+
import type { PublicSurface } from '../schemas/public-surface.schema.js';
|
|
4
|
+
export declare function buildPublicSurface(pages: RouteInventory['routes'], gaps: Gap[]): PublicSurface;
|
|
5
|
+
//# sourceMappingURL=public-surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-surface.d.ts","sourceRoot":"","sources":["../../src/tools/public-surface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAmD,MAAM,qCAAqC,CAAC;AAE1H,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,aAAa,CAY9F"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function buildPublicSurface(pages, gaps) {
|
|
2
|
+
const accessibilityViolations = [];
|
|
3
|
+
const brokenLinks = [];
|
|
4
|
+
for (const r of pages) {
|
|
5
|
+
for (const v of r.a11yViolations) {
|
|
6
|
+
accessibilityViolations.push({ ...v, path: r.path });
|
|
7
|
+
}
|
|
8
|
+
for (const b of r.brokenLinks) {
|
|
9
|
+
brokenLinks.push({ ...b, path: r.path });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return { pages, gaps, accessibilityViolations, brokenLinks };
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OAuthProvider } from './oauth-providers.js';
|
|
2
|
+
export interface SerializedProvider {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
patterns: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function loadUserProviders(): OAuthProvider[];
|
|
8
|
+
export declare function addUserProvider(input: {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
pattern: string;
|
|
12
|
+
}): void;
|
|
13
|
+
export declare function removeUserProvider(id: string): boolean;
|
|
14
|
+
export declare function listUserProviders(): SerializedProvider[];
|
|
15
|
+
//# sourceMappingURL=user-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-providers.d.ts","sourceRoot":"","sources":["../../src/tools/user-providers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CAOnD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAc3F;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAStD;AAED,wBAAgB,iBAAiB,IAAI,kBAAkB,EAAE,CAExD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
const USER_PROVIDERS_PATH = join(homedir(), '.qulib', 'providers.json');
|
|
5
|
+
export function loadUserProviders() {
|
|
6
|
+
const raw = loadSerialized();
|
|
7
|
+
return raw.map((p) => ({
|
|
8
|
+
id: p.id,
|
|
9
|
+
label: p.label,
|
|
10
|
+
patterns: p.patterns.map((src) => new RegExp(src, 'i')),
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
export function addUserProvider(input) {
|
|
14
|
+
const existing = loadSerialized();
|
|
15
|
+
const idx = existing.findIndex((p) => p.id === input.id);
|
|
16
|
+
if (idx >= 0) {
|
|
17
|
+
const p = existing[idx];
|
|
18
|
+
if (!p.patterns.includes(input.pattern)) {
|
|
19
|
+
p.patterns.push(input.pattern);
|
|
20
|
+
}
|
|
21
|
+
p.label = input.label;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
existing.push({ id: input.id, label: input.label, patterns: [input.pattern] });
|
|
25
|
+
}
|
|
26
|
+
ensureDir();
|
|
27
|
+
writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(existing, null, 2), 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
export function removeUserProvider(id) {
|
|
30
|
+
const existing = loadSerialized();
|
|
31
|
+
const filtered = existing.filter((p) => p.id !== id);
|
|
32
|
+
if (filtered.length === existing.length) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
ensureDir();
|
|
36
|
+
writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(filtered, null, 2), 'utf-8');
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
export function listUserProviders() {
|
|
40
|
+
return loadSerialized();
|
|
41
|
+
}
|
|
42
|
+
function loadSerialized() {
|
|
43
|
+
if (!existsSync(USER_PROVIDERS_PATH)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(readFileSync(USER_PROVIDERS_PATH, 'utf-8'));
|
|
48
|
+
if (!Array.isArray(parsed)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function ensureDir() {
|
|
58
|
+
const dir = dirname(USER_PROVIDERS_PATH);
|
|
59
|
+
if (!existsSync(dir)) {
|
|
60
|
+
mkdirSync(dir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qulib/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Qulib — analyze deployed web apps for honest quality gaps (CLI + programmatic API)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tapesh Nagarwal",
|
|
@@ -37,7 +37,11 @@
|
|
|
37
37
|
"dev": "tsx src/cli/index.ts",
|
|
38
38
|
"analyze": "tsx src/cli/index.ts analyze",
|
|
39
39
|
"clean": "tsx src/cli/index.ts clean",
|
|
40
|
-
"build": "tsc"
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"test": "node --import tsx/esm --test src/llm/cost-intelligence.test.ts src/tools/gap-engine.test.ts src/tools/auth-block-gap.test.ts",
|
|
42
|
+
"test:integration": "node --import tsx/esm --test src/analyze.integration.test.ts",
|
|
43
|
+
"smoke": "tsx src/cli/index.ts analyze --url https://example.com --ephemeral",
|
|
44
|
+
"cost-doctor": "tsx src/cli/index.ts cost doctor"
|
|
41
45
|
},
|
|
42
46
|
"dependencies": {
|
|
43
47
|
"@axe-core/playwright": "^4.9.0",
|