@prodcycle/prodcycle 0.4.2 → 0.6.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.
- package/README.md +14 -5
- package/dist/api-client.d.ts +156 -3
- package/dist/api-client.js +471 -33
- package/dist/cli.d.ts +14 -1
- package/dist/cli.js +378 -13
- package/dist/index.d.ts +60 -20
- package/dist/index.js +78 -12
- package/dist/utils/fs.js +117 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -31,18 +31,27 @@ npm install @prodcycle/prodcycle
|
|
|
31
31
|
### CLI
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
# Scan current directory against
|
|
34
|
+
# Scan current directory against all 3 frameworks (default: soc2, hipaa, nist-csf).
|
|
35
|
+
# Auto-flips to SARIF in known CI environments so output drops into
|
|
36
|
+
# code-scanning dashboards without extra wiring.
|
|
37
|
+
prodcycle scan .
|
|
38
|
+
|
|
39
|
+
# Pin a specific framework or subset
|
|
35
40
|
prodcycle scan . --framework soc2,hipaa
|
|
41
|
+
prodcycle scan . --framework hipaa --severity-threshold high
|
|
36
42
|
|
|
37
|
-
#
|
|
38
|
-
prodcycle scan . --
|
|
43
|
+
# Explicit SARIF (overrides the CI auto-flip)
|
|
44
|
+
prodcycle scan . --format sarif --output results.sarif
|
|
39
45
|
|
|
40
|
-
#
|
|
41
|
-
prodcycle scan . --
|
|
46
|
+
# CI: scan only files changed in the PR
|
|
47
|
+
prodcycle scan . --pr origin/main..HEAD
|
|
42
48
|
|
|
43
49
|
# Auto-configure compliance hooks/instructions for your coding agents
|
|
44
50
|
# (Claude Code, Cursor, Codex, OpenCode, GitHub Copilot, Gemini CLI)
|
|
45
51
|
prodcycle init --agent all
|
|
52
|
+
|
|
53
|
+
# Scaffold a CI workflow that delegates to prodcycle/actions/compliance
|
|
54
|
+
prodcycle init --ci github # also: gitlab | circleci
|
|
46
55
|
```
|
|
47
56
|
|
|
48
57
|
Subcommands: `scan` (full repo scan), `gate` (JSON payload from stdin), `hook` (coding-agent post-edit hook), `init` (agent setup).
|
package/dist/api-client.d.ts
CHANGED
|
@@ -16,11 +16,164 @@ export interface GateOptions {
|
|
|
16
16
|
apiUrl?: string;
|
|
17
17
|
config?: Record<string, unknown>;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Set when `validateChunked`'s post-`/complete` enrichment GET failed and the
|
|
21
|
+
* structured `findings` could not be recovered. Distinguishable from the
|
|
22
|
+
* server-side `scannerError`: this signals "we know there are N findings (per
|
|
23
|
+
* the summary) but we couldn't fetch them — retry with `prodcycle scans
|
|
24
|
+
* <id>`," not "we couldn't certify the scan." Surfaced as a named field
|
|
25
|
+
* (rather than via the `[key: string]: unknown` index signature) so
|
|
26
|
+
* TypeScript callers get a typed contract instead of `unknown`.
|
|
27
|
+
*
|
|
28
|
+
* `code` distinguishes:
|
|
29
|
+
* - `BACKFILL_GET_FAILED`: GET threw / non-2xx — backfill couldn't run
|
|
30
|
+
* - `BACKFILL_GET_RETURNED_EMPTY`: GET succeeded but findings were still
|
|
31
|
+
* empty despite `summary.total > 0`. Usually means eventual consistency
|
|
32
|
+
* between the `/complete` writer and the scan-record reader; retrying
|
|
33
|
+
* after a short delay typically populates the findings. Surfaced as a
|
|
34
|
+
* separate code so SARIF/dashboard consumers can decide whether to
|
|
35
|
+
* auto-retry vs. surface as a hard error.
|
|
36
|
+
*/
|
|
37
|
+
export interface BackfillError {
|
|
38
|
+
code: 'BACKFILL_GET_FAILED' | 'BACKFILL_GET_RETURNED_EMPTY';
|
|
39
|
+
message: string;
|
|
40
|
+
scanId: string;
|
|
41
|
+
summaryTotal: number;
|
|
42
|
+
}
|
|
43
|
+
export interface ScanResult {
|
|
44
|
+
scanId?: string;
|
|
45
|
+
passed: boolean;
|
|
46
|
+
findingsCount?: number;
|
|
47
|
+
findings?: unknown[];
|
|
48
|
+
summary?: unknown;
|
|
49
|
+
prompt?: string;
|
|
50
|
+
status?: 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';
|
|
51
|
+
backfillError?: BackfillError;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
}
|
|
54
|
+
interface ApiErrorBody {
|
|
55
|
+
status: 'error';
|
|
56
|
+
statusCode: number;
|
|
57
|
+
error?: {
|
|
58
|
+
type?: string;
|
|
59
|
+
message?: string;
|
|
60
|
+
suggestion?: string;
|
|
61
|
+
details?: {
|
|
62
|
+
maxBytes?: number;
|
|
63
|
+
maxFiles?: number;
|
|
64
|
+
chunkSizeBytes?: number;
|
|
65
|
+
receivedBytes?: number;
|
|
66
|
+
suggestedEndpoint?: string;
|
|
67
|
+
[key: string]: unknown;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Error thrown for any non-2xx response. Carries the parsed body + status so
|
|
73
|
+
* callers can branch on `details.suggestedEndpoint` (413 → chunked-session
|
|
74
|
+
* fallback) or `Retry-After` (429 / 503 → backoff + retry).
|
|
75
|
+
*/
|
|
76
|
+
export declare class ApiError extends Error {
|
|
77
|
+
readonly statusCode: number;
|
|
78
|
+
readonly body: ApiErrorBody | null;
|
|
79
|
+
readonly retryAfterSeconds: number | null;
|
|
80
|
+
constructor(statusCode: number, body: ApiErrorBody | null, retryAfterSeconds: number | null, message: string);
|
|
81
|
+
}
|
|
19
82
|
export declare class ComplianceApiClient {
|
|
20
83
|
private apiUrl;
|
|
21
84
|
private apiKey;
|
|
22
85
|
constructor(apiUrl?: string, apiKey?: string);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Synchronous validate. On a 413 with `details.suggestedEndpoint ===
|
|
88
|
+
* '/v1/compliance/scans'`, silently falls back to the chunked-session
|
|
89
|
+
* flow so large-repo CI jobs don't have to know the difference.
|
|
90
|
+
*/
|
|
91
|
+
validate(files: Record<string, string>, frameworks: string[], options?: ScanOptions): Promise<ScanResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Hook endpoint — small per-write call from coding agents. No
|
|
94
|
+
* suggestedEndpoint fallback because /hook keeps the historical 50 MB
|
|
95
|
+
* ceiling; if a single hook write exceeds that, the caller's batching
|
|
96
|
+
* is the bug to fix.
|
|
97
|
+
*/
|
|
98
|
+
hook(files: Record<string, string>, frameworks: string[], options?: ScanOptions): Promise<ScanResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Open a chunked scan session. Returns a `scanId` that subsequent
|
|
101
|
+
* `appendChunk` / `completeSession` calls reference. Server-side TTL is
|
|
102
|
+
* 30 minutes by default — abandoned sessions self-clean via the
|
|
103
|
+
* stale-session reaper.
|
|
104
|
+
*/
|
|
105
|
+
openSession(frameworks: string[], options?: ScanOptions): Promise<{
|
|
106
|
+
scanId: string;
|
|
107
|
+
chunkSizeBytes: number;
|
|
108
|
+
maxFilesPerChunk: number;
|
|
109
|
+
expiresAt: string;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* Append a chunk of files to an open session. Each call has its own
|
|
113
|
+
* /hook-style cap (50 MB / 2000 files). The server caches per-content
|
|
114
|
+
* findings, so re-scans of unchanged files are O(1).
|
|
115
|
+
*/
|
|
116
|
+
appendChunk(scanId: string, files: Record<string, string>): Promise<{
|
|
117
|
+
filesScanned: number;
|
|
118
|
+
cachedFiles: number;
|
|
119
|
+
findingsAdded: number;
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* Finalize a chunked session: flips status to COMPLETED, computes
|
|
123
|
+
* summary + passed, returns final findings.
|
|
124
|
+
*/
|
|
125
|
+
completeSession(scanId: string): Promise<ScanResult>;
|
|
126
|
+
/**
|
|
127
|
+
* High-level helper: open → append (in chunks) → complete. Returns the
|
|
128
|
+
* same shape as `validate()` so callers that auto-fallback don't have
|
|
129
|
+
* to special-case the result.
|
|
130
|
+
*
|
|
131
|
+
* Caller can pre-set `chunkMaxBytes` / `chunkMaxFiles` on `options.config`
|
|
132
|
+
* to override the conservative defaults.
|
|
133
|
+
*/
|
|
134
|
+
validateChunked(files: Record<string, string>, frameworks: string[], options?: ScanOptions): Promise<ScanResult>;
|
|
135
|
+
/**
|
|
136
|
+
* Some server versions of `POST /scans/:id/complete` return only the summary,
|
|
137
|
+
* leaving `findings` empty even when `summary.total > 0`. The findings are
|
|
138
|
+
* persisted on the scan record and recoverable via `GET /scans/:id`. Call
|
|
139
|
+
* this after `completeSession` (and any other path where the response shape
|
|
140
|
+
* may be summary-only) so SARIF/JSON consumers always see structured findings,
|
|
141
|
+
* not just a count. No-op when findings are already present or the scan is
|
|
142
|
+
* genuinely clean.
|
|
143
|
+
*
|
|
144
|
+
* Timeout: the follow-up GET goes through `this.request`, which wraps every
|
|
145
|
+
* fetch with `AbortSignal.timeout(REQUEST_TIMEOUT_MS)` (120 s default,
|
|
146
|
+
* tunable via `PC_REQUEST_TIMEOUT_MS`). A stalled server can't hang
|
|
147
|
+
* `validateChunked` indefinitely; if the abort fires, the catch below
|
|
148
|
+
* falls through with the original summary-only result.
|
|
149
|
+
*/
|
|
150
|
+
private backfillFindingsIfMissing;
|
|
151
|
+
/**
|
|
152
|
+
* Async-validate: returns a `scanId` immediately; caller polls
|
|
153
|
+
* `getScan(scanId)` until status is COMPLETED or FAILED. Useful for CI
|
|
154
|
+
* runners that don't want to hold a connection for a 60 s scan.
|
|
155
|
+
*/
|
|
156
|
+
validateAsync(files: Record<string, string>, frameworks: string[], options?: ScanOptions): Promise<{
|
|
157
|
+
scanId: string;
|
|
158
|
+
}>;
|
|
159
|
+
/**
|
|
160
|
+
* Fetch the current state of any scan (sync, async, or chunked-session).
|
|
161
|
+
*/
|
|
162
|
+
getScan(scanId: string): Promise<ScanResult>;
|
|
163
|
+
/**
|
|
164
|
+
* High-level helper: kicks off an async-validate, polls until terminal,
|
|
165
|
+
* returns the same shape as `validate()`.
|
|
166
|
+
*/
|
|
167
|
+
validateAndPoll(files: Record<string, string>, frameworks: string[], options?: ScanOptions): Promise<ScanResult>;
|
|
168
|
+
private buildOptions;
|
|
169
|
+
/**
|
|
170
|
+
* Single HTTP request with:
|
|
171
|
+
* - auth header
|
|
172
|
+
* - 429/503 + Retry-After honoring (up to MAX_RETRY_ATTEMPTS)
|
|
173
|
+
* - structured error with parsed body so callers can introspect
|
|
174
|
+
* `details.suggestedEndpoint` etc.
|
|
175
|
+
*/
|
|
176
|
+
private request;
|
|
26
177
|
}
|
|
178
|
+
export declare function chunkFiles(files: Record<string, string>, maxBytes: number, maxFiles: number): Record<string, string>[];
|
|
179
|
+
export {};
|