@vibecheckai/cli 3.9.0 → 3.9.1
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.
|
@@ -97,6 +97,22 @@ const sym = {
|
|
|
97
97
|
arrow: "→",
|
|
98
98
|
arrowRight: "▸",
|
|
99
99
|
star: "★",
|
|
100
|
+
// Aliases for compatibility
|
|
101
|
+
success: "✓",
|
|
102
|
+
error: "✗",
|
|
103
|
+
// Additional symbols
|
|
104
|
+
key: "🔑",
|
|
105
|
+
list: "📋",
|
|
106
|
+
badge: "🏅",
|
|
107
|
+
timer: "⏱️",
|
|
108
|
+
add: "+",
|
|
109
|
+
remove: "-",
|
|
110
|
+
person: "👤",
|
|
111
|
+
expired: "⌛",
|
|
112
|
+
review: "👁️",
|
|
113
|
+
pending: "○",
|
|
114
|
+
active: "●",
|
|
115
|
+
skip: "↷",
|
|
100
116
|
// Progress
|
|
101
117
|
blockFull: "█",
|
|
102
118
|
blockMed: "▓",
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck ci - Enterprise CI Wiring
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* One-command enterprise CI wiring for GitHub Actions
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Auto-detects your stack (Next.js, Fastify, etc.), package manager, Node version,
|
|
9
|
+
* and creates optimized workflows with SARIF output, PR comments, and status checks.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const { parseGlobalFlags, shouldSuppressOutput, isJsonMode } = require("./lib/global-flags");
|
|
17
|
+
const { EXIT } = require("./lib/exit-codes");
|
|
18
|
+
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
// CLI OUTPUT
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
let _cliOutput = null;
|
|
24
|
+
function getCliOutput() {
|
|
25
|
+
if (!_cliOutput) _cliOutput = require("./lib/unified-cli-output");
|
|
26
|
+
return _cliOutput;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
30
|
+
// DETECTION UTILITIES
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
|
|
33
|
+
function detectPackageManager(projectPath) {
|
|
34
|
+
if (fs.existsSync(path.join(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
35
|
+
if (fs.existsSync(path.join(projectPath, "yarn.lock"))) return "yarn";
|
|
36
|
+
if (fs.existsSync(path.join(projectPath, "bun.lockb"))) return "bun";
|
|
37
|
+
return "npm";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function detectNodeVersion(projectPath) {
|
|
41
|
+
// Try .nvmrc
|
|
42
|
+
const nvmrcPath = path.join(projectPath, ".nvmrc");
|
|
43
|
+
if (fs.existsSync(nvmrcPath)) {
|
|
44
|
+
const version = fs.readFileSync(nvmrcPath, "utf-8").trim();
|
|
45
|
+
return version.replace(/^v/, "");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Try package.json engines
|
|
49
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
50
|
+
if (fs.existsSync(pkgPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
53
|
+
if (pkg.engines?.node) {
|
|
54
|
+
const match = pkg.engines.node.match(/(\d+)/);
|
|
55
|
+
if (match) return match[1];
|
|
56
|
+
}
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return "20"; // Default to LTS
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function detectFramework(projectPath) {
|
|
64
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
65
|
+
if (!fs.existsSync(pkgPath)) return "unknown";
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
69
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
70
|
+
|
|
71
|
+
if (deps["next"]) return "nextjs";
|
|
72
|
+
if (deps["@fastify/core"] || deps["fastify"]) return "fastify";
|
|
73
|
+
if (deps["express"]) return "express";
|
|
74
|
+
if (deps["@nestjs/core"]) return "nestjs";
|
|
75
|
+
if (deps["vite"]) return "vite";
|
|
76
|
+
if (deps["nuxt"]) return "nuxt";
|
|
77
|
+
if (deps["remix"]) return "remix";
|
|
78
|
+
if (deps["@sveltejs/kit"]) return "sveltekit";
|
|
79
|
+
|
|
80
|
+
return "generic";
|
|
81
|
+
} catch {
|
|
82
|
+
return "unknown";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
87
|
+
// WORKFLOW GENERATION
|
|
88
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
89
|
+
|
|
90
|
+
function generateGitHubActionsWorkflow(options) {
|
|
91
|
+
const { packageManager, nodeVersion, framework, full } = options;
|
|
92
|
+
|
|
93
|
+
const installCmd = {
|
|
94
|
+
npm: "npm ci",
|
|
95
|
+
yarn: "yarn install --frozen-lockfile",
|
|
96
|
+
pnpm: "pnpm install --frozen-lockfile",
|
|
97
|
+
bun: "bun install --frozen-lockfile",
|
|
98
|
+
}[packageManager];
|
|
99
|
+
|
|
100
|
+
const workflow = `# Generated by vibecheck ci
|
|
101
|
+
# https://docs.vibecheckai.dev/ci
|
|
102
|
+
|
|
103
|
+
name: VibeCheck
|
|
104
|
+
|
|
105
|
+
on:
|
|
106
|
+
push:
|
|
107
|
+
branches: [main, master, develop]
|
|
108
|
+
pull_request:
|
|
109
|
+
branches: [main, master, develop]
|
|
110
|
+
workflow_dispatch:
|
|
111
|
+
|
|
112
|
+
permissions:
|
|
113
|
+
contents: read
|
|
114
|
+
security-events: write
|
|
115
|
+
pull-requests: write
|
|
116
|
+
|
|
117
|
+
jobs:
|
|
118
|
+
vibecheck:
|
|
119
|
+
name: Security & Quality Scan
|
|
120
|
+
runs-on: ubuntu-latest
|
|
121
|
+
steps:
|
|
122
|
+
- name: Checkout
|
|
123
|
+
uses: actions/checkout@v4
|
|
124
|
+
with:
|
|
125
|
+
fetch-depth: 0
|
|
126
|
+
|
|
127
|
+
- name: Setup Node.js
|
|
128
|
+
uses: actions/setup-node@v4
|
|
129
|
+
with:
|
|
130
|
+
node-version: '${nodeVersion}'
|
|
131
|
+
cache: '${packageManager === "yarn" ? "yarn" : packageManager === "pnpm" ? "pnpm" : "npm"}'
|
|
132
|
+
|
|
133
|
+
- name: Install dependencies
|
|
134
|
+
run: ${installCmd}
|
|
135
|
+
|
|
136
|
+
- name: Install VibeCheck CLI
|
|
137
|
+
run: npm install -g @vibecheckai/cli
|
|
138
|
+
|
|
139
|
+
- name: Run VibeCheck Audit
|
|
140
|
+
id: audit
|
|
141
|
+
run: vibecheck audit --ci --sarif --output vibecheck-results.sarif
|
|
142
|
+
env:
|
|
143
|
+
VIBECHECK_API_KEY: \${{ secrets.VIBECHECK_API_KEY }}
|
|
144
|
+
continue-on-error: true
|
|
145
|
+
|
|
146
|
+
- name: Upload SARIF to GitHub
|
|
147
|
+
if: always()
|
|
148
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
149
|
+
with:
|
|
150
|
+
sarif_file: vibecheck-results.sarif
|
|
151
|
+
category: vibecheck
|
|
152
|
+
|
|
153
|
+
- name: Run VibeCheck Ship
|
|
154
|
+
if: github.event_name == 'pull_request'
|
|
155
|
+
run: vibecheck ship --ci --json > ship-verdict.json
|
|
156
|
+
env:
|
|
157
|
+
VIBECHECK_API_KEY: \${{ secrets.VIBECHECK_API_KEY }}
|
|
158
|
+
continue-on-error: true
|
|
159
|
+
|
|
160
|
+
- name: Comment PR with Verdict
|
|
161
|
+
if: github.event_name == 'pull_request'
|
|
162
|
+
uses: actions/github-script@v7
|
|
163
|
+
with:
|
|
164
|
+
script: |
|
|
165
|
+
const fs = require('fs');
|
|
166
|
+
let verdict = { verdict: 'UNKNOWN', score: 0 };
|
|
167
|
+
try {
|
|
168
|
+
verdict = JSON.parse(fs.readFileSync('ship-verdict.json', 'utf8'));
|
|
169
|
+
} catch (e) {}
|
|
170
|
+
|
|
171
|
+
const emoji = verdict.verdict === 'SHIP' ? '🚀' : verdict.verdict === 'WARN' ? '⚠️' : '🚫';
|
|
172
|
+
const body = \`## \${emoji} VibeCheck Verdict: **\${verdict.verdict}**
|
|
173
|
+
|
|
174
|
+
Score: \${verdict.score}/100
|
|
175
|
+
|
|
176
|
+
[View full report](https://vibecheckai.dev/reports/\${context.repo.owner}/\${context.repo.repo}/\${context.sha})
|
|
177
|
+
\`;
|
|
178
|
+
|
|
179
|
+
github.rest.issues.createComment({
|
|
180
|
+
issue_number: context.issue.number,
|
|
181
|
+
owner: context.repo.owner,
|
|
182
|
+
repo: context.repo.repo,
|
|
183
|
+
body: body
|
|
184
|
+
});
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
return workflow;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
191
|
+
// HELP
|
|
192
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
193
|
+
|
|
194
|
+
function printHelp() {
|
|
195
|
+
const { ansi, sym, renderMinimalHeader } = getCliOutput();
|
|
196
|
+
|
|
197
|
+
renderMinimalHeader("ci", "free");
|
|
198
|
+
|
|
199
|
+
console.log(`${ansi.bold}vibecheck ci${ansi.reset} - One-command enterprise CI wiring
|
|
200
|
+
|
|
201
|
+
${ansi.bold}USAGE${ansi.reset}
|
|
202
|
+
vibecheck ci [options]
|
|
203
|
+
|
|
204
|
+
${ansi.bold}OPTIONS${ansi.reset}
|
|
205
|
+
${ansi.cyan}--dry-run${ansi.reset} Preview without creating files
|
|
206
|
+
${ansi.cyan}--full${ansi.reset} Create all workflows (audit, ship, e2e, security)
|
|
207
|
+
${ansi.cyan}--validate${ansi.reset} Validate existing workflows
|
|
208
|
+
${ansi.cyan}--provider${ansi.reset} CI provider: github, gitlab, azure (default: github)
|
|
209
|
+
${ansi.cyan}--json${ansi.reset} Output as JSON
|
|
210
|
+
${ansi.cyan}--path <dir>${ansi.reset} Project path
|
|
211
|
+
|
|
212
|
+
${ansi.bold}EXAMPLES${ansi.reset}
|
|
213
|
+
${ansi.dim}# Auto-detect and create CI workflows${ansi.reset}
|
|
214
|
+
vibecheck ci
|
|
215
|
+
|
|
216
|
+
${ansi.dim}# Preview without creating files${ansi.reset}
|
|
217
|
+
vibecheck ci --dry-run
|
|
218
|
+
|
|
219
|
+
${ansi.dim}# Create all workflows${ansi.reset}
|
|
220
|
+
vibecheck ci --full
|
|
221
|
+
|
|
222
|
+
${ansi.dim}# Validate existing workflows${ansi.reset}
|
|
223
|
+
vibecheck ci --validate
|
|
224
|
+
`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
228
|
+
// MAIN RUNNER
|
|
229
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
230
|
+
|
|
231
|
+
async function runCI(args = [], context = {}) {
|
|
232
|
+
const { flags, cleanArgs } = parseGlobalFlags(args);
|
|
233
|
+
const quiet = shouldSuppressOutput(flags);
|
|
234
|
+
const json = isJsonMode(flags);
|
|
235
|
+
const projectPath = flags.path || context.repoRoot || process.cwd();
|
|
236
|
+
|
|
237
|
+
// Handle help
|
|
238
|
+
if (flags.help) {
|
|
239
|
+
printHelp();
|
|
240
|
+
return EXIT.SUCCESS;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const dryRun = args.includes("--dry-run");
|
|
244
|
+
const full = args.includes("--full");
|
|
245
|
+
const validate = args.includes("--validate");
|
|
246
|
+
const provider = args.find(a => a.startsWith("--provider="))?.split("=")[1] || "github";
|
|
247
|
+
|
|
248
|
+
const { ansi, sym, renderMinimalHeader, renderSuccess, renderWarning, renderError } = getCliOutput();
|
|
249
|
+
|
|
250
|
+
if (!quiet && !json) {
|
|
251
|
+
renderMinimalHeader("ci", "free");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
// Detect project settings
|
|
256
|
+
const packageManager = detectPackageManager(projectPath);
|
|
257
|
+
const nodeVersion = detectNodeVersion(projectPath);
|
|
258
|
+
const framework = detectFramework(projectPath);
|
|
259
|
+
|
|
260
|
+
if (!quiet && !json) {
|
|
261
|
+
console.log(` ${ansi.dim}Detected:${ansi.reset}`);
|
|
262
|
+
console.log(` Package Manager: ${ansi.cyan}${packageManager}${ansi.reset}`);
|
|
263
|
+
console.log(` Node Version: ${ansi.cyan}${nodeVersion}${ansi.reset}`);
|
|
264
|
+
console.log(` Framework: ${ansi.cyan}${framework}${ansi.reset}`);
|
|
265
|
+
console.log();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Handle validate
|
|
269
|
+
if (validate) {
|
|
270
|
+
const workflowDir = path.join(projectPath, ".github", "workflows");
|
|
271
|
+
const vibecheckWorkflow = path.join(workflowDir, "vibecheck.yml");
|
|
272
|
+
|
|
273
|
+
if (fs.existsSync(vibecheckWorkflow)) {
|
|
274
|
+
renderSuccess("vibecheck.yml workflow exists");
|
|
275
|
+
return EXIT.SUCCESS;
|
|
276
|
+
} else {
|
|
277
|
+
renderWarning("vibecheck.yml workflow not found");
|
|
278
|
+
console.log(` ${ansi.dim}Run 'vibecheck ci' to create it${ansi.reset}\n`);
|
|
279
|
+
return EXIT.NOT_FOUND || 4;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Generate workflow
|
|
284
|
+
const workflow = generateGitHubActionsWorkflow({
|
|
285
|
+
packageManager,
|
|
286
|
+
nodeVersion,
|
|
287
|
+
framework,
|
|
288
|
+
full,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (dryRun) {
|
|
292
|
+
if (json) {
|
|
293
|
+
console.log(JSON.stringify({
|
|
294
|
+
success: true,
|
|
295
|
+
dryRun: true,
|
|
296
|
+
provider,
|
|
297
|
+
detection: { packageManager, nodeVersion, framework },
|
|
298
|
+
workflow: workflow,
|
|
299
|
+
}, null, 2));
|
|
300
|
+
} else {
|
|
301
|
+
console.log(` ${ansi.yellow}${sym.warning}${ansi.reset} ${ansi.bold}DRY RUN${ansi.reset} - No files created\n`);
|
|
302
|
+
console.log(` ${ansi.dim}Would create:${ansi.reset} .github/workflows/vibecheck.yml\n`);
|
|
303
|
+
console.log(` ${ansi.dim}Preview:${ansi.reset}`);
|
|
304
|
+
console.log(` ${ansi.dim}${"─".repeat(60)}${ansi.reset}`);
|
|
305
|
+
workflow.split("\n").slice(0, 20).forEach(line => {
|
|
306
|
+
console.log(` ${ansi.dim}${line}${ansi.reset}`);
|
|
307
|
+
});
|
|
308
|
+
console.log(` ${ansi.dim}... (${workflow.split("\n").length - 20} more lines)${ansi.reset}`);
|
|
309
|
+
console.log(` ${ansi.dim}${"─".repeat(60)}${ansi.reset}\n`);
|
|
310
|
+
}
|
|
311
|
+
return EXIT.SUCCESS;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Create workflow file
|
|
315
|
+
const workflowDir = path.join(projectPath, ".github", "workflows");
|
|
316
|
+
const workflowPath = path.join(workflowDir, "vibecheck.yml");
|
|
317
|
+
|
|
318
|
+
// Create directories
|
|
319
|
+
fs.mkdirSync(workflowDir, { recursive: true });
|
|
320
|
+
|
|
321
|
+
// Write workflow
|
|
322
|
+
fs.writeFileSync(workflowPath, workflow, "utf-8");
|
|
323
|
+
|
|
324
|
+
if (json) {
|
|
325
|
+
console.log(JSON.stringify({
|
|
326
|
+
success: true,
|
|
327
|
+
created: [workflowPath],
|
|
328
|
+
provider,
|
|
329
|
+
detection: { packageManager, nodeVersion, framework },
|
|
330
|
+
}, null, 2));
|
|
331
|
+
} else {
|
|
332
|
+
renderSuccess(`Created .github/workflows/vibecheck.yml`);
|
|
333
|
+
console.log();
|
|
334
|
+
console.log(` ${ansi.bold}Next steps:${ansi.reset}`);
|
|
335
|
+
console.log(` 1. Add ${ansi.cyan}VIBECHECK_API_KEY${ansi.reset} to your repository secrets`);
|
|
336
|
+
console.log(` 2. Push to trigger the workflow`);
|
|
337
|
+
console.log();
|
|
338
|
+
console.log(` ${ansi.dim}Get your API key: https://vibecheckai.dev/settings/keys${ansi.reset}\n`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return EXIT.SUCCESS;
|
|
342
|
+
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (json) {
|
|
345
|
+
console.log(JSON.stringify({ success: false, error: error.message }));
|
|
346
|
+
} else {
|
|
347
|
+
renderError(`CI setup failed: ${error.message}`);
|
|
348
|
+
}
|
|
349
|
+
return EXIT.INTERNAL_ERROR || 10;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports = { runCI };
|
|
@@ -34,8 +34,8 @@ const {
|
|
|
34
34
|
AUTO_REASONS,
|
|
35
35
|
} = require("./lib/checkpoint");
|
|
36
36
|
|
|
37
|
-
const { parseGlobalFlags, shouldSuppressOutput, isJsonMode } = require("./lib/
|
|
38
|
-
const {
|
|
37
|
+
const { parseGlobalFlags, shouldSuppressOutput, isJsonMode } = require("./lib/global-flags");
|
|
38
|
+
const { c: colors, sym: symbols, box: renderBox, table: renderTable } = require("./lib/ui");
|
|
39
39
|
|
|
40
40
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
41
41
|
// EXIT CODES
|