@prodcycle/prodcycle 0.6.0 → 0.6.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.
- package/dist/cli.d.ts +37 -0
- package/dist/cli.js +53 -5
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -13,3 +13,40 @@
|
|
|
13
13
|
* by hand.
|
|
14
14
|
*/
|
|
15
15
|
export declare function isCiEnvironment(env?: NodeJS.ProcessEnv): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the files to scan for a `hook` invocation. Supports:
|
|
18
|
+
* - `--file <path>` — read that file from disk
|
|
19
|
+
* - stdin: `{"files": {path: content}}` (same as gate)
|
|
20
|
+
* - stdin: `{"file_path": "...", "content": "..."}` (single file)
|
|
21
|
+
* - stdin: Claude Code PostToolUse shape —
|
|
22
|
+
* `{"tool_input": {"file_path": "...", "content"|"new_string": "..."}}`
|
|
23
|
+
* When only `file_path` is given and we can read the file, we do.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Convert the user-supplied `--file <path>` value into a repo-relative key.
|
|
27
|
+
*
|
|
28
|
+
* The compliance API rejects absolute paths (`File path must be relative`).
|
|
29
|
+
* Two failure modes the naive implementation hit on macOS:
|
|
30
|
+
*
|
|
31
|
+
* 1. Absolute paths under cwd silently became cryptic 400s. Fixed by
|
|
32
|
+
* `path.relative(cwd, absolute)` — works on Linux.
|
|
33
|
+
* 2. macOS `/tmp` is a symlink to `/private/tmp`. `path.resolve()` does
|
|
34
|
+
* NOT follow symlinks, but `process.cwd()` returns the physical path
|
|
35
|
+
* via the kernel's `getcwd()`. Result: `path.resolve('/tmp/repo/x')`
|
|
36
|
+
* = `/tmp/repo/x` while cwd is `/private/tmp/repo`, so the relative
|
|
37
|
+
* path is `../../../tmp/repo/x` and the file is incorrectly rejected
|
|
38
|
+
* as "outside cwd" — exactly the agent-hook scenario this targets.
|
|
39
|
+
* Fix: realpath both sides before comparing.
|
|
40
|
+
*
|
|
41
|
+
* Pure function (no fs I/O of its own, no `process.exit`) so it's directly
|
|
42
|
+
* unit-testable. Caller passes the original input, the realpath of the
|
|
43
|
+
* resolved file (`fs.realpathSync(path.resolve(filePath))`), and the
|
|
44
|
+
* realpath of cwd. Tests construct realpath inputs themselves.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveHookFileKey(inputPath: string, realpathFile: string, realpathCwd: string): {
|
|
47
|
+
ok: true;
|
|
48
|
+
key: string;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.isCiEnvironment = isCiEnvironment;
|
|
38
|
+
exports.resolveHookFileKey = resolveHookFileKey;
|
|
38
39
|
const commander_1 = require("commander");
|
|
39
40
|
const child_process_1 = require("child_process");
|
|
40
41
|
const fs = __importStar(require("fs"));
|
|
@@ -237,14 +238,14 @@ program
|
|
|
237
238
|
program
|
|
238
239
|
.command('gate')
|
|
239
240
|
.description('Evaluate a JSON payload of files from stdin (low-latency hook endpoint)')
|
|
240
|
-
.option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2')
|
|
241
|
+
.option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2,hipaa,nist-csf')
|
|
241
242
|
.option('--format <format>', 'Output format: json, sarif, table, prompt', 'prompt')
|
|
242
243
|
.option('--output <file>', 'Write report to file')
|
|
243
244
|
.option('--api-url <url>', 'Compliance API base URL (or PC_API_URL env)')
|
|
244
245
|
.option('--api-key <key>', 'API key for compliance API (or PC_API_KEY env)')
|
|
245
246
|
.action(async (opts) => {
|
|
246
247
|
try {
|
|
247
|
-
const frameworks = parseList(opts.framework) ?? ['soc2'];
|
|
248
|
+
const frameworks = parseList(opts.framework) ?? ['soc2', 'hipaa', 'nist-csf'];
|
|
248
249
|
const format = (opts.format ?? 'prompt');
|
|
249
250
|
const stdin = await readStdin();
|
|
250
251
|
if (!stdin.trim()) {
|
|
@@ -331,7 +332,7 @@ program
|
|
|
331
332
|
program
|
|
332
333
|
.command('hook')
|
|
333
334
|
.description('Run as coding-agent post-edit hook (reads stdin or --file)')
|
|
334
|
-
.option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2')
|
|
335
|
+
.option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2,hipaa,nist-csf')
|
|
335
336
|
.option('--format <format>', 'Output format: json, sarif, table, prompt', 'prompt')
|
|
336
337
|
.option('--file <path>', 'Scan this file from disk (alternative to reading content from stdin)')
|
|
337
338
|
.option('--fail-on <levels>', 'Severities that cause non-zero exit', 'critical,high')
|
|
@@ -340,7 +341,7 @@ program
|
|
|
340
341
|
.option('--api-key <key>', 'API key for compliance API (or PC_API_KEY env)')
|
|
341
342
|
.action(async (opts) => {
|
|
342
343
|
try {
|
|
343
|
-
const frameworks = parseList(opts.framework) ?? ['soc2'];
|
|
344
|
+
const frameworks = parseList(opts.framework) ?? ['soc2', 'hipaa', 'nist-csf'];
|
|
344
345
|
const format = (opts.format ?? 'prompt');
|
|
345
346
|
const files = await collectHookFiles(opts.file);
|
|
346
347
|
if (!files || Object.keys(files).length === 0) {
|
|
@@ -371,6 +372,43 @@ program
|
|
|
371
372
|
* `{"tool_input": {"file_path": "...", "content"|"new_string": "..."}}`
|
|
372
373
|
* When only `file_path` is given and we can read the file, we do.
|
|
373
374
|
*/
|
|
375
|
+
/**
|
|
376
|
+
* Convert the user-supplied `--file <path>` value into a repo-relative key.
|
|
377
|
+
*
|
|
378
|
+
* The compliance API rejects absolute paths (`File path must be relative`).
|
|
379
|
+
* Two failure modes the naive implementation hit on macOS:
|
|
380
|
+
*
|
|
381
|
+
* 1. Absolute paths under cwd silently became cryptic 400s. Fixed by
|
|
382
|
+
* `path.relative(cwd, absolute)` — works on Linux.
|
|
383
|
+
* 2. macOS `/tmp` is a symlink to `/private/tmp`. `path.resolve()` does
|
|
384
|
+
* NOT follow symlinks, but `process.cwd()` returns the physical path
|
|
385
|
+
* via the kernel's `getcwd()`. Result: `path.resolve('/tmp/repo/x')`
|
|
386
|
+
* = `/tmp/repo/x` while cwd is `/private/tmp/repo`, so the relative
|
|
387
|
+
* path is `../../../tmp/repo/x` and the file is incorrectly rejected
|
|
388
|
+
* as "outside cwd" — exactly the agent-hook scenario this targets.
|
|
389
|
+
* Fix: realpath both sides before comparing.
|
|
390
|
+
*
|
|
391
|
+
* Pure function (no fs I/O of its own, no `process.exit`) so it's directly
|
|
392
|
+
* unit-testable. Caller passes the original input, the realpath of the
|
|
393
|
+
* resolved file (`fs.realpathSync(path.resolve(filePath))`), and the
|
|
394
|
+
* realpath of cwd. Tests construct realpath inputs themselves.
|
|
395
|
+
*/
|
|
396
|
+
function resolveHookFileKey(inputPath, realpathFile, realpathCwd) {
|
|
397
|
+
if (!path.isAbsolute(inputPath)) {
|
|
398
|
+
// Relative input passes through verbatim — no symlink ambiguity.
|
|
399
|
+
return { ok: true, key: inputPath };
|
|
400
|
+
}
|
|
401
|
+
const relative = path.relative(realpathCwd, realpathFile);
|
|
402
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
403
|
+
return {
|
|
404
|
+
ok: false,
|
|
405
|
+
error: `hook: --file ${inputPath} is outside the current directory ` +
|
|
406
|
+
`(${realpathCwd}). Pass a path relative to the repo root, or ` +
|
|
407
|
+
`cd into the repo first.`,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return { ok: true, key: relative };
|
|
411
|
+
}
|
|
374
412
|
async function collectHookFiles(filePath) {
|
|
375
413
|
if (filePath) {
|
|
376
414
|
const absolute = path.resolve(filePath);
|
|
@@ -379,7 +417,17 @@ async function collectHookFiles(filePath) {
|
|
|
379
417
|
process.exit(2);
|
|
380
418
|
}
|
|
381
419
|
const content = fs.readFileSync(absolute, 'utf8');
|
|
382
|
-
|
|
420
|
+
// Realpath both sides so the macOS `/tmp → /private/tmp` symlink doesn't
|
|
421
|
+
// make a valid agent-hook path (e.g. `/tmp/repo/main.tf`) appear outside
|
|
422
|
+
// cwd. See `resolveHookFileKey` JSDoc for the full rationale.
|
|
423
|
+
const realpathFile = fs.realpathSync(absolute);
|
|
424
|
+
const realpathCwd = fs.realpathSync(process.cwd());
|
|
425
|
+
const resolved = resolveHookFileKey(filePath, realpathFile, realpathCwd);
|
|
426
|
+
if (!resolved.ok) {
|
|
427
|
+
console.error(resolved.error);
|
|
428
|
+
process.exit(2);
|
|
429
|
+
}
|
|
430
|
+
return { [resolved.key]: content };
|
|
383
431
|
}
|
|
384
432
|
const stdin = await readStdin();
|
|
385
433
|
if (!stdin.trim()) {
|
package/package.json
CHANGED