@qulib/core 0.5.1 → 0.5.3

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 (97) hide show
  1. package/README.md +9 -9
  2. package/dist/cli/index.js +3 -3
  3. package/dist/llm/context-builder.js +2 -2
  4. package/dist/tools/auth/detect.d.ts +8 -0
  5. package/dist/tools/auth/detect.d.ts.map +1 -1
  6. package/dist/tools/auth/detect.js +46 -9
  7. package/package.json +2 -2
  8. package/dist/tools/apply-auth.d.ts +0 -4
  9. package/dist/tools/apply-auth.d.ts.map +0 -1
  10. package/dist/tools/apply-auth.js +0 -35
  11. package/dist/tools/auth/block-gap.d.ts +0 -9
  12. package/dist/tools/auth/block-gap.d.ts.map +0 -1
  13. package/dist/tools/auth/block-gap.js +0 -52
  14. package/dist/tools/auth/detector.d.ts +0 -23
  15. package/dist/tools/auth/detector.d.ts.map +0 -1
  16. package/dist/tools/auth/detector.js +0 -526
  17. package/dist/tools/auth/explorer.d.ts +0 -4
  18. package/dist/tools/auth/explorer.d.ts.map +0 -1
  19. package/dist/tools/auth/explorer.js +0 -346
  20. package/dist/tools/auth/oauth-providers.d.ts +0 -7
  21. package/dist/tools/auth/oauth-providers.d.ts.map +0 -1
  22. package/dist/tools/auth/oauth-providers.js +0 -21
  23. package/dist/tools/auth/surface-analyzer.d.ts +0 -4
  24. package/dist/tools/auth/surface-analyzer.d.ts.map +0 -1
  25. package/dist/tools/auth/surface-analyzer.js +0 -170
  26. package/dist/tools/auth/user-providers.d.ts +0 -15
  27. package/dist/tools/auth/user-providers.d.ts.map +0 -1
  28. package/dist/tools/auth/user-providers.js +0 -62
  29. package/dist/tools/auth-block-gap.d.ts +0 -9
  30. package/dist/tools/auth-block-gap.d.ts.map +0 -1
  31. package/dist/tools/auth-block-gap.js +0 -52
  32. package/dist/tools/auth-detector.d.ts +0 -23
  33. package/dist/tools/auth-detector.d.ts.map +0 -1
  34. package/dist/tools/auth-detector.js +0 -526
  35. package/dist/tools/auth-explorer.d.ts +0 -4
  36. package/dist/tools/auth-explorer.d.ts.map +0 -1
  37. package/dist/tools/auth-explorer.js +0 -346
  38. package/dist/tools/auth-surface-analyzer.d.ts +0 -4
  39. package/dist/tools/auth-surface-analyzer.d.ts.map +0 -1
  40. package/dist/tools/auth-surface-analyzer.js +0 -170
  41. package/dist/tools/auth.d.ts +0 -4
  42. package/dist/tools/auth.d.ts.map +0 -1
  43. package/dist/tools/auth.js +0 -35
  44. package/dist/tools/automation-maturity.d.ts +0 -4
  45. package/dist/tools/automation-maturity.d.ts.map +0 -1
  46. package/dist/tools/automation-maturity.js +0 -219
  47. package/dist/tools/browser.d.ts +0 -3
  48. package/dist/tools/browser.d.ts.map +0 -1
  49. package/dist/tools/browser.js +0 -13
  50. package/dist/tools/cypress-explorer.d.ts +0 -8
  51. package/dist/tools/cypress-explorer.d.ts.map +0 -1
  52. package/dist/tools/cypress-explorer.js +0 -5
  53. package/dist/tools/explorer-factory.d.ts +0 -4
  54. package/dist/tools/explorer-factory.d.ts.map +0 -1
  55. package/dist/tools/explorer-factory.js +0 -12
  56. package/dist/tools/explorer.interface.d.ts +0 -7
  57. package/dist/tools/explorer.interface.d.ts.map +0 -1
  58. package/dist/tools/explorer.interface.js +0 -1
  59. package/dist/tools/explorers/cypress-explorer.d.ts +0 -8
  60. package/dist/tools/explorers/cypress-explorer.d.ts.map +0 -1
  61. package/dist/tools/explorers/cypress-explorer.js +0 -5
  62. package/dist/tools/explorers/explorer.interface.d.ts +0 -7
  63. package/dist/tools/explorers/explorer.interface.d.ts.map +0 -1
  64. package/dist/tools/explorers/explorer.interface.js +0 -1
  65. package/dist/tools/explorers/playwright-explorer.d.ts +0 -8
  66. package/dist/tools/explorers/playwright-explorer.d.ts.map +0 -1
  67. package/dist/tools/explorers/playwright-explorer.js +0 -172
  68. package/dist/tools/framework-detector.d.ts +0 -15
  69. package/dist/tools/framework-detector.d.ts.map +0 -1
  70. package/dist/tools/framework-detector.js +0 -153
  71. package/dist/tools/gap-engine.d.ts +0 -8
  72. package/dist/tools/gap-engine.d.ts.map +0 -1
  73. package/dist/tools/gap-engine.js +0 -138
  74. package/dist/tools/oauth-providers.d.ts +0 -7
  75. package/dist/tools/oauth-providers.d.ts.map +0 -1
  76. package/dist/tools/oauth-providers.js +0 -21
  77. package/dist/tools/playwright-explorer.d.ts +0 -8
  78. package/dist/tools/playwright-explorer.d.ts.map +0 -1
  79. package/dist/tools/playwright-explorer.js +0 -172
  80. package/dist/tools/public-surface.d.ts +0 -5
  81. package/dist/tools/public-surface.d.ts.map +0 -1
  82. package/dist/tools/public-surface.js +0 -13
  83. package/dist/tools/repo/framework-detector.d.ts +0 -15
  84. package/dist/tools/repo/framework-detector.d.ts.map +0 -1
  85. package/dist/tools/repo/framework-detector.js +0 -153
  86. package/dist/tools/repo/scanner.d.ts +0 -19
  87. package/dist/tools/repo/scanner.d.ts.map +0 -1
  88. package/dist/tools/repo/scanner.js +0 -181
  89. package/dist/tools/repo-scanner.d.ts +0 -19
  90. package/dist/tools/repo-scanner.d.ts.map +0 -1
  91. package/dist/tools/repo-scanner.js +0 -181
  92. package/dist/tools/scoring/gap-engine.d.ts +0 -8
  93. package/dist/tools/scoring/gap-engine.d.ts.map +0 -1
  94. package/dist/tools/scoring/gap-engine.js +0 -138
  95. package/dist/tools/user-providers.d.ts +0 -15
  96. package/dist/tools/user-providers.d.ts.map +0 -1
  97. package/dist/tools/user-providers.js +0 -62
package/README.md CHANGED
@@ -49,13 +49,13 @@ This opens a real browser. Log in normally (OAuth, magic link, password manager,
49
49
 
50
50
  ### Automated form login (`auth login`)
51
51
 
52
- When **`detect-auth`** shows **`authOptions`** with **`type: "form-login"`** and **`requirements.method: "credentials"`** (including click-to-reveal paths such as Scholastic Sync), you can save a storage state **without** manual clicking:
52
+ When **`detect-auth`** shows **`authOptions`** with **`type: "form-login"`** and **`requirements.method: "credentials"`** (including click-to-reveal paths such as NQ Login), you can save a storage state **without** manual clicking:
53
53
 
54
54
  ```bash
55
- qulib auth login --base-url https://platform.scholastic.com \
56
- --auth-path scholastic-sync \
57
- --credentials-file ~/.qulib/scholastic-creds.json \
58
- --out ~/.qulib/scholastic-state.json
55
+ qulib auth login --base-url https://notquality.com \
56
+ --auth-path nq-login \
57
+ --credentials-file ~/.qulib/nq-creds.json \
58
+ --out ~/.qulib/nq-state.json
59
59
  ```
60
60
 
61
61
  The JSON file must map **field `name`** values from `authOptions` to secrets, e.g. `{"username":"…","password":"…","hidden.datasource":"…"}`. Prefer **`--credentials-file`** over **`--credentials`** so values are not stored in shell history.
@@ -63,8 +63,8 @@ The JSON file must map **field `name`** values from `authOptions` to secrets, e.
63
63
  Then analyze with the saved session:
64
64
 
65
65
  ```bash
66
- qulib analyze --url https://platform.scholastic.com \
67
- --auth-storage-state ~/.qulib/scholastic-state.json
66
+ qulib analyze --url https://notquality.com \
67
+ --auth-storage-state ~/.qulib/nq-state.json
68
68
  ```
69
69
 
70
70
  Use **`--auth-path <id>`** when multiple **`form-login`** paths appear in **`authOptions`**. Use **`--success-url-contains <substring>`** for stricter success detection; otherwise Qulib infers success from URL changes or the password field disappearing (and warns if it cannot confirm).
@@ -102,9 +102,9 @@ For unfamiliar apps (especially enterprise SSO with several buttons), run **`qul
102
102
  Unknown SSO buttons include **`unrecognizedButtons`** with a hint. Teach this machine to recognize a label next time:
103
103
 
104
104
  ```bash
105
- qulib auth providers add --id scholastic-sync --label "Scholastic Sync" --pattern "scholastic sync"
105
+ qulib auth providers add --id nq-login --label "NQ Login" --pattern "nq login"
106
106
  qulib auth providers list
107
- qulib auth providers remove --id scholastic-sync
107
+ qulib auth providers remove --id nq-login
108
108
  ```
109
109
 
110
110
  Patterns live in **`~/.qulib/providers.json`** (per user, not in the repo). Built-in public platforms stay in qulib’s curated list; tenant-specific names are never shipped as built-ins.
package/dist/cli/index.js CHANGED
@@ -225,9 +225,9 @@ providersCmd
225
225
  providersCmd
226
226
  .command('add')
227
227
  .description('Register a custom provider pattern (case-insensitive regex source)')
228
- .requiredOption('--id <id>', 'Stable id (kebab-case), e.g. scholastic-sync')
228
+ .requiredOption('--id <id>', 'Stable id (kebab-case), e.g. nq-login')
229
229
  .requiredOption('--label <label>', 'Human-readable label')
230
- .requiredOption('--pattern <regex>', 'Regex source, e.g. scholastic sync')
230
+ .requiredOption('--pattern <regex>', 'Regex source, e.g. nq login')
231
231
  .action(async (opts) => {
232
232
  try {
233
233
  new RegExp(opts.pattern, 'i');
@@ -289,7 +289,7 @@ authCmd
289
289
  .command('login')
290
290
  .description('Detect form-login on the URL, fill credentials, and save the storage state automatically (uses selectors from detect-auth)')
291
291
  .requiredOption('--base-url <url>', 'The base URL of the app to log into')
292
- .option('--auth-path <id>', 'Specific authOption id to use (e.g. "scholastic-sync") when multiple form-login paths exist')
292
+ .option('--auth-path <id>', 'Specific authOption id to use (e.g. "nq-login") when multiple form-login paths exist')
293
293
  .option('--credentials <json>', 'JSON object mapping field name → value, e.g. \'{"username":"a","password":"b","hidden.datasource":"NYC"}\'')
294
294
  .option('--credentials-file <path>', 'Path to a JSON file with the credentials object (keeps secrets out of shell history)')
295
295
  .option('--out <path>', 'Output file path for the storage state JSON', './qulib-storage-state.json')
@@ -6,7 +6,7 @@ export function buildGapPrompt(gaps, limit) {
6
6
  })
7
7
  .slice(0, limit);
8
8
  const gapList = topGaps
9
- .map((g, i) => `${i + 1}. [${g.severity}] ${g.category} at ${g.path}: ${g.reason}`)
9
+ .map((g) => `- id:${g.id} [${g.severity}] ${g.category} at ${g.path}: ${g.reason}`)
10
10
  .join('\n');
11
11
  return `You are a QA engineer. Given these quality gaps found in a web application, generate test scenarios.
12
12
 
@@ -28,6 +28,6 @@ Each item must match this exact shape:
28
28
  "recommendations": [
29
29
  { "adapter": "playwright|cypress-e2e|cypress-component|api|accessibility", "reason": "string", "confidence": "high|medium|low" }
30
30
  ],
31
- "sourceGapIds": ["string"]
31
+ "sourceGapIds": ["<one or more gap ids from the list, copied exactly as id:xxxx>"]
32
32
  }`;
33
33
  }
@@ -7,6 +7,14 @@ export interface StorageStateValidationResult {
7
7
  reasonCode: StorageStateInvalidReason | 'ok';
8
8
  reason: string;
9
9
  }
10
+ export declare function pickCredentialFieldName(attrs: {
11
+ name?: string | null;
12
+ id?: string | null;
13
+ autocomplete?: string | null;
14
+ type?: string | null;
15
+ placeholder?: string | null;
16
+ ariaLabel?: string | null;
17
+ }): string;
10
18
  export declare function evaluateStorageStateValidity(signals: {
11
19
  expectedOrigin: string;
12
20
  finalUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAIzE,MAAM,MAAM,yBAAyB,GACjC,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,yBAAyB,GACzB,iBAAiB,GACjB,SAAS,CAAC;AAEd,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB;AAwQD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,OAAO,CAAC;CAC9B,GAAG,4BAA4B,CA6B/B;AAOD,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAgD9C;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,SAAS,SAAQ,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAelD;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,SAAS,SAAQ,GAChB,OAAO,CAAC,4BAA4B,CAAC,CAyDvC;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,SAAQ,EACjB,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAqJvB"}
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAIzE,MAAM,MAAM,yBAAyB,GACjC,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,yBAAyB,GACzB,iBAAiB,GACjB,SAAS,CAAC;AAEd,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB;AA4FD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,CAgBT;AAiMD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,OAAO,CAAC;CAC9B,GAAG,4BAA4B,CA6B/B;AAOD,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAgD9C;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,SAAS,SAAQ,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAelD;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,SAAS,SAAQ,GAChB,OAAO,CAAC,4BAA4B,CAAC,CAyDvC;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,SAAQ,EACjB,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAqJvB"}
@@ -49,6 +49,9 @@ async function firstTextInputNameForLogin(page) {
49
49
  function debugAuth() {
50
50
  return process.env.QULIB_DEBUG === '1';
51
51
  }
52
+ function isLoginishPath(pathname) {
53
+ return /login|sign[- ]?in|auth|sso|oauth|signin/i.test(pathname);
54
+ }
52
55
  function slugify(label) {
53
56
  const s = label
54
57
  .toLowerCase()
@@ -80,21 +83,45 @@ async function resolveVisibleFieldLabel(page, el) {
80
83
  const typ = (await el.getAttribute('type'))?.trim();
81
84
  return typ && typ !== 'select' ? typ : 'text';
82
85
  }
83
- async function deriveCredentialFieldName(el) {
84
- const name = (await el.getAttribute('name'))?.trim();
86
+ function looksLikeExampleValue(s) {
87
+ return /@|:\/\//.test(s);
88
+ }
89
+ export function pickCredentialFieldName(attrs) {
90
+ const name = attrs.name?.trim();
85
91
  if (name)
86
92
  return name;
87
- const placeholder = (await el.getAttribute('placeholder'))?.trim();
88
- if (placeholder)
89
- return slugify(placeholder);
90
- const aria = (await el.getAttribute('aria-label'))?.trim();
91
- if (aria)
92
- return slugify(aria);
93
- const id = (await el.getAttribute('id'))?.trim();
93
+ const id = attrs.id?.trim();
94
94
  if (id)
95
95
  return slugify(id);
96
+ const autocomplete = attrs.autocomplete?.trim().toLowerCase();
97
+ if (autocomplete === 'username' || autocomplete === 'email')
98
+ return autocomplete;
99
+ if (autocomplete === 'current-password' || autocomplete === 'new-password')
100
+ return 'password';
101
+ const type = attrs.type?.trim().toLowerCase();
102
+ if (type === 'email')
103
+ return 'email';
104
+ if (type === 'password')
105
+ return 'password';
106
+ const placeholder = attrs.placeholder?.trim();
107
+ if (placeholder && !looksLikeExampleValue(placeholder))
108
+ return slugify(placeholder);
109
+ const aria = attrs.ariaLabel?.trim();
110
+ if (aria && !looksLikeExampleValue(aria))
111
+ return slugify(aria);
96
112
  return 'field';
97
113
  }
114
+ async function deriveCredentialFieldName(el) {
115
+ const [name, id, autocomplete, type, placeholder, ariaLabel] = await Promise.all([
116
+ el.getAttribute('name'),
117
+ el.getAttribute('id'),
118
+ el.getAttribute('autocomplete'),
119
+ el.getAttribute('type'),
120
+ el.getAttribute('placeholder'),
121
+ el.getAttribute('aria-label'),
122
+ ]);
123
+ return pickCredentialFieldName({ name, id, autocomplete, type, placeholder, ariaLabel });
124
+ }
98
125
  async function buildCredentialFieldsFromVisibleForm(page) {
99
126
  const fields = [];
100
127
  const seen = new Set();
@@ -169,6 +196,7 @@ async function probeClickToRevealForms(page, loginUrl, alreadyMatchedTexts, time
169
196
  seenLabels.add(label);
170
197
  candidateAttempts += 1;
171
198
  const originBefore = new URL(page.url()).origin;
199
+ const pathBefore = new URL(page.url()).pathname;
172
200
  if (debugAuth()) {
173
201
  progress?.debug(`detect_auth click-reveal try label="${label.slice(0, 80)}"`);
174
202
  }
@@ -207,6 +235,15 @@ async function probeClickToRevealForms(page, loginUrl, alreadyMatchedTexts, time
207
235
  await waitNetworkIdleBestEffort(page);
208
236
  continue;
209
237
  }
238
+ const afterUrl = new URL(page.url());
239
+ if (afterUrl.pathname !== pathBefore && !isLoginishPath(afterUrl.pathname)) {
240
+ if (debugAuth()) {
241
+ progress?.debug(`detect_auth click-reveal skip (CTA navigation to non-login path): ${afterUrl.pathname}`);
242
+ }
243
+ await page.goto(loginUrl, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
244
+ await waitNetworkIdleBestEffort(page);
245
+ continue;
246
+ }
210
247
  await waitNetworkIdleBestEffort(page);
211
248
  try {
212
249
  await page.locator('input[type="password"]:visible').first().waitFor({ state: 'visible', timeout: 5000 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qulib/core",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Qulib — analyze deployed web apps for honest quality gaps (CLI + programmatic API)",
5
5
  "license": "MIT",
6
6
  "author": "Tapesh Nagarwal",
@@ -48,7 +48,7 @@
48
48
  "analyze": "tsx src/cli/index.ts analyze",
49
49
  "clean": "tsx src/cli/index.ts clean",
50
50
  "build": "tsc",
51
- "test": "node --import tsx/esm --test src/llm/__tests__/cost-intelligence.test.ts src/tools/scoring/__tests__/gaps.test.ts src/tools/auth/__tests__/gaps.test.ts src/tools/auth/__tests__/detect.test.ts src/tools/scoring/__tests__/automation-maturity.test.ts src/harness/__tests__/state-manager.test.ts src/telemetry/__tests__/redact-url.test.ts src/cli/__tests__/auth-login.test.ts src/cli/__tests__/cli-version.test.ts src/__tests__/analyze.storage-state-invalid.test.ts src/__tests__/analyze.fixtures.test.ts",
51
+ "test": "node --import tsx/esm --test src/llm/__tests__/cost-intelligence.test.ts src/llm/__tests__/context-builder.test.ts src/tools/scoring/__tests__/gaps.test.ts src/tools/auth/__tests__/gaps.test.ts src/tools/auth/__tests__/detect.test.ts src/tools/scoring/__tests__/automation-maturity.test.ts src/harness/__tests__/state-manager.test.ts src/telemetry/__tests__/redact-url.test.ts src/cli/__tests__/auth-login.test.ts src/cli/__tests__/cli-version.test.ts src/__tests__/analyze.storage-state-invalid.test.ts src/__tests__/analyze.fixtures.test.ts",
52
52
  "test:integration": "node --import tsx/esm --test src/__tests__/analyze.integration.test.ts",
53
53
  "smoke": "tsx src/cli/index.ts analyze --url https://example.com --ephemeral",
54
54
  "cost-doctor": "tsx src/cli/index.ts cost doctor"
@@ -1,4 +0,0 @@
1
- import type { Browser, BrowserContext } from '@playwright/test';
2
- import type { AuthConfig } from '../schemas/config.schema.js';
3
- export declare function createAuthenticatedContext(browser: Browser, auth: AuthConfig | undefined, timeoutMs: number): Promise<BrowserContext>;
4
- //# sourceMappingURL=apply-auth.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"apply-auth.d.ts","sourceRoot":"","sources":["../../src/tools/apply-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,UAAU,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,CAsCzB"}
@@ -1,35 +0,0 @@
1
- import { resolve } from 'node:path';
2
- export async function createAuthenticatedContext(browser, auth, timeoutMs) {
3
- if (!auth) {
4
- return browser.newContext();
5
- }
6
- if (auth.type === 'storage-state') {
7
- const storagePath = resolve(process.cwd(), auth.path);
8
- return browser.newContext({ storageState: storagePath });
9
- }
10
- const context = await browser.newContext();
11
- const page = await context.newPage();
12
- try {
13
- await page.goto(auth.loginUrl, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
14
- await page.fill(auth.selectors.username, auth.credentials.username);
15
- await page.fill(auth.selectors.password, auth.credentials.password);
16
- await page.click(auth.selectors.submit);
17
- const urlFragment = auth.successIndicator.urlContains;
18
- if (urlFragment) {
19
- await page.waitForURL((url) => url.toString().includes(urlFragment), {
20
- timeout: timeoutMs,
21
- });
22
- }
23
- const visibleSelector = auth.successIndicator.selectorVisible;
24
- if (visibleSelector) {
25
- await page.waitForSelector(visibleSelector, {
26
- timeout: timeoutMs,
27
- state: 'visible',
28
- });
29
- }
30
- }
31
- finally {
32
- await page.close();
33
- }
34
- return context;
35
- }
@@ -1,9 +0,0 @@
1
- import type { Gap } from '../../schemas/gap-analysis.schema.js';
2
- import type { StorageStateInvalidReason } from './detector.js';
3
- export declare function buildAuthBlockGap(url: string): Gap;
4
- export declare function buildStorageStateInvalidGap(input: {
5
- url: string;
6
- reasonCode: StorageStateInvalidReason;
7
- reason: string;
8
- }): Gap;
9
- //# sourceMappingURL=block-gap.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"block-gap.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/block-gap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sCAAsC,CAAC;AAChE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAmB/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAYlD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,yBAAyB,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,GAAG,CAqBN"}
@@ -1,52 +0,0 @@
1
- function safeOriginAndPath(url) {
2
- try {
3
- const u = new URL(url);
4
- return `${u.origin}${u.pathname}`;
5
- }
6
- catch {
7
- return url;
8
- }
9
- }
10
- function safeHost(url) {
11
- try {
12
- return new URL(url).hostname;
13
- }
14
- catch {
15
- return url;
16
- }
17
- }
18
- export function buildAuthBlockGap(url) {
19
- const host = safeHost(url);
20
- const safeUrl = safeOriginAndPath(url);
21
- return {
22
- id: 'auth-block',
23
- path: '/',
24
- severity: 'critical',
25
- category: 'coverage',
26
- reason: `Scan blocked by authentication. No authenticated pages were evaluated for ${host}.`,
27
- description: 'Scan blocked by authentication. 0 authenticated pages were evaluated.',
28
- recommendation: `Run \`qulib auth init --base-url ${safeUrl}\` to capture a storage state, then re-run with --auth storage-state.`,
29
- };
30
- }
31
- export function buildStorageStateInvalidGap(input) {
32
- const host = safeHost(input.url);
33
- const safeUrl = safeOriginAndPath(input.url);
34
- const recoveryByCode = {
35
- 'missing-file': `Storage state file was not found. Run \`qulib auth login --base-url ${safeUrl} --out <path>\` (or \`qulib auth init\`) to capture a fresh state, then re-run \`qulib analyze --url ${safeUrl} --auth-storage-state <path>\`.`,
36
- 'unreadable-file': `Storage state file exists but could not be read. Check file permissions, then re-run \`qulib auth login\` if needed.`,
37
- 'invalid-json': `Storage state file is not valid JSON. Run \`qulib auth login --base-url ${safeUrl} --out <path>\` again to regenerate it.`,
38
- 'wrong-origin': `Storage state belongs to a different origin than ${host}. Re-run \`qulib auth login --base-url ${safeUrl}\` against this target and pass the new file to \`qulib analyze\`.`,
39
- 'expired-or-unauthorized': `The session in the storage state has expired or is unauthorized. Run \`qulib auth login --base-url ${safeUrl}\` to capture a fresh state, then re-run \`qulib analyze --url ${safeUrl} --auth-storage-state <path>\`.`,
40
- 'no-auth-cookies': `Storage state file contains no cookies or localStorage entries — it is effectively empty. Run \`qulib auth login --base-url ${safeUrl}\` to capture a real session.`,
41
- unknown: `Storage state could not be validated. Try \`qulib auth login --base-url ${safeUrl}\` again, and verify the file was saved on the same origin.`,
42
- };
43
- return {
44
- id: 'storage-state-invalid',
45
- path: '/',
46
- severity: 'critical',
47
- category: 'coverage',
48
- reason: `Authenticated scan could not continue because the provided storage state is invalid for ${host}. Reason: ${input.reasonCode} — ${input.reason}.`,
49
- description: `Storage state validation failed before crawling. The session was checked against ${host} and rejected with reason code "${input.reasonCode}".`,
50
- recommendation: recoveryByCode[input.reasonCode],
51
- };
52
- }
@@ -1,23 +0,0 @@
1
- import type { Page } from '@playwright/test';
2
- import type { DetectedAuth } from '../../schemas/config.schema.js';
3
- import type { AnalyzeProgressSink } from '../../harness/progress-log.js';
4
- export type StorageStateInvalidReason = 'missing-file' | 'unreadable-file' | 'invalid-json' | 'wrong-origin' | 'expired-or-unauthorized' | 'no-auth-cookies' | 'unknown';
5
- export interface StorageStateValidationResult {
6
- valid: boolean;
7
- reasonCode: StorageStateInvalidReason | 'ok';
8
- reason: string;
9
- }
10
- export declare function evaluateStorageStateValidity(signals: {
11
- expectedOrigin: string;
12
- finalUrl: string;
13
- visiblePasswordCount: number;
14
- hadUnauthorizedHttp: boolean;
15
- }): StorageStateValidationResult;
16
- export declare function preflightStorageStateFile(storagePath: string): Promise<StorageStateValidationResult | null>;
17
- export declare function waitForReturnToOrigin(page: Page, baseUrl: string, timeoutMs?: number): Promise<{
18
- returned: boolean;
19
- finalUrl: string;
20
- }>;
21
- export declare function validateStorageState(url: string, storagePath: string, timeoutMs?: number): Promise<StorageStateValidationResult>;
22
- export declare function detectAuth(url: string, timeoutMs?: number, progress?: AnalyzeProgressSink): Promise<DetectedAuth>;
23
- //# sourceMappingURL=detector.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAIzE,MAAM,MAAM,yBAAyB,GACjC,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,yBAAyB,GACzB,iBAAiB,GACjB,SAAS,CAAC;AAEd,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB;AAsQD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,OAAO,CAAC;CAC9B,GAAG,4BAA4B,CA6B/B;AAOD,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAgD9C;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,SAAS,SAAQ,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAelD;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,SAAS,SAAQ,GAChB,OAAO,CAAC,4BAA4B,CAAC,CAyDvC;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,SAAQ,EACjB,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAqJvB"}