@dujaunpaul/qass 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.
- package/LICENSE +40 -0
- package/README.md +146 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +117 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +128 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/diff-analyzer.d.ts +3 -0
- package/dist/core/diff-analyzer.d.ts.map +1 -0
- package/dist/core/diff-analyzer.js +194 -0
- package/dist/core/diff-analyzer.js.map +1 -0
- package/dist/core/discover.d.ts +3 -0
- package/dist/core/discover.d.ts.map +1 -0
- package/dist/core/discover.js +51 -0
- package/dist/core/discover.js.map +1 -0
- package/dist/core/license.d.ts +13 -0
- package/dist/core/license.d.ts.map +1 -0
- package/dist/core/license.js +132 -0
- package/dist/core/license.js.map +1 -0
- package/dist/core/report.d.ts +4 -0
- package/dist/core/report.d.ts.map +1 -0
- package/dist/core/report.js +95 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/runner.d.ts +3 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.js +136 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/core/test-planner.d.ts +3 -0
- package/dist/core/test-planner.d.ts.map +1 -0
- package/dist/core/test-planner.js +107 -0
- package/dist/core/test-planner.js.map +1 -0
- package/dist/integrations/cursor-rule.d.ts +2 -0
- package/dist/integrations/cursor-rule.d.ts.map +1 -0
- package/dist/integrations/cursor-rule.js +46 -0
- package/dist/integrations/cursor-rule.js.map +1 -0
- package/dist/integrations/mcp-server.d.ts +67 -0
- package/dist/integrations/mcp-server.d.ts.map +1 -0
- package/dist/integrations/mcp-server.js +61 -0
- package/dist/integrations/mcp-server.js.map +1 -0
- package/dist/runners/api/api-runner.d.ts +3 -0
- package/dist/runners/api/api-runner.d.ts.map +1 -0
- package/dist/runners/api/api-runner.js +258 -0
- package/dist/runners/api/api-runner.js.map +1 -0
- package/dist/runners/api/endpoint-discovery.d.ts +3 -0
- package/dist/runners/api/endpoint-discovery.d.ts.map +1 -0
- package/dist/runners/api/endpoint-discovery.js +106 -0
- package/dist/runners/api/endpoint-discovery.js.map +1 -0
- package/dist/runners/e2e/playwright-runner.d.ts +3 -0
- package/dist/runners/e2e/playwright-runner.d.ts.map +1 -0
- package/dist/runners/e2e/playwright-runner.js +309 -0
- package/dist/runners/e2e/playwright-runner.js.map +1 -0
- package/dist/runners/security/dynamic-checker.d.ts +3 -0
- package/dist/runners/security/dynamic-checker.d.ts.map +1 -0
- package/dist/runners/security/dynamic-checker.js +136 -0
- package/dist/runners/security/dynamic-checker.js.map +1 -0
- package/dist/runners/security/rules/auth-middleware.d.ts +13 -0
- package/dist/runners/security/rules/auth-middleware.d.ts.map +1 -0
- package/dist/runners/security/rules/auth-middleware.js +94 -0
- package/dist/runners/security/rules/auth-middleware.js.map +1 -0
- package/dist/runners/security/rules/config-audit.d.ts +14 -0
- package/dist/runners/security/rules/config-audit.d.ts.map +1 -0
- package/dist/runners/security/rules/config-audit.js +91 -0
- package/dist/runners/security/rules/config-audit.js.map +1 -0
- package/dist/runners/security/rules/dep-audit.d.ts +7 -0
- package/dist/runners/security/rules/dep-audit.d.ts.map +1 -0
- package/dist/runners/security/rules/dep-audit.js +82 -0
- package/dist/runners/security/rules/dep-audit.js.map +1 -0
- package/dist/runners/security/rules/input-sanitization.d.ts +12 -0
- package/dist/runners/security/rules/input-sanitization.d.ts.map +1 -0
- package/dist/runners/security/rules/input-sanitization.js +64 -0
- package/dist/runners/security/rules/input-sanitization.js.map +1 -0
- package/dist/runners/security/rules/rate-limit-audit.d.ts +11 -0
- package/dist/runners/security/rules/rate-limit-audit.d.ts.map +1 -0
- package/dist/runners/security/rules/rate-limit-audit.js +51 -0
- package/dist/runners/security/rules/rate-limit-audit.js.map +1 -0
- package/dist/runners/security/rules/secrets-scan.d.ts +4 -0
- package/dist/runners/security/rules/secrets-scan.d.ts.map +1 -0
- package/dist/runners/security/rules/secrets-scan.js +129 -0
- package/dist/runners/security/rules/secrets-scan.js.map +1 -0
- package/dist/runners/security/rules/xss-vectors.d.ts +13 -0
- package/dist/runners/security/rules/xss-vectors.d.ts.map +1 -0
- package/dist/runners/security/rules/xss-vectors.js +76 -0
- package/dist/runners/security/rules/xss-vectors.js.map +1 -0
- package/dist/runners/security/static-analyzer.d.ts +7 -0
- package/dist/runners/security/static-analyzer.d.ts.map +1 -0
- package/dist/runners/security/static-analyzer.js +87 -0
- package/dist/runners/security/static-analyzer.js.map +1 -0
- package/dist/runners/unit/unit-runner.d.ts +3 -0
- package/dist/runners/unit/unit-runner.d.ts.map +1 -0
- package/dist/runners/unit/unit-runner.js +157 -0
- package/dist/runners/unit/unit-runner.js.map +1 -0
- package/dist/types.d.ts +153 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/util/glob.d.ts +2 -0
- package/dist/util/glob.d.ts.map +1 -0
- package/dist/util/glob.js +32 -0
- package/dist/util/glob.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
QASS — Proprietary Software License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dujaun Paul. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and its source code are the proprietary property of Dujaun Paul
|
|
6
|
+
("Licensor"). By installing, downloading, or using this software ("QASS"), you
|
|
7
|
+
agree to the following terms:
|
|
8
|
+
|
|
9
|
+
1. GRANT OF USE
|
|
10
|
+
You are granted a non-exclusive, non-transferable, revocable license to use
|
|
11
|
+
QASS in accordance with the plan you have purchased (Free, Pro, or Team).
|
|
12
|
+
|
|
13
|
+
2. RESTRICTIONS
|
|
14
|
+
You may NOT:
|
|
15
|
+
- Copy, modify, merge, or create derivative works of this software
|
|
16
|
+
- Distribute, sublicense, sell, or make this software available to third
|
|
17
|
+
parties
|
|
18
|
+
- Reverse engineer, decompile, or disassemble this software
|
|
19
|
+
- Remove or alter any proprietary notices or labels
|
|
20
|
+
|
|
21
|
+
3. FREE TIER
|
|
22
|
+
The Free tier grants usage of the core security scanner and basic smoke
|
|
23
|
+
testing features at no cost, subject to these license terms. Free tier
|
|
24
|
+
usage does not grant any rights to the source code.
|
|
25
|
+
|
|
26
|
+
4. PAID TIERS
|
|
27
|
+
Pro and Team features require a valid license key. Usage without a valid
|
|
28
|
+
license key constitutes a violation of this agreement.
|
|
29
|
+
|
|
30
|
+
5. NO WARRANTY
|
|
31
|
+
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
IMPLIED. THE LICENSOR SHALL NOT BE LIABLE FOR ANY CLAIMS, DAMAGES, OR OTHER
|
|
33
|
+
LIABILITY ARISING FROM THE USE OF THIS SOFTWARE.
|
|
34
|
+
|
|
35
|
+
6. TERMINATION
|
|
36
|
+
This license is effective until terminated. It terminates automatically if
|
|
37
|
+
you fail to comply with any term. Upon termination, you must destroy all
|
|
38
|
+
copies of this software in your possession.
|
|
39
|
+
|
|
40
|
+
For licensing inquiries: dujaun@qass.dev
|
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# QASS
|
|
2
|
+
|
|
3
|
+
**QA + Security Scanner for vibe-coded apps.**
|
|
4
|
+
|
|
5
|
+
Your AI writes code. QASS catches the security holes, broken flows, and silent failures it left behind — before your users do. Works with Cursor, Windsurf, Copilot, and any AI editor.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g qass
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run without installing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx qass scan --project .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Initialize config in your project
|
|
23
|
+
qass init --project .
|
|
24
|
+
|
|
25
|
+
# Run a full security scan
|
|
26
|
+
qass scan --project . --full
|
|
27
|
+
|
|
28
|
+
# Run tests based on your latest git changes
|
|
29
|
+
qass test --project . --diff HEAD
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## What It Catches
|
|
33
|
+
|
|
34
|
+
### Free
|
|
35
|
+
|
|
36
|
+
- **7 static security rules** — missing auth middleware, SQL/NoSQL injection, hardcoded secrets, XSS vectors, CORS misconfiguration, rate limiting gaps, dependency CVEs
|
|
37
|
+
- **Basic smoke crawl** — page load verification, console error detection
|
|
38
|
+
- **Endpoint discovery** — auto-detects Express routes
|
|
39
|
+
- **Git diff analysis** — only scans what changed
|
|
40
|
+
- **AI-readable reports** — structured for your AI editor to read and fix
|
|
41
|
+
|
|
42
|
+
### Pro
|
|
43
|
+
|
|
44
|
+
- **Full smoke crawl** — clicks every button, fills every form, catches silent failures
|
|
45
|
+
- **Visual regression** — pixel-diff screenshots against baselines
|
|
46
|
+
- **Flow testing** — multi-step user journeys defined in YAML
|
|
47
|
+
- **API testing** — auth, plan gating, response validation with Supabase support
|
|
48
|
+
- **Dynamic security probing** — tests live endpoints for error disclosure, missing headers
|
|
49
|
+
|
|
50
|
+
## How It Works With AI Editors
|
|
51
|
+
|
|
52
|
+
QASS generates a rule file that tells your AI editor to run tests after every code change:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Generate a Cursor Rule
|
|
56
|
+
qass cursor-rule --project .
|
|
57
|
+
|
|
58
|
+
# Creates .cursor/rules/qass.mdc
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The rule instructs your AI to:
|
|
62
|
+
|
|
63
|
+
1. Run `qass test` after making changes
|
|
64
|
+
2. Read the report at `.qass/results/latest.md`
|
|
65
|
+
3. Fix every finding (each has exact file, line, and fix instructions)
|
|
66
|
+
4. Re-run until clean
|
|
67
|
+
5. Only then tell you it's done
|
|
68
|
+
|
|
69
|
+
This works with any AI editor that can run terminal commands — Cursor, Windsurf, Copilot, Bolt, Lovable.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
QASS uses a `.qass/config.yaml` file in your project root:
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
project:
|
|
77
|
+
name: my-app
|
|
78
|
+
|
|
79
|
+
services:
|
|
80
|
+
api:
|
|
81
|
+
framework: express
|
|
82
|
+
entry: src/server.ts
|
|
83
|
+
port: 3001
|
|
84
|
+
frontend:
|
|
85
|
+
framework: nextjs
|
|
86
|
+
port: 3000
|
|
87
|
+
|
|
88
|
+
security:
|
|
89
|
+
static_rules:
|
|
90
|
+
- auth-middleware
|
|
91
|
+
- input-sanitization
|
|
92
|
+
- secrets-scan
|
|
93
|
+
- xss-vectors
|
|
94
|
+
- config-audit
|
|
95
|
+
- rate-limit-audit
|
|
96
|
+
- dep-audit
|
|
97
|
+
severity_threshold: LOW
|
|
98
|
+
|
|
99
|
+
paths:
|
|
100
|
+
api_routes: "src/**/*.routes.ts"
|
|
101
|
+
middleware: "src/middleware/**"
|
|
102
|
+
frontend_pages: "app/**/page.tsx"
|
|
103
|
+
components: "components/**/*.tsx"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Run `qass init` to generate a default config.
|
|
107
|
+
|
|
108
|
+
## CLI Commands
|
|
109
|
+
|
|
110
|
+
| Command | Description |
|
|
111
|
+
|---------|-------------|
|
|
112
|
+
| `qass init` | Initialize `.qass/config.yaml` in your project |
|
|
113
|
+
| `qass scan` | Run security scan only |
|
|
114
|
+
| `qass test` | Run full test suite (security + API + E2E + unit) |
|
|
115
|
+
| `qass discover` | List discovered endpoints, pages, and accounts |
|
|
116
|
+
| `qass cursor-rule` | Generate AI editor rule file |
|
|
117
|
+
| `qass activate <key>` | Activate a Pro/Team license |
|
|
118
|
+
| `qass status` | Show current license and plan info |
|
|
119
|
+
|
|
120
|
+
## Reports
|
|
121
|
+
|
|
122
|
+
QASS generates reports in two formats:
|
|
123
|
+
|
|
124
|
+
- **`.qass/results/latest.json`** — machine-readable, for programmatic use
|
|
125
|
+
- **`.qass/results/latest.md`** — human/AI-readable, with fix instructions
|
|
126
|
+
|
|
127
|
+
Each finding includes:
|
|
128
|
+
|
|
129
|
+
```markdown
|
|
130
|
+
#### MEDIUM: input-sanitization — routes/contacts.ts:6
|
|
131
|
+
**Issue**: Unsanitized user input passed to .filter()
|
|
132
|
+
**Fix**: Use a sanitization function: const q = sanitize(req.query.q);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Requirements
|
|
136
|
+
|
|
137
|
+
- Node.js >= 20.11.0
|
|
138
|
+
- Git (for diff analysis)
|
|
139
|
+
- Playwright (optional, for E2E testing): `npm i -D playwright`
|
|
140
|
+
- Vitest (optional, for unit test generation): `npm i -D vitest`
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
Proprietary. See [LICENSE](./LICENSE) for details.
|
|
145
|
+
|
|
146
|
+
Free tier available. Pro and Team require a license key — see [qass.dev](https://qass.dev) for pricing.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { loadConfig, initConfig } from "./core/config.js";
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program
|
|
7
|
+
.name("qass")
|
|
8
|
+
.description("QA + Security Scanner for vibe-coded apps. Ship fast. Ship safe.")
|
|
9
|
+
.version("0.1.0");
|
|
10
|
+
program
|
|
11
|
+
.command("init")
|
|
12
|
+
.description("Initialize QASS config in a project")
|
|
13
|
+
.option("-p, --project <path>", "Project root path", ".")
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
try {
|
|
16
|
+
const configPath = await initConfig(opts.project);
|
|
17
|
+
console.log(chalk.green("✓") + ` Config created at ${configPath}`);
|
|
18
|
+
console.log(chalk.dim(" Edit .qass/config.yaml to configure your project."));
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
console.error(chalk.red("✗"), e instanceof Error ? e.message : "Failed to initialize");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
program
|
|
26
|
+
.command("test")
|
|
27
|
+
.description("Run tests based on git diff")
|
|
28
|
+
.option("-p, --project <path>", "Project root path", ".")
|
|
29
|
+
.option("-d, --diff <ref>", "Git diff reference", "HEAD")
|
|
30
|
+
.option("-t, --type <type>", "Test type: api, e2e, unit, security, smoke, all", "all")
|
|
31
|
+
.action(async (opts) => {
|
|
32
|
+
try {
|
|
33
|
+
const config = await loadConfig(opts.project);
|
|
34
|
+
console.log(chalk.blue("QASS") +
|
|
35
|
+
chalk.dim(` testing ${config.project.name}...`));
|
|
36
|
+
const { runTests } = await import("./core/runner.js");
|
|
37
|
+
await runTests(config, opts.project, opts.diff, opts.type);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.error(chalk.red("✗"), e instanceof Error ? e.message : "Test run failed");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
program
|
|
45
|
+
.command("scan")
|
|
46
|
+
.description("Run security scan only")
|
|
47
|
+
.option("-p, --project <path>", "Project root path", ".")
|
|
48
|
+
.option("-d, --diff <ref>", "Git diff reference", "HEAD")
|
|
49
|
+
.option("--full", "Scan all files, not just changed ones", false)
|
|
50
|
+
.action(async (opts) => {
|
|
51
|
+
try {
|
|
52
|
+
const config = await loadConfig(opts.project);
|
|
53
|
+
console.log(chalk.blue("QASS") +
|
|
54
|
+
chalk.dim(` scanning ${config.project.name}...`));
|
|
55
|
+
const { runTests } = await import("./core/runner.js");
|
|
56
|
+
await runTests(config, opts.project, opts.diff, "security", opts.full);
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
console.error(chalk.red("✗"), e instanceof Error ? e.message : "Scan failed");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
program
|
|
64
|
+
.command("discover")
|
|
65
|
+
.description("List discovered endpoints and pages")
|
|
66
|
+
.option("-p, --project <path>", "Project root path", ".")
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
try {
|
|
69
|
+
const config = await loadConfig(opts.project);
|
|
70
|
+
const { discover } = await import("./core/discover.js");
|
|
71
|
+
await discover(config, opts.project);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
console.error(chalk.red("✗"), e instanceof Error ? e.message : "Discovery failed");
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
program
|
|
79
|
+
.command("cursor-rule")
|
|
80
|
+
.description("Generate Cursor Rule for this project")
|
|
81
|
+
.option("-p, --project <path>", "Project root path", ".")
|
|
82
|
+
.action(async (opts) => {
|
|
83
|
+
try {
|
|
84
|
+
const { generateCursorRule } = await import("./integrations/cursor-rule.js");
|
|
85
|
+
const rulePath = await generateCursorRule(opts.project);
|
|
86
|
+
console.log(chalk.green("✓") + ` Cursor rule created at ${rulePath}`);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
console.error(chalk.red("✗"), e instanceof Error ? e.message : "Failed to generate rule");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
program
|
|
94
|
+
.command("activate <key>")
|
|
95
|
+
.description("Activate a Pro or Team license key")
|
|
96
|
+
.action(async (key) => {
|
|
97
|
+
const { activateLicense } = await import("./core/license.js");
|
|
98
|
+
const ok = await activateLicense(key);
|
|
99
|
+
if (!ok)
|
|
100
|
+
process.exit(1);
|
|
101
|
+
});
|
|
102
|
+
program
|
|
103
|
+
.command("deactivate")
|
|
104
|
+
.description("Remove license from this machine")
|
|
105
|
+
.action(async () => {
|
|
106
|
+
const { deactivateLicense } = await import("./core/license.js");
|
|
107
|
+
await deactivateLicense();
|
|
108
|
+
});
|
|
109
|
+
program
|
|
110
|
+
.command("status")
|
|
111
|
+
.description("Show current license and plan info")
|
|
112
|
+
.action(async () => {
|
|
113
|
+
const { showStatus } = await import("./core/license.js");
|
|
114
|
+
await showStatus();
|
|
115
|
+
});
|
|
116
|
+
program.parse();
|
|
117
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,wBAAsB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBzE;AAiBD,wBAAsB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmCrE"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { readFile, access, copyFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
const CONFIG_DIR = ".qass";
|
|
5
|
+
const CONFIG_FILE = "config.yaml";
|
|
6
|
+
export async function loadConfig(projectPath) {
|
|
7
|
+
const configPath = resolve(projectPath, CONFIG_DIR, CONFIG_FILE);
|
|
8
|
+
try {
|
|
9
|
+
await access(configPath);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new Error(`No QASS config found at ${configPath}\nRun 'qass init --project ${projectPath}' to create one.`);
|
|
13
|
+
}
|
|
14
|
+
const raw = await readFile(configPath, "utf-8");
|
|
15
|
+
const envResolved = resolveEnvVars(raw);
|
|
16
|
+
const config = parseYaml(envResolved);
|
|
17
|
+
validateConfig(config);
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
20
|
+
function resolveEnvVars(content) {
|
|
21
|
+
return content.replace(/\$\{(\w+)\}/g, (_, name) => {
|
|
22
|
+
return process.env[name] ?? "";
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function validateConfig(config) {
|
|
26
|
+
if (!config.project?.name) {
|
|
27
|
+
throw new Error("Config missing required field: project.name");
|
|
28
|
+
}
|
|
29
|
+
if (!config.project?.root) {
|
|
30
|
+
throw new Error("Config missing required field: project.root");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function initConfig(projectPath) {
|
|
34
|
+
const targetDir = resolve(projectPath, CONFIG_DIR);
|
|
35
|
+
const targetFile = join(targetDir, CONFIG_FILE);
|
|
36
|
+
try {
|
|
37
|
+
await access(targetFile);
|
|
38
|
+
throw new Error(`Config already exists at ${targetFile}`);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
if (e instanceof Error && e.message.startsWith("Config already exists")) {
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
await mkdir(targetDir, { recursive: true });
|
|
46
|
+
const templatePath = resolve(import.meta.dirname, "..", "templates", "config.example.yaml");
|
|
47
|
+
try {
|
|
48
|
+
await access(templatePath);
|
|
49
|
+
await copyFile(templatePath, targetFile);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
const defaultConfig = generateDefaultConfig(projectPath);
|
|
53
|
+
const { writeFile } = await import("node:fs/promises");
|
|
54
|
+
await writeFile(targetFile, defaultConfig, "utf-8");
|
|
55
|
+
}
|
|
56
|
+
await mkdir(join(targetDir, "results"), { recursive: true });
|
|
57
|
+
await mkdir(join(targetDir, "baselines"), { recursive: true });
|
|
58
|
+
return targetFile;
|
|
59
|
+
}
|
|
60
|
+
function generateDefaultConfig(projectPath) {
|
|
61
|
+
const name = projectPath.split(/[\\/]/).pop() ?? "my-project";
|
|
62
|
+
return `project:
|
|
63
|
+
name: ${name}
|
|
64
|
+
root: .
|
|
65
|
+
|
|
66
|
+
# services:
|
|
67
|
+
# api:
|
|
68
|
+
# start: "npm run dev"
|
|
69
|
+
# port: 3000
|
|
70
|
+
# health: "http://localhost:3000/health"
|
|
71
|
+
# frontend:
|
|
72
|
+
# start: "npm run dev"
|
|
73
|
+
# port: 3001
|
|
74
|
+
# url: "http://localhost:3001"
|
|
75
|
+
|
|
76
|
+
# auth:
|
|
77
|
+
# provider: supabase
|
|
78
|
+
# supabase_url: \${NEXT_PUBLIC_SUPABASE_URL}
|
|
79
|
+
# supabase_anon_key: \${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
|
80
|
+
|
|
81
|
+
# test_accounts:
|
|
82
|
+
# - email: test@example.com
|
|
83
|
+
# password: TestPass123!
|
|
84
|
+
# role: default
|
|
85
|
+
|
|
86
|
+
paths:
|
|
87
|
+
api_routes: "src/**/*.routes.ts"
|
|
88
|
+
frontend_pages: "app/**/page.tsx"
|
|
89
|
+
components: "components/**/*.tsx"
|
|
90
|
+
|
|
91
|
+
# route_mounting:
|
|
92
|
+
# example.routes.ts: /api/v1/example
|
|
93
|
+
|
|
94
|
+
# feature_matrix:
|
|
95
|
+
# feature_name: [role1, role2]
|
|
96
|
+
|
|
97
|
+
security:
|
|
98
|
+
enabled: true
|
|
99
|
+
severity_threshold: LOW
|
|
100
|
+
static_rules:
|
|
101
|
+
- auth-middleware
|
|
102
|
+
- input-sanitization
|
|
103
|
+
- secrets-scan
|
|
104
|
+
- xss-vectors
|
|
105
|
+
- config-audit
|
|
106
|
+
- rate-limit-audit
|
|
107
|
+
- dep-audit
|
|
108
|
+
dynamic_checks: true
|
|
109
|
+
|
|
110
|
+
e2e:
|
|
111
|
+
viewports:
|
|
112
|
+
- { width: 1280, height: 720, name: desktop }
|
|
113
|
+
- { width: 375, height: 812, name: mobile }
|
|
114
|
+
smoke_crawl: true
|
|
115
|
+
visual_regression: true
|
|
116
|
+
visual_threshold: 0.01
|
|
117
|
+
stuck_timeout: 10000
|
|
118
|
+
slow_response: 3000
|
|
119
|
+
|
|
120
|
+
# flows:
|
|
121
|
+
# example_flow:
|
|
122
|
+
# as: default
|
|
123
|
+
# steps:
|
|
124
|
+
# - goto: /
|
|
125
|
+
# - assert_visible: "h1"
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-analyzer.d.ts","sourceRoot":"","sources":["../../src/core/diff-analyzer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EAGb,MAAM,aAAa,CAAC;AAsCrB,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,YAAY,CAAC,CAevB"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { minimatch } from "minimatch";
|
|
4
|
+
const exec = promisify(execFile);
|
|
5
|
+
const FEATURE_KEYWORDS = {
|
|
6
|
+
logo_upload: ["logo", "logoUpload", "LogoUpload", "logo_upload"],
|
|
7
|
+
custom_branding: [
|
|
8
|
+
"branding",
|
|
9
|
+
"customBranding",
|
|
10
|
+
"brand_color",
|
|
11
|
+
"brandColor",
|
|
12
|
+
"display_name",
|
|
13
|
+
],
|
|
14
|
+
route_optimization: [
|
|
15
|
+
"routeOptimiz",
|
|
16
|
+
"optimize-route",
|
|
17
|
+
"optimizeRoute",
|
|
18
|
+
"route_optimization",
|
|
19
|
+
],
|
|
20
|
+
fleet_management: [
|
|
21
|
+
"fleet",
|
|
22
|
+
"Fleet",
|
|
23
|
+
"requireFleetOwner",
|
|
24
|
+
"fleetOperator",
|
|
25
|
+
"fleet_operators",
|
|
26
|
+
],
|
|
27
|
+
analytics: ["analytics", "Analytics", "analyticsRoutes"],
|
|
28
|
+
api_access: ["apiAccess", "api_access"],
|
|
29
|
+
yaady_bot: ["yaady", "Yaady", "whatsapp", "WhatsApp", "yaadyBot"],
|
|
30
|
+
auth: ["auth", "signIn", "signUp", "requireCourier", "requireAdmin"],
|
|
31
|
+
plan_gating: [
|
|
32
|
+
"planGate",
|
|
33
|
+
"requireActiveCourierSubscription",
|
|
34
|
+
"SUBSCRIPTION_REQUIRED",
|
|
35
|
+
"PLAN_UPGRADE_REQUIRED",
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
export async function analyzeDiff(projectPath, diffRef, config) {
|
|
39
|
+
const changedFiles = await getChangedFiles(projectPath, diffRef);
|
|
40
|
+
const categorized = categorizeFiles(changedFiles, config);
|
|
41
|
+
const affectedFeatures = detectAffectedFeatures(categorized, config);
|
|
42
|
+
const affectedRoles = detectAffectedRoles(affectedFeatures, config);
|
|
43
|
+
const changeCategories = [
|
|
44
|
+
...new Set(categorized.map((f) => f.category)),
|
|
45
|
+
];
|
|
46
|
+
return {
|
|
47
|
+
changedFiles: categorized,
|
|
48
|
+
affectedFeatures,
|
|
49
|
+
affectedRoles,
|
|
50
|
+
changeCategories,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function getChangedFiles(projectPath, diffRef) {
|
|
54
|
+
const files = [];
|
|
55
|
+
try {
|
|
56
|
+
const { stdout: nameStatus } = await exec("git", ["diff", "--name-status", diffRef], { cwd: projectPath });
|
|
57
|
+
for (const line of nameStatus.trim().split("\n")) {
|
|
58
|
+
if (!line)
|
|
59
|
+
continue;
|
|
60
|
+
const [status, ...pathParts] = line.split("\t");
|
|
61
|
+
const filePath = pathParts.join("\t");
|
|
62
|
+
if (!filePath)
|
|
63
|
+
continue;
|
|
64
|
+
files.push({
|
|
65
|
+
path: filePath,
|
|
66
|
+
status: status.startsWith("R") ? "R" : status,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
try {
|
|
72
|
+
const { stdout: nameStatus } = await exec("git", ["diff", "--name-status", "--staged"], { cwd: projectPath });
|
|
73
|
+
for (const line of nameStatus.trim().split("\n")) {
|
|
74
|
+
if (!line)
|
|
75
|
+
continue;
|
|
76
|
+
const [status, ...pathParts] = line.split("\t");
|
|
77
|
+
const filePath = pathParts.join("\t");
|
|
78
|
+
if (!filePath)
|
|
79
|
+
continue;
|
|
80
|
+
files.push({
|
|
81
|
+
path: filePath,
|
|
82
|
+
status: status.startsWith("R") ? "R" : status,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
if (file.status === "D")
|
|
92
|
+
continue;
|
|
93
|
+
try {
|
|
94
|
+
const { stdout: diff } = await exec("git", ["diff", diffRef, "--", file.path], { cwd: projectPath });
|
|
95
|
+
file.diff = diff;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// no diff available
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return files;
|
|
102
|
+
}
|
|
103
|
+
function categorizeFiles(files, config) {
|
|
104
|
+
const paths = config.paths ?? {};
|
|
105
|
+
return files.map((file) => {
|
|
106
|
+
let category = "other";
|
|
107
|
+
for (const [key, pattern] of Object.entries(paths)) {
|
|
108
|
+
if (minimatch(file.path, pattern)) {
|
|
109
|
+
category = mapKeyToCategory(key);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (category === "other") {
|
|
114
|
+
category = inferCategory(file.path);
|
|
115
|
+
}
|
|
116
|
+
const statusMap = {
|
|
117
|
+
A: "added",
|
|
118
|
+
M: "modified",
|
|
119
|
+
D: "deleted",
|
|
120
|
+
R: "renamed",
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
path: file.path,
|
|
124
|
+
status: statusMap[file.status] ?? "modified",
|
|
125
|
+
category,
|
|
126
|
+
diff: file.diff,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function mapKeyToCategory(key) {
|
|
131
|
+
const mapping = {
|
|
132
|
+
api_routes: "api_route",
|
|
133
|
+
api_app: "config",
|
|
134
|
+
frontend_pages: "frontend_page",
|
|
135
|
+
components: "component",
|
|
136
|
+
shared_lib: "shared_lib",
|
|
137
|
+
middleware: "middleware",
|
|
138
|
+
};
|
|
139
|
+
return mapping[key] ?? "other";
|
|
140
|
+
}
|
|
141
|
+
function inferCategory(filePath) {
|
|
142
|
+
if (/\.routes\.(ts|js)$/.test(filePath))
|
|
143
|
+
return "api_route";
|
|
144
|
+
if (/middleware/i.test(filePath))
|
|
145
|
+
return "middleware";
|
|
146
|
+
if (/page\.(tsx|jsx)$/.test(filePath))
|
|
147
|
+
return "frontend_page";
|
|
148
|
+
if (/components?\//i.test(filePath))
|
|
149
|
+
return "component";
|
|
150
|
+
if (/migrations?\//i.test(filePath))
|
|
151
|
+
return "migration";
|
|
152
|
+
if (/package\.json$|tsconfig|\.env|config\.(ts|js|yaml|yml)$/.test(filePath))
|
|
153
|
+
return "config";
|
|
154
|
+
if (/shared|packages\//i.test(filePath))
|
|
155
|
+
return "shared_lib";
|
|
156
|
+
return "other";
|
|
157
|
+
}
|
|
158
|
+
function detectAffectedFeatures(files, _config) {
|
|
159
|
+
const features = new Set();
|
|
160
|
+
const allDiffs = files.map((f) => f.diff ?? "").join("\n");
|
|
161
|
+
for (const [feature, keywords] of Object.entries(FEATURE_KEYWORDS)) {
|
|
162
|
+
if (keywords.some((kw) => allDiffs.includes(kw))) {
|
|
163
|
+
features.add(feature);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
if (file.path.includes("fleet"))
|
|
168
|
+
features.add("fleet_management");
|
|
169
|
+
if (file.path.includes("analytics"))
|
|
170
|
+
features.add("analytics");
|
|
171
|
+
if (file.path.includes("yaady"))
|
|
172
|
+
features.add("yaady_bot");
|
|
173
|
+
if (file.path.includes("auth"))
|
|
174
|
+
features.add("auth");
|
|
175
|
+
if (file.path.includes("subscription"))
|
|
176
|
+
features.add("plan_gating");
|
|
177
|
+
}
|
|
178
|
+
return [...features];
|
|
179
|
+
}
|
|
180
|
+
function detectAffectedRoles(features, config) {
|
|
181
|
+
const roles = new Set();
|
|
182
|
+
const matrix = config.feature_matrix ?? {};
|
|
183
|
+
for (const feature of features) {
|
|
184
|
+
const featureRoles = matrix[feature];
|
|
185
|
+
if (featureRoles) {
|
|
186
|
+
featureRoles.forEach((r) => roles.add(r));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (roles.size === 0 && config.test_accounts) {
|
|
190
|
+
config.test_accounts.forEach((a) => roles.add(a.role));
|
|
191
|
+
}
|
|
192
|
+
return [...roles];
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=diff-analyzer.js.map
|