sapper-ai 0.5.0 → 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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +300 -30
- package/dist/harden.d.ts +28 -0
- package/dist/harden.d.ts.map +1 -0
- package/dist/harden.js +309 -0
- package/dist/mcp/jsonc.d.ts +3 -0
- package/dist/mcp/jsonc.d.ts.map +1 -0
- package/dist/mcp/jsonc.js +119 -0
- package/dist/mcp/wrapConfig.d.ts +22 -0
- package/dist/mcp/wrapConfig.d.ts.map +1 -0
- package/dist/mcp/wrapConfig.js +192 -0
- package/dist/policyYaml.d.ts +3 -0
- package/dist/policyYaml.d.ts.map +1 -0
- package/dist/policyYaml.js +27 -0
- package/dist/postinstall.js +1 -1
- package/dist/quarantine.d.ts +13 -0
- package/dist/quarantine.d.ts.map +1 -0
- package/dist/quarantine.js +22 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +1061 -59
- package/dist/scan.d.ts +13 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +92 -23
- package/dist/utils/env.d.ts +3 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +25 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +47 -0
- package/dist/utils/repoRoot.d.ts +2 -0
- package/dist/utils/repoRoot.d.ts.map +1 -0
- package/dist/utils/repoRoot.js +20 -0
- package/dist/utils/semver.d.ts +2 -0
- package/dist/utils/semver.d.ts.map +1 -0
- package/dist/utils/semver.js +7 -0
- package/package.json +4 -7
package/dist/scan.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export interface ScanOptions {
|
|
2
2
|
targets?: string[];
|
|
3
|
+
policyPath?: string;
|
|
3
4
|
fix?: boolean;
|
|
4
5
|
deep?: boolean;
|
|
5
6
|
system?: boolean;
|
|
@@ -14,10 +15,16 @@ export interface ScanResult {
|
|
|
14
15
|
scope: string;
|
|
15
16
|
target: string;
|
|
16
17
|
ai: boolean;
|
|
18
|
+
filters: {
|
|
19
|
+
configLikeOnly: boolean;
|
|
20
|
+
};
|
|
17
21
|
summary: {
|
|
18
22
|
totalFiles: number;
|
|
23
|
+
eligibleFiles: number;
|
|
19
24
|
scannedFiles: number;
|
|
20
25
|
skippedFiles: number;
|
|
26
|
+
skippedNotEligible: number;
|
|
27
|
+
skippedEmptyOrUnreadable: number;
|
|
21
28
|
threats: number;
|
|
22
29
|
};
|
|
23
30
|
findings: Array<{
|
|
@@ -30,6 +37,12 @@ export interface ScanResult {
|
|
|
30
37
|
snippet: string;
|
|
31
38
|
detectors: string[];
|
|
32
39
|
aiAnalysis: string | null;
|
|
40
|
+
ruleMatches: Array<{
|
|
41
|
+
label: string;
|
|
42
|
+
severity: 'high' | 'medium';
|
|
43
|
+
matchText: string;
|
|
44
|
+
context: string;
|
|
45
|
+
}>;
|
|
33
46
|
}>;
|
|
34
47
|
}
|
|
35
48
|
export declare function runScan(options?: ScanOptions): Promise<number>;
|
package/dist/scan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,EAAE,CAAC,EAAE,OAAO,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAgBD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,EAAE;QACP,cAAc,EAAE,OAAO,CAAA;KACxB,CAAA;IACD,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAA;QAClB,aAAa,EAAE,MAAM,CAAA;QACrB,YAAY,EAAE,MAAM,CAAA;QACpB,YAAY,EAAE,MAAM,CAAA;QACpB,kBAAkB,EAAE,MAAM,CAAA;QAC1B,wBAAwB,EAAE,MAAM,CAAA;QAChC,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;IACD,QAAQ,EAAE,KAAK,CAAC;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,UAAU,EAAE,MAAM,CAAA;QAClB,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,MAAM,EAAE,CAAA;QAClB,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,EAAE,MAAM,EAAE,CAAA;QACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;QACzB,WAAW,EAAE,KAAK,CAAC;YACjB,KAAK,EAAE,MAAM,CAAA;YACb,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAA;YAC3B,SAAS,EAAE,MAAM,CAAA;YACjB,OAAO,EAAE,MAAM,CAAA;SAChB,CAAC,CAAA;KACH,CAAC,CAAA;CACH;AAgcD,wBAAsB,OAAO,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkRxE"}
|
package/dist/scan.js
CHANGED
|
@@ -34,13 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.runScan = runScan;
|
|
37
|
-
const node_fs_1 = require("node:fs");
|
|
38
37
|
const promises_1 = require("node:fs/promises");
|
|
39
38
|
const node_os_1 = require("node:os");
|
|
40
39
|
const node_path_1 = require("node:path");
|
|
41
40
|
const core_1 = require("@sapper-ai/core");
|
|
42
41
|
const presets_1 = require("./presets");
|
|
43
|
-
const
|
|
42
|
+
const repoRoot_1 = require("./utils/repoRoot");
|
|
44
43
|
const GREEN = '\x1b[32m';
|
|
45
44
|
const YELLOW = '\x1b[33m';
|
|
46
45
|
const RED = '\x1b[31m';
|
|
@@ -55,21 +54,18 @@ const SYSTEM_SCAN_PATHS = (() => {
|
|
|
55
54
|
(0, node_path_1.join)(home, 'Library', 'Application Support', 'Claude'),
|
|
56
55
|
];
|
|
57
56
|
})();
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
57
|
+
function resolvePolicy(cwd, options) {
|
|
58
|
+
const manager = new core_1.PolicyManager();
|
|
59
|
+
const explicitPath = options.policyPath ?? process.env.SAPPERAI_POLICY_PATH;
|
|
60
|
+
if (explicitPath) {
|
|
61
|
+
return manager.loadFromFile(explicitPath);
|
|
64
62
|
}
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const configPath = findConfigFile(cwd);
|
|
69
|
-
if (!configPath) {
|
|
63
|
+
const repoRoot = (0, repoRoot_1.findRepoRoot)(cwd);
|
|
64
|
+
const resolved = (0, core_1.resolvePolicyPath)({ repoRoot, homeDir: (0, node_os_1.homedir)() });
|
|
65
|
+
if (!resolved) {
|
|
70
66
|
return { ...presets_1.presets.standard.policy };
|
|
71
67
|
}
|
|
72
|
-
return
|
|
68
|
+
return manager.loadFromFile(resolved.path);
|
|
73
69
|
}
|
|
74
70
|
function getThresholds(policy) {
|
|
75
71
|
const extended = policy;
|
|
@@ -231,11 +227,11 @@ async function readFileIfPresent(filePath) {
|
|
|
231
227
|
}
|
|
232
228
|
async function scanFile(filePath, policy, scanner, detectors, fix, quarantineManager) {
|
|
233
229
|
if (!(0, core_1.isConfigLikeFile)(filePath)) {
|
|
234
|
-
return { scanned: false };
|
|
230
|
+
return { scanned: false, skipReason: 'not_eligible' };
|
|
235
231
|
}
|
|
236
232
|
const raw = await readFileIfPresent(filePath);
|
|
237
233
|
if (raw === null || raw.trim().length === 0) {
|
|
238
|
-
return { scanned: false };
|
|
234
|
+
return { scanned: false, skipReason: 'empty_or_unreadable' };
|
|
239
235
|
}
|
|
240
236
|
const fileSurface = (0, core_1.normalizeSurfaceText)(raw);
|
|
241
237
|
const targetType = (0, core_1.classifyTargetType)(filePath);
|
|
@@ -243,6 +239,11 @@ async function scanFile(filePath, policy, scanner, detectors, fix, quarantineMan
|
|
|
243
239
|
{
|
|
244
240
|
id: `${targetType}:${(0, core_1.buildEntryName)(filePath)}`,
|
|
245
241
|
surface: fileSurface,
|
|
242
|
+
meta: {
|
|
243
|
+
scanSource: 'file_surface',
|
|
244
|
+
sourcePath: filePath,
|
|
245
|
+
sourceType: targetType,
|
|
246
|
+
},
|
|
246
247
|
},
|
|
247
248
|
];
|
|
248
249
|
if (filePath.endsWith('.json')) {
|
|
@@ -250,7 +251,15 @@ async function scanFile(filePath, policy, scanner, detectors, fix, quarantineMan
|
|
|
250
251
|
const parsed = JSON.parse(raw);
|
|
251
252
|
const mcpTargets = (0, core_1.collectMcpTargetsFromJson)(filePath, parsed);
|
|
252
253
|
for (const t of mcpTargets) {
|
|
253
|
-
targets.push({
|
|
254
|
+
targets.push({
|
|
255
|
+
id: `${t.type}:${t.name}`,
|
|
256
|
+
surface: t.surface,
|
|
257
|
+
meta: {
|
|
258
|
+
scanSource: 'file_surface',
|
|
259
|
+
sourcePath: t.source,
|
|
260
|
+
sourceType: t.type,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
254
263
|
}
|
|
255
264
|
}
|
|
256
265
|
catch {
|
|
@@ -259,7 +268,7 @@ async function scanFile(filePath, policy, scanner, detectors, fix, quarantineMan
|
|
|
259
268
|
let bestDecision = null;
|
|
260
269
|
let bestThreat = null;
|
|
261
270
|
for (const target of targets) {
|
|
262
|
-
const decision = await scanner.scanTool(target.id, target.surface, policy, detectors);
|
|
271
|
+
const decision = await scanner.scanTool(target.id, target.surface, policy, detectors, target.meta);
|
|
263
272
|
if (!bestDecision || decision.risk > bestDecision.risk) {
|
|
264
273
|
bestDecision = decision;
|
|
265
274
|
}
|
|
@@ -299,6 +308,37 @@ function extractPatternsFromReasons(reasons) {
|
|
|
299
308
|
function toDetectorsList(decision) {
|
|
300
309
|
return uniq(decision.evidence.map((e) => e.detectorId));
|
|
301
310
|
}
|
|
311
|
+
function extractRuleMatches(decision) {
|
|
312
|
+
const evidence = Array.isArray(decision.evidence) ? decision.evidence : [];
|
|
313
|
+
const output = evidence.find((e) => e && e.detectorId === 'rules');
|
|
314
|
+
if (!output || !output.evidence || typeof output.evidence !== 'object') {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
const maybeMatches = output.evidence.matches;
|
|
318
|
+
if (!Array.isArray(maybeMatches)) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
const results = [];
|
|
322
|
+
for (const m of maybeMatches) {
|
|
323
|
+
if (!m || typeof m !== 'object')
|
|
324
|
+
continue;
|
|
325
|
+
const match = m;
|
|
326
|
+
const label = typeof match.label === 'string' ? match.label : '';
|
|
327
|
+
const severity = match.severity === 'high' ? 'high' : 'medium';
|
|
328
|
+
const matchText = typeof match.matchText === 'string' ? match.matchText : '';
|
|
329
|
+
const context = typeof match.context === 'string'
|
|
330
|
+
? match.context
|
|
331
|
+
: typeof match.sample === 'string'
|
|
332
|
+
? match.sample
|
|
333
|
+
: '';
|
|
334
|
+
if (!label || !matchText)
|
|
335
|
+
continue;
|
|
336
|
+
results.push({ label, severity, matchText, context });
|
|
337
|
+
if (results.length >= 24)
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
return results;
|
|
341
|
+
}
|
|
302
342
|
function truncateSnippet(text, maxChars) {
|
|
303
343
|
if (text.length <= maxChars) {
|
|
304
344
|
return text;
|
|
@@ -313,6 +353,7 @@ async function buildScanResult(params) {
|
|
|
313
353
|
const reasons = f.decision.reasons;
|
|
314
354
|
const patterns = extractPatternsFromReasons(reasons);
|
|
315
355
|
const detectors = toDetectorsList(f.decision);
|
|
356
|
+
const ruleMatches = extractRuleMatches(f.decision);
|
|
316
357
|
return {
|
|
317
358
|
filePath: f.filePath,
|
|
318
359
|
risk: f.decision.risk,
|
|
@@ -323,6 +364,7 @@ async function buildScanResult(params) {
|
|
|
323
364
|
snippet,
|
|
324
365
|
detectors,
|
|
325
366
|
aiAnalysis: f.aiAnalysis ?? null,
|
|
367
|
+
ruleMatches,
|
|
326
368
|
};
|
|
327
369
|
}));
|
|
328
370
|
return {
|
|
@@ -331,10 +373,16 @@ async function buildScanResult(params) {
|
|
|
331
373
|
scope: params.scope,
|
|
332
374
|
target: params.target,
|
|
333
375
|
ai: params.ai,
|
|
376
|
+
filters: {
|
|
377
|
+
configLikeOnly: true,
|
|
378
|
+
},
|
|
334
379
|
summary: {
|
|
335
380
|
totalFiles: params.totalFiles,
|
|
381
|
+
eligibleFiles: params.eligibleFiles,
|
|
336
382
|
scannedFiles: params.scannedFiles,
|
|
337
383
|
skippedFiles: params.skippedFiles,
|
|
384
|
+
skippedNotEligible: params.skippedNotEligible,
|
|
385
|
+
skippedEmptyOrUnreadable: params.skippedEmptyOrUnreadable,
|
|
338
386
|
threats: params.threats,
|
|
339
387
|
},
|
|
340
388
|
findings,
|
|
@@ -342,7 +390,7 @@ async function buildScanResult(params) {
|
|
|
342
390
|
}
|
|
343
391
|
async function runScan(options = {}) {
|
|
344
392
|
const cwd = process.cwd();
|
|
345
|
-
const policy = resolvePolicy(cwd);
|
|
393
|
+
const policy = resolvePolicy(cwd, { policyPath: options.policyPath });
|
|
346
394
|
const fix = options.fix === true;
|
|
347
395
|
const aiEnabled = options.ai === true;
|
|
348
396
|
let llmConfig = null;
|
|
@@ -384,6 +432,8 @@ async function runScan(options = {}) {
|
|
|
384
432
|
}
|
|
385
433
|
const files = Array.from(fileSet).sort();
|
|
386
434
|
console.log(` Collecting files... ${files.length} files found`);
|
|
435
|
+
const eligibleByName = files.filter((f) => (0, core_1.isConfigLikeFile)(f)).length;
|
|
436
|
+
console.log(` Filter: config-like only (${eligibleByName} eligible / ${files.length} total)`);
|
|
387
437
|
console.log();
|
|
388
438
|
if (aiEnabled) {
|
|
389
439
|
console.log(' Phase 1/2: Rules scan');
|
|
@@ -391,6 +441,9 @@ async function runScan(options = {}) {
|
|
|
391
441
|
}
|
|
392
442
|
const scannedFindings = [];
|
|
393
443
|
let scannedFiles = 0;
|
|
444
|
+
let eligibleFiles = 0;
|
|
445
|
+
let skippedNotEligible = 0;
|
|
446
|
+
let skippedEmptyOrUnreadable = 0;
|
|
394
447
|
const total = files.length;
|
|
395
448
|
const progressWidth = Math.max(10, Math.min(30, (process.stdout.columns ?? 80) - 30));
|
|
396
449
|
for (let i = 0; i < files.length; i += 1) {
|
|
@@ -408,6 +461,15 @@ async function runScan(options = {}) {
|
|
|
408
461
|
}
|
|
409
462
|
}
|
|
410
463
|
const result = await scanFile(filePath, policy, scanner, detectors, fix, quarantineManager);
|
|
464
|
+
if (result.skipReason === 'not_eligible') {
|
|
465
|
+
skippedNotEligible += 1;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
eligibleFiles += 1;
|
|
469
|
+
if (result.skipReason === 'empty_or_unreadable') {
|
|
470
|
+
skippedEmptyOrUnreadable += 1;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
411
473
|
if (result.scanned && result.decision) {
|
|
412
474
|
scannedFiles += 1;
|
|
413
475
|
scannedFindings.push({ filePath, decision: result.decision, quarantinedId: result.quarantinedId });
|
|
@@ -454,7 +516,11 @@ async function runScan(options = {}) {
|
|
|
454
516
|
const surface = (0, core_1.normalizeSurfaceText)(raw);
|
|
455
517
|
const targetType = (0, core_1.classifyTargetType)(finding.filePath);
|
|
456
518
|
const id = `${targetType}:${(0, core_1.buildEntryName)(finding.filePath)}`;
|
|
457
|
-
const aiDecision = await scanner.scanTool(id, surface, aiPolicy, aiDetectors
|
|
519
|
+
const aiDecision = await scanner.scanTool(id, surface, aiPolicy, aiDetectors, {
|
|
520
|
+
scanSource: 'file_surface',
|
|
521
|
+
sourcePath: finding.filePath,
|
|
522
|
+
sourceType: targetType,
|
|
523
|
+
});
|
|
458
524
|
const mergedReasons = uniq([...finding.decision.reasons, ...aiDecision.reasons]);
|
|
459
525
|
const existingEvidence = finding.decision.evidence;
|
|
460
526
|
const mergedEvidence = [...existingEvidence];
|
|
@@ -490,15 +556,18 @@ async function runScan(options = {}) {
|
|
|
490
556
|
}
|
|
491
557
|
}
|
|
492
558
|
}
|
|
493
|
-
const skippedFiles =
|
|
559
|
+
const skippedFiles = skippedNotEligible + skippedEmptyOrUnreadable;
|
|
494
560
|
const threats = scannedFindings.filter((f) => isThreat(f.decision, policy));
|
|
495
561
|
const scanResult = await buildScanResult({
|
|
496
562
|
scope: scopeLabel,
|
|
497
563
|
target: targets.join(', '),
|
|
498
564
|
ai: aiEnabled,
|
|
499
565
|
totalFiles: total,
|
|
566
|
+
eligibleFiles,
|
|
500
567
|
scannedFiles,
|
|
501
568
|
skippedFiles,
|
|
569
|
+
skippedNotEligible,
|
|
570
|
+
skippedEmptyOrUnreadable,
|
|
502
571
|
threats: threats.length,
|
|
503
572
|
findings: scannedFindings,
|
|
504
573
|
});
|
|
@@ -531,12 +600,12 @@ async function runScan(options = {}) {
|
|
|
531
600
|
}
|
|
532
601
|
}
|
|
533
602
|
if (threats.length === 0) {
|
|
534
|
-
const msg = ` ✓ All clear — ${scannedFiles} files scanned, 0 threats detected`;
|
|
603
|
+
const msg = ` ✓ All clear — ${scannedFiles}/${eligibleFiles} eligible files scanned, 0 threats detected (${total} total files)`;
|
|
535
604
|
console.log(color ? `${GREEN}${msg}${RESET}` : msg);
|
|
536
605
|
console.log();
|
|
537
606
|
}
|
|
538
607
|
else {
|
|
539
|
-
const warn = ` ⚠ ${scannedFiles} files scanned, ${threats.length} threats detected`;
|
|
608
|
+
const warn = ` ⚠ ${scannedFiles}/${eligibleFiles} eligible files scanned, ${threats.length} threats detected (${total} total files)`;
|
|
540
609
|
console.log(color ? `${RED}${warn}${RESET}` : warn);
|
|
541
610
|
console.log();
|
|
542
611
|
const tableLines = renderFindingsTable(threats, {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAQlF;AAED,wBAAgB,OAAO,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAYrE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseBoolean = parseBoolean;
|
|
4
|
+
exports.isCiEnv = isCiEnv;
|
|
5
|
+
function parseBoolean(value, fallback) {
|
|
6
|
+
if (value === undefined)
|
|
7
|
+
return fallback;
|
|
8
|
+
const normalized = value.trim().toLowerCase();
|
|
9
|
+
if (normalized === 'true' || normalized === '1' || normalized === 'yes')
|
|
10
|
+
return true;
|
|
11
|
+
if (normalized === 'false' || normalized === '0' || normalized === 'no')
|
|
12
|
+
return false;
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
function isCiEnv(env = process.env) {
|
|
16
|
+
// CI providers generally set CI=true; also treat GitHub Actions as CI even if CI is unset.
|
|
17
|
+
const ci = env.CI;
|
|
18
|
+
if (ci && ci.trim().length > 0 && ci !== '0' && ci.toLowerCase() !== 'false') {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (env.GITHUB_ACTIONS && env.GITHUB_ACTIONS !== 'false') {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function readFileIfExists(filePath: string): Promise<string | null>;
|
|
2
|
+
export declare function ensureDir(dirPath: string): Promise<void>;
|
|
3
|
+
export declare function backupFile(originalPath: string): Promise<string>;
|
|
4
|
+
export declare function atomicWriteFile(filePath: string, content: string): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"AAIA,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAM/E;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D;AAkBD,wBAAsB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKtE;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAStF"}
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readFileIfExists = readFileIfExists;
|
|
4
|
+
exports.ensureDir = ensureDir;
|
|
5
|
+
exports.backupFile = backupFile;
|
|
6
|
+
exports.atomicWriteFile = atomicWriteFile;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
async function readFileIfExists(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
return await (0, promises_1.readFile)(filePath, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function ensureDir(dirPath) {
|
|
19
|
+
await (0, promises_1.mkdir)(dirPath, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
function nextBackupPath(originalPath) {
|
|
22
|
+
const first = `${originalPath}.bak`;
|
|
23
|
+
if (!(0, node_fs_1.existsSync)(first)) {
|
|
24
|
+
return first;
|
|
25
|
+
}
|
|
26
|
+
for (let i = 1; i < 1000; i += 1) {
|
|
27
|
+
const candidate = `${originalPath}.bak.${i}`;
|
|
28
|
+
if (!(0, node_fs_1.existsSync)(candidate)) {
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Unable to find free backup path for ${originalPath}`);
|
|
33
|
+
}
|
|
34
|
+
async function backupFile(originalPath) {
|
|
35
|
+
const backupPath = nextBackupPath(originalPath);
|
|
36
|
+
await ensureDir((0, node_path_1.dirname)(backupPath));
|
|
37
|
+
await (0, promises_1.copyFile)(originalPath, backupPath);
|
|
38
|
+
return backupPath;
|
|
39
|
+
}
|
|
40
|
+
async function atomicWriteFile(filePath, content) {
|
|
41
|
+
const dir = (0, node_path_1.dirname)(filePath);
|
|
42
|
+
await ensureDir(dir);
|
|
43
|
+
const tmpName = `.${(0, node_path_1.basename)(filePath)}.tmp.${process.pid}.${Date.now()}`;
|
|
44
|
+
const tmpPath = (0, node_path_1.join)(dir, tmpName);
|
|
45
|
+
await (0, promises_1.writeFile)(tmpPath, content, 'utf8');
|
|
46
|
+
await (0, promises_1.rename)(tmpPath, filePath);
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repoRoot.d.ts","sourceRoot":"","sources":["../../src/utils/repoRoot.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAiBrD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findRepoRoot = findRepoRoot;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
function findRepoRoot(startDir) {
|
|
7
|
+
const start = (0, node_path_1.resolve)(startDir);
|
|
8
|
+
let current = start;
|
|
9
|
+
while (true) {
|
|
10
|
+
const gitPath = (0, node_path_1.resolve)(current, '.git');
|
|
11
|
+
if ((0, node_fs_1.existsSync)(gitPath)) {
|
|
12
|
+
return current;
|
|
13
|
+
}
|
|
14
|
+
const parent = (0, node_path_1.dirname)(current);
|
|
15
|
+
if (parent === current) {
|
|
16
|
+
return start;
|
|
17
|
+
}
|
|
18
|
+
current = parent;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../../src/utils/semver.ts"],"names":[],"mappings":"AAGA,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSemver = isSemver;
|
|
4
|
+
const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
|
|
5
|
+
function isSemver(version) {
|
|
6
|
+
return SEMVER_RE.test(version.trim());
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapper-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI security guardrails - single install, sensible defaults",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"security",
|
|
@@ -40,17 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@inquirer/select": "^4.0.0",
|
|
43
|
-
"@sapper-ai/core": "0.2.
|
|
43
|
+
"@sapper-ai/core": "0.2.2",
|
|
44
|
+
"@sapper-ai/mcp": "0.3.0",
|
|
44
45
|
"@sapper-ai/types": "0.2.1"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
|
-
"@sapper-ai/
|
|
48
|
-
"@sapper-ai/openai": "^0.2.1"
|
|
48
|
+
"@sapper-ai/openai": "^0.2.2"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
|
-
"@sapper-ai/mcp": {
|
|
52
|
-
"optional": true
|
|
53
|
-
},
|
|
54
51
|
"@sapper-ai/openai": {
|
|
55
52
|
"optional": true
|
|
56
53
|
}
|