osv-security-cli 0.1.0 → 0.1.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.
@@ -25,6 +25,7 @@ var RuntimeConfigSchema = z.object({
25
25
  package_manager_js: z.string(),
26
26
  execution: z.enum(["docker", "local"]),
27
27
  docker_service: z.string(),
28
+ docker_workdir: z.string().optional(),
28
29
  test_command: z.string(),
29
30
  build_commands: z.object({
30
31
  frontend: z.string(),
@@ -82,7 +83,7 @@ var GateValidationError = class extends Error {
82
83
  };
83
84
 
84
85
  // src/config/loader.ts
85
- var DEFAULT_CONFIG_PATH = ".github/agents/project-config.yml";
86
+ var DEFAULT_CONFIG_PATH = "project-config.yml";
86
87
  async function loadConfig(configPath, cwd = process.cwd()) {
87
88
  const absolutePath = resolve(cwd, configPath);
88
89
  let raw;
@@ -116,70 +117,66 @@ ${issues}`,
116
117
  }
117
118
 
118
119
  // src/config/generator.ts
119
- import { stringify } from "yaml";
120
120
  function generateConfigYaml(opts = {}) {
121
- const config = {
122
- project: {
123
- name: opts.projectName ?? "My Laravel Project",
124
- client: opts.client ?? "Client Name"
125
- },
126
- runtime: {
127
- php: opts.phpVersion ?? "8.2",
128
- laravel: opts.laravelVersion ?? "10.x",
129
- node: opts.nodeVersion ?? "20.x",
130
- package_manager_php: "composer",
131
- package_manager_js: "npm",
132
- execution: opts.execution ?? "docker",
133
- docker_service: opts.dockerService ?? "app",
134
- test_command: opts.testCommand ?? "php artisan test --compact",
135
- build_commands: {
136
- frontend: opts.frontendBuildCommand ?? "npm run development-frontend",
137
- backend: opts.backendBuildCommand ?? "npm run development-backend"
138
- }
139
- },
140
- protected_packages: {
141
- composer: [
142
- {
143
- package: "laravel/framework",
144
- constraint: "^10.0",
145
- reason: "Major upgrade to Laravel 11 requires a dedicated project"
146
- },
147
- {
148
- package: "livewire/livewire",
149
- constraint: "^2.12",
150
- reason: "Livewire 3 has breaking API changes; do not migrate without a project"
151
- }
152
- ],
153
- npm: [
154
- {
155
- package: "alpinejs",
156
- constraint: "^3.10.2",
157
- reason: "Alpine v4 may have breaking syntax changes"
158
- },
159
- {
160
- package: "tailwindcss",
161
- constraint: "^3.3.3",
162
- reason: "Tailwind v4 has breaking config and migration requirements"
163
- }
164
- ]
165
- },
166
- safe_update_policy: {
167
- allow_patch_and_minor_within_constraints: true,
168
- require_authorization_for_constraint_change: true,
169
- authorization_format: "sim, confirmo breaking changes para [vendor/pacote]"
170
- },
171
- conflict_resolution: "stop_and_ask"
172
- };
173
- const header = [
174
- "# OSV Security CLI \u2014 project configuration",
175
- "# Generated by: osv-security init",
176
- "# Documentation: https://github.com/google/osv-scanner",
177
- "#",
178
- "# - protected_packages: packages that must never be auto-upgraded beyond their constraint",
179
- "# - safe_update_policy: patch/minor within constraints are auto-safe; constraint changes require authorization",
180
- ""
181
- ].join("\n");
182
- return header + stringify(config, { lineWidth: 0 });
121
+ const projectName = opts.projectName ?? "My Laravel Project";
122
+ const client = opts.client ?? "Client Name";
123
+ const execution = opts.execution ?? "docker";
124
+ const dockerService = opts.dockerService ?? "app";
125
+ const dockerWorkdir = opts.dockerWorkdir;
126
+ const phpVersion = opts.phpVersion ?? "8.2";
127
+ const laravelVersion = opts.laravelVersion ?? "10.x";
128
+ const nodeVersion = opts.nodeVersion ?? "20.x";
129
+ const testCommand = opts.testCommand ?? "php artisan test --compact";
130
+ const frontendBuild = opts.frontendBuildCommand ?? "npm run development-frontend";
131
+ const backendBuild = opts.backendBuildCommand ?? "npm run development-backend";
132
+ const workdirLine = dockerWorkdir ? `
133
+ docker_workdir: '${dockerWorkdir}'` : "";
134
+ return `# OSV Security CLI \u2014 project configuration
135
+ # Generated by: osv-security init
136
+ # Documentation: https://github.com/google/osv-scanner
137
+ #
138
+ # protected_packages: packages that must NEVER be auto-upgraded beyond their stated constraint.
139
+ # Any update requiring a constraint change needs explicit per-package authorization.
140
+ # safe_update_policy: patch/minor updates within existing constraints are auto-safe.
141
+
142
+ project:
143
+ name: '${projectName}'
144
+ client: '${client}'
145
+
146
+ runtime:
147
+ php: '${phpVersion}'
148
+ laravel: '${laravelVersion}'
149
+ node: '${nodeVersion}'
150
+ package_manager_php: 'composer'
151
+ package_manager_js: 'npm'
152
+ execution: '${execution}'
153
+ docker_service: '${dockerService}'${workdirLine}
154
+ test_command: '${testCommand}'
155
+ build_commands:
156
+ frontend: '${frontendBuild}'
157
+ backend: '${backendBuild}'
158
+
159
+ # Add packages that must not be auto-upgraded beyond their constraint.
160
+ # Examples:
161
+ # composer:
162
+ # - package: 'laravel/framework'
163
+ # constraint: '^10.0'
164
+ # reason: 'Major upgrade to Laravel 11 requires a dedicated project'
165
+ # npm:
166
+ # - package: 'tailwindcss'
167
+ # constraint: '^3.3.3'
168
+ # reason: 'Tailwind v4 has breaking config changes'
169
+ protected_packages:
170
+ composer: []
171
+ npm: []
172
+
173
+ safe_update_policy:
174
+ allow_patch_and_minor_within_constraints: true
175
+ require_authorization_for_constraint_change: true
176
+ authorization_format: 'sim, confirmo breaking changes para [vendor/pacote]'
177
+
178
+ conflict_resolution: 'stop_and_ask'
179
+ `;
183
180
  }
184
181
 
185
182
  // src/executor/local-executor.ts
@@ -233,19 +230,19 @@ var DockerExecutor = class {
233
230
  constructor(service, options = {}) {
234
231
  this.service = service;
235
232
  this.dryRun = options.dryRun ?? false;
233
+ this.workdir = options.workdir;
236
234
  }
237
235
  dryRun;
238
236
  environment = "docker";
237
+ workdir;
238
+ buildDockerCommand(command) {
239
+ const workdirFlag = this.workdir ? `--workdir ${this.workdir} ` : "";
240
+ return `docker-compose exec -T ${workdirFlag}${this.service} sh -c "${command.replace(/"/g, '\\"')}"`;
241
+ }
239
242
  async run(command, options = {}) {
240
- const dockerCommand = `docker-compose exec -T ${this.service} sh -c "${command.replace(/"/g, '\\"')}"`;
243
+ const dockerCommand = this.buildDockerCommand(command);
241
244
  if (this.dryRun) {
242
- return {
243
- stdout: "",
244
- stderr: "",
245
- exitCode: 0,
246
- command: dockerCommand,
247
- dryRun: true
248
- };
245
+ return { stdout: "", stderr: "", exitCode: 0, command: dockerCommand, dryRun: true };
249
246
  }
250
247
  try {
251
248
  const result = await execa2(dockerCommand, {
@@ -313,7 +310,7 @@ var logger = {
313
310
  };
314
311
 
315
312
  // src/environment/detector.ts
316
- async function detectEnvironment(preferredEnv, dockerService, cwd, dryRun = false) {
313
+ async function detectEnvironment(preferredEnv, dockerService, cwd, dryRun = false, dockerWorkdir) {
317
314
  if (preferredEnv === "local") {
318
315
  logger.debug("Using local execution (configured preference)");
319
316
  return new LocalExecutor({ dryRun });
@@ -324,8 +321,9 @@ async function detectEnvironment(preferredEnv, dockerService, cwd, dryRun = fals
324
321
  { cwd }
325
322
  );
326
323
  if (result.exitCode === 0 && parseInt(result.stdout.trim(), 10) > 0) {
327
- logger.debug(`Using Docker execution (service: ${dockerService})`);
328
- return new DockerExecutor(dockerService, { dryRun });
324
+ const workdirInfo = dockerWorkdir ? ` (workdir: ${dockerWorkdir})` : "";
325
+ logger.debug(`Using Docker execution (service: ${dockerService}${workdirInfo})`);
326
+ return new DockerExecutor(dockerService, { dryRun, workdir: dockerWorkdir });
329
327
  }
330
328
  logger.warn(`Docker service "${dockerService}" not running \u2014 falling back to local execution`);
331
329
  return new LocalExecutor({ dryRun });
@@ -1085,11 +1083,24 @@ function executiveReportFilename(client, project) {
1085
1083
  return `[${client} ${project}] Report OSV Scanner - ${year}-${month} - ${monthEn}.md`;
1086
1084
  }
1087
1085
 
1086
+ // src/utils/prompt.ts
1087
+ import { createInterface } from "readline/promises";
1088
+ async function prompt(question, defaultValue) {
1089
+ const hint = defaultValue ? ` (default: ${defaultValue})` : "";
1090
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1091
+ try {
1092
+ const answer = await rl.question(`${question}${hint}: `);
1093
+ return answer.trim() || defaultValue || "";
1094
+ } finally {
1095
+ rl.close();
1096
+ }
1097
+ }
1098
+
1088
1099
  // bin/osv-security.ts
1089
1100
  var program = new Command();
1090
1101
  program.name("osv-security").description("OSV vulnerability scanning and safe dependency update CLI").version("0.1.0");
1091
1102
  var commonOptions = (cmd) => cmd.option("-c, --config <path>", "Path to project-config.yml", DEFAULT_CONFIG_PATH).option("--cwd <path>", "Working directory", process.cwd()).option("--dry-run", "Show commands without executing", false).option("-v, --verbose", "Verbose output", false).option("-q, --quiet", "Suppress all output except errors and final report", false).option("--json", "Output results as JSON", false).option("-o, --output <path>", "Write report to file");
1092
- program.command("init").description("Generate a project-config.yml template in the current project").option("--project-name <name>", "Project name").option("--client <name>", "Client name").option("--execution <mode>", "Execution mode: docker or local", "docker").option("--docker-service <service>", "Docker Compose service name", "app").option("--php-version <version>", "PHP version", "8.2").option("--laravel-version <version>", "Laravel version", "10.x").option("--node-version <version>", "Node.js version", "20.x").option("--test-command <cmd>", "Test command", "php artisan test --compact").option("--cwd <path>", "Working directory", process.cwd()).option("--output <path>", "Output path (default: .github/agents/project-config.yml)").option("--force", "Overwrite existing file", false).action(async (opts) => {
1103
+ program.command("init").description("Generate a project-config.yml template in the current project").option("--project-name <name>", "Project name").option("--client <name>", "Client name").option("--execution <mode>", "Execution mode: docker or local", "docker").option("--docker-service <service>", "Docker Compose service name", "app").option("--docker-workdir <path>", "Working directory inside the container (e.g. /var/www/html)").option("--php-version <version>", "PHP version", "8.2").option("--laravel-version <version>", "Laravel version", "10.x").option("--node-version <version>", "Node.js version", "20.x").option("--test-command <cmd>", "Test command", "php artisan test --compact").option("--cwd <path>", "Working directory", process.cwd()).option("--output <path>", "Output path (default: .github/agents/project-config.yml)").option("--force", "Overwrite existing file", false).action(async (opts) => {
1093
1104
  const { access, mkdir } = await import("fs/promises");
1094
1105
  const { dirname } = await import("path");
1095
1106
  const outputPath = opts.output ? resolve2(opts.cwd, opts.output) : resolve2(opts.cwd, DEFAULT_CONFIG_PATH);
@@ -1105,11 +1116,14 @@ Use --force to overwrite.
1105
1116
  } catch {
1106
1117
  }
1107
1118
  }
1119
+ const projectName = opts.projectName ?? await prompt("Project name", "My Laravel Project");
1120
+ const client = opts.client ?? await prompt("Client name", "Client Name");
1108
1121
  const yaml = generateConfigYaml({
1109
- projectName: opts.projectName,
1110
- client: opts.client,
1122
+ projectName,
1123
+ client,
1111
1124
  execution: opts.execution,
1112
1125
  dockerService: opts.dockerService,
1126
+ dockerWorkdir: opts.dockerWorkdir,
1113
1127
  phpVersion: opts.phpVersion,
1114
1128
  laravelVersion: opts.laravelVersion,
1115
1129
  nodeVersion: opts.nodeVersion,
@@ -1127,6 +1141,8 @@ Next steps:
1127
1141
  process.stdout.write(` 2. Review protected_packages \u2014 add any packages that must not be auto-upgraded
1128
1142
  `);
1129
1143
  process.stdout.write(` 3. Run: osv-security scan --cwd <your-project-dir>
1144
+ `);
1145
+ process.stdout.write(` (config will be loaded from project-config.yml at project root by default)
1130
1146
  `);
1131
1147
  });
1132
1148
  commonOptions(
@@ -1154,7 +1170,8 @@ async function runCommand(command, opts) {
1154
1170
  config.runtime.execution,
1155
1171
  config.runtime.docker_service,
1156
1172
  opts.cwd,
1157
- opts.dryRun
1173
+ opts.dryRun,
1174
+ config.runtime.docker_workdir
1158
1175
  );
1159
1176
  if (command === "scan") {
1160
1177
  const scanResult = await runScanner(runner, config, opts.cwd);