Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bin/sparepack.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ redact:
- pattern: "acme-corp|ACME"
replace: "example-org"

# Remap paths — strip a prefix so pack paths do not mirror repo paths.
# Use when packing from a monorepo root, e.g. "packages/api/src/" -> "src/"
# remap:
# - from: packages/api/src/
# to: src/

# Extra scan rules, on top of the built-in credential and PII patterns.
# scanRules:
# - id: internal-service
Expand Down
18 changes: 17 additions & 1 deletion src/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { compileCustomRule } from './scan.mjs'
export const CONFIG_NAMES = ['sparepack.yaml', 'sparepack.yml']

const FILE_KEYS = ['include', 'interfaces', 'tests']
const KNOWN_KEYS = new Set([...FILE_KEYS, 'task', 'fixtures', 'redact', 'scanRules', 'allowFindings', 'out'])
const KNOWN_KEYS = new Set([...FILE_KEYS, 'task', 'fixtures', 'redact', 'scanRules', 'allowFindings', 'out', 'remap'])

class ConfigError extends Error {}

Expand Down Expand Up @@ -81,6 +81,21 @@ function parseFixtures(raw) {
if (typeof raw !== 'object' || Array.isArray(raw)) {
fail('"fixtures" must be a mapping of path -> generator')
}

function parseRemap(raw) {
if (raw === undefined || raw === null) return []
if (!Array.isArray(raw)) fail('"remap" must be a list')
return raw.map((entry, i) => {
if (typeof entry !== 'object' || entry === null) fail('remap[' + i + '] must be a mapping with "from" and "to"')
if (typeof entry.from !== 'string' || !entry.from.trim()) fail('remap[' + i + '].from must be a non-empty string')
if (typeof entry.to !== 'string') fail('remap[' + i + '].to must be a string')
if (entry.from.split(/[\\/]/).includes('..')) fail('remap[' + i + '].from must not contain ".."')
var from = entry.from.replace(/[\\/]+$/, '') + '/'
var to = entry.to.replace(/[\\/]+$/, '') + '/'
return { from, to }
})
}

return Object.entries(raw).map(([path, spec]) => {
validatePattern(path, 'fixtures')
if (typeof spec !== 'string' || !spec.trim()) {
Expand Down Expand Up @@ -132,6 +147,7 @@ export function parseConfig(text, { source = 'sparepack.yaml' } = {}) {
}
return entry
}),
remap: parseRemap(raw.remap),
}

validatePattern(config.out, 'out')
Expand Down
11 changes: 11 additions & 0 deletions src/pack.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ export async function buildPack(root, config) {
})
}

// Remap paths — strip prefixes so pack paths don't mirror repo paths.
for (let file of files) {
for (let rule of config.remap ?? []) {
if (file.path.startsWith(rule.from)) {
file.path = rule.to + file.path.slice(rule.from.length);
break;
}
}
}


// Redact first, then scan. Scanning before redaction would report findings the author
// already handled; scanning after is the only way to know the redactions were enough.
const findings = []
Expand Down