valicode 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +58 -0
  2. package/bin/valicode.js +170 -0
  3. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # valicode
2
+
3
+ Official npm wrapper for the Valicode CLI.
4
+
5
+ Valicode audits AI-generated code before it reaches production. The npm package uses the same Python CLI published as `valicode` on PyPI, so npm and pip users get the same scan engine and API behavior.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 18+
10
+ - Python 3.10+
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g valicode
16
+ ```
17
+
18
+ Then run:
19
+
20
+ ```bash
21
+ valicode --version
22
+ valicode login
23
+ valicode scan .
24
+ ```
25
+
26
+ ## One-off use
27
+
28
+ ```bash
29
+ npx valicode scan . --output summary
30
+ ```
31
+
32
+ ## CI
33
+
34
+ ```bash
35
+ npx valicode scan . --fail-under 60 --format sarif > valicode.sarif
36
+ ```
37
+
38
+ ## How it works
39
+
40
+ This package provides the `valicode` command for npm users. On first run, it checks for Python 3.10 or newer, creates an isolated Python environment under your user cache directory, and installs/updates the PyPI package with:
41
+
42
+ ```bash
43
+ python -m pip install --upgrade valicode
44
+ ```
45
+
46
+ Then it runs the Python CLI module directly.
47
+
48
+ If automatic install is blocked by your Python environment, install Valicode manually:
49
+
50
+ ```bash
51
+ pipx install valicode
52
+ ```
53
+
54
+ or:
55
+
56
+ ```bash
57
+ python3 -m pip install --user valicode
58
+ ```
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("node:child_process");
4
+ const { existsSync, mkdirSync } = require("node:fs");
5
+ const { homedir } = require("node:os");
6
+ const { join } = require("node:path");
7
+
8
+ const MIN_PYTHON = { major: 3, minor: 10 };
9
+ const PYTHON_CANDIDATES = process.platform === "win32" ? ["py", "python3", "python"] : ["python3", "python"];
10
+
11
+ function run(command, args, options = {}) {
12
+ return spawnSync(command, args, {
13
+ encoding: "utf8",
14
+ stdio: options.stdio || "pipe",
15
+ env: process.env
16
+ });
17
+ }
18
+
19
+ function parseVersion(output) {
20
+ const match = output.match(/Python\s+(\d+)\.(\d+)\.(\d+)/);
21
+ if (!match) return null;
22
+ return {
23
+ major: Number(match[1]),
24
+ minor: Number(match[2]),
25
+ patch: Number(match[3])
26
+ };
27
+ }
28
+
29
+ function isSupportedPython(version) {
30
+ if (!version) return false;
31
+ if (version.major > MIN_PYTHON.major) return true;
32
+ return version.major === MIN_PYTHON.major && version.minor >= MIN_PYTHON.minor;
33
+ }
34
+
35
+ function findPython() {
36
+ for (const candidate of PYTHON_CANDIDATES) {
37
+ const args = candidate === "py" ? ["-3", "--version"] : ["--version"];
38
+ const result = run(candidate, args);
39
+ const version = parseVersion(`${result.stdout || ""}${result.stderr || ""}`);
40
+ if (result.status === 0 && isSupportedPython(version)) {
41
+ return {
42
+ command: candidate,
43
+ prefixArgs: candidate === "py" ? ["-3"] : [],
44
+ version
45
+ };
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+
51
+ function pythonArgs(python, args) {
52
+ return [...python.prefixArgs, ...args];
53
+ }
54
+
55
+ function hasValicodeModule(python) {
56
+ const result = run(python.command, pythonArgs(python, ["-c", "import aov.cli"]));
57
+ return result.status === 0;
58
+ }
59
+
60
+ function getCacheDir() {
61
+ if (process.env.VALICODE_NPM_CACHE_DIR) return process.env.VALICODE_NPM_CACHE_DIR;
62
+ const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
63
+ return join(base, "valicode", "npm-cli");
64
+ }
65
+
66
+ function getVenvPythonPath(venvDir) {
67
+ return process.platform === "win32" ? join(venvDir, "Scripts", "python.exe") : join(venvDir, "bin", "python");
68
+ }
69
+
70
+ function createVenv(systemPython, venvDir) {
71
+ mkdirSync(venvDir, { recursive: true });
72
+ return run(systemPython.command, pythonArgs(systemPython, ["-m", "venv", venvDir]), { stdio: "inherit" });
73
+ }
74
+
75
+ function venvPython(venvDir) {
76
+ return {
77
+ command: getVenvPythonPath(venvDir),
78
+ prefixArgs: []
79
+ };
80
+ }
81
+
82
+ function ensureVenv(systemPython) {
83
+ const venvDir = join(getCacheDir(), "python");
84
+ const pythonPath = getVenvPythonPath(venvDir);
85
+ if (!existsSync(pythonPath)) {
86
+ process.stderr.write(`Creating Valicode Python environment at ${venvDir}\n`);
87
+ const created = createVenv(systemPython, venvDir);
88
+ if (created.status !== 0 || !existsSync(pythonPath)) {
89
+ process.stderr.write(
90
+ [
91
+ "",
92
+ "Could not create a Python virtual environment for Valicode.",
93
+ "Install Python with venv support, then try again.",
94
+ "On Ubuntu/Debian: sudo apt install python3-venv",
95
+ "On macOS: install Python from python.org or Homebrew.",
96
+ ""
97
+ ].join("\n")
98
+ );
99
+ process.exit(created.status || 1);
100
+ }
101
+ }
102
+ return venvPython(venvDir);
103
+ }
104
+
105
+ function installValicode(python) {
106
+ process.stderr.write("Installing/updating Valicode CLI from PyPI...\n");
107
+ return run(
108
+ python.command,
109
+ pythonArgs(python, ["-m", "pip", "install", "--upgrade", "valicode"]),
110
+ { stdio: "inherit" }
111
+ );
112
+ }
113
+
114
+ function printPythonHelp() {
115
+ process.stderr.write(
116
+ [
117
+ "Valicode requires Python 3.10 or newer.",
118
+ "",
119
+ "Install Python with venv support, then run one of:",
120
+ " npm install -g valicode",
121
+ " pipx install valicode",
122
+ " python3 -m pip install valicode",
123
+ ""
124
+ ].join("\n")
125
+ );
126
+ }
127
+
128
+ function main() {
129
+ const python = findPython();
130
+ if (!python) {
131
+ printPythonHelp();
132
+ process.exit(1);
133
+ }
134
+
135
+ const runtimePython = ensureVenv(python);
136
+
137
+ if (!hasValicodeModule(runtimePython)) {
138
+ const install = installValicode(runtimePython);
139
+ if (install.status !== 0 || !hasValicodeModule(runtimePython)) {
140
+ process.stderr.write(
141
+ [
142
+ "",
143
+ "Could not install the Valicode Python CLI automatically.",
144
+ "Try: pipx install valicode",
145
+ "Or install directly with pip inside your own virtual environment.",
146
+ ""
147
+ ].join("\n")
148
+ );
149
+ process.exit(install.status || 1);
150
+ }
151
+ }
152
+
153
+ const result = spawnSync(
154
+ runtimePython.command,
155
+ pythonArgs(runtimePython, [
156
+ "-c",
157
+ "import sys; sys.argv[0] = 'valicode'; from aov.cli import cli; cli()",
158
+ ...process.argv.slice(2)
159
+ ]),
160
+ { stdio: "inherit", env: process.env }
161
+ );
162
+
163
+ if (result.error) {
164
+ process.stderr.write(`${result.error.message}\n`);
165
+ process.exit(1);
166
+ }
167
+ process.exit(result.status ?? 0);
168
+ }
169
+
170
+ main();
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "valicode",
3
+ "version": "2.0.0",
4
+ "description": "Valicode CLI npm wrapper",
5
+ "license": "UNLICENSED",
6
+ "homepage": "https://valicode.sbs",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/in1v/valicode.git",
10
+ "directory": "packages/npm-cli"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/in1v/valicode-cli/issues"
14
+ },
15
+ "keywords": [
16
+ "ai",
17
+ "code-review",
18
+ "security",
19
+ "cli",
20
+ "github",
21
+ "valicode"
22
+ ],
23
+ "bin": {
24
+ "valicode": "bin/valicode.js"
25
+ },
26
+ "files": [
27
+ "bin",
28
+ "README.md"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "test": "node test/smoke.js"
38
+ }
39
+ }