@veraxhq/verax 0.2.1 → 0.4.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 +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +133 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +84 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -15,6 +15,7 @@ export function writeScanSummary(projectDir, url, projectType, learnTruth, obser
|
|
|
15
15
|
let expectationsSummary = { total: 0, navigation: 0, networkActions: 0, stateActions: 0 };
|
|
16
16
|
if (manifestPath && existsSync(manifestPath)) {
|
|
17
17
|
try {
|
|
18
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
18
19
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
19
20
|
expectationsSummary = computeExpectationsSummary(manifest);
|
|
20
21
|
} catch (error) {
|
|
@@ -34,6 +35,7 @@ export function writeScanSummary(projectDir, url, projectType, learnTruth, obser
|
|
|
34
35
|
const decisionsPath = resolve(runDirOpt, 'decisions.json');
|
|
35
36
|
if (existsSync(decisionsPath)) {
|
|
36
37
|
try {
|
|
38
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
37
39
|
const decisions = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
|
|
38
40
|
const { DecisionRecorder } = require('./core/determinism-model.js');
|
|
39
41
|
const recorder = DecisionRecorder.fromExport(decisions);
|
|
@@ -14,14 +14,34 @@
|
|
|
14
14
|
|
|
15
15
|
import { existsSync, mkdirSync, writeFileSync, appendFileSync } from 'fs';
|
|
16
16
|
import { resolve } from 'path';
|
|
17
|
-
import {
|
|
17
|
+
import { generateRunId as generateDeterministicRunId } from '../core/run-id.js';
|
|
18
|
+
|
|
19
|
+
const ZERO_BUDGET = Object.freeze({
|
|
20
|
+
maxScanDurationMs: 0,
|
|
21
|
+
maxInteractionsPerPage: 0,
|
|
22
|
+
maxUniqueUrls: 0,
|
|
23
|
+
interactionTimeoutMs: 0,
|
|
24
|
+
navigationTimeoutMs: 0,
|
|
25
|
+
});
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
28
|
* Generate a unique run ID.
|
|
21
29
|
* @returns {string} - 8-character hex ID
|
|
22
30
|
*/
|
|
23
|
-
export function generateRunId() {
|
|
24
|
-
|
|
31
|
+
export function generateRunId(seed = 'about:blank') {
|
|
32
|
+
let baseOrigin = 'about:blank';
|
|
33
|
+
try {
|
|
34
|
+
baseOrigin = new URL(seed).origin;
|
|
35
|
+
} catch {
|
|
36
|
+
baseOrigin = seed;
|
|
37
|
+
}
|
|
38
|
+
return generateDeterministicRunId({
|
|
39
|
+
url: seed,
|
|
40
|
+
safetyFlags: {},
|
|
41
|
+
baseOrigin,
|
|
42
|
+
scanBudget: ZERO_BUDGET,
|
|
43
|
+
manifestPath: null,
|
|
44
|
+
});
|
|
25
45
|
}
|
|
26
46
|
|
|
27
47
|
/**
|
|
@@ -30,8 +50,8 @@ export function generateRunId() {
|
|
|
30
50
|
* @param {string} runId - Run identifier
|
|
31
51
|
* @returns {Object} - Paths to each artifact location
|
|
32
52
|
*/
|
|
33
|
-
export function initArtifactPaths(projectRoot, runId = null) {
|
|
34
|
-
const id = runId || generateRunId();
|
|
53
|
+
export function initArtifactPaths(projectRoot, runId = null, seed = projectRoot) {
|
|
54
|
+
const id = runId || generateRunId(seed);
|
|
35
55
|
const runDir = resolve(projectRoot, '.verax', 'runs', id);
|
|
36
56
|
|
|
37
57
|
const paths = {
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS SPINNER DETECTION RULES
|
|
3
|
+
*
|
|
4
|
+
* Truth boundary for detecting CSS-only loading indicators (spinners)
|
|
5
|
+
* without semantic attributes (aria-busy, data-loading, role).
|
|
6
|
+
*
|
|
7
|
+
* Hard rules encoded as constants and predicates. No prose, only code.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Reason codes for CSS spinner detection decisions
|
|
12
|
+
*/
|
|
13
|
+
export const CSS_SPINNER_REASON_CODES = {
|
|
14
|
+
DETECTED_BORDER_SPINNER: 'UI_CSS_SPINNER_DETECTED_BORDER',
|
|
15
|
+
DETECTED_ROTATION_ANIMATION: 'UI_CSS_SPINNER_DETECTED_ROTATION',
|
|
16
|
+
DETECTED_PULSE_ANIMATION: 'UI_CSS_SPINNER_DETECTED_PULSE',
|
|
17
|
+
REJECTED_DECORATIVE: 'UI_CSS_SPINNER_REJECTED_DECORATIVE',
|
|
18
|
+
REJECTED_TOO_LARGE: 'UI_CSS_SPINNER_REJECTED_TOO_LARGE',
|
|
19
|
+
REJECTED_ALWAYS_PRESENT: 'UI_CSS_SPINNER_REJECTED_ALWAYS_PRESENT',
|
|
20
|
+
REJECTED_NO_CORROBORATION: 'UI_CSS_SPINNER_REJECTED_NO_CORROBORATION',
|
|
21
|
+
REJECTED_TIMING_WINDOW: 'UI_CSS_SPINNER_TIMED',
|
|
22
|
+
ACCEPTED_WITH_CORROBORATION: 'UI_CSS_SPINNER_ACCEPTED_WITH_CORROBORATION'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maximum size for a spinner element (in pixels)
|
|
27
|
+
* Larger elements are likely decorative, not loading indicators
|
|
28
|
+
*/
|
|
29
|
+
export const MAX_SPINNER_SIZE = 100; // pixels (width or height)
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Minimum size for a spinner element (in pixels)
|
|
33
|
+
* Very small elements might be decorative dots
|
|
34
|
+
*/
|
|
35
|
+
export const MIN_SPINNER_SIZE = 8; // pixels
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Timing window for spinner appearance (milliseconds)
|
|
39
|
+
* Spinner must appear within this window after interaction to count as loading feedback
|
|
40
|
+
*/
|
|
41
|
+
export const SPINNER_TIMING_WINDOW_MS = 2000; // 2 seconds
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if an element has border-based spinner pattern
|
|
45
|
+
* Pattern: border + border-top (or border-left) with different color, creating circular spinner
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} computedStyle - Computed style object
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export function isBorderSpinnerPattern(computedStyle) {
|
|
51
|
+
if (!computedStyle) return false;
|
|
52
|
+
|
|
53
|
+
const borderWidth = parseFloat(computedStyle.borderWidth) || 0;
|
|
54
|
+
const borderTopWidth = parseFloat(computedStyle.borderTopWidth) || 0;
|
|
55
|
+
const borderLeftWidth = parseFloat(computedStyle.borderLeftWidth) || 0;
|
|
56
|
+
|
|
57
|
+
// Must have border
|
|
58
|
+
if (borderWidth < 2 && borderTopWidth < 2 && borderLeftWidth < 2) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check for different border colors (spinner pattern)
|
|
63
|
+
const borderColor = computedStyle.borderColor || '';
|
|
64
|
+
const borderTopColor = computedStyle.borderTopColor || '';
|
|
65
|
+
const borderLeftColor = computedStyle.borderLeftColor || '';
|
|
66
|
+
|
|
67
|
+
// Border-top or border-left should have different color than main border
|
|
68
|
+
const hasDifferentTopColor = borderTopColor && borderColor && borderTopColor !== borderColor && borderTopColor !== 'rgba(0, 0, 0, 0)';
|
|
69
|
+
const hasDifferentLeftColor = borderLeftColor && borderColor && borderLeftColor !== borderColor && borderLeftColor !== 'rgba(0, 0, 0, 0)';
|
|
70
|
+
|
|
71
|
+
// Check for circular shape (border-radius >= 50%)
|
|
72
|
+
const borderRadius = computedStyle.borderRadius || '';
|
|
73
|
+
const isCircular = borderRadius.includes('50%') || borderRadius.includes('999') || parseFloat(borderRadius) > 20;
|
|
74
|
+
|
|
75
|
+
return (hasDifferentTopColor || hasDifferentLeftColor) && isCircular;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if an element has rotation animation
|
|
80
|
+
* Pattern: animation-name includes rotation, or transform: rotate() in keyframes
|
|
81
|
+
*
|
|
82
|
+
* @param {Object} computedStyle - Computed style object
|
|
83
|
+
* @param {Object} element - DOM element
|
|
84
|
+
* @returns {boolean}
|
|
85
|
+
*/
|
|
86
|
+
export function isRotationAnimation(computedStyle, element) {
|
|
87
|
+
if (!computedStyle || !element) return false;
|
|
88
|
+
|
|
89
|
+
const animationName = computedStyle.animationName || '';
|
|
90
|
+
const animation = computedStyle.animation || '';
|
|
91
|
+
|
|
92
|
+
// Check animation name for spinner-related keywords (but don't rely only on names)
|
|
93
|
+
const spinnerKeywords = ['spin', 'rotate', 'loading', 'loader'];
|
|
94
|
+
const hasSpinnerKeyword = spinnerKeywords.some(keyword =>
|
|
95
|
+
animationName.toLowerCase().includes(keyword) || animation.toLowerCase().includes(keyword)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Check for transform: rotate() in computed style (indicates rotation)
|
|
99
|
+
const transform = computedStyle.transform || '';
|
|
100
|
+
const hasRotation = transform.includes('rotate');
|
|
101
|
+
|
|
102
|
+
// Check for animation-duration (must be animated)
|
|
103
|
+
const animationDuration = computedStyle.animationDuration || '';
|
|
104
|
+
const isAnimated = animationDuration && animationDuration !== '0s' && !animationDuration.includes('none');
|
|
105
|
+
|
|
106
|
+
// Must have rotation AND be animated
|
|
107
|
+
return (hasRotation || hasSpinnerKeyword) && isAnimated;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if an element has pulse animation (opacity or scale pulsing)
|
|
112
|
+
* Pattern: animation that changes opacity or scale repeatedly
|
|
113
|
+
*
|
|
114
|
+
* @param {Object} computedStyle - Computed style object
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
117
|
+
export function isPulseAnimation(computedStyle) {
|
|
118
|
+
if (!computedStyle) return false;
|
|
119
|
+
|
|
120
|
+
const animationName = computedStyle.animationName || '';
|
|
121
|
+
const animation = computedStyle.animation || '';
|
|
122
|
+
|
|
123
|
+
// Check for pulse-related keywords
|
|
124
|
+
const pulseKeywords = ['pulse', 'fade', 'loading'];
|
|
125
|
+
const hasPulseKeyword = pulseKeywords.some(keyword =>
|
|
126
|
+
animationName.toLowerCase().includes(keyword) || animation.toLowerCase().includes(keyword)
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Check for animation-duration
|
|
130
|
+
const animationDuration = computedStyle.animationDuration || '';
|
|
131
|
+
const isAnimated = animationDuration && animationDuration !== '0s' && !animationDuration.includes('none');
|
|
132
|
+
|
|
133
|
+
// Check for opacity animation (common in pulse patterns)
|
|
134
|
+
const opacity = parseFloat(computedStyle.opacity) || 1;
|
|
135
|
+
const hasOpacityVariation = opacity < 1; // Partially transparent suggests pulsing
|
|
136
|
+
|
|
137
|
+
return hasPulseKeyword && isAnimated && hasOpacityVariation;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if element size is within spinner bounds
|
|
142
|
+
*
|
|
143
|
+
* @param {number} width - Element width in pixels
|
|
144
|
+
* @param {number} height - Element height in pixels
|
|
145
|
+
* @returns {boolean}
|
|
146
|
+
*/
|
|
147
|
+
export function isSpinnerSize(width, height) {
|
|
148
|
+
const maxDim = Math.max(width, height);
|
|
149
|
+
const minDim = Math.min(width, height);
|
|
150
|
+
|
|
151
|
+
// Must be within size bounds
|
|
152
|
+
if (maxDim > MAX_SPINNER_SIZE || minDim < MIN_SPINNER_SIZE) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Should be roughly square (spinners are usually circular/square)
|
|
157
|
+
const aspectRatio = maxDim / (minDim || 1);
|
|
158
|
+
return aspectRatio <= 2.0; // Allow some rectangular tolerance
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if spinner appeared within timing window
|
|
163
|
+
*
|
|
164
|
+
* @param {number} appearanceTime - Time when spinner appeared (ms since epoch)
|
|
165
|
+
* @param {number} interactionTime - Time when interaction occurred (ms since epoch)
|
|
166
|
+
* @returns {boolean}
|
|
167
|
+
*/
|
|
168
|
+
export function isWithinTimingWindow(appearanceTime, interactionTime) {
|
|
169
|
+
if (!appearanceTime || !interactionTime) return false;
|
|
170
|
+
const timeDiff = appearanceTime - interactionTime;
|
|
171
|
+
return timeDiff >= 0 && timeDiff <= SPINNER_TIMING_WINDOW_MS;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check if element is likely decorative (always present, large, not near interaction)
|
|
176
|
+
*
|
|
177
|
+
* @param {Object} element - DOM element
|
|
178
|
+
* @param {Object} computedStyle - Computed style object
|
|
179
|
+
* @param {boolean} wasPresentBefore - Whether element existed before interaction
|
|
180
|
+
* @returns {boolean}
|
|
181
|
+
*/
|
|
182
|
+
export function isDecorativeElement(element, computedStyle, wasPresentBefore) {
|
|
183
|
+
if (!element || !computedStyle) return false;
|
|
184
|
+
|
|
185
|
+
// If element was always present, likely decorative
|
|
186
|
+
if (wasPresentBefore) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check size
|
|
191
|
+
const width = element.offsetWidth || 0;
|
|
192
|
+
const height = element.offsetHeight || 0;
|
|
193
|
+
if (!isSpinnerSize(width, height)) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check if element is very large (likely decorative)
|
|
198
|
+
if (width > MAX_SPINNER_SIZE || height > MAX_SPINNER_SIZE) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUTH BOUNDARY: State-Driven Navigation / View Switch Detection Rules
|
|
3
|
+
*
|
|
4
|
+
* Hard rules encoded as constants and predicates. No prose, only code.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detectable view switch function name patterns (strict allowlist)
|
|
9
|
+
* Only literal string/number arguments are accepted.
|
|
10
|
+
*/
|
|
11
|
+
export const VIEW_SWITCH_FUNCTION_PATTERNS = {
|
|
12
|
+
// React setState patterns
|
|
13
|
+
react: [
|
|
14
|
+
/^setView$/i,
|
|
15
|
+
/^setPage$/i,
|
|
16
|
+
/^setStep$/i,
|
|
17
|
+
/^setScreen$/i,
|
|
18
|
+
/^setTab$/i,
|
|
19
|
+
/^setModalOpen$/i,
|
|
20
|
+
/^setDrawerOpen$/i,
|
|
21
|
+
/^setPanelOpen$/i,
|
|
22
|
+
/^setActiveTab$/i,
|
|
23
|
+
/^setActiveView$/i,
|
|
24
|
+
/^setCurrentStep$/i,
|
|
25
|
+
/^setCurrentPage$/i,
|
|
26
|
+
/^setCurrentScreen$/i,
|
|
27
|
+
/^setCurrentView$/i,
|
|
28
|
+
/^setShowModal$/i,
|
|
29
|
+
/^setShowDrawer$/i,
|
|
30
|
+
/^setShowPanel$/i,
|
|
31
|
+
/^setIsModalOpen$/i,
|
|
32
|
+
/^setIsDrawerOpen$/i,
|
|
33
|
+
/^setIsPanelOpen$/i
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
// Redux dispatch action types
|
|
37
|
+
redux: [
|
|
38
|
+
/^NAVIGATE$/i,
|
|
39
|
+
/^SET_VIEW$/i,
|
|
40
|
+
/^SET_STEP$/i,
|
|
41
|
+
/^SET_PAGE$/i,
|
|
42
|
+
/^SET_SCREEN$/i,
|
|
43
|
+
/^SET_TAB$/i,
|
|
44
|
+
/^OPEN_MODAL$/i,
|
|
45
|
+
/^CLOSE_MODAL$/i,
|
|
46
|
+
/^OPEN_DRAWER$/i,
|
|
47
|
+
/^CLOSE_DRAWER$/i,
|
|
48
|
+
/^OPEN_PANEL$/i,
|
|
49
|
+
/^CLOSE_PANEL$/i,
|
|
50
|
+
/^SWITCH_VIEW$/i,
|
|
51
|
+
/^SWITCH_TAB$/i,
|
|
52
|
+
/^SWITCH_STEP$/i
|
|
53
|
+
],
|
|
54
|
+
|
|
55
|
+
// Generic function call patterns
|
|
56
|
+
generic: [
|
|
57
|
+
/^showModal$/i,
|
|
58
|
+
/^hideModal$/i,
|
|
59
|
+
/^openDrawer$/i,
|
|
60
|
+
/^closeDrawer$/i,
|
|
61
|
+
/^openPanel$/i,
|
|
62
|
+
/^closePanel$/i,
|
|
63
|
+
/^switchView$/i,
|
|
64
|
+
/^switchTab$/i,
|
|
65
|
+
/^switchStep$/i,
|
|
66
|
+
/^navigateTo$/i,
|
|
67
|
+
/^goToView$/i,
|
|
68
|
+
/^goToStep$/i,
|
|
69
|
+
/^goToPage$/i,
|
|
70
|
+
/^goToScreen$/i
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* View switch kinds (categories)
|
|
76
|
+
*/
|
|
77
|
+
export const VIEW_SWITCH_KINDS = {
|
|
78
|
+
TAB: 'tab',
|
|
79
|
+
VIEW: 'view',
|
|
80
|
+
MODAL: 'modal',
|
|
81
|
+
DRAWER: 'drawer',
|
|
82
|
+
PANEL: 'panel',
|
|
83
|
+
STEP: 'step',
|
|
84
|
+
SCREEN: 'screen',
|
|
85
|
+
PAGE: 'page'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Reason codes for truth boundary decisions
|
|
90
|
+
*/
|
|
91
|
+
export const VIEW_SWITCH_REASON_CODES = {
|
|
92
|
+
DETECTABLE_LITERAL_ARG: 'DETECTABLE_LITERAL_ARG',
|
|
93
|
+
REJECTED_COMPLEX_EXPRESSION: 'REJECTED_COMPLEX_EXPRESSION',
|
|
94
|
+
REJECTED_DYNAMIC_VALUE: 'REJECTED_DYNAMIC_VALUE',
|
|
95
|
+
REJECTED_MEMBER_EXPRESSION: 'REJECTED_MEMBER_EXPRESSION',
|
|
96
|
+
REJECTED_FUNCTION_CALL: 'REJECTED_FUNCTION_CALL',
|
|
97
|
+
REJECTED_NOT_IN_ALLOWLIST: 'REJECTED_NOT_IN_ALLOWLIST',
|
|
98
|
+
ACCEPTED_STRING_LITERAL: 'ACCEPTED_STRING_LITERAL',
|
|
99
|
+
ACCEPTED_NUMBER_LITERAL: 'ACCEPTED_NUMBER_LITERAL'
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a function name matches view switch patterns
|
|
104
|
+
* @param {string} functionName - Function name to check
|
|
105
|
+
* @returns {Object|null} - { kind, pattern } or null
|
|
106
|
+
*/
|
|
107
|
+
export function isViewSwitchFunction(functionName) {
|
|
108
|
+
if (!functionName || typeof functionName !== 'string') return null;
|
|
109
|
+
|
|
110
|
+
// Check React patterns
|
|
111
|
+
for (const pattern of VIEW_SWITCH_FUNCTION_PATTERNS.react) {
|
|
112
|
+
if (pattern.test(functionName)) {
|
|
113
|
+
const kind = inferViewSwitchKind(functionName);
|
|
114
|
+
return { kind, pattern: 'react', functionName };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check Redux patterns
|
|
119
|
+
for (const pattern of VIEW_SWITCH_FUNCTION_PATTERNS.redux) {
|
|
120
|
+
if (pattern.test(functionName)) {
|
|
121
|
+
const kind = inferViewSwitchKind(functionName);
|
|
122
|
+
return { kind, pattern: 'redux', functionName };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check generic patterns
|
|
127
|
+
for (const pattern of VIEW_SWITCH_FUNCTION_PATTERNS.generic) {
|
|
128
|
+
if (pattern.test(functionName)) {
|
|
129
|
+
const kind = inferViewSwitchKind(functionName);
|
|
130
|
+
return { kind, pattern: 'generic', functionName };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Infer view switch kind from function name
|
|
139
|
+
* @param {string} functionName - Function name
|
|
140
|
+
* @returns {string} - View switch kind
|
|
141
|
+
*/
|
|
142
|
+
function inferViewSwitchKind(functionName) {
|
|
143
|
+
const lower = functionName.toLowerCase();
|
|
144
|
+
|
|
145
|
+
if (lower.includes('tab')) return VIEW_SWITCH_KINDS.TAB;
|
|
146
|
+
if (lower.includes('modal')) return VIEW_SWITCH_KINDS.MODAL;
|
|
147
|
+
if (lower.includes('drawer')) return VIEW_SWITCH_KINDS.DRAWER;
|
|
148
|
+
if (lower.includes('panel')) return VIEW_SWITCH_KINDS.PANEL;
|
|
149
|
+
if (lower.includes('step')) return VIEW_SWITCH_KINDS.STEP;
|
|
150
|
+
if (lower.includes('screen')) return VIEW_SWITCH_KINDS.SCREEN;
|
|
151
|
+
if (lower.includes('page')) return VIEW_SWITCH_KINDS.PAGE;
|
|
152
|
+
if (lower.includes('view')) return VIEW_SWITCH_KINDS.VIEW;
|
|
153
|
+
|
|
154
|
+
return VIEW_SWITCH_KINDS.VIEW; // Default
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check if an AST node is a detectable literal argument
|
|
159
|
+
* TRUTH BOUNDARY: Only StringLiteral and NumericLiteral are accepted
|
|
160
|
+
* @param {Object} node - AST node
|
|
161
|
+
* @returns {Object|null} - { value, reasonCode } or null
|
|
162
|
+
*/
|
|
163
|
+
export function isDetectableLiteralArg(node) {
|
|
164
|
+
if (!node) return null;
|
|
165
|
+
|
|
166
|
+
// String literal: setView('settings')
|
|
167
|
+
if (node.type === 'StringLiteral') {
|
|
168
|
+
return {
|
|
169
|
+
value: node.value,
|
|
170
|
+
reasonCode: VIEW_SWITCH_REASON_CODES.ACCEPTED_STRING_LITERAL
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Number literal: setStep(2)
|
|
175
|
+
if (node.type === 'NumericLiteral') {
|
|
176
|
+
return {
|
|
177
|
+
value: String(node.value),
|
|
178
|
+
reasonCode: VIEW_SWITCH_REASON_CODES.ACCEPTED_NUMBER_LITERAL
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Template literal without interpolation: setView(`settings`)
|
|
183
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
184
|
+
const value = node.quasis[0]?.value?.cooked;
|
|
185
|
+
if (value) {
|
|
186
|
+
return {
|
|
187
|
+
value,
|
|
188
|
+
reasonCode: VIEW_SWITCH_REASON_CODES.ACCEPTED_STRING_LITERAL
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// REJECTED: Complex expressions
|
|
194
|
+
if (node.type === 'CallExpression') {
|
|
195
|
+
return { reasonCode: VIEW_SWITCH_REASON_CODES.REJECTED_FUNCTION_CALL };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (node.type === 'MemberExpression') {
|
|
199
|
+
return { reasonCode: VIEW_SWITCH_REASON_CODES.REJECTED_MEMBER_EXPRESSION };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (node.type === 'Identifier') {
|
|
203
|
+
return { reasonCode: VIEW_SWITCH_REASON_CODES.REJECTED_DYNAMIC_VALUE };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { reasonCode: VIEW_SWITCH_REASON_CODES.REJECTED_COMPLEX_EXPRESSION };
|
|
207
|
+
}
|
|
208
|
+
|
|
@@ -45,6 +45,7 @@ export async function createArtifactsZip(runDir, outputPath = null) {
|
|
|
45
45
|
|
|
46
46
|
// Handle errors
|
|
47
47
|
archive.on('error', (err) => {
|
|
48
|
+
output.destroy();
|
|
48
49
|
reject(err);
|
|
49
50
|
});
|
|
50
51
|
|
|
@@ -52,6 +53,11 @@ export async function createArtifactsZip(runDir, outputPath = null) {
|
|
|
52
53
|
resolvePromise(outputPath);
|
|
53
54
|
});
|
|
54
55
|
|
|
56
|
+
output.on('error', (err) => {
|
|
57
|
+
archive.destroy();
|
|
58
|
+
reject(err);
|
|
59
|
+
});
|
|
60
|
+
|
|
55
61
|
// Pipe archive data to file
|
|
56
62
|
archive.pipe(output);
|
|
57
63
|
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wave 7 — Config File Support
|
|
3
|
-
*
|
|
4
|
-
* Loads and validates .verax/config.json configuration file.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync, existsSync } from 'fs';
|
|
8
|
-
import { resolve } from 'path';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Default config values
|
|
12
|
-
*/
|
|
13
|
-
const DEFAULT_CONFIG = {
|
|
14
|
-
defaultUrl: 'http://localhost:3000',
|
|
15
|
-
projectRoot: '.',
|
|
16
|
-
outDir: '.verax/runs',
|
|
17
|
-
ciDefaults: {
|
|
18
|
-
json: true,
|
|
19
|
-
zip: true,
|
|
20
|
-
explain: false
|
|
21
|
-
},
|
|
22
|
-
safety: {
|
|
23
|
-
allowlistDomains: [],
|
|
24
|
-
denyKeywords: ['delete', 'remove', 'billing', 'payment']
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Validate config structure
|
|
30
|
-
* @param {Object} config - Config object to validate
|
|
31
|
-
* @returns {Object} { valid: boolean, errors: string[] }
|
|
32
|
-
*/
|
|
33
|
-
export function validateConfig(config) {
|
|
34
|
-
const errors = [];
|
|
35
|
-
|
|
36
|
-
if (typeof config !== 'object' || config === null) {
|
|
37
|
-
errors.push('Config must be an object');
|
|
38
|
-
return { valid: false, errors };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Validate defaultUrl
|
|
42
|
-
if (config.defaultUrl !== undefined) {
|
|
43
|
-
if (typeof config.defaultUrl !== 'string' || config.defaultUrl.trim() === '') {
|
|
44
|
-
errors.push('defaultUrl must be a non-empty string');
|
|
45
|
-
} else {
|
|
46
|
-
try {
|
|
47
|
-
new URL(config.defaultUrl);
|
|
48
|
-
} catch (e) {
|
|
49
|
-
errors.push(`defaultUrl is not a valid URL: ${config.defaultUrl}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Validate projectRoot
|
|
55
|
-
if (config.projectRoot !== undefined && typeof config.projectRoot !== 'string') {
|
|
56
|
-
errors.push('projectRoot must be a string');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Validate outDir
|
|
60
|
-
if (config.outDir !== undefined && typeof config.outDir !== 'string') {
|
|
61
|
-
errors.push('outDir must be a string');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Validate ciDefaults
|
|
65
|
-
if (config.ciDefaults !== undefined) {
|
|
66
|
-
if (typeof config.ciDefaults !== 'object' || config.ciDefaults === null) {
|
|
67
|
-
errors.push('ciDefaults must be an object');
|
|
68
|
-
} else {
|
|
69
|
-
if (config.ciDefaults.json !== undefined && typeof config.ciDefaults.json !== 'boolean') {
|
|
70
|
-
errors.push('ciDefaults.json must be a boolean');
|
|
71
|
-
}
|
|
72
|
-
if (config.ciDefaults.zip !== undefined && typeof config.ciDefaults.zip !== 'boolean') {
|
|
73
|
-
errors.push('ciDefaults.zip must be a boolean');
|
|
74
|
-
}
|
|
75
|
-
if (config.ciDefaults.explain !== undefined && typeof config.ciDefaults.explain !== 'boolean') {
|
|
76
|
-
errors.push('ciDefaults.explain must be a boolean');
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Validate safety
|
|
82
|
-
if (config.safety !== undefined) {
|
|
83
|
-
if (typeof config.safety !== 'object' || config.safety === null) {
|
|
84
|
-
errors.push('safety must be an object');
|
|
85
|
-
} else {
|
|
86
|
-
if (config.safety.allowlistDomains !== undefined) {
|
|
87
|
-
if (!Array.isArray(config.safety.allowlistDomains)) {
|
|
88
|
-
errors.push('safety.allowlistDomains must be an array');
|
|
89
|
-
} else {
|
|
90
|
-
for (const domain of config.safety.allowlistDomains) {
|
|
91
|
-
if (typeof domain !== 'string') {
|
|
92
|
-
errors.push('safety.allowlistDomains must contain only strings');
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (config.safety.denyKeywords !== undefined) {
|
|
99
|
-
if (!Array.isArray(config.safety.denyKeywords)) {
|
|
100
|
-
errors.push('safety.denyKeywords must be an array');
|
|
101
|
-
} else {
|
|
102
|
-
for (const keyword of config.safety.denyKeywords) {
|
|
103
|
-
if (typeof keyword !== 'string') {
|
|
104
|
-
errors.push('safety.denyKeywords must contain only strings');
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
valid: errors.length === 0,
|
|
115
|
-
errors
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Load config file from project directory
|
|
121
|
-
* @param {string} projectRoot - Project root directory
|
|
122
|
-
* @returns {Object|null} Config object or null if not found
|
|
123
|
-
* @throws {Error} If config file exists but is invalid
|
|
124
|
-
*/
|
|
125
|
-
export function loadConfig(projectRoot) {
|
|
126
|
-
const configPath = resolve(projectRoot, '.verax', 'config.json');
|
|
127
|
-
|
|
128
|
-
if (!existsSync(configPath)) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
const content = readFileSync(configPath, 'utf-8');
|
|
134
|
-
const config = JSON.parse(content);
|
|
135
|
-
|
|
136
|
-
const validation = validateConfig(config);
|
|
137
|
-
if (!validation.valid) {
|
|
138
|
-
throw new Error(`Invalid config file: ${validation.errors.join(', ')}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Merge with defaults
|
|
142
|
-
return {
|
|
143
|
-
...DEFAULT_CONFIG,
|
|
144
|
-
...config,
|
|
145
|
-
ciDefaults: {
|
|
146
|
-
...DEFAULT_CONFIG.ciDefaults,
|
|
147
|
-
...(config.ciDefaults || {})
|
|
148
|
-
},
|
|
149
|
-
safety: {
|
|
150
|
-
...DEFAULT_CONFIG.safety,
|
|
151
|
-
...(config.safety || {})
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
} catch (error) {
|
|
155
|
-
if (error instanceof SyntaxError) {
|
|
156
|
-
throw new Error(`Config file is not valid JSON: ${error.message}`);
|
|
157
|
-
}
|
|
158
|
-
throw error;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get default config (for init)
|
|
164
|
-
* @returns {Object} Default config object
|
|
165
|
-
*/
|
|
166
|
-
export function getDefaultConfig() {
|
|
167
|
-
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
168
|
-
}
|
|
169
|
-
|
|
File without changes
|