@tantainnovative/ndpr-toolkit
Version:
Nigeria Data Protection Toolkit — enterprise-grade compliance components for the Nigeria Data Protection Act (NDPA) 2023
142 lines (119 loc) • 5.58 kB
JavaScript
// ndpr — NDPA 2023 compliance CLI.
// `ndpr audit` scores a compliance config against the toolkit's engine
// (compliance score + GAID 2025 DCPMI / CAR / breach checks) and exits
// non-zero when the audit fails — drop it into CI as a compliance gate.
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const pkg = require('../package.json');
const DEFAULT_CONFIG_NAMES = ['ndpr.audit.json', 'ndpr.config.json'];
const STARTER_CONFIG = {
minScore: 70,
compliance: {
consent: { hasConsentMechanism: true, hasPurposeSpecification: true, hasWithdrawalMechanism: true, hasMinorProtection: false, consentRecordsRetained: true },
dsr: { hasRequestMechanism: true, supportsAccess: true, supportsRectification: true, supportsErasure: false, supportsPortability: false, supportsObjection: false, responseTimelineDays: 30 },
dpia: { conductedForHighRisk: true, documentedRisks: true, mitigationMeasures: true },
breach: { hasNotificationProcess: true, notifiesWithin72Hours: true, hasRiskAssessment: true, hasRecordKeeping: true },
policy: { hasPrivacyPolicy: true, isPubliclyAccessible: true, lastUpdated: '2026-01-01', coversAllSections: true },
lawfulBasis: { documentedForAllProcessing: true, hasLegitimateInterestAssessment: false },
crossBorder: { hasTransferMechanisms: true, adequacyAssessed: true, ndpcApprovalObtained: false },
ropa: { maintained: true, includesAllProcessing: true, lastReviewed: '2026-01-01' },
},
dcpmi: { dataSubjectsInSixMonths: 0 },
};
function parseArgs(argv) {
const args = { _: [], flags: {} };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith('--')) {
const key = a.slice(2);
const next = argv[i + 1];
if (next !== undefined && !next.startsWith('--')) { args.flags[key] = next; i++; }
else args.flags[key] = true;
} else {
args._.push(a);
}
}
return args;
}
const HELP = `ndpr ${pkg.version} — NDPA 2023 compliance CLI
Usage:
ndpr audit [options] Run a compliance audit and exit non-zero on failure
ndpr --help Show this help
ndpr --version Print the version
Options for "audit":
--config <path> Path to the audit config JSON
(default: ./ndpr.audit.json or ./ndpr.config.json)
--min-score <n> Minimum overall compliance score to pass (default 70)
--json Output the full result as JSON
--no-color Disable coloured output
--init Write a starter ndpr.audit.json and exit
Config shape: { minScore?, compliance, dcpmi?, car?, breaches? }
See https://ndprtoolkit.com.ng/docs/guides/audit-cli`;
function findConfig(explicit) {
if (explicit) return resolve(process.cwd(), explicit);
for (const name of DEFAULT_CONFIG_NAMES) {
const p = resolve(process.cwd(), name);
if (existsSync(p)) return p;
}
return null;
}
async function main() {
const argv = process.argv.slice(2);
const args = parseArgs(argv);
const command = args._[0] ?? (args.flags.help || args.flags.version ? null : 'audit');
if (args.flags.version) { process.stdout.write(`${pkg.version}\n`); return 0; }
if (args.flags.help || command === 'help') { process.stdout.write(`${HELP}\n`); return 0; }
if (command !== 'audit') {
process.stderr.write(`Unknown command: ${command}\n\n${HELP}\n`);
return 2;
}
if (args.flags.init) {
const out = resolve(process.cwd(), 'ndpr.audit.json');
if (existsSync(out)) { process.stderr.write(`Refusing to overwrite existing ${out}\n`); return 1; }
writeFileSync(out, JSON.stringify(STARTER_CONFIG, null, 2) + '\n');
process.stdout.write(`Wrote starter config to ${out}\n`);
return 0;
}
const configPath = findConfig(typeof args.flags.config === 'string' ? args.flags.config : undefined);
if (!configPath) {
process.stderr.write('No audit config found. Create one with "ndpr audit --init" or pass --config <path>.\n');
return 2;
}
if (!existsSync(configPath)) {
process.stderr.write(`Config not found: ${configPath}\n`);
return 2;
}
let config;
try {
config = JSON.parse(readFileSync(configPath, 'utf8'));
} catch (err) {
process.stderr.write(`Failed to parse ${configPath}: ${err.message}\n`);
return 2;
}
if (!config || typeof config.compliance !== 'object') {
process.stderr.write(`Config ${configPath} must contain a "compliance" object.\n`);
return 2;
}
const { runNdprAudit, formatNdprAuditReport } = await import('@tantainnovative/ndpr-toolkit/server');
const minScore = args.flags['min-score'] !== undefined ? Number(args.flags['min-score']) : config.minScore;
const options = { ...(config.options ?? {}) };
if (minScore !== undefined && !Number.isNaN(minScore)) options.minScore = minScore;
const result = runNdprAudit(
{ compliance: config.compliance, dcpmi: config.dcpmi, car: config.car, breaches: config.breaches },
options,
);
if (args.flags.json) {
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
} else {
const color = !args.flags['no-color'] && process.stdout.isTTY === true;
process.stdout.write(formatNdprAuditReport(result, { color }) + '\n');
}
return result.passed ? 0 : 1;
}
main().then(
(code) => process.exit(code),
(err) => { process.stderr.write(`ndpr: ${err?.stack || err}\n`); process.exit(2); },
);