@probelabs/visor 0.1.25 → 0.1.30
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 +142 -5
- package/dist/action-cli-bridge.d.ts +2 -0
- package/dist/action-cli-bridge.d.ts.map +1 -1
- package/dist/check-execution-engine.d.ts.map +1 -1
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/config.d.ts +5 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/defaults/.visor.yaml +374 -0
- package/dist/github-check-service.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1139 -587
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/config.d.ts +6 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/config-loader.d.ts +90 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-merger.d.ts +42 -0
- package/dist/utils/config-merger.d.ts.map +1 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
/******/ (() => { // webpackBootstrap
|
|
2
3
|
/******/ var __webpack_modules__ = ({
|
|
3
4
|
|
|
@@ -80653,408 +80654,6 @@ exports.fromPromise = function (fn) {
|
|
|
80653
80654
|
}
|
|
80654
80655
|
|
|
80655
80656
|
|
|
80656
|
-
/***/ }),
|
|
80657
|
-
|
|
80658
|
-
/***/ 42058:
|
|
80659
|
-
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
80660
|
-
|
|
80661
|
-
"use strict";
|
|
80662
|
-
|
|
80663
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
80664
|
-
if (k2 === undefined) k2 = k;
|
|
80665
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
80666
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
80667
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
80668
|
-
}
|
|
80669
|
-
Object.defineProperty(o, k2, desc);
|
|
80670
|
-
}) : (function(o, m, k, k2) {
|
|
80671
|
-
if (k2 === undefined) k2 = k;
|
|
80672
|
-
o[k2] = m[k];
|
|
80673
|
-
}));
|
|
80674
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
80675
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
80676
|
-
}) : function(o, v) {
|
|
80677
|
-
o["default"] = v;
|
|
80678
|
-
});
|
|
80679
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
80680
|
-
var ownKeys = function(o) {
|
|
80681
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
80682
|
-
var ar = [];
|
|
80683
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
80684
|
-
return ar;
|
|
80685
|
-
};
|
|
80686
|
-
return ownKeys(o);
|
|
80687
|
-
};
|
|
80688
|
-
return function (mod) {
|
|
80689
|
-
if (mod && mod.__esModule) return mod;
|
|
80690
|
-
var result = {};
|
|
80691
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
80692
|
-
__setModuleDefault(result, mod);
|
|
80693
|
-
return result;
|
|
80694
|
-
};
|
|
80695
|
-
})();
|
|
80696
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
80697
|
-
exports.ActionCliBridge = void 0;
|
|
80698
|
-
const child_process_1 = __nccwpck_require__(35317);
|
|
80699
|
-
const fs_1 = __nccwpck_require__(79896);
|
|
80700
|
-
const path = __importStar(__nccwpck_require__(16928));
|
|
80701
|
-
/**
|
|
80702
|
-
* Bridge between GitHub Action and Visor CLI
|
|
80703
|
-
*/
|
|
80704
|
-
class ActionCliBridge {
|
|
80705
|
-
githubToken;
|
|
80706
|
-
context;
|
|
80707
|
-
constructor(githubToken, context) {
|
|
80708
|
-
this.githubToken = githubToken;
|
|
80709
|
-
this.context = context;
|
|
80710
|
-
}
|
|
80711
|
-
/**
|
|
80712
|
-
* Determine if Visor CLI should be used based on inputs
|
|
80713
|
-
*/
|
|
80714
|
-
shouldUseVisor(inputs) {
|
|
80715
|
-
return !!(inputs['config-path'] ||
|
|
80716
|
-
inputs['visor-config-path'] ||
|
|
80717
|
-
inputs.checks ||
|
|
80718
|
-
inputs['visor-checks']);
|
|
80719
|
-
}
|
|
80720
|
-
/**
|
|
80721
|
-
* Parse GitHub Action inputs to CLI arguments
|
|
80722
|
-
*/
|
|
80723
|
-
parseGitHubInputsToCliArgs(inputs) {
|
|
80724
|
-
const args = [];
|
|
80725
|
-
// Add config path if specified (prefer new input name over legacy)
|
|
80726
|
-
const configPath = inputs['config-path'] || inputs['visor-config-path'];
|
|
80727
|
-
if (configPath && configPath.trim() !== '') {
|
|
80728
|
-
args.push('--config', configPath);
|
|
80729
|
-
}
|
|
80730
|
-
// Add checks if specified (prefer new input name over legacy)
|
|
80731
|
-
const checksInput = inputs.checks || inputs['visor-checks'];
|
|
80732
|
-
if (checksInput) {
|
|
80733
|
-
const checks = checksInput
|
|
80734
|
-
.split(',')
|
|
80735
|
-
.map(check => check.trim())
|
|
80736
|
-
.filter(check => this.isValidCheck(check));
|
|
80737
|
-
// CRITICAL FIX: When "all" is specified, don't add any --check arguments
|
|
80738
|
-
// This allows CLI to extract all checks from the config file
|
|
80739
|
-
if (checks.length > 0 && !checks.includes('all')) {
|
|
80740
|
-
// Only add specific checks if "all" is not in the list
|
|
80741
|
-
for (const check of checks) {
|
|
80742
|
-
args.push('--check', check);
|
|
80743
|
-
}
|
|
80744
|
-
}
|
|
80745
|
-
// When checks includes 'all', we intentionally don't add any --check arguments
|
|
80746
|
-
// The CLI will then use all checks defined in .visor.yaml
|
|
80747
|
-
}
|
|
80748
|
-
// Add output format if specified
|
|
80749
|
-
if (inputs['output-format']) {
|
|
80750
|
-
args.push('--output', inputs['output-format']);
|
|
80751
|
-
}
|
|
80752
|
-
else {
|
|
80753
|
-
// Always use JSON output for programmatic processing
|
|
80754
|
-
args.push('--output', 'json');
|
|
80755
|
-
}
|
|
80756
|
-
// Add debug flag if enabled
|
|
80757
|
-
if (inputs.debug === 'true') {
|
|
80758
|
-
args.push('--debug');
|
|
80759
|
-
}
|
|
80760
|
-
// Add max parallelism if specified
|
|
80761
|
-
if (inputs['max-parallelism']) {
|
|
80762
|
-
args.push('--max-parallelism', inputs['max-parallelism']);
|
|
80763
|
-
}
|
|
80764
|
-
// Add fail-fast flag if enabled
|
|
80765
|
-
if (inputs['fail-fast'] === 'true') {
|
|
80766
|
-
args.push('--fail-fast');
|
|
80767
|
-
}
|
|
80768
|
-
return args;
|
|
80769
|
-
}
|
|
80770
|
-
/**
|
|
80771
|
-
* Execute CLI with GitHub context
|
|
80772
|
-
*/
|
|
80773
|
-
async executeCliWithContext(inputs, options = {}) {
|
|
80774
|
-
const { workingDir = process.cwd(), timeout = 300000 } = options; // 5 min timeout
|
|
80775
|
-
try {
|
|
80776
|
-
const cliArgs = this.parseGitHubInputsToCliArgs(inputs);
|
|
80777
|
-
// Set up environment variables for CLI
|
|
80778
|
-
const env = {
|
|
80779
|
-
...process.env,
|
|
80780
|
-
GITHUB_EVENT_NAME: this.context.event_name,
|
|
80781
|
-
GITHUB_CONTEXT: JSON.stringify(this.context),
|
|
80782
|
-
GITHUB_REPOSITORY_OWNER: this.context.repository?.owner.login || inputs.owner || '',
|
|
80783
|
-
GITHUB_REPOSITORY: this.context.repository
|
|
80784
|
-
? `${this.context.repository.owner.login}/${this.context.repository.name}`
|
|
80785
|
-
: `${inputs.owner || ''}/${inputs.repo || ''}`,
|
|
80786
|
-
};
|
|
80787
|
-
// Pass GitHub App credentials if they exist in inputs (use GitHub Actions input format)
|
|
80788
|
-
if (inputs['app-id']) {
|
|
80789
|
-
env['INPUT_APP-ID'] = inputs['app-id'];
|
|
80790
|
-
}
|
|
80791
|
-
if (inputs['private-key']) {
|
|
80792
|
-
env['INPUT_PRIVATE-KEY'] = inputs['private-key'];
|
|
80793
|
-
}
|
|
80794
|
-
if (inputs['installation-id']) {
|
|
80795
|
-
env['INPUT_INSTALLATION-ID'] = inputs['installation-id'];
|
|
80796
|
-
}
|
|
80797
|
-
// Pass GitHub token using GitHub Actions input format
|
|
80798
|
-
const isUsingGitHubApp = inputs['app-id'] && inputs['private-key'];
|
|
80799
|
-
if (this.githubToken && !isUsingGitHubApp) {
|
|
80800
|
-
env['INPUT_GITHUB-TOKEN'] = this.githubToken;
|
|
80801
|
-
}
|
|
80802
|
-
// Also pass owner and repo as inputs
|
|
80803
|
-
if (inputs.owner) {
|
|
80804
|
-
env.INPUT_OWNER = inputs.owner;
|
|
80805
|
-
}
|
|
80806
|
-
if (inputs.repo) {
|
|
80807
|
-
env.INPUT_REPO = inputs.repo;
|
|
80808
|
-
}
|
|
80809
|
-
console.log(`🚀 Executing Visor CLI with args: ${cliArgs.join(' ')}`);
|
|
80810
|
-
// Use bundled index.js for CLI execution
|
|
80811
|
-
// When running as a GitHub Action, GITHUB_ACTION_PATH points to the action's directory
|
|
80812
|
-
// The bundled index.js contains both action and CLI functionality
|
|
80813
|
-
const actionPath = process.env.GITHUB_ACTION_PATH;
|
|
80814
|
-
const bundledPath = actionPath
|
|
80815
|
-
? path.join(actionPath, 'dist', 'index.js')
|
|
80816
|
-
: path.join(__dirname, 'index.js'); // When running locally
|
|
80817
|
-
// Pass --cli flag to force CLI mode even when GITHUB_ACTIONS env var is set
|
|
80818
|
-
const result = await this.executeCommand('node', [bundledPath, '--cli', ...cliArgs], {
|
|
80819
|
-
cwd: workingDir,
|
|
80820
|
-
env,
|
|
80821
|
-
timeout,
|
|
80822
|
-
});
|
|
80823
|
-
if (result.exitCode === 0) {
|
|
80824
|
-
// Try to parse CLI output for additional data
|
|
80825
|
-
const cliOutput = this.parseCliOutput(result.output);
|
|
80826
|
-
return {
|
|
80827
|
-
success: true,
|
|
80828
|
-
output: result.output,
|
|
80829
|
-
exitCode: result.exitCode,
|
|
80830
|
-
cliOutput,
|
|
80831
|
-
};
|
|
80832
|
-
}
|
|
80833
|
-
else {
|
|
80834
|
-
return {
|
|
80835
|
-
success: false,
|
|
80836
|
-
output: result.output,
|
|
80837
|
-
error: result.error,
|
|
80838
|
-
exitCode: result.exitCode,
|
|
80839
|
-
};
|
|
80840
|
-
}
|
|
80841
|
-
}
|
|
80842
|
-
catch (error) {
|
|
80843
|
-
return {
|
|
80844
|
-
success: false,
|
|
80845
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
80846
|
-
exitCode: -1,
|
|
80847
|
-
};
|
|
80848
|
-
}
|
|
80849
|
-
}
|
|
80850
|
-
/**
|
|
80851
|
-
* Merge CLI and Action outputs for backward compatibility
|
|
80852
|
-
*/
|
|
80853
|
-
mergeActionAndCliOutputs(actionInputs, cliResult, legacyOutputs) {
|
|
80854
|
-
const outputs = {
|
|
80855
|
-
// Preserve legacy outputs if present
|
|
80856
|
-
...(legacyOutputs || {}),
|
|
80857
|
-
};
|
|
80858
|
-
if (cliResult.success && cliResult.cliOutput) {
|
|
80859
|
-
const cli = cliResult.cliOutput;
|
|
80860
|
-
if (cli.reviewScore !== undefined) {
|
|
80861
|
-
outputs['review-score'] = cli.reviewScore.toString();
|
|
80862
|
-
}
|
|
80863
|
-
if (cli.issuesFound !== undefined) {
|
|
80864
|
-
outputs['issues-found'] = cli.issuesFound.toString();
|
|
80865
|
-
}
|
|
80866
|
-
if (cli.autoReviewCompleted !== undefined) {
|
|
80867
|
-
outputs['auto-review-completed'] = cli.autoReviewCompleted.toString();
|
|
80868
|
-
}
|
|
80869
|
-
}
|
|
80870
|
-
return outputs;
|
|
80871
|
-
}
|
|
80872
|
-
/**
|
|
80873
|
-
* Execute command with timeout and proper error handling
|
|
80874
|
-
*/
|
|
80875
|
-
executeCommand(command, args, options = {}) {
|
|
80876
|
-
return new Promise((resolve, reject) => {
|
|
80877
|
-
const { cwd, env, timeout = 30000 } = options;
|
|
80878
|
-
const child = (0, child_process_1.spawn)(command, args, {
|
|
80879
|
-
cwd,
|
|
80880
|
-
env,
|
|
80881
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80882
|
-
});
|
|
80883
|
-
let output = '';
|
|
80884
|
-
let error = '';
|
|
80885
|
-
let timeoutHandle = null;
|
|
80886
|
-
if (child.stdout) {
|
|
80887
|
-
child.stdout.on('data', data => {
|
|
80888
|
-
output += data.toString();
|
|
80889
|
-
});
|
|
80890
|
-
}
|
|
80891
|
-
if (child.stderr) {
|
|
80892
|
-
child.stderr.on('data', data => {
|
|
80893
|
-
error += data.toString();
|
|
80894
|
-
});
|
|
80895
|
-
}
|
|
80896
|
-
child.on('close', code => {
|
|
80897
|
-
if (timeoutHandle) {
|
|
80898
|
-
clearTimeout(timeoutHandle);
|
|
80899
|
-
}
|
|
80900
|
-
resolve({
|
|
80901
|
-
output: output.trim(),
|
|
80902
|
-
error: error.trim(),
|
|
80903
|
-
exitCode: code || 0,
|
|
80904
|
-
});
|
|
80905
|
-
});
|
|
80906
|
-
child.on('error', err => {
|
|
80907
|
-
if (timeoutHandle) {
|
|
80908
|
-
clearTimeout(timeoutHandle);
|
|
80909
|
-
}
|
|
80910
|
-
reject(new Error(`Command execution failed: ${err.message}`));
|
|
80911
|
-
});
|
|
80912
|
-
// Set timeout if specified
|
|
80913
|
-
if (timeout > 0) {
|
|
80914
|
-
timeoutHandle = setTimeout(() => {
|
|
80915
|
-
child.kill('SIGTERM');
|
|
80916
|
-
reject(new Error(`Command execution timed out after ${timeout}ms`));
|
|
80917
|
-
}, timeout);
|
|
80918
|
-
}
|
|
80919
|
-
});
|
|
80920
|
-
}
|
|
80921
|
-
/**
|
|
80922
|
-
* Parse CLI JSON output to extract relevant data
|
|
80923
|
-
*/
|
|
80924
|
-
parseCliOutput(output) {
|
|
80925
|
-
try {
|
|
80926
|
-
// Look for JSON output in the CLI result
|
|
80927
|
-
const lines = output.split('\n');
|
|
80928
|
-
for (const line of lines) {
|
|
80929
|
-
const trimmed = line.trim();
|
|
80930
|
-
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
80931
|
-
const parsed = JSON.parse(trimmed);
|
|
80932
|
-
// Extract relevant data that can be used for Action outputs
|
|
80933
|
-
return {
|
|
80934
|
-
reviewScore: parsed.reviewScore || parsed.overallScore,
|
|
80935
|
-
issuesFound: parsed.issuesFound || parsed.totalIssues,
|
|
80936
|
-
autoReviewCompleted: parsed.autoReviewCompleted || false,
|
|
80937
|
-
};
|
|
80938
|
-
}
|
|
80939
|
-
}
|
|
80940
|
-
return {};
|
|
80941
|
-
}
|
|
80942
|
-
catch {
|
|
80943
|
-
console.log('Could not parse CLI output as JSON, using default values');
|
|
80944
|
-
return {};
|
|
80945
|
-
}
|
|
80946
|
-
}
|
|
80947
|
-
/**
|
|
80948
|
-
* Check if a check type is valid
|
|
80949
|
-
*/
|
|
80950
|
-
isValidCheck(check) {
|
|
80951
|
-
const validChecks = ['performance', 'architecture', 'security', 'style', 'all'];
|
|
80952
|
-
return validChecks.includes(check);
|
|
80953
|
-
}
|
|
80954
|
-
/**
|
|
80955
|
-
* Create temporary config file from action inputs
|
|
80956
|
-
*/
|
|
80957
|
-
async createTempConfigFromInputs(inputs, options = {}) {
|
|
80958
|
-
const { workingDir = process.cwd() } = options;
|
|
80959
|
-
if (!inputs['visor-checks']) {
|
|
80960
|
-
return null;
|
|
80961
|
-
}
|
|
80962
|
-
const checks = inputs['visor-checks']
|
|
80963
|
-
.split(',')
|
|
80964
|
-
.map(check => check.trim())
|
|
80965
|
-
.filter(check => this.isValidCheck(check));
|
|
80966
|
-
if (checks.length === 0) {
|
|
80967
|
-
return null;
|
|
80968
|
-
}
|
|
80969
|
-
// Create a basic Visor config from the checks
|
|
80970
|
-
const config = {
|
|
80971
|
-
version: '1.0',
|
|
80972
|
-
checks: {},
|
|
80973
|
-
output: {
|
|
80974
|
-
pr_comment: {
|
|
80975
|
-
format: 'markdown',
|
|
80976
|
-
group_by: 'check',
|
|
80977
|
-
collapse: true,
|
|
80978
|
-
},
|
|
80979
|
-
},
|
|
80980
|
-
};
|
|
80981
|
-
// Map GitHub Action checks to Visor config format
|
|
80982
|
-
for (const check of checks) {
|
|
80983
|
-
const checkName = `${check}-check`;
|
|
80984
|
-
config.checks[checkName] = {
|
|
80985
|
-
type: 'ai',
|
|
80986
|
-
prompt: this.getPromptForCheck(check),
|
|
80987
|
-
on: ['pr_opened', 'pr_updated'],
|
|
80988
|
-
};
|
|
80989
|
-
}
|
|
80990
|
-
// Write temporary config file
|
|
80991
|
-
const tempConfigPath = path.join(workingDir, '.visor-temp.yaml');
|
|
80992
|
-
try {
|
|
80993
|
-
const yaml = __nccwpck_require__(74281);
|
|
80994
|
-
const yamlContent = yaml.dump(config);
|
|
80995
|
-
await fs_1.promises.writeFile(tempConfigPath, yamlContent, 'utf8');
|
|
80996
|
-
return tempConfigPath;
|
|
80997
|
-
}
|
|
80998
|
-
catch (error) {
|
|
80999
|
-
console.error('Failed to create temporary config file:', error);
|
|
81000
|
-
return null;
|
|
81001
|
-
}
|
|
81002
|
-
}
|
|
81003
|
-
/**
|
|
81004
|
-
* Get AI prompt for a specific check type
|
|
81005
|
-
*/
|
|
81006
|
-
getPromptForCheck(check) {
|
|
81007
|
-
const prompts = {
|
|
81008
|
-
security: `Review this code for security vulnerabilities, focusing on:
|
|
81009
|
-
- SQL injection, XSS, CSRF vulnerabilities
|
|
81010
|
-
- Authentication and authorization flaws
|
|
81011
|
-
- Sensitive data exposure
|
|
81012
|
-
- Input validation issues
|
|
81013
|
-
- Cryptographic weaknesses`,
|
|
81014
|
-
performance: `Analyze this code for performance issues, focusing on:
|
|
81015
|
-
- Database query efficiency (N+1 problems, missing indexes)
|
|
81016
|
-
- Memory usage and potential leaks
|
|
81017
|
-
- Algorithmic complexity issues
|
|
81018
|
-
- Caching opportunities
|
|
81019
|
-
- Resource utilization`,
|
|
81020
|
-
architecture: `Review the architectural aspects of this code, focusing on:
|
|
81021
|
-
- Design patterns and code organization
|
|
81022
|
-
- Separation of concerns
|
|
81023
|
-
- SOLID principles adherence
|
|
81024
|
-
- Code maintainability and extensibility
|
|
81025
|
-
- Technical debt`,
|
|
81026
|
-
style: `Review code style and maintainability, focusing on:
|
|
81027
|
-
- Consistent naming conventions
|
|
81028
|
-
- Code formatting and readability
|
|
81029
|
-
- Documentation quality
|
|
81030
|
-
- Error handling patterns
|
|
81031
|
-
- Code complexity`,
|
|
81032
|
-
all: `Perform a comprehensive code review covering:
|
|
81033
|
-
- Security vulnerabilities and best practices
|
|
81034
|
-
- Performance optimization opportunities
|
|
81035
|
-
- Architectural improvements
|
|
81036
|
-
- Code style and maintainability
|
|
81037
|
-
- Documentation and testing coverage`,
|
|
81038
|
-
};
|
|
81039
|
-
return prompts[check];
|
|
81040
|
-
}
|
|
81041
|
-
/**
|
|
81042
|
-
* Cleanup temporary files
|
|
81043
|
-
*/
|
|
81044
|
-
async cleanup(options = {}) {
|
|
81045
|
-
const { workingDir = process.cwd() } = options;
|
|
81046
|
-
const tempConfigPath = path.join(workingDir, '.visor-temp.yaml');
|
|
81047
|
-
try {
|
|
81048
|
-
await fs_1.promises.unlink(tempConfigPath);
|
|
81049
|
-
}
|
|
81050
|
-
catch {
|
|
81051
|
-
// Ignore cleanup errors
|
|
81052
|
-
}
|
|
81053
|
-
}
|
|
81054
|
-
}
|
|
81055
|
-
exports.ActionCliBridge = ActionCliBridge;
|
|
81056
|
-
|
|
81057
|
-
|
|
81058
80657
|
/***/ }),
|
|
81059
80658
|
|
|
81060
80659
|
/***/ 51796:
|
|
@@ -83665,7 +83264,8 @@ async function main() {
|
|
|
83665
83264
|
const cli = new cli_1.CLI();
|
|
83666
83265
|
const configManager = new config_1.ConfigManager();
|
|
83667
83266
|
// Check for help flag before parsing
|
|
83668
|
-
|
|
83267
|
+
// Filter out --cli flag which is only used to force CLI mode in GitHub Actions
|
|
83268
|
+
const args = process.argv.slice(2).filter(arg => arg !== '--cli');
|
|
83669
83269
|
if (args.includes('--help') || args.includes('-h')) {
|
|
83670
83270
|
console.log(cli.getHelpText());
|
|
83671
83271
|
process.exit(0);
|
|
@@ -83680,7 +83280,9 @@ async function main() {
|
|
|
83680
83280
|
let config;
|
|
83681
83281
|
if (cliOptions.configPath) {
|
|
83682
83282
|
try {
|
|
83683
|
-
config = await configManager.loadConfig(cliOptions.configPath
|
|
83283
|
+
config = await configManager.loadConfig(cliOptions.configPath, {
|
|
83284
|
+
allowedRemotePatterns: cliOptions.allowedRemotePatterns,
|
|
83285
|
+
});
|
|
83684
83286
|
}
|
|
83685
83287
|
catch (error) {
|
|
83686
83288
|
console.error(`⚠️ Warning: ${error instanceof Error ? error.message : 'Configuration file not found'}`);
|
|
@@ -83689,7 +83291,9 @@ async function main() {
|
|
|
83689
83291
|
}
|
|
83690
83292
|
}
|
|
83691
83293
|
else {
|
|
83692
|
-
config = await configManager.findAndLoadConfig(
|
|
83294
|
+
config = await configManager.findAndLoadConfig({
|
|
83295
|
+
allowedRemotePatterns: cliOptions.allowedRemotePatterns,
|
|
83296
|
+
});
|
|
83693
83297
|
}
|
|
83694
83298
|
// Merge CLI options with configuration
|
|
83695
83299
|
const mergedConfig = configManager.mergeWithCliOptions(config, cliOptions);
|
|
@@ -83964,6 +83568,7 @@ class CLI {
|
|
|
83964
83568
|
.option('--max-parallelism <count>', 'Maximum number of checks to run in parallel (default: 3)', value => parseInt(value, 10))
|
|
83965
83569
|
.option('--debug', 'Enable debug mode for detailed output')
|
|
83966
83570
|
.option('--fail-fast', 'Stop execution on first failure condition')
|
|
83571
|
+
.option('--no-remote-extends', 'Disable loading configurations from remote URLs')
|
|
83967
83572
|
.addHelpText('after', this.getExamplesText())
|
|
83968
83573
|
.exitOverride(); // Prevent automatic process.exit for better error handling
|
|
83969
83574
|
// Add validation for options
|
|
@@ -83996,6 +83601,7 @@ class CLI {
|
|
|
83996
83601
|
.option('--max-parallelism <count>', 'Maximum number of checks to run in parallel (default: 3)', value => parseInt(value, 10))
|
|
83997
83602
|
.option('--debug', 'Enable debug mode for detailed output')
|
|
83998
83603
|
.option('--fail-fast', 'Stop execution on first failure condition')
|
|
83604
|
+
.option('--allowed-remote-patterns <patterns>', 'Comma-separated list of allowed URL prefixes for remote config extends (e.g., "https://github.com/,https://raw.githubusercontent.com/")')
|
|
83999
83605
|
.allowUnknownOption(false)
|
|
84000
83606
|
.allowExcessArguments(false) // Don't allow positional arguments
|
|
84001
83607
|
.addHelpText('after', this.getExamplesText())
|
|
@@ -84006,6 +83612,17 @@ class CLI {
|
|
|
84006
83612
|
this.validateOptions(options);
|
|
84007
83613
|
// Remove duplicates and preserve order
|
|
84008
83614
|
const uniqueChecks = [...new Set(options.check)];
|
|
83615
|
+
// Set environment variable if no-remote-extends is set
|
|
83616
|
+
if (options.noRemoteExtends) {
|
|
83617
|
+
process.env.VISOR_NO_REMOTE_EXTENDS = 'true';
|
|
83618
|
+
}
|
|
83619
|
+
// Parse allowed remote patterns if provided
|
|
83620
|
+
let allowedRemotePatterns;
|
|
83621
|
+
if (options.allowedRemotePatterns) {
|
|
83622
|
+
allowedRemotePatterns = options.allowedRemotePatterns
|
|
83623
|
+
.split(',')
|
|
83624
|
+
.map((p) => p.trim());
|
|
83625
|
+
}
|
|
84009
83626
|
return {
|
|
84010
83627
|
checks: uniqueChecks,
|
|
84011
83628
|
output: options.output,
|
|
@@ -84014,6 +83631,7 @@ class CLI {
|
|
|
84014
83631
|
maxParallelism: options.maxParallelism,
|
|
84015
83632
|
debug: options.debug,
|
|
84016
83633
|
failFast: options.failFast,
|
|
83634
|
+
allowedRemotePatterns,
|
|
84017
83635
|
help: options.help,
|
|
84018
83636
|
version: options.version,
|
|
84019
83637
|
};
|
|
@@ -84250,6 +83868,8 @@ const yaml = __importStar(__nccwpck_require__(74281));
|
|
|
84250
83868
|
const fs = __importStar(__nccwpck_require__(79896));
|
|
84251
83869
|
const path = __importStar(__nccwpck_require__(16928));
|
|
84252
83870
|
const simple_git_1 = __importDefault(__nccwpck_require__(59065));
|
|
83871
|
+
const config_loader_1 = __nccwpck_require__(73836);
|
|
83872
|
+
const config_merger_1 = __nccwpck_require__(20730);
|
|
84253
83873
|
/**
|
|
84254
83874
|
* Configuration manager for Visor
|
|
84255
83875
|
*/
|
|
@@ -84269,7 +83889,7 @@ class ConfigManager {
|
|
|
84269
83889
|
* Load configuration from a file
|
|
84270
83890
|
*/
|
|
84271
83891
|
async loadConfig(configPath, options = {}) {
|
|
84272
|
-
const { validate = true, mergeDefaults = true } = options;
|
|
83892
|
+
const { validate = true, mergeDefaults = true, allowedRemotePatterns } = options;
|
|
84273
83893
|
try {
|
|
84274
83894
|
if (!fs.existsSync(configPath)) {
|
|
84275
83895
|
throw new Error(`Configuration file not found: ${configPath}`);
|
|
@@ -84286,6 +83906,34 @@ class ConfigManager {
|
|
|
84286
83906
|
if (!parsedConfig || typeof parsedConfig !== 'object') {
|
|
84287
83907
|
throw new Error('Configuration file must contain a valid YAML object');
|
|
84288
83908
|
}
|
|
83909
|
+
// Handle extends directive if present
|
|
83910
|
+
if (parsedConfig.extends) {
|
|
83911
|
+
const loaderOptions = {
|
|
83912
|
+
baseDir: path.dirname(configPath),
|
|
83913
|
+
allowRemote: this.isRemoteExtendsAllowed(),
|
|
83914
|
+
maxDepth: 10,
|
|
83915
|
+
allowedRemotePatterns,
|
|
83916
|
+
};
|
|
83917
|
+
const loader = new config_loader_1.ConfigLoader(loaderOptions);
|
|
83918
|
+
const merger = new config_merger_1.ConfigMerger();
|
|
83919
|
+
// Process extends
|
|
83920
|
+
const extends_ = Array.isArray(parsedConfig.extends)
|
|
83921
|
+
? parsedConfig.extends
|
|
83922
|
+
: [parsedConfig.extends];
|
|
83923
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
83924
|
+
const { extends: _extendsField, ...configWithoutExtends } = parsedConfig;
|
|
83925
|
+
// Load and merge all parent configurations
|
|
83926
|
+
let mergedConfig = {};
|
|
83927
|
+
for (const source of extends_) {
|
|
83928
|
+
console.log(`📦 Extending from: ${source}`);
|
|
83929
|
+
const parentConfig = await loader.fetchConfig(source);
|
|
83930
|
+
mergedConfig = merger.merge(mergedConfig, parentConfig);
|
|
83931
|
+
}
|
|
83932
|
+
// Merge with current config (child overrides parent)
|
|
83933
|
+
parsedConfig = merger.merge(mergedConfig, configWithoutExtends);
|
|
83934
|
+
// Remove disabled checks (those with empty 'on' array)
|
|
83935
|
+
parsedConfig = merger.removeDisabledChecks(parsedConfig);
|
|
83936
|
+
}
|
|
84289
83937
|
if (validate) {
|
|
84290
83938
|
this.validateConfig(parsedConfig);
|
|
84291
83939
|
}
|
|
@@ -84308,7 +83956,7 @@ class ConfigManager {
|
|
|
84308
83956
|
/**
|
|
84309
83957
|
* Find and load configuration from default locations
|
|
84310
83958
|
*/
|
|
84311
|
-
async findAndLoadConfig() {
|
|
83959
|
+
async findAndLoadConfig(options = {}) {
|
|
84312
83960
|
// Try to find the git repository root first, fall back to current directory
|
|
84313
83961
|
const gitRoot = await this.findGitRepositoryRoot();
|
|
84314
83962
|
const searchDirs = [gitRoot, process.cwd()].filter(Boolean);
|
|
@@ -84316,7 +83964,7 @@ class ConfigManager {
|
|
|
84316
83964
|
const possiblePaths = [path.join(baseDir, '.visor.yaml'), path.join(baseDir, '.visor.yml')];
|
|
84317
83965
|
for (const configPath of possiblePaths) {
|
|
84318
83966
|
if (fs.existsSync(configPath)) {
|
|
84319
|
-
return this.loadConfig(configPath);
|
|
83967
|
+
return this.loadConfig(configPath, options);
|
|
84320
83968
|
}
|
|
84321
83969
|
}
|
|
84322
83970
|
}
|
|
@@ -84611,6 +84259,18 @@ class ConfigManager {
|
|
|
84611
84259
|
}
|
|
84612
84260
|
}
|
|
84613
84261
|
}
|
|
84262
|
+
/**
|
|
84263
|
+
* Check if remote extends are allowed
|
|
84264
|
+
*/
|
|
84265
|
+
isRemoteExtendsAllowed() {
|
|
84266
|
+
// Check environment variable first
|
|
84267
|
+
if (process.env.VISOR_NO_REMOTE_EXTENDS === 'true' ||
|
|
84268
|
+
process.env.VISOR_NO_REMOTE_EXTENDS === '1') {
|
|
84269
|
+
return false;
|
|
84270
|
+
}
|
|
84271
|
+
// Default to allowing remote extends
|
|
84272
|
+
return true;
|
|
84273
|
+
}
|
|
84614
84274
|
/**
|
|
84615
84275
|
* Merge configuration with default values
|
|
84616
84276
|
*/
|
|
@@ -85834,7 +85494,9 @@ class GitHubCheckService {
|
|
|
85834
85494
|
});
|
|
85835
85495
|
}
|
|
85836
85496
|
// Footer
|
|
85497
|
+
sections.push('');
|
|
85837
85498
|
sections.push('---');
|
|
85499
|
+
sections.push('');
|
|
85838
85500
|
sections.push('*Generated by [Visor](https://github.com/probelabs/visor) - AI-powered code review*');
|
|
85839
85501
|
return sections.join('\n');
|
|
85840
85502
|
}
|
|
@@ -86274,6 +85936,9 @@ exports.CommentManager = CommentManager;
|
|
|
86274
85936
|
|
|
86275
85937
|
"use strict";
|
|
86276
85938
|
|
|
85939
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
85940
|
+
// GitHub event objects have complex dynamic structures that are difficult to fully type
|
|
85941
|
+
// Using 'any' for these objects is acceptable as they come from external GitHub webhooks
|
|
86277
85942
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
86278
85943
|
if (k2 === undefined) k2 = k;
|
|
86279
85944
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -86312,10 +85977,10 @@ exports.run = run;
|
|
|
86312
85977
|
const rest_1 = __nccwpck_require__(47432);
|
|
86313
85978
|
const auth_app_1 = __nccwpck_require__(76479);
|
|
86314
85979
|
const core_1 = __nccwpck_require__(37484);
|
|
85980
|
+
const path = __importStar(__nccwpck_require__(16928));
|
|
86315
85981
|
const commands_1 = __nccwpck_require__(99153);
|
|
86316
85982
|
const pr_analyzer_1 = __nccwpck_require__(80100);
|
|
86317
85983
|
const reviewer_1 = __nccwpck_require__(532);
|
|
86318
|
-
const action_cli_bridge_1 = __nccwpck_require__(42058);
|
|
86319
85984
|
const config_1 = __nccwpck_require__(22973);
|
|
86320
85985
|
const github_check_service_1 = __nccwpck_require__(21367);
|
|
86321
85986
|
/**
|
|
@@ -86396,11 +86061,9 @@ async function run() {
|
|
|
86396
86061
|
try {
|
|
86397
86062
|
const { octokit, authType } = await createAuthenticatedOctokit();
|
|
86398
86063
|
console.log(`✅ Authenticated successfully using ${authType}`);
|
|
86399
|
-
// Get token for passing to CLI bridge (might be undefined if using App auth)
|
|
86400
|
-
const token = (0, core_1.getInput)('github-token') || '';
|
|
86401
86064
|
// Collect all GitHub Action inputs
|
|
86402
86065
|
const inputs = {
|
|
86403
|
-
'github-token': token,
|
|
86066
|
+
'github-token': (0, core_1.getInput)('github-token') || '',
|
|
86404
86067
|
owner: (0, core_1.getInput)('owner') || process.env.GITHUB_REPOSITORY_OWNER,
|
|
86405
86068
|
repo: (0, core_1.getInput)('repo') || process.env.GITHUB_REPOSITORY?.split('/')[1],
|
|
86406
86069
|
debug: (0, core_1.getInput)('debug'),
|
|
@@ -86419,11 +86082,25 @@ async function run() {
|
|
|
86419
86082
|
'fail-on-api-error': (0, core_1.getInput)('fail-on-api-error') || undefined,
|
|
86420
86083
|
'min-score': (0, core_1.getInput)('min-score') || undefined,
|
|
86421
86084
|
'max-parallelism': (0, core_1.getInput)('max-parallelism') || undefined,
|
|
86085
|
+
'ai-provider': (0, core_1.getInput)('ai-provider') || undefined,
|
|
86086
|
+
'ai-model': (0, core_1.getInput)('ai-model') || undefined,
|
|
86422
86087
|
// Legacy inputs for backward compatibility
|
|
86423
86088
|
'visor-config-path': (0, core_1.getInput)('visor-config-path') || undefined,
|
|
86424
86089
|
'visor-checks': (0, core_1.getInput)('visor-checks') || undefined,
|
|
86425
86090
|
};
|
|
86426
86091
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
86092
|
+
// Load GitHub event data from event file
|
|
86093
|
+
let eventData = {};
|
|
86094
|
+
if (process.env.GITHUB_EVENT_PATH) {
|
|
86095
|
+
try {
|
|
86096
|
+
const fs = await Promise.resolve().then(() => __importStar(__nccwpck_require__(79896)));
|
|
86097
|
+
const eventContent = fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8');
|
|
86098
|
+
eventData = JSON.parse(eventContent);
|
|
86099
|
+
}
|
|
86100
|
+
catch (error) {
|
|
86101
|
+
console.error('Failed to load GitHub event data:', error);
|
|
86102
|
+
}
|
|
86103
|
+
}
|
|
86427
86104
|
// Create GitHub context for CLI bridge
|
|
86428
86105
|
const context = {
|
|
86429
86106
|
event_name: eventName || 'unknown',
|
|
@@ -86433,51 +86110,57 @@ async function run() {
|
|
|
86433
86110
|
name: process.env.GITHUB_REPOSITORY.split('/')[1],
|
|
86434
86111
|
}
|
|
86435
86112
|
: undefined,
|
|
86436
|
-
event:
|
|
86437
|
-
payload:
|
|
86113
|
+
event: eventData,
|
|
86114
|
+
payload: eventData,
|
|
86438
86115
|
};
|
|
86439
|
-
//
|
|
86440
|
-
const cliBridge = new action_cli_bridge_1.ActionCliBridge(token, context);
|
|
86441
|
-
// Check if we should use Visor CLI
|
|
86116
|
+
// Debug logging for inputs
|
|
86442
86117
|
console.log('Debug: inputs.debug =', inputs.debug);
|
|
86443
86118
|
console.log('Debug: inputs.checks =', inputs.checks);
|
|
86444
86119
|
console.log('Debug: inputs.config-path =', inputs['config-path']);
|
|
86445
86120
|
console.log('Debug: inputs.visor-checks =', inputs['visor-checks']);
|
|
86446
86121
|
console.log('Debug: inputs.visor-config-path =', inputs['visor-config-path']);
|
|
86447
|
-
|
|
86448
|
-
|
|
86449
|
-
// Instead of spawning subprocess, directly run CLI logic
|
|
86450
|
-
const cliArgs = cliBridge.parseGitHubInputsToCliArgs(inputs);
|
|
86451
|
-
// Set argv for CLI parsing
|
|
86452
|
-
process.argv = ['node', 'visor', ...cliArgs];
|
|
86453
|
-
// Import and run CLI directly
|
|
86454
|
-
const { main } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(10091)));
|
|
86455
|
-
await main();
|
|
86456
|
-
return;
|
|
86457
|
-
}
|
|
86458
|
-
// Default behavior: Use Visor config to determine what to run
|
|
86122
|
+
// Always use config-driven mode in GitHub Actions
|
|
86123
|
+
// The CLI mode is only for local development, not for GitHub Actions
|
|
86459
86124
|
console.log('🤖 Using config-driven mode');
|
|
86460
86125
|
// Load config to determine which checks should run for this event
|
|
86461
86126
|
const configManager = new config_1.ConfigManager();
|
|
86462
86127
|
let config;
|
|
86128
|
+
// First try to load user config, then fall back to defaults/.visor.yaml
|
|
86129
|
+
const configPath = inputs['config-path'] || inputs['visor-config-path'];
|
|
86463
86130
|
try {
|
|
86464
|
-
|
|
86465
|
-
|
|
86131
|
+
if (configPath) {
|
|
86132
|
+
// Load specified config
|
|
86133
|
+
config = await configManager.loadConfig(configPath);
|
|
86134
|
+
console.log(`📋 Loaded config from: ${configPath}`);
|
|
86135
|
+
}
|
|
86136
|
+
else {
|
|
86137
|
+
// Try to find config in project
|
|
86138
|
+
config = await configManager.findAndLoadConfig();
|
|
86139
|
+
console.log('📋 Loaded Visor config from project');
|
|
86140
|
+
}
|
|
86466
86141
|
}
|
|
86467
86142
|
catch {
|
|
86468
|
-
//
|
|
86469
|
-
|
|
86470
|
-
|
|
86471
|
-
|
|
86472
|
-
|
|
86473
|
-
|
|
86474
|
-
|
|
86475
|
-
|
|
86476
|
-
|
|
86143
|
+
// Fall back to bundled default config
|
|
86144
|
+
try {
|
|
86145
|
+
const defaultConfigPath = path.join(process.env.GITHUB_ACTION_PATH || __dirname, 'defaults', '.visor.yaml');
|
|
86146
|
+
config = await configManager.loadConfig(defaultConfigPath);
|
|
86147
|
+
console.log('📋 Using bundled default configuration from defaults/.visor.yaml');
|
|
86148
|
+
}
|
|
86149
|
+
catch {
|
|
86150
|
+
// Ultimate fallback if even defaults/.visor.yaml can't be loaded
|
|
86151
|
+
config = {
|
|
86152
|
+
version: '1.0',
|
|
86153
|
+
checks: {},
|
|
86154
|
+
output: {
|
|
86155
|
+
pr_comment: {
|
|
86156
|
+
format: 'markdown',
|
|
86157
|
+
group_by: 'check',
|
|
86158
|
+
collapse: false,
|
|
86159
|
+
},
|
|
86477
86160
|
},
|
|
86478
|
-
}
|
|
86479
|
-
|
|
86480
|
-
|
|
86161
|
+
};
|
|
86162
|
+
console.log('⚠️ Could not load defaults/.visor.yaml, using minimal configuration');
|
|
86163
|
+
}
|
|
86481
86164
|
}
|
|
86482
86165
|
// Determine which event we're handling and run appropriate checks
|
|
86483
86166
|
await handleEvent(octokit, inputs, eventName, context, config);
|
|
@@ -86486,58 +86169,6 @@ async function run() {
|
|
|
86486
86169
|
(0, core_1.setFailed)(error instanceof Error ? error.message : 'Unknown error');
|
|
86487
86170
|
}
|
|
86488
86171
|
}
|
|
86489
|
-
/**
|
|
86490
|
-
* Handle Visor CLI mode
|
|
86491
|
-
*/
|
|
86492
|
-
async function handleVisorMode(cliBridge, inputs, _context, _octokit) {
|
|
86493
|
-
try {
|
|
86494
|
-
// Note: PR auto-review cases are now handled upstream in the main run() function
|
|
86495
|
-
// Execute CLI with the provided config file (no temp config creation)
|
|
86496
|
-
const result = await cliBridge.executeCliWithContext(inputs);
|
|
86497
|
-
if (result.success) {
|
|
86498
|
-
console.log('✅ Visor CLI execution completed successfully');
|
|
86499
|
-
// Parse JSON output for PR comment creation
|
|
86500
|
-
let cliOutput;
|
|
86501
|
-
try {
|
|
86502
|
-
// Extract JSON from CLI output
|
|
86503
|
-
const outputLines = result.output?.split('\n') || [];
|
|
86504
|
-
const jsonLine = outputLines.find(line => line.trim().startsWith('{') && line.trim().endsWith('}'));
|
|
86505
|
-
if (jsonLine) {
|
|
86506
|
-
cliOutput = JSON.parse(jsonLine);
|
|
86507
|
-
console.log('📊 CLI Review Results:', cliOutput);
|
|
86508
|
-
// Note: PR comment posting is now handled by handlePullRequestVisorMode for PR events
|
|
86509
|
-
// CLI mode output is intended for non-PR scenarios
|
|
86510
|
-
}
|
|
86511
|
-
else {
|
|
86512
|
-
console.log('📄 CLI Output (non-JSON):', result.output);
|
|
86513
|
-
}
|
|
86514
|
-
}
|
|
86515
|
-
catch (parseError) {
|
|
86516
|
-
console.log('⚠️ Could not parse CLI output as JSON:', parseError);
|
|
86517
|
-
console.log('📄 Raw CLI Output:', result.output);
|
|
86518
|
-
}
|
|
86519
|
-
// Set outputs based on CLI result
|
|
86520
|
-
const outputs = cliBridge.mergeActionAndCliOutputs(inputs, result);
|
|
86521
|
-
// Add additional outputs from parsed JSON
|
|
86522
|
-
if (cliOutput) {
|
|
86523
|
-
outputs['total-issues'] = cliOutput.totalIssues?.toString() || '0';
|
|
86524
|
-
outputs['critical-issues'] = cliOutput.criticalIssues?.toString() || '0';
|
|
86525
|
-
}
|
|
86526
|
-
for (const [key, value] of Object.entries(outputs)) {
|
|
86527
|
-
(0, core_1.setOutput)(key, value);
|
|
86528
|
-
}
|
|
86529
|
-
}
|
|
86530
|
-
else {
|
|
86531
|
-
console.error('❌ Visor CLI execution failed');
|
|
86532
|
-
console.error(result.error || result.output);
|
|
86533
|
-
(0, core_1.setFailed)(result.error || 'CLI execution failed');
|
|
86534
|
-
}
|
|
86535
|
-
}
|
|
86536
|
-
catch (error) {
|
|
86537
|
-
console.error('❌ Visor mode error:', error);
|
|
86538
|
-
(0, core_1.setFailed)(error instanceof Error ? error.message : 'Visor mode failed');
|
|
86539
|
-
}
|
|
86540
|
-
}
|
|
86541
86172
|
function mapGitHubEventToTrigger(eventName, action) {
|
|
86542
86173
|
if (!eventName)
|
|
86543
86174
|
return 'pr_updated';
|
|
@@ -86571,14 +86202,53 @@ async function handleEvent(octokit, inputs, eventName, context, config) {
|
|
|
86571
86202
|
// Map GitHub event to our event trigger format
|
|
86572
86203
|
const eventType = mapGitHubEventToTrigger(eventName, context.event?.action);
|
|
86573
86204
|
// Find checks that should run for this event
|
|
86574
|
-
|
|
86205
|
+
let checksToRun = [];
|
|
86206
|
+
// First, get all checks that are configured for this event type
|
|
86207
|
+
const eventChecks = [];
|
|
86575
86208
|
for (const [checkName, checkConfig] of Object.entries(config.checks || {})) {
|
|
86576
86209
|
// Check if this check should run for this event
|
|
86577
86210
|
const checkEvents = checkConfig.on || ['pr_opened', 'pr_updated'];
|
|
86578
86211
|
if (checkEvents.includes(eventType)) {
|
|
86579
|
-
|
|
86212
|
+
eventChecks.push(checkName);
|
|
86213
|
+
}
|
|
86214
|
+
}
|
|
86215
|
+
// Now apply the 'checks' input filter if provided
|
|
86216
|
+
const checksInput = inputs.checks || inputs['visor-checks'];
|
|
86217
|
+
if (checksInput && checksInput.trim() !== '') {
|
|
86218
|
+
const requestedChecks = checksInput.split(',').map(c => c.trim());
|
|
86219
|
+
if (requestedChecks.includes('all')) {
|
|
86220
|
+
// If 'all' is specified, run all event checks
|
|
86221
|
+
checksToRun = eventChecks;
|
|
86222
|
+
console.log('📋 Running all available checks for this event');
|
|
86223
|
+
}
|
|
86224
|
+
else {
|
|
86225
|
+
// Filter to only the requested checks that are also configured for this event
|
|
86226
|
+
// Map simplified check names to actual config check names if needed
|
|
86227
|
+
for (const requested of requestedChecks) {
|
|
86228
|
+
// Try exact match first
|
|
86229
|
+
if (eventChecks.includes(requested)) {
|
|
86230
|
+
checksToRun.push(requested);
|
|
86231
|
+
}
|
|
86232
|
+
else {
|
|
86233
|
+
// Try with '-check' suffix (e.g., 'security' -> 'security-check')
|
|
86234
|
+
const withSuffix = `${requested}-check`;
|
|
86235
|
+
if (eventChecks.includes(withSuffix)) {
|
|
86236
|
+
checksToRun.push(withSuffix);
|
|
86237
|
+
}
|
|
86238
|
+
else {
|
|
86239
|
+
// Try to find any check that contains the requested string
|
|
86240
|
+
const matching = eventChecks.filter(check => check.toLowerCase().includes(requested.toLowerCase()));
|
|
86241
|
+
checksToRun.push(...matching);
|
|
86242
|
+
}
|
|
86243
|
+
}
|
|
86244
|
+
}
|
|
86245
|
+
console.log(`📋 Running requested checks: ${requestedChecks.join(', ')}`);
|
|
86580
86246
|
}
|
|
86581
86247
|
}
|
|
86248
|
+
else {
|
|
86249
|
+
// No checks input provided, run all event checks
|
|
86250
|
+
checksToRun = eventChecks;
|
|
86251
|
+
}
|
|
86582
86252
|
if (checksToRun.length === 0) {
|
|
86583
86253
|
console.log(`ℹ️ No checks configured to run for event: ${eventType}`);
|
|
86584
86254
|
return;
|
|
@@ -86587,11 +86257,15 @@ async function handleEvent(octokit, inputs, eventName, context, config) {
|
|
|
86587
86257
|
// Handle different GitHub events
|
|
86588
86258
|
switch (eventName) {
|
|
86589
86259
|
case 'issue_comment':
|
|
86590
|
-
await handleIssueComment(octokit, owner, repo);
|
|
86260
|
+
await handleIssueComment(octokit, owner, repo, context, inputs, config, checksToRun);
|
|
86591
86261
|
break;
|
|
86592
86262
|
case 'pull_request':
|
|
86593
86263
|
// Run the checks that are configured for this event
|
|
86594
|
-
await handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun);
|
|
86264
|
+
await handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun, context);
|
|
86265
|
+
break;
|
|
86266
|
+
case 'issues':
|
|
86267
|
+
// Handle issue events (opened, closed, etc)
|
|
86268
|
+
await handleIssueEvent(octokit, owner, repo, context, inputs, config, checksToRun);
|
|
86595
86269
|
break;
|
|
86596
86270
|
case 'push':
|
|
86597
86271
|
// Could handle push events that are associated with PRs
|
|
@@ -86635,8 +86309,81 @@ function resolveDependencies(checkIds, config, resolved = new Set(), visiting =
|
|
|
86635
86309
|
}
|
|
86636
86310
|
return result;
|
|
86637
86311
|
}
|
|
86638
|
-
|
|
86639
|
-
|
|
86312
|
+
/**
|
|
86313
|
+
* Handle issue events (opened, edited, etc)
|
|
86314
|
+
*/
|
|
86315
|
+
async function handleIssueEvent(octokit, owner, repo, context, inputs, config, checksToRun) {
|
|
86316
|
+
const issue = context.event?.issue;
|
|
86317
|
+
const action = context.event?.action;
|
|
86318
|
+
if (!issue) {
|
|
86319
|
+
console.log('No issue found in context');
|
|
86320
|
+
return;
|
|
86321
|
+
}
|
|
86322
|
+
// Skip if this is a pull request (has pull_request property)
|
|
86323
|
+
if (issue.pull_request) {
|
|
86324
|
+
console.log('Skipping PR-related issue event');
|
|
86325
|
+
return;
|
|
86326
|
+
}
|
|
86327
|
+
console.log(`Processing issue #${issue.number} event: ${action} with checks: ${checksToRun.join(', ')}`);
|
|
86328
|
+
// For issue events, we need to create a PR-like structure for the checks to process
|
|
86329
|
+
// This allows us to reuse the existing check infrastructure
|
|
86330
|
+
const prInfo = {
|
|
86331
|
+
number: issue.number,
|
|
86332
|
+
title: issue.title || '',
|
|
86333
|
+
body: issue.body || '',
|
|
86334
|
+
author: issue.user?.login || 'unknown',
|
|
86335
|
+
base: 'main', // Issues don't have branches
|
|
86336
|
+
head: 'issue', // Issues don't have branches
|
|
86337
|
+
files: [], // No file changes for issues
|
|
86338
|
+
additions: 0,
|
|
86339
|
+
deletions: 0,
|
|
86340
|
+
totalAdditions: 0,
|
|
86341
|
+
totalDeletions: 0,
|
|
86342
|
+
eventType: mapGitHubEventToTrigger('issues', action),
|
|
86343
|
+
};
|
|
86344
|
+
// Run the checks using CheckExecutionEngine
|
|
86345
|
+
const { CheckExecutionEngine } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(80299)));
|
|
86346
|
+
const engine = new CheckExecutionEngine();
|
|
86347
|
+
try {
|
|
86348
|
+
const result = await engine.executeGroupedChecks(prInfo, checksToRun, undefined, // timeout
|
|
86349
|
+
config, undefined, // outputFormat
|
|
86350
|
+
inputs.debug === 'true');
|
|
86351
|
+
// Format and post results as a comment on the issue
|
|
86352
|
+
if (Object.keys(result).length > 0) {
|
|
86353
|
+
let commentBody = `## 🤖 Issue Assistant Results\n\n`;
|
|
86354
|
+
for (const checks of Object.values(result)) {
|
|
86355
|
+
for (const check of checks) {
|
|
86356
|
+
if (check.content && check.content.trim()) {
|
|
86357
|
+
commentBody += `### ${check.checkName}\n`;
|
|
86358
|
+
commentBody += `${check.content}\n\n`;
|
|
86359
|
+
}
|
|
86360
|
+
}
|
|
86361
|
+
}
|
|
86362
|
+
commentBody += `\n---\n*Powered by [Visor](https://github.com/probelabs/visor)*`;
|
|
86363
|
+
// Post comment to the issue
|
|
86364
|
+
await octokit.rest.issues.createComment({
|
|
86365
|
+
owner,
|
|
86366
|
+
repo,
|
|
86367
|
+
issue_number: issue.number,
|
|
86368
|
+
body: commentBody,
|
|
86369
|
+
});
|
|
86370
|
+
console.log(`✅ Posted issue assistant results to issue #${issue.number}`);
|
|
86371
|
+
}
|
|
86372
|
+
else {
|
|
86373
|
+
console.log('No results from issue assistant checks');
|
|
86374
|
+
}
|
|
86375
|
+
// Set outputs for GitHub Actions
|
|
86376
|
+
(0, core_1.setOutput)('review-completed', 'true');
|
|
86377
|
+
(0, core_1.setOutput)('checks-executed', checksToRun.length.toString());
|
|
86378
|
+
}
|
|
86379
|
+
catch (error) {
|
|
86380
|
+
console.error('Error running issue assistant checks:', error);
|
|
86381
|
+
(0, core_1.setOutput)('review-completed', 'false');
|
|
86382
|
+
(0, core_1.setOutput)('error', error instanceof Error ? error.message : 'Unknown error');
|
|
86383
|
+
throw error;
|
|
86384
|
+
}
|
|
86385
|
+
}
|
|
86386
|
+
async function handleIssueComment(octokit, owner, repo, context, inputs, actionConfig, _actionChecksToRun) {
|
|
86640
86387
|
const comment = context.event?.comment;
|
|
86641
86388
|
const issue = context.event?.issue;
|
|
86642
86389
|
if (!comment || !issue) {
|
|
@@ -86652,33 +86399,42 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
86652
86399
|
}
|
|
86653
86400
|
// Process comments on both issues and PRs
|
|
86654
86401
|
// (issue.pull_request exists for PR comments, doesn't exist for issue comments)
|
|
86402
|
+
const isPullRequest = !!issue.pull_request;
|
|
86655
86403
|
// Load configuration to get available commands
|
|
86656
86404
|
const configManager = new config_1.ConfigManager();
|
|
86657
86405
|
let config;
|
|
86658
86406
|
const commandRegistry = {};
|
|
86659
|
-
|
|
86660
|
-
|
|
86661
|
-
|
|
86662
|
-
|
|
86663
|
-
|
|
86664
|
-
|
|
86665
|
-
|
|
86666
|
-
|
|
86667
|
-
|
|
86668
|
-
|
|
86669
|
-
|
|
86670
|
-
|
|
86671
|
-
|
|
86672
|
-
|
|
86407
|
+
// Use provided config if available (from action), otherwise load it
|
|
86408
|
+
if (actionConfig) {
|
|
86409
|
+
config = actionConfig;
|
|
86410
|
+
}
|
|
86411
|
+
else {
|
|
86412
|
+
try {
|
|
86413
|
+
config = await configManager.findAndLoadConfig();
|
|
86414
|
+
}
|
|
86415
|
+
catch {
|
|
86416
|
+
console.log('Could not load config, using defaults');
|
|
86417
|
+
config = undefined;
|
|
86418
|
+
}
|
|
86419
|
+
}
|
|
86420
|
+
// Build command registry from config
|
|
86421
|
+
if (config?.checks) {
|
|
86422
|
+
// Add 'review' command that runs all checks
|
|
86423
|
+
commandRegistry['review'] = Object.keys(config.checks);
|
|
86424
|
+
// Also add individual check names as commands
|
|
86425
|
+
for (const [checkId, checkConfig] of Object.entries(config.checks)) {
|
|
86426
|
+
// Legacy: check if it has old 'command' property
|
|
86427
|
+
if (checkConfig.command) {
|
|
86428
|
+
if (!commandRegistry[checkConfig.command]) {
|
|
86429
|
+
commandRegistry[checkConfig.command] = [];
|
|
86673
86430
|
}
|
|
86674
|
-
|
|
86675
|
-
commandRegistry[checkId] = [checkId];
|
|
86431
|
+
commandRegistry[checkConfig.command].push(checkId);
|
|
86676
86432
|
}
|
|
86433
|
+
// New: add check name as command
|
|
86434
|
+
commandRegistry[checkId] = [checkId];
|
|
86677
86435
|
}
|
|
86678
86436
|
}
|
|
86679
|
-
|
|
86680
|
-
console.log('Could not load config, using defaults');
|
|
86681
|
-
config = undefined;
|
|
86437
|
+
else {
|
|
86682
86438
|
// Default commands when no config is available
|
|
86683
86439
|
commandRegistry['review'] = ['security', 'performance', 'style', 'architecture'];
|
|
86684
86440
|
}
|
|
@@ -86695,22 +86451,40 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
86695
86451
|
const reviewer = new reviewer_1.PRReviewer(octokit);
|
|
86696
86452
|
switch (command.type) {
|
|
86697
86453
|
case 'status':
|
|
86698
|
-
|
|
86699
|
-
|
|
86700
|
-
|
|
86701
|
-
|
|
86702
|
-
|
|
86703
|
-
|
|
86704
|
-
|
|
86705
|
-
|
|
86706
|
-
|
|
86707
|
-
|
|
86708
|
-
|
|
86709
|
-
|
|
86710
|
-
|
|
86711
|
-
|
|
86712
|
-
|
|
86713
|
-
|
|
86454
|
+
if (isPullRequest) {
|
|
86455
|
+
const statusPrInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, 'issue_comment');
|
|
86456
|
+
const statusComment = `## 📊 PR Status\n\n` +
|
|
86457
|
+
`**Title:** ${statusPrInfo.title}\n` +
|
|
86458
|
+
`**Author:** ${statusPrInfo.author}\n` +
|
|
86459
|
+
`**Files Changed:** ${statusPrInfo.files.length}\n` +
|
|
86460
|
+
`**Additions:** +${statusPrInfo.totalAdditions}\n` +
|
|
86461
|
+
`**Deletions:** -${statusPrInfo.totalDeletions}\n` +
|
|
86462
|
+
`**Base:** ${statusPrInfo.base} → **Head:** ${statusPrInfo.head}\n\n` +
|
|
86463
|
+
`\n---\n\n` +
|
|
86464
|
+
`*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*`;
|
|
86465
|
+
await octokit.rest.issues.createComment({
|
|
86466
|
+
owner,
|
|
86467
|
+
repo,
|
|
86468
|
+
issue_number: prNumber,
|
|
86469
|
+
body: statusComment,
|
|
86470
|
+
});
|
|
86471
|
+
}
|
|
86472
|
+
else {
|
|
86473
|
+
const statusComment = `## 📊 Issue Status\n\n` +
|
|
86474
|
+
`**Title:** ${issue.title || 'N/A'}\n` +
|
|
86475
|
+
`**Author:** ${issue.user?.login || 'unknown'}\n` +
|
|
86476
|
+
`**State:** ${issue.state || 'open'}\n` +
|
|
86477
|
+
`**Comments:** ${issue.comments || 0}\n` +
|
|
86478
|
+
`**Created:** ${issue.created_at || 'unknown'}\n` +
|
|
86479
|
+
`\n---\n\n` +
|
|
86480
|
+
`*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*`;
|
|
86481
|
+
await octokit.rest.issues.createComment({
|
|
86482
|
+
owner,
|
|
86483
|
+
repo,
|
|
86484
|
+
issue_number: prNumber,
|
|
86485
|
+
body: statusComment,
|
|
86486
|
+
});
|
|
86487
|
+
}
|
|
86714
86488
|
break;
|
|
86715
86489
|
case 'help':
|
|
86716
86490
|
await octokit.rest.issues.createComment({
|
|
@@ -86727,7 +86501,28 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
86727
86501
|
// Resolve all dependencies recursively
|
|
86728
86502
|
const checkIds = resolveDependencies(initialCheckIds, config);
|
|
86729
86503
|
console.log(`Running checks for command /${command.type} (initial: ${initialCheckIds.join(', ')}, resolved: ${checkIds.join(', ')})`);
|
|
86730
|
-
|
|
86504
|
+
// Different handling for PRs vs Issues
|
|
86505
|
+
let prInfo;
|
|
86506
|
+
if (isPullRequest) {
|
|
86507
|
+
// It's a PR comment - fetch the PR diff
|
|
86508
|
+
prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, 'issue_comment');
|
|
86509
|
+
}
|
|
86510
|
+
else {
|
|
86511
|
+
// It's an issue comment - create a minimal PRInfo structure for issue assistant
|
|
86512
|
+
prInfo = {
|
|
86513
|
+
number: issue.number,
|
|
86514
|
+
title: issue.title || '',
|
|
86515
|
+
body: issue.body || '',
|
|
86516
|
+
author: issue.user?.login || 'unknown',
|
|
86517
|
+
base: 'main',
|
|
86518
|
+
head: 'issue',
|
|
86519
|
+
files: [],
|
|
86520
|
+
totalAdditions: 0,
|
|
86521
|
+
totalDeletions: 0,
|
|
86522
|
+
fullDiff: '',
|
|
86523
|
+
eventType: 'issue_comment'
|
|
86524
|
+
};
|
|
86525
|
+
}
|
|
86731
86526
|
// Extract common arguments
|
|
86732
86527
|
const focus = command.args?.find(arg => arg.startsWith('--focus='))?.split('=')[1];
|
|
86733
86528
|
const format = command.args?.find(arg => arg.startsWith('--format='))?.split('=')[1];
|
|
@@ -86739,17 +86534,47 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
86739
86534
|
}
|
|
86740
86535
|
}
|
|
86741
86536
|
}
|
|
86537
|
+
// Only run checks that are appropriate for the context
|
|
86538
|
+
const filteredCheckIds = checkIds.filter(checkId => {
|
|
86539
|
+
if (!config?.checks?.[checkId])
|
|
86540
|
+
return false;
|
|
86541
|
+
const checkConfig = config.checks[checkId];
|
|
86542
|
+
const checkEvents = checkConfig.on || ['pr_opened', 'pr_updated'];
|
|
86543
|
+
// For issue comments, only run checks that are configured for issue_comment events
|
|
86544
|
+
if (!isPullRequest) {
|
|
86545
|
+
return checkEvents.includes('issue_comment');
|
|
86546
|
+
}
|
|
86547
|
+
// For PR comments, run checks configured for PR events or issue_comment
|
|
86548
|
+
return checkEvents.includes('pr_updated') || checkEvents.includes('issue_comment');
|
|
86549
|
+
});
|
|
86550
|
+
if (filteredCheckIds.length === 0) {
|
|
86551
|
+
console.log(`No checks configured to run for ${isPullRequest ? 'PR' : 'issue'} comments`);
|
|
86552
|
+
await octokit.rest.issues.createComment({
|
|
86553
|
+
owner,
|
|
86554
|
+
repo,
|
|
86555
|
+
issue_number: prNumber,
|
|
86556
|
+
body: `⚠️ No checks are configured to run for ${isPullRequest ? 'PR' : 'issue'} comments with command /${command.type}\n\n*Powered by [Visor](https://probelabs.com/visor)*`,
|
|
86557
|
+
});
|
|
86558
|
+
return;
|
|
86559
|
+
}
|
|
86742
86560
|
const groupedResults = await reviewer.reviewPR(owner, repo, prNumber, prInfo, {
|
|
86743
86561
|
focus,
|
|
86744
86562
|
format,
|
|
86745
86563
|
config: config,
|
|
86746
|
-
checks:
|
|
86564
|
+
checks: filteredCheckIds,
|
|
86747
86565
|
parallelExecution: false,
|
|
86748
86566
|
});
|
|
86749
|
-
|
|
86750
|
-
|
|
86751
|
-
|
|
86752
|
-
|
|
86567
|
+
// Check if commenting is enabled before posting
|
|
86568
|
+
const shouldComment = inputs['comment-on-pr'] !== 'false';
|
|
86569
|
+
if (shouldComment) {
|
|
86570
|
+
await reviewer.postReviewComment(owner, repo, prNumber, groupedResults, {
|
|
86571
|
+
focus,
|
|
86572
|
+
format,
|
|
86573
|
+
});
|
|
86574
|
+
}
|
|
86575
|
+
else {
|
|
86576
|
+
console.log('📝 Skipping comment (comment-on-pr is disabled)');
|
|
86577
|
+
}
|
|
86753
86578
|
// Calculate total check results from grouped results
|
|
86754
86579
|
const totalChecks = Object.values(groupedResults).flat().length;
|
|
86755
86580
|
(0, core_1.setOutput)('checks-executed', totalChecks.toString());
|
|
@@ -86757,8 +86582,7 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
86757
86582
|
break;
|
|
86758
86583
|
}
|
|
86759
86584
|
}
|
|
86760
|
-
async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun) {
|
|
86761
|
-
const context = JSON.parse(process.env.GITHUB_CONTEXT || '{}');
|
|
86585
|
+
async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun, context) {
|
|
86762
86586
|
const pullRequest = context.event?.pull_request;
|
|
86763
86587
|
const action = context.event?.action;
|
|
86764
86588
|
if (!pullRequest) {
|
|
@@ -86773,8 +86597,22 @@ async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config,
|
|
|
86773
86597
|
const commentId = `pr-review-${prNumber}`;
|
|
86774
86598
|
// Map the action to event type
|
|
86775
86599
|
const eventType = mapGitHubEventToTrigger('pull_request', action);
|
|
86776
|
-
// Fetch PR diff
|
|
86777
|
-
|
|
86600
|
+
// Fetch PR diff (handle test scenarios gracefully)
|
|
86601
|
+
let prInfo;
|
|
86602
|
+
try {
|
|
86603
|
+
prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, eventType);
|
|
86604
|
+
}
|
|
86605
|
+
catch (error) {
|
|
86606
|
+
// Handle test scenarios with mock repos
|
|
86607
|
+
if (inputs['ai-provider'] === 'mock' || inputs['ai-model'] === 'mock') {
|
|
86608
|
+
console.log(`📋 Running in test mode with mock provider - using empty PR data`);
|
|
86609
|
+
(0, core_1.setOutput)('review-completed', 'true');
|
|
86610
|
+
(0, core_1.setOutput)('issues-found', '0');
|
|
86611
|
+
(0, core_1.setOutput)('checks-executed', '0');
|
|
86612
|
+
return;
|
|
86613
|
+
}
|
|
86614
|
+
throw error;
|
|
86615
|
+
}
|
|
86778
86616
|
if (prInfo.files.length === 0) {
|
|
86779
86617
|
console.log('⚠️ No files changed in this PR - skipping review');
|
|
86780
86618
|
(0, core_1.setOutput)('review-completed', 'true');
|
|
@@ -86811,28 +86649,43 @@ async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config,
|
|
|
86811
86649
|
if (checkResults?.checkRunMap) {
|
|
86812
86650
|
await completeGitHubChecks(octokit, owner, repo, checkResults.checkRunMap, groupedResults, config);
|
|
86813
86651
|
}
|
|
86814
|
-
// Post review comment
|
|
86815
|
-
|
|
86816
|
-
|
|
86817
|
-
|
|
86818
|
-
|
|
86819
|
-
|
|
86652
|
+
// Post review comment (only if comment-on-pr is not disabled)
|
|
86653
|
+
const shouldComment = inputs['comment-on-pr'] !== 'false';
|
|
86654
|
+
if (shouldComment) {
|
|
86655
|
+
await reviewer.postReviewComment(owner, repo, prNumber, groupedResults, {
|
|
86656
|
+
commentId,
|
|
86657
|
+
triggeredBy: action,
|
|
86658
|
+
commitSha: pullRequest.head?.sha,
|
|
86659
|
+
});
|
|
86660
|
+
}
|
|
86661
|
+
else {
|
|
86662
|
+
console.log('📝 Skipping PR comment (comment-on-pr is disabled)');
|
|
86663
|
+
}
|
|
86820
86664
|
// Set outputs
|
|
86821
86665
|
(0, core_1.setOutput)('review-completed', 'true');
|
|
86822
86666
|
(0, core_1.setOutput)('checks-executed', checksToExecute.length.toString());
|
|
86823
86667
|
(0, core_1.setOutput)('pr-action', action);
|
|
86824
86668
|
}
|
|
86825
86669
|
async function handleRepoInfo(octokit, owner, repo) {
|
|
86826
|
-
|
|
86827
|
-
|
|
86828
|
-
|
|
86829
|
-
|
|
86830
|
-
|
|
86831
|
-
|
|
86832
|
-
|
|
86833
|
-
|
|
86834
|
-
|
|
86835
|
-
|
|
86670
|
+
try {
|
|
86671
|
+
const { data: repoData } = await octokit.rest.repos.get({
|
|
86672
|
+
owner,
|
|
86673
|
+
repo,
|
|
86674
|
+
});
|
|
86675
|
+
(0, core_1.setOutput)('repo-name', repoData.name);
|
|
86676
|
+
(0, core_1.setOutput)('repo-description', repoData.description || '');
|
|
86677
|
+
(0, core_1.setOutput)('repo-stars', repoData.stargazers_count.toString());
|
|
86678
|
+
console.log(`Repository: ${repoData.full_name}`);
|
|
86679
|
+
console.log(`Description: ${repoData.description || 'No description'}`);
|
|
86680
|
+
console.log(`Stars: ${repoData.stargazers_count}`);
|
|
86681
|
+
}
|
|
86682
|
+
catch {
|
|
86683
|
+
// Handle test scenarios or missing repos gracefully
|
|
86684
|
+
console.log(`📋 Running in test mode or repository not accessible: ${owner}/${repo}`);
|
|
86685
|
+
(0, core_1.setOutput)('repo-name', repo);
|
|
86686
|
+
(0, core_1.setOutput)('repo-description', 'Test repository');
|
|
86687
|
+
(0, core_1.setOutput)('repo-stars', '0');
|
|
86688
|
+
}
|
|
86836
86689
|
}
|
|
86837
86690
|
/**
|
|
86838
86691
|
* Filter checks based on their if conditions and API requirements
|
|
@@ -87194,27 +87047,31 @@ async function markCheckAsFailed(checkService, owner, repo, checkRunId, checkNam
|
|
|
87194
87047
|
}
|
|
87195
87048
|
// Entry point - execute immediately when the script is run
|
|
87196
87049
|
// Note: require.main === module check doesn't work reliably with ncc bundling
|
|
87197
|
-
|
|
87198
|
-
|
|
87199
|
-
|
|
87200
|
-
|
|
87201
|
-
|
|
87202
|
-
|
|
87203
|
-
|
|
87204
|
-
|
|
87205
|
-
|
|
87206
|
-
|
|
87207
|
-
|
|
87208
|
-
|
|
87209
|
-
|
|
87050
|
+
// Only execute if not in test environment
|
|
87051
|
+
if (process.env.NODE_ENV !== 'test' && process.env.JEST_WORKER_ID === undefined) {
|
|
87052
|
+
(() => {
|
|
87053
|
+
// Simple mode detection: use GITHUB_ACTIONS env var which is always 'true' in GitHub Actions
|
|
87054
|
+
// Also check for --cli flag to force CLI mode even in GitHub Actions environment
|
|
87055
|
+
const isGitHubAction = process.env.GITHUB_ACTIONS === 'true' && !process.argv.includes('--cli');
|
|
87056
|
+
if (isGitHubAction) {
|
|
87057
|
+
// Run as GitHub Action
|
|
87058
|
+
run();
|
|
87059
|
+
}
|
|
87060
|
+
else {
|
|
87061
|
+
// Import and run CLI
|
|
87062
|
+
Promise.resolve().then(() => __importStar(__nccwpck_require__(10091))).then(({ main }) => {
|
|
87063
|
+
main().catch(error => {
|
|
87064
|
+
console.error('CLI execution failed:', error);
|
|
87065
|
+
process.exit(1);
|
|
87066
|
+
});
|
|
87067
|
+
})
|
|
87068
|
+
.catch(error => {
|
|
87069
|
+
console.error('Failed to import CLI module:', error);
|
|
87210
87070
|
process.exit(1);
|
|
87211
87071
|
});
|
|
87212
|
-
}
|
|
87213
|
-
|
|
87214
|
-
|
|
87215
|
-
});
|
|
87216
|
-
}
|
|
87217
|
-
})();
|
|
87072
|
+
}
|
|
87073
|
+
})();
|
|
87074
|
+
}
|
|
87218
87075
|
|
|
87219
87076
|
|
|
87220
87077
|
/***/ }),
|
|
@@ -89001,7 +88858,7 @@ class WebhookCheckProvider extends check_provider_interface_1.CheckProvider {
|
|
|
89001
88858
|
}
|
|
89002
88859
|
catch (error) {
|
|
89003
88860
|
clearTimeout(timeoutId);
|
|
89004
|
-
if (error
|
|
88861
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
89005
88862
|
throw new Error(`Webhook request timed out after ${timeout}ms`);
|
|
89006
88863
|
}
|
|
89007
88864
|
throw error;
|
|
@@ -89235,7 +89092,7 @@ class PRReviewer {
|
|
|
89235
89092
|
comment += '\n\n' + this.formatDebugSection(debugInfo);
|
|
89236
89093
|
comment += '\n\n';
|
|
89237
89094
|
}
|
|
89238
|
-
comment += `\n---\n*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*`;
|
|
89095
|
+
comment += `\n\n---\n\n*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*`;
|
|
89239
89096
|
return comment;
|
|
89240
89097
|
}
|
|
89241
89098
|
formatDebugSection(debug) {
|
|
@@ -89421,6 +89278,699 @@ class SessionRegistry {
|
|
|
89421
89278
|
exports.SessionRegistry = SessionRegistry;
|
|
89422
89279
|
|
|
89423
89280
|
|
|
89281
|
+
/***/ }),
|
|
89282
|
+
|
|
89283
|
+
/***/ 73836:
|
|
89284
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
89285
|
+
|
|
89286
|
+
"use strict";
|
|
89287
|
+
|
|
89288
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
89289
|
+
if (k2 === undefined) k2 = k;
|
|
89290
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
89291
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
89292
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
89293
|
+
}
|
|
89294
|
+
Object.defineProperty(o, k2, desc);
|
|
89295
|
+
}) : (function(o, m, k, k2) {
|
|
89296
|
+
if (k2 === undefined) k2 = k;
|
|
89297
|
+
o[k2] = m[k];
|
|
89298
|
+
}));
|
|
89299
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
89300
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
89301
|
+
}) : function(o, v) {
|
|
89302
|
+
o["default"] = v;
|
|
89303
|
+
});
|
|
89304
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
89305
|
+
var ownKeys = function(o) {
|
|
89306
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
89307
|
+
var ar = [];
|
|
89308
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
89309
|
+
return ar;
|
|
89310
|
+
};
|
|
89311
|
+
return ownKeys(o);
|
|
89312
|
+
};
|
|
89313
|
+
return function (mod) {
|
|
89314
|
+
if (mod && mod.__esModule) return mod;
|
|
89315
|
+
var result = {};
|
|
89316
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
89317
|
+
__setModuleDefault(result, mod);
|
|
89318
|
+
return result;
|
|
89319
|
+
};
|
|
89320
|
+
})();
|
|
89321
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
89322
|
+
exports.ConfigLoader = exports.ConfigSourceType = void 0;
|
|
89323
|
+
const fs = __importStar(__nccwpck_require__(79896));
|
|
89324
|
+
const path = __importStar(__nccwpck_require__(16928));
|
|
89325
|
+
const yaml = __importStar(__nccwpck_require__(74281));
|
|
89326
|
+
/**
|
|
89327
|
+
* Configuration source types
|
|
89328
|
+
*/
|
|
89329
|
+
var ConfigSourceType;
|
|
89330
|
+
(function (ConfigSourceType) {
|
|
89331
|
+
ConfigSourceType["LOCAL"] = "local";
|
|
89332
|
+
ConfigSourceType["REMOTE"] = "remote";
|
|
89333
|
+
ConfigSourceType["DEFAULT"] = "default";
|
|
89334
|
+
})(ConfigSourceType || (exports.ConfigSourceType = ConfigSourceType = {}));
|
|
89335
|
+
/**
|
|
89336
|
+
* Utility class for loading configurations from various sources
|
|
89337
|
+
*/
|
|
89338
|
+
class ConfigLoader {
|
|
89339
|
+
options;
|
|
89340
|
+
cache = new Map();
|
|
89341
|
+
loadedConfigs = new Set();
|
|
89342
|
+
constructor(options = {}) {
|
|
89343
|
+
this.options = options;
|
|
89344
|
+
this.options = {
|
|
89345
|
+
allowRemote: true,
|
|
89346
|
+
cacheTTL: 5 * 60 * 1000, // 5 minutes
|
|
89347
|
+
timeout: 30 * 1000, // 30 seconds
|
|
89348
|
+
maxDepth: 10,
|
|
89349
|
+
allowedRemotePatterns: [], // Empty by default for security
|
|
89350
|
+
projectRoot: this.findProjectRoot(),
|
|
89351
|
+
...options,
|
|
89352
|
+
};
|
|
89353
|
+
}
|
|
89354
|
+
/**
|
|
89355
|
+
* Determine the source type from a string
|
|
89356
|
+
*/
|
|
89357
|
+
getSourceType(source) {
|
|
89358
|
+
if (source === 'default') {
|
|
89359
|
+
return ConfigSourceType.DEFAULT;
|
|
89360
|
+
}
|
|
89361
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
89362
|
+
return ConfigSourceType.REMOTE;
|
|
89363
|
+
}
|
|
89364
|
+
return ConfigSourceType.LOCAL;
|
|
89365
|
+
}
|
|
89366
|
+
/**
|
|
89367
|
+
* Fetch configuration from any source
|
|
89368
|
+
*/
|
|
89369
|
+
async fetchConfig(source, currentDepth = 0) {
|
|
89370
|
+
// Check recursion depth
|
|
89371
|
+
if (currentDepth >= (this.options.maxDepth || 10)) {
|
|
89372
|
+
throw new Error(`Maximum extends depth (${this.options.maxDepth}) exceeded. Check for circular dependencies.`);
|
|
89373
|
+
}
|
|
89374
|
+
// Check for circular dependencies
|
|
89375
|
+
const normalizedSource = this.normalizeSource(source);
|
|
89376
|
+
if (this.loadedConfigs.has(normalizedSource)) {
|
|
89377
|
+
throw new Error(`Circular dependency detected: ${normalizedSource} is already in the extends chain`);
|
|
89378
|
+
}
|
|
89379
|
+
const sourceType = this.getSourceType(source);
|
|
89380
|
+
try {
|
|
89381
|
+
this.loadedConfigs.add(normalizedSource);
|
|
89382
|
+
switch (sourceType) {
|
|
89383
|
+
case ConfigSourceType.DEFAULT:
|
|
89384
|
+
return await this.fetchDefaultConfig();
|
|
89385
|
+
case ConfigSourceType.REMOTE:
|
|
89386
|
+
if (!this.options.allowRemote) {
|
|
89387
|
+
throw new Error('Remote extends are disabled. Enable with --allow-remote-extends or remove VISOR_NO_REMOTE_EXTENDS environment variable.');
|
|
89388
|
+
}
|
|
89389
|
+
return await this.fetchRemoteConfig(source);
|
|
89390
|
+
case ConfigSourceType.LOCAL:
|
|
89391
|
+
return await this.fetchLocalConfig(source);
|
|
89392
|
+
default:
|
|
89393
|
+
throw new Error(`Unknown configuration source: ${source}`);
|
|
89394
|
+
}
|
|
89395
|
+
}
|
|
89396
|
+
finally {
|
|
89397
|
+
this.loadedConfigs.delete(normalizedSource);
|
|
89398
|
+
}
|
|
89399
|
+
}
|
|
89400
|
+
/**
|
|
89401
|
+
* Normalize source path/URL for comparison
|
|
89402
|
+
*/
|
|
89403
|
+
normalizeSource(source) {
|
|
89404
|
+
const sourceType = this.getSourceType(source);
|
|
89405
|
+
switch (sourceType) {
|
|
89406
|
+
case ConfigSourceType.DEFAULT:
|
|
89407
|
+
return 'default';
|
|
89408
|
+
case ConfigSourceType.REMOTE:
|
|
89409
|
+
return source.toLowerCase();
|
|
89410
|
+
case ConfigSourceType.LOCAL:
|
|
89411
|
+
const basePath = this.options.baseDir || process.cwd();
|
|
89412
|
+
return path.resolve(basePath, source);
|
|
89413
|
+
default:
|
|
89414
|
+
return source;
|
|
89415
|
+
}
|
|
89416
|
+
}
|
|
89417
|
+
/**
|
|
89418
|
+
* Load configuration from local file system
|
|
89419
|
+
*/
|
|
89420
|
+
async fetchLocalConfig(filePath) {
|
|
89421
|
+
const basePath = this.options.baseDir || process.cwd();
|
|
89422
|
+
const resolvedPath = path.resolve(basePath, filePath);
|
|
89423
|
+
// Validate against path traversal attacks
|
|
89424
|
+
this.validateLocalPath(resolvedPath);
|
|
89425
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
89426
|
+
throw new Error(`Configuration file not found: ${resolvedPath}`);
|
|
89427
|
+
}
|
|
89428
|
+
try {
|
|
89429
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
89430
|
+
const config = yaml.load(content);
|
|
89431
|
+
if (!config || typeof config !== 'object') {
|
|
89432
|
+
throw new Error(`Invalid YAML in configuration file: ${resolvedPath}`);
|
|
89433
|
+
}
|
|
89434
|
+
// Update base directory for nested extends
|
|
89435
|
+
const previousBaseDir = this.options.baseDir;
|
|
89436
|
+
this.options.baseDir = path.dirname(resolvedPath);
|
|
89437
|
+
try {
|
|
89438
|
+
// Process extends if present
|
|
89439
|
+
if (config.extends) {
|
|
89440
|
+
const processedConfig = await this.processExtends(config);
|
|
89441
|
+
return processedConfig;
|
|
89442
|
+
}
|
|
89443
|
+
return config;
|
|
89444
|
+
}
|
|
89445
|
+
finally {
|
|
89446
|
+
// Restore previous base directory
|
|
89447
|
+
this.options.baseDir = previousBaseDir;
|
|
89448
|
+
}
|
|
89449
|
+
}
|
|
89450
|
+
catch (error) {
|
|
89451
|
+
if (error instanceof Error) {
|
|
89452
|
+
throw new Error(`Failed to load configuration from ${resolvedPath}: ${error.message}`);
|
|
89453
|
+
}
|
|
89454
|
+
throw error;
|
|
89455
|
+
}
|
|
89456
|
+
}
|
|
89457
|
+
/**
|
|
89458
|
+
* Fetch configuration from remote URL
|
|
89459
|
+
*/
|
|
89460
|
+
async fetchRemoteConfig(url) {
|
|
89461
|
+
// Validate URL protocol
|
|
89462
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
89463
|
+
throw new Error(`Invalid URL: ${url}. Only HTTP and HTTPS protocols are supported.`);
|
|
89464
|
+
}
|
|
89465
|
+
// Validate against SSRF attacks
|
|
89466
|
+
this.validateRemoteURL(url);
|
|
89467
|
+
// Check cache
|
|
89468
|
+
const cacheEntry = this.cache.get(url);
|
|
89469
|
+
if (cacheEntry && Date.now() - cacheEntry.timestamp < cacheEntry.ttl) {
|
|
89470
|
+
console.log(`📦 Using cached configuration from: ${url}`);
|
|
89471
|
+
return cacheEntry.config;
|
|
89472
|
+
}
|
|
89473
|
+
console.log(`⬇️ Fetching remote configuration from: ${url}`);
|
|
89474
|
+
try {
|
|
89475
|
+
const controller = new AbortController();
|
|
89476
|
+
const timeoutId = setTimeout(() => controller.abort(), this.options.timeout || 30000);
|
|
89477
|
+
const response = await fetch(url, {
|
|
89478
|
+
signal: controller.signal,
|
|
89479
|
+
headers: {
|
|
89480
|
+
'User-Agent': 'Visor/1.0',
|
|
89481
|
+
},
|
|
89482
|
+
});
|
|
89483
|
+
clearTimeout(timeoutId);
|
|
89484
|
+
if (!response.ok) {
|
|
89485
|
+
throw new Error(`Failed to fetch config: ${response.status} ${response.statusText}`);
|
|
89486
|
+
}
|
|
89487
|
+
const content = await response.text();
|
|
89488
|
+
const config = yaml.load(content);
|
|
89489
|
+
if (!config || typeof config !== 'object') {
|
|
89490
|
+
throw new Error(`Invalid YAML in remote configuration: ${url}`);
|
|
89491
|
+
}
|
|
89492
|
+
// Cache the configuration
|
|
89493
|
+
this.cache.set(url, {
|
|
89494
|
+
config,
|
|
89495
|
+
timestamp: Date.now(),
|
|
89496
|
+
ttl: this.options.cacheTTL || 5 * 60 * 1000,
|
|
89497
|
+
});
|
|
89498
|
+
// Process extends if present
|
|
89499
|
+
if (config.extends) {
|
|
89500
|
+
return await this.processExtends(config);
|
|
89501
|
+
}
|
|
89502
|
+
return config;
|
|
89503
|
+
}
|
|
89504
|
+
catch (error) {
|
|
89505
|
+
if (error instanceof Error) {
|
|
89506
|
+
if (error.name === 'AbortError') {
|
|
89507
|
+
throw new Error(`Timeout fetching configuration from ${url} (${this.options.timeout}ms)`);
|
|
89508
|
+
}
|
|
89509
|
+
throw new Error(`Failed to fetch remote configuration from ${url}: ${error.message}`);
|
|
89510
|
+
}
|
|
89511
|
+
throw error;
|
|
89512
|
+
}
|
|
89513
|
+
}
|
|
89514
|
+
/**
|
|
89515
|
+
* Load bundled default configuration
|
|
89516
|
+
*/
|
|
89517
|
+
async fetchDefaultConfig() {
|
|
89518
|
+
// First try to find the bundled default config
|
|
89519
|
+
let packageRoot = this.findPackageRoot();
|
|
89520
|
+
if (!packageRoot) {
|
|
89521
|
+
// Fallback: try relative to current file
|
|
89522
|
+
packageRoot = path.resolve(__dirname, '..', '..');
|
|
89523
|
+
}
|
|
89524
|
+
const defaultConfigPath = path.join(packageRoot, 'defaults', '.visor.yaml');
|
|
89525
|
+
if (fs.existsSync(defaultConfigPath)) {
|
|
89526
|
+
console.log(`📦 Loading bundled default configuration`);
|
|
89527
|
+
const content = fs.readFileSync(defaultConfigPath, 'utf8');
|
|
89528
|
+
const config = yaml.load(content);
|
|
89529
|
+
if (!config || typeof config !== 'object') {
|
|
89530
|
+
throw new Error('Invalid default configuration');
|
|
89531
|
+
}
|
|
89532
|
+
// Default configs shouldn't have extends, but handle it just in case
|
|
89533
|
+
if (config.extends) {
|
|
89534
|
+
return await this.processExtends(config);
|
|
89535
|
+
}
|
|
89536
|
+
return config;
|
|
89537
|
+
}
|
|
89538
|
+
// Return minimal default if bundled config not found
|
|
89539
|
+
console.warn('⚠️ Bundled default configuration not found, using minimal defaults');
|
|
89540
|
+
return {
|
|
89541
|
+
version: '1.0',
|
|
89542
|
+
checks: {},
|
|
89543
|
+
output: {
|
|
89544
|
+
pr_comment: {
|
|
89545
|
+
format: 'markdown',
|
|
89546
|
+
group_by: 'check',
|
|
89547
|
+
collapse: true,
|
|
89548
|
+
},
|
|
89549
|
+
},
|
|
89550
|
+
};
|
|
89551
|
+
}
|
|
89552
|
+
/**
|
|
89553
|
+
* Process extends directive in a configuration
|
|
89554
|
+
*/
|
|
89555
|
+
async processExtends(config) {
|
|
89556
|
+
if (!config.extends) {
|
|
89557
|
+
return config;
|
|
89558
|
+
}
|
|
89559
|
+
const extends_ = Array.isArray(config.extends) ? config.extends : [config.extends];
|
|
89560
|
+
// Remove extends from the config to avoid infinite recursion
|
|
89561
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
89562
|
+
const { extends: _extendsField, ...configWithoutExtends } = config;
|
|
89563
|
+
// Load all parent configurations
|
|
89564
|
+
const parentConfigs = [];
|
|
89565
|
+
for (const source of extends_) {
|
|
89566
|
+
const parentConfig = await this.fetchConfig(source, this.loadedConfigs.size);
|
|
89567
|
+
parentConfigs.push(parentConfig);
|
|
89568
|
+
}
|
|
89569
|
+
// Merge configurations (will be implemented in config-merger.ts)
|
|
89570
|
+
// For now, we'll import it dynamically
|
|
89571
|
+
const { ConfigMerger } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(20730)));
|
|
89572
|
+
const merger = new ConfigMerger();
|
|
89573
|
+
// Merge all parent configs together first
|
|
89574
|
+
let mergedParents = {};
|
|
89575
|
+
for (const parentConfig of parentConfigs) {
|
|
89576
|
+
mergedParents = merger.merge(mergedParents, parentConfig);
|
|
89577
|
+
}
|
|
89578
|
+
// Then merge with the current config (child overrides parent)
|
|
89579
|
+
return merger.merge(mergedParents, configWithoutExtends);
|
|
89580
|
+
}
|
|
89581
|
+
/**
|
|
89582
|
+
* Find project root directory (for security validation)
|
|
89583
|
+
*/
|
|
89584
|
+
findProjectRoot() {
|
|
89585
|
+
// Try to find git root first
|
|
89586
|
+
try {
|
|
89587
|
+
const { execSync } = __nccwpck_require__(35317);
|
|
89588
|
+
const gitRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
89589
|
+
if (gitRoot)
|
|
89590
|
+
return gitRoot;
|
|
89591
|
+
}
|
|
89592
|
+
catch {
|
|
89593
|
+
// Not a git repo, continue
|
|
89594
|
+
}
|
|
89595
|
+
// Fall back to finding package.json
|
|
89596
|
+
const packageRoot = this.findPackageRoot();
|
|
89597
|
+
if (packageRoot)
|
|
89598
|
+
return packageRoot;
|
|
89599
|
+
// Last resort: use current working directory
|
|
89600
|
+
return process.cwd();
|
|
89601
|
+
}
|
|
89602
|
+
/**
|
|
89603
|
+
* Validate remote URL against allowlist
|
|
89604
|
+
*/
|
|
89605
|
+
validateRemoteURL(url) {
|
|
89606
|
+
// If allowlist is empty, allow all URLs (backward compatibility)
|
|
89607
|
+
const allowedPatterns = this.options.allowedRemotePatterns || [];
|
|
89608
|
+
if (allowedPatterns.length === 0) {
|
|
89609
|
+
return;
|
|
89610
|
+
}
|
|
89611
|
+
// Check if URL matches any allowed pattern
|
|
89612
|
+
const isAllowed = allowedPatterns.some(pattern => url.startsWith(pattern));
|
|
89613
|
+
if (!isAllowed) {
|
|
89614
|
+
throw new Error(`Security error: URL ${url} is not in the allowed list. Allowed patterns: ${allowedPatterns.join(', ')}`);
|
|
89615
|
+
}
|
|
89616
|
+
}
|
|
89617
|
+
/**
|
|
89618
|
+
* Validate local path against traversal attacks
|
|
89619
|
+
*/
|
|
89620
|
+
validateLocalPath(resolvedPath) {
|
|
89621
|
+
const projectRoot = this.options.projectRoot || process.cwd();
|
|
89622
|
+
const normalizedPath = path.normalize(resolvedPath);
|
|
89623
|
+
const normalizedRoot = path.normalize(projectRoot);
|
|
89624
|
+
// Check if the resolved path is within the project root
|
|
89625
|
+
if (!normalizedPath.startsWith(normalizedRoot)) {
|
|
89626
|
+
throw new Error(`Security error: Path traversal detected. Cannot access files outside project root: ${projectRoot}`);
|
|
89627
|
+
}
|
|
89628
|
+
// Additional check for sensitive system files
|
|
89629
|
+
const sensitivePatterns = [
|
|
89630
|
+
'/etc/passwd',
|
|
89631
|
+
'/etc/shadow',
|
|
89632
|
+
'/.ssh/',
|
|
89633
|
+
'/.aws/',
|
|
89634
|
+
'/.env',
|
|
89635
|
+
'/private/',
|
|
89636
|
+
];
|
|
89637
|
+
const lowerPath = normalizedPath.toLowerCase();
|
|
89638
|
+
for (const pattern of sensitivePatterns) {
|
|
89639
|
+
if (lowerPath.includes(pattern)) {
|
|
89640
|
+
throw new Error(`Security error: Cannot access potentially sensitive file: ${pattern}`);
|
|
89641
|
+
}
|
|
89642
|
+
}
|
|
89643
|
+
}
|
|
89644
|
+
/**
|
|
89645
|
+
* Find package root directory
|
|
89646
|
+
*/
|
|
89647
|
+
findPackageRoot() {
|
|
89648
|
+
let currentDir = __dirname;
|
|
89649
|
+
const root = path.parse(currentDir).root;
|
|
89650
|
+
while (currentDir !== root) {
|
|
89651
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
89652
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
89653
|
+
try {
|
|
89654
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
89655
|
+
// Check if this is the Visor package
|
|
89656
|
+
if (packageJson.name === '@probelabs/visor') {
|
|
89657
|
+
return currentDir;
|
|
89658
|
+
}
|
|
89659
|
+
}
|
|
89660
|
+
catch {
|
|
89661
|
+
// Continue searching
|
|
89662
|
+
}
|
|
89663
|
+
}
|
|
89664
|
+
currentDir = path.dirname(currentDir);
|
|
89665
|
+
}
|
|
89666
|
+
return null;
|
|
89667
|
+
}
|
|
89668
|
+
/**
|
|
89669
|
+
* Clear the configuration cache
|
|
89670
|
+
*/
|
|
89671
|
+
clearCache() {
|
|
89672
|
+
this.cache.clear();
|
|
89673
|
+
}
|
|
89674
|
+
/**
|
|
89675
|
+
* Reset the loaded configs tracking (for testing)
|
|
89676
|
+
*/
|
|
89677
|
+
reset() {
|
|
89678
|
+
this.loadedConfigs.clear();
|
|
89679
|
+
this.clearCache();
|
|
89680
|
+
}
|
|
89681
|
+
}
|
|
89682
|
+
exports.ConfigLoader = ConfigLoader;
|
|
89683
|
+
|
|
89684
|
+
|
|
89685
|
+
/***/ }),
|
|
89686
|
+
|
|
89687
|
+
/***/ 20730:
|
|
89688
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
89689
|
+
|
|
89690
|
+
"use strict";
|
|
89691
|
+
|
|
89692
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
89693
|
+
exports.ConfigMerger = void 0;
|
|
89694
|
+
/**
|
|
89695
|
+
* Utility class for merging Visor configurations with proper override semantics
|
|
89696
|
+
*/
|
|
89697
|
+
class ConfigMerger {
|
|
89698
|
+
/**
|
|
89699
|
+
* Merge two configurations with child overriding parent
|
|
89700
|
+
* @param parent - Base configuration
|
|
89701
|
+
* @param child - Configuration to merge on top
|
|
89702
|
+
* @returns Merged configuration
|
|
89703
|
+
*/
|
|
89704
|
+
merge(parent, child) {
|
|
89705
|
+
// Start with a deep copy of parent
|
|
89706
|
+
const result = this.deepCopy(parent);
|
|
89707
|
+
// Merge simple properties (child overrides parent)
|
|
89708
|
+
if (child.version !== undefined)
|
|
89709
|
+
result.version = child.version;
|
|
89710
|
+
if (child.ai_model !== undefined)
|
|
89711
|
+
result.ai_model = child.ai_model;
|
|
89712
|
+
if (child.ai_provider !== undefined)
|
|
89713
|
+
result.ai_provider = child.ai_provider;
|
|
89714
|
+
if (child.max_parallelism !== undefined)
|
|
89715
|
+
result.max_parallelism = child.max_parallelism;
|
|
89716
|
+
if (child.fail_fast !== undefined)
|
|
89717
|
+
result.fail_fast = child.fail_fast;
|
|
89718
|
+
if (child.fail_if !== undefined)
|
|
89719
|
+
result.fail_if = child.fail_if;
|
|
89720
|
+
if (child.failure_conditions !== undefined)
|
|
89721
|
+
result.failure_conditions = child.failure_conditions;
|
|
89722
|
+
// Merge environment variables (deep merge)
|
|
89723
|
+
if (child.env) {
|
|
89724
|
+
result.env = this.mergeObjects(parent.env || {}, child.env);
|
|
89725
|
+
}
|
|
89726
|
+
// Merge output configuration (deep merge)
|
|
89727
|
+
if (child.output) {
|
|
89728
|
+
result.output = this.mergeOutputConfig(parent.output, child.output);
|
|
89729
|
+
}
|
|
89730
|
+
// Merge checks (special handling)
|
|
89731
|
+
if (child.checks) {
|
|
89732
|
+
result.checks = this.mergeChecks(parent.checks || {}, child.checks);
|
|
89733
|
+
}
|
|
89734
|
+
// Note: extends should not be in the final merged config
|
|
89735
|
+
// It's only used during the loading process
|
|
89736
|
+
return result;
|
|
89737
|
+
}
|
|
89738
|
+
/**
|
|
89739
|
+
* Deep copy an object
|
|
89740
|
+
*/
|
|
89741
|
+
deepCopy(obj) {
|
|
89742
|
+
if (obj === null || obj === undefined) {
|
|
89743
|
+
return obj;
|
|
89744
|
+
}
|
|
89745
|
+
if (obj instanceof Date) {
|
|
89746
|
+
return new Date(obj.getTime());
|
|
89747
|
+
}
|
|
89748
|
+
if (obj instanceof Array) {
|
|
89749
|
+
const copy = [];
|
|
89750
|
+
for (const item of obj) {
|
|
89751
|
+
copy.push(this.deepCopy(item));
|
|
89752
|
+
}
|
|
89753
|
+
return copy;
|
|
89754
|
+
}
|
|
89755
|
+
if (obj instanceof Object) {
|
|
89756
|
+
const copy = {};
|
|
89757
|
+
for (const key in obj) {
|
|
89758
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
89759
|
+
copy[key] = this.deepCopy(obj[key]);
|
|
89760
|
+
}
|
|
89761
|
+
}
|
|
89762
|
+
return copy;
|
|
89763
|
+
}
|
|
89764
|
+
return obj;
|
|
89765
|
+
}
|
|
89766
|
+
/**
|
|
89767
|
+
* Merge two objects (child overrides parent)
|
|
89768
|
+
*/
|
|
89769
|
+
mergeObjects(parent, child) {
|
|
89770
|
+
const result = { ...parent };
|
|
89771
|
+
for (const key in child) {
|
|
89772
|
+
if (Object.prototype.hasOwnProperty.call(child, key)) {
|
|
89773
|
+
const parentValue = parent[key];
|
|
89774
|
+
const childValue = child[key];
|
|
89775
|
+
if (childValue === null || childValue === undefined) {
|
|
89776
|
+
// null/undefined in child removes the key
|
|
89777
|
+
delete result[key];
|
|
89778
|
+
}
|
|
89779
|
+
else if (typeof parentValue === 'object' &&
|
|
89780
|
+
typeof childValue === 'object' &&
|
|
89781
|
+
!Array.isArray(parentValue) &&
|
|
89782
|
+
!Array.isArray(childValue) &&
|
|
89783
|
+
parentValue !== null &&
|
|
89784
|
+
childValue !== null) {
|
|
89785
|
+
// Deep merge objects
|
|
89786
|
+
result[key] = this.mergeObjects(parentValue, childValue);
|
|
89787
|
+
}
|
|
89788
|
+
else {
|
|
89789
|
+
// Child overrides parent (including arrays)
|
|
89790
|
+
result[key] = this.deepCopy(childValue);
|
|
89791
|
+
}
|
|
89792
|
+
}
|
|
89793
|
+
}
|
|
89794
|
+
return result;
|
|
89795
|
+
}
|
|
89796
|
+
/**
|
|
89797
|
+
* Merge output configurations
|
|
89798
|
+
*/
|
|
89799
|
+
mergeOutputConfig(parent, child) {
|
|
89800
|
+
if (!child)
|
|
89801
|
+
return parent;
|
|
89802
|
+
if (!parent)
|
|
89803
|
+
return child;
|
|
89804
|
+
const result = this.deepCopy(parent);
|
|
89805
|
+
// Merge pr_comment
|
|
89806
|
+
if (child.pr_comment) {
|
|
89807
|
+
result.pr_comment = this.mergeObjects((parent.pr_comment || {}), child.pr_comment);
|
|
89808
|
+
}
|
|
89809
|
+
// Merge file_comment
|
|
89810
|
+
if (child.file_comment !== undefined) {
|
|
89811
|
+
if (child.file_comment === null) {
|
|
89812
|
+
delete result.file_comment;
|
|
89813
|
+
}
|
|
89814
|
+
else {
|
|
89815
|
+
result.file_comment = this.mergeObjects((parent.file_comment || {}), child.file_comment);
|
|
89816
|
+
}
|
|
89817
|
+
}
|
|
89818
|
+
// Merge github_checks
|
|
89819
|
+
if (child.github_checks !== undefined) {
|
|
89820
|
+
if (child.github_checks === null) {
|
|
89821
|
+
delete result.github_checks;
|
|
89822
|
+
}
|
|
89823
|
+
else {
|
|
89824
|
+
result.github_checks = this.mergeObjects((parent.github_checks || {}), child.github_checks);
|
|
89825
|
+
}
|
|
89826
|
+
}
|
|
89827
|
+
return result;
|
|
89828
|
+
}
|
|
89829
|
+
/**
|
|
89830
|
+
* Merge check configurations with special handling
|
|
89831
|
+
*/
|
|
89832
|
+
mergeChecks(parent, child) {
|
|
89833
|
+
const result = {};
|
|
89834
|
+
// Start with all parent checks
|
|
89835
|
+
for (const [checkName, checkConfig] of Object.entries(parent)) {
|
|
89836
|
+
result[checkName] = this.deepCopy(checkConfig);
|
|
89837
|
+
}
|
|
89838
|
+
// Process child checks
|
|
89839
|
+
for (const [checkName, childConfig] of Object.entries(child)) {
|
|
89840
|
+
const parentConfig = parent[checkName];
|
|
89841
|
+
if (!parentConfig) {
|
|
89842
|
+
// New check - need to process appendPrompt even without parent
|
|
89843
|
+
const copiedConfig = this.deepCopy(childConfig);
|
|
89844
|
+
// Handle appendPrompt for new checks (convert to prompt)
|
|
89845
|
+
if (copiedConfig.appendPrompt !== undefined) {
|
|
89846
|
+
// If there's no parent, appendPrompt becomes the prompt
|
|
89847
|
+
if (!copiedConfig.prompt) {
|
|
89848
|
+
copiedConfig.prompt = copiedConfig.appendPrompt;
|
|
89849
|
+
}
|
|
89850
|
+
else {
|
|
89851
|
+
// If both prompt and appendPrompt exist in child, append them
|
|
89852
|
+
copiedConfig.prompt = copiedConfig.prompt + '\n\n' + copiedConfig.appendPrompt;
|
|
89853
|
+
}
|
|
89854
|
+
// Remove appendPrompt from final config
|
|
89855
|
+
delete copiedConfig.appendPrompt;
|
|
89856
|
+
}
|
|
89857
|
+
result[checkName] = copiedConfig;
|
|
89858
|
+
}
|
|
89859
|
+
else {
|
|
89860
|
+
// Merge existing check
|
|
89861
|
+
result[checkName] = this.mergeCheckConfig(parentConfig, childConfig);
|
|
89862
|
+
}
|
|
89863
|
+
}
|
|
89864
|
+
return result;
|
|
89865
|
+
}
|
|
89866
|
+
/**
|
|
89867
|
+
* Merge individual check configurations
|
|
89868
|
+
*/
|
|
89869
|
+
mergeCheckConfig(parent, child) {
|
|
89870
|
+
const result = this.deepCopy(parent);
|
|
89871
|
+
// Simple properties (child overrides parent)
|
|
89872
|
+
if (child.type !== undefined)
|
|
89873
|
+
result.type = child.type;
|
|
89874
|
+
if (child.prompt !== undefined)
|
|
89875
|
+
result.prompt = child.prompt;
|
|
89876
|
+
// Handle appendPrompt - append to existing prompt
|
|
89877
|
+
if (child.appendPrompt !== undefined) {
|
|
89878
|
+
if (result.prompt) {
|
|
89879
|
+
// Append with a newline separator if parent has a prompt
|
|
89880
|
+
result.prompt = result.prompt + '\n\n' + child.appendPrompt;
|
|
89881
|
+
}
|
|
89882
|
+
else {
|
|
89883
|
+
// If no parent prompt, appendPrompt becomes the prompt
|
|
89884
|
+
result.prompt = child.appendPrompt;
|
|
89885
|
+
}
|
|
89886
|
+
// Don't carry forward appendPrompt to avoid re-appending
|
|
89887
|
+
delete result.appendPrompt;
|
|
89888
|
+
}
|
|
89889
|
+
if (child.exec !== undefined)
|
|
89890
|
+
result.exec = child.exec;
|
|
89891
|
+
if (child.stdin !== undefined)
|
|
89892
|
+
result.stdin = child.stdin;
|
|
89893
|
+
if (child.url !== undefined)
|
|
89894
|
+
result.url = child.url;
|
|
89895
|
+
if (child.focus !== undefined)
|
|
89896
|
+
result.focus = child.focus;
|
|
89897
|
+
if (child.command !== undefined)
|
|
89898
|
+
result.command = child.command;
|
|
89899
|
+
if (child.ai_model !== undefined)
|
|
89900
|
+
result.ai_model = child.ai_model;
|
|
89901
|
+
if (child.ai_provider !== undefined)
|
|
89902
|
+
result.ai_provider = child.ai_provider;
|
|
89903
|
+
if (child.group !== undefined)
|
|
89904
|
+
result.group = child.group;
|
|
89905
|
+
if (child.schema !== undefined)
|
|
89906
|
+
result.schema = child.schema;
|
|
89907
|
+
if (child.if !== undefined)
|
|
89908
|
+
result.if = child.if;
|
|
89909
|
+
if (child.reuse_ai_session !== undefined)
|
|
89910
|
+
result.reuse_ai_session = child.reuse_ai_session;
|
|
89911
|
+
if (child.fail_if !== undefined)
|
|
89912
|
+
result.fail_if = child.fail_if;
|
|
89913
|
+
if (child.failure_conditions !== undefined)
|
|
89914
|
+
result.failure_conditions = child.failure_conditions;
|
|
89915
|
+
// Special handling for 'on' array
|
|
89916
|
+
if (child.on !== undefined) {
|
|
89917
|
+
if (Array.isArray(child.on) && child.on.length === 0) {
|
|
89918
|
+
// Empty array disables the check
|
|
89919
|
+
result.on = [];
|
|
89920
|
+
}
|
|
89921
|
+
else {
|
|
89922
|
+
// Replace parent's on array
|
|
89923
|
+
result.on = [...child.on];
|
|
89924
|
+
}
|
|
89925
|
+
}
|
|
89926
|
+
// Arrays that get replaced (not concatenated)
|
|
89927
|
+
if (child.triggers !== undefined) {
|
|
89928
|
+
result.triggers = child.triggers ? [...child.triggers] : undefined;
|
|
89929
|
+
}
|
|
89930
|
+
if (child.depends_on !== undefined) {
|
|
89931
|
+
result.depends_on = child.depends_on ? [...child.depends_on] : undefined;
|
|
89932
|
+
}
|
|
89933
|
+
// Deep merge objects
|
|
89934
|
+
if (child.env) {
|
|
89935
|
+
result.env = this.mergeObjects((parent.env || {}), child.env);
|
|
89936
|
+
}
|
|
89937
|
+
if (child.ai) {
|
|
89938
|
+
result.ai = this.mergeObjects((parent.ai || {}), child.ai);
|
|
89939
|
+
}
|
|
89940
|
+
if (child.template) {
|
|
89941
|
+
result.template = this.mergeObjects((parent.template || {}), child.template);
|
|
89942
|
+
}
|
|
89943
|
+
return result;
|
|
89944
|
+
}
|
|
89945
|
+
/**
|
|
89946
|
+
* Check if a check is disabled (has empty 'on' array)
|
|
89947
|
+
*/
|
|
89948
|
+
isCheckDisabled(check) {
|
|
89949
|
+
return Array.isArray(check.on) && check.on.length === 0;
|
|
89950
|
+
}
|
|
89951
|
+
/**
|
|
89952
|
+
* Remove disabled checks from the configuration
|
|
89953
|
+
*/
|
|
89954
|
+
removeDisabledChecks(config) {
|
|
89955
|
+
if (!config.checks)
|
|
89956
|
+
return config;
|
|
89957
|
+
const result = this.deepCopy(config);
|
|
89958
|
+
const enabledChecks = {};
|
|
89959
|
+
for (const [checkName, checkConfig] of Object.entries(result.checks)) {
|
|
89960
|
+
if (!this.isCheckDisabled(checkConfig)) {
|
|
89961
|
+
enabledChecks[checkName] = checkConfig;
|
|
89962
|
+
}
|
|
89963
|
+
else {
|
|
89964
|
+
console.log(`ℹ️ Check '${checkName}' is disabled (empty 'on' array)`);
|
|
89965
|
+
}
|
|
89966
|
+
}
|
|
89967
|
+
result.checks = enabledChecks;
|
|
89968
|
+
return result;
|
|
89969
|
+
}
|
|
89970
|
+
}
|
|
89971
|
+
exports.ConfigMerger = ConfigMerger;
|
|
89972
|
+
|
|
89973
|
+
|
|
89424
89974
|
/***/ }),
|
|
89425
89975
|
|
|
89426
89976
|
/***/ 58749:
|
|
@@ -109824,7 +110374,8 @@ ${fixedContent}
|
|
|
109824
110374
|
modifiedLine = modifiedLine.replace(/\[([^\]"]*\([^\]"]*)\]/g, (match, content) => {
|
|
109825
110375
|
if (!content.trim().startsWith('"') || !content.trim().endsWith('"')) {
|
|
109826
110376
|
wasFixed = true;
|
|
109827
|
-
|
|
110377
|
+
const safeContent = content.replace(/"/g, "'");
|
|
110378
|
+
return `["${safeContent}"]`;
|
|
109828
110379
|
}
|
|
109829
110380
|
return match;
|
|
109830
110381
|
});
|
|
@@ -109833,7 +110384,8 @@ ${fixedContent}
|
|
|
109833
110384
|
modifiedLine = modifiedLine.replace(/\{([^{}"]*\([^{}"]*)\}/g, (match, content) => {
|
|
109834
110385
|
if (!content.trim().startsWith('"') || !content.trim().endsWith('"')) {
|
|
109835
110386
|
wasFixed = true;
|
|
109836
|
-
|
|
110387
|
+
const safeContent = content.replace(/"/g, "'");
|
|
110388
|
+
return `{"${safeContent}"}`;
|
|
109837
110389
|
}
|
|
109838
110390
|
return match;
|
|
109839
110391
|
});
|
|
@@ -109904,6 +110456,8 @@ ${fixedContent}
|
|
|
109904
110456
|
};
|
|
109905
110457
|
}
|
|
109906
110458
|
}
|
|
110459
|
+
const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
|
|
110460
|
+
const updatedValidation = await validateMermaidResponse(fixedResponse);
|
|
109907
110461
|
if (debug) {
|
|
109908
110462
|
const stillInvalidAfterHtml2 = updatedValidation?.diagrams?.filter((d) => !d.isValid)?.length || invalidCount;
|
|
109909
110463
|
console.log(`[DEBUG] Mermaid validation: ${stillInvalidAfterHtml2} diagrams still invalid after HTML entity decoding, starting AI fixing...`);
|
|
@@ -109920,8 +110474,6 @@ ${fixedContent}
|
|
|
109920
110474
|
debug,
|
|
109921
110475
|
tracer
|
|
109922
110476
|
});
|
|
109923
|
-
const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
|
|
109924
|
-
const updatedValidation = await validateMermaidResponse(fixedResponse);
|
|
109925
110477
|
const stillInvalidDiagrams = updatedValidation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
|
|
109926
110478
|
if (debug) {
|
|
109927
110479
|
console.log(`[DEBUG] Mermaid validation: Found ${stillInvalidDiagrams.length} diagrams requiring AI fixing`);
|