@usebetterdev/audit-cli 0.5.0-beta.1 → 0.5.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/check.d.ts +64 -0
- package/dist/check.js +20 -0
- package/dist/check.js.map +1 -0
- package/dist/chunk-55KKYFKR.js +408 -0
- package/dist/chunk-55KKYFKR.js.map +1 -0
- package/dist/chunk-7GSN73TA.js +345 -0
- package/dist/chunk-7GSN73TA.js.map +1 -0
- package/dist/chunk-AGFBL646.js +10 -0
- package/dist/chunk-AGFBL646.js.map +1 -0
- package/dist/chunk-HDO5P6X7.js +77 -0
- package/dist/chunk-HDO5P6X7.js.map +1 -0
- package/dist/chunk-M46VJ3FO.js +182 -0
- package/dist/chunk-M46VJ3FO.js.map +1 -0
- package/dist/chunk-O5LHE2AC.js +119 -0
- package/dist/chunk-O5LHE2AC.js.map +1 -0
- package/dist/chunk-SJSGTCG4.js +225 -0
- package/dist/chunk-SJSGTCG4.js.map +1 -0
- package/dist/chunk-WVH5TQ2O.js +101 -0
- package/dist/chunk-WVH5TQ2O.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +155 -0
- package/dist/cli.js.map +1 -0
- package/dist/detect-adapter-DNHcPCKz.d.ts +7 -0
- package/dist/export.d.ts +38 -0
- package/dist/export.js +19 -0
- package/dist/export.js.map +1 -0
- package/dist/migrate.d.ts +31 -0
- package/dist/migrate.js +9 -0
- package/dist/migrate.js.map +1 -0
- package/dist/purge.d.ts +52 -0
- package/dist/purge.js +15 -0
- package/dist/purge.js.map +1 -0
- package/dist/stats.d.ts +19 -0
- package/dist/stats.js +7 -0
- package/dist/stats.js.map +1 -0
- package/package.json +20 -2
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateMigrationSql
|
|
3
|
+
} from "./chunk-O5LHE2AC.js";
|
|
4
|
+
import {
|
|
5
|
+
detectAdapter,
|
|
6
|
+
detectDialect,
|
|
7
|
+
findMigrationDirectory,
|
|
8
|
+
formatTimestamp
|
|
9
|
+
} from "./chunk-HDO5P6X7.js";
|
|
10
|
+
|
|
11
|
+
// src/migrate.ts
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
import pc from "picocolors";
|
|
15
|
+
var AUDIT_TABLE_REGEX = /create\s+table\b[^;]*\baudit_logs\b/i;
|
|
16
|
+
var MAX_SCAN_DEPTH = 3;
|
|
17
|
+
function migrationAlreadyExists(directory, depth = 0) {
|
|
18
|
+
if (depth > MAX_SCAN_DEPTH) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (!existsSync(directory)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const entries = readdirSync(directory, { withFileTypes: true });
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
if (entry.isSymbolicLink()) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const fullPath = join(directory, entry.name);
|
|
31
|
+
if (entry.isFile() && entry.name.endsWith(".sql")) {
|
|
32
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
33
|
+
if (AUDIT_TABLE_REGEX.test(content)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
if (migrationAlreadyExists(fullPath, depth + 1)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
async function migrate(options = {}) {
|
|
48
|
+
const cwd = options.cwd ?? process.cwd();
|
|
49
|
+
const dialect = options.dialect ?? detectDialect(options.databaseUrl ?? process.env["DATABASE_URL"]);
|
|
50
|
+
const sql = generateMigrationSql(dialect);
|
|
51
|
+
if (options.dryRun === true) {
|
|
52
|
+
process.stdout.write(sql);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const adapter = options.adapter ?? detectAdapter(cwd);
|
|
56
|
+
if (adapter === void 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Could not detect ORM adapter. Install drizzle-orm or @prisma/client, or pass --adapter drizzle|prisma."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
let outputPath;
|
|
62
|
+
let filePath;
|
|
63
|
+
if (options.output !== void 0) {
|
|
64
|
+
if (options.output.endsWith(".sql")) {
|
|
65
|
+
filePath = options.output;
|
|
66
|
+
outputPath = dirname(filePath);
|
|
67
|
+
} else {
|
|
68
|
+
outputPath = options.output;
|
|
69
|
+
filePath = adapter === "prisma" ? join(outputPath, "migration.sql") : join(outputPath, `${formatTimestamp(/* @__PURE__ */ new Date())}_audit_logs.sql`);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
const migrationDir = findMigrationDirectory(cwd, adapter);
|
|
73
|
+
outputPath = migrationDir;
|
|
74
|
+
filePath = adapter === "prisma" ? join(migrationDir, "migration.sql") : join(migrationDir, `${formatTimestamp(/* @__PURE__ */ new Date())}_audit_logs.sql`);
|
|
75
|
+
}
|
|
76
|
+
const scanDir = adapter === "prisma" ? join(cwd, "prisma", "migrations") : outputPath;
|
|
77
|
+
if (migrationAlreadyExists(scanDir)) {
|
|
78
|
+
console.log(
|
|
79
|
+
pc.green("\u2713") + " audit_logs migration already exists \u2014 up to date."
|
|
80
|
+
);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (options.output !== void 0 && existsSync(filePath)) {
|
|
84
|
+
console.warn(pc.yellow(`Warning: overwriting existing file ${filePath}`));
|
|
85
|
+
}
|
|
86
|
+
mkdirSync(outputPath, { recursive: true });
|
|
87
|
+
writeFileSync(filePath, sql, "utf-8");
|
|
88
|
+
console.log(`${pc.green("\u2713")} Wrote migration: ${pc.dim(filePath)}`);
|
|
89
|
+
console.log("");
|
|
90
|
+
console.log(pc.bold("Next steps:"));
|
|
91
|
+
if (adapter === "drizzle") {
|
|
92
|
+
console.log(` ${pc.yellow("$")} npx drizzle-kit migrate`);
|
|
93
|
+
} else {
|
|
94
|
+
console.log(` ${pc.yellow("$")} npx prisma migrate dev`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
migrate
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=chunk-WVH5TQ2O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["/**\n * `better-audit migrate` — Generate the audit_logs table migration.\n *\n * Generates appropriate DDL for the configured database:\n * - Postgres: JSONB columns, TIMESTAMPTZ, gen_random_uuid()\n * - MySQL: JSON columns, DATETIME(6), UUID() default\n * - SQLite: TEXT columns for JSON, no UUID default\n *\n * Writes the migration file into the appropriate ORM directory,\n * or prints to stdout with --dry-run.\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport pc from \"picocolors\";\nimport { generateMigrationSql, type DatabaseDialect } from \"./generate-sql.js\";\nimport {\n detectAdapter,\n detectDialect,\n findMigrationDirectory,\n type AdapterType,\n} from \"./detect-adapter.js\";\nimport { formatTimestamp } from \"./utils.js\";\n\nexport interface MigrateOptions {\n /** Output SQL to stdout instead of writing a file */\n dryRun?: boolean | undefined;\n /** Override ORM auto-detection */\n adapter?: AdapterType | undefined;\n /** Override database dialect detection */\n dialect?: DatabaseDialect | undefined;\n /** Override output directory/file */\n output?: string | undefined;\n /** Working directory (defaults to process.cwd()) */\n cwd?: string | undefined;\n /** Database connection URL for dialect detection (defaults to DATABASE_URL env) */\n databaseUrl?: string | undefined;\n}\n\n/** Regex to detect an existing audit_logs CREATE TABLE statement (case-insensitive). */\nconst AUDIT_TABLE_REGEX = /create\\s+table\\b[^;]*\\baudit_logs\\b/i;\n\n/** Maximum directory depth for the idempotency scan. */\nconst MAX_SCAN_DEPTH = 3;\n\n/**\n * Check if a migration for `audit_logs` already exists in the given directory.\n * Scans `.sql` files up to `MAX_SCAN_DEPTH` levels deep, skipping symlinks.\n */\nfunction migrationAlreadyExists(directory: string, depth: number = 0): boolean {\n if (depth > MAX_SCAN_DEPTH) {\n return false;\n }\n if (!existsSync(directory)) {\n return false;\n }\n\n try {\n const entries = readdirSync(directory, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isSymbolicLink()) {\n continue;\n }\n const fullPath = join(directory, entry.name);\n if (entry.isFile() && entry.name.endsWith(\".sql\")) {\n const content = readFileSync(fullPath, \"utf-8\");\n if (AUDIT_TABLE_REGEX.test(content)) {\n return true;\n }\n }\n if (entry.isDirectory()) {\n if (migrationAlreadyExists(fullPath, depth + 1)) {\n return true;\n }\n }\n }\n } catch {\n // Read error — treat as no existing migration\n }\n\n return false;\n}\n\nexport async function migrate(options: MigrateOptions = {}): Promise<void> {\n const cwd = options.cwd ?? process.cwd();\n\n // 1. Resolve dialect\n const dialect: DatabaseDialect =\n options.dialect ?? detectDialect(options.databaseUrl ?? process.env[\"DATABASE_URL\"]);\n\n // 2. Generate SQL\n const sql = generateMigrationSql(dialect);\n\n // 3. Dry-run: print to stdout and return\n if (options.dryRun === true) {\n process.stdout.write(sql);\n return;\n }\n\n // 4. Resolve adapter\n const adapter: AdapterType | undefined =\n options.adapter ?? detectAdapter(cwd);\n\n if (adapter === undefined) {\n throw new Error(\n \"Could not detect ORM adapter. Install drizzle-orm or @prisma/client, \" +\n \"or pass --adapter drizzle|prisma.\",\n );\n }\n\n // 5. Resolve output directory\n let outputPath: string;\n let filePath: string;\n\n if (options.output !== undefined) {\n if (options.output.endsWith(\".sql\")) {\n filePath = options.output;\n outputPath = dirname(filePath);\n } else {\n outputPath = options.output;\n filePath =\n adapter === \"prisma\"\n ? join(outputPath, \"migration.sql\")\n : join(outputPath, `${formatTimestamp(new Date())}_audit_logs.sql`);\n }\n } else {\n const migrationDir = findMigrationDirectory(cwd, adapter);\n outputPath = migrationDir;\n filePath =\n adapter === \"prisma\"\n ? join(migrationDir, \"migration.sql\")\n : join(migrationDir, `${formatTimestamp(new Date())}_audit_logs.sql`);\n }\n\n // 6. Check idempotency — scan parent migration directory\n const scanDir =\n adapter === \"prisma\"\n ? join(cwd, \"prisma\", \"migrations\")\n : outputPath;\n\n if (migrationAlreadyExists(scanDir)) {\n console.log(\n pc.green(\"✓\") + \" audit_logs migration already exists — up to date.\",\n );\n return;\n }\n\n // 7. Warn if output file already exists (explicit --output path)\n if (options.output !== undefined && existsSync(filePath)) {\n console.warn(pc.yellow(`Warning: overwriting existing file ${filePath}`));\n }\n\n // 8. Write migration file\n mkdirSync(outputPath, { recursive: true });\n writeFileSync(filePath, sql, \"utf-8\");\n\n console.log(`${pc.green(\"✓\")} Wrote migration: ${pc.dim(filePath)}`);\n console.log(\"\");\n console.log(pc.bold(\"Next steps:\"));\n\n if (adapter === \"drizzle\") {\n console.log(` ${pc.yellow(\"$\")} npx drizzle-kit migrate`);\n } else {\n console.log(` ${pc.yellow(\"$\")} npx prisma migrate dev`);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
CHANGED
|
@@ -1 +1,156 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CheckFailedError,
|
|
4
|
+
check
|
|
5
|
+
} from "./chunk-55KKYFKR.js";
|
|
6
|
+
import {
|
|
7
|
+
exportLogs
|
|
8
|
+
} from "./chunk-M46VJ3FO.js";
|
|
9
|
+
import {
|
|
10
|
+
migrate
|
|
11
|
+
} from "./chunk-WVH5TQ2O.js";
|
|
12
|
+
import "./chunk-O5LHE2AC.js";
|
|
13
|
+
import {
|
|
14
|
+
purge
|
|
15
|
+
} from "./chunk-SJSGTCG4.js";
|
|
16
|
+
import "./chunk-7GSN73TA.js";
|
|
17
|
+
import "./chunk-HDO5P6X7.js";
|
|
18
|
+
import {
|
|
19
|
+
stats
|
|
20
|
+
} from "./chunk-AGFBL646.js";
|
|
21
|
+
|
|
22
|
+
// src/cli.ts
|
|
23
|
+
import { readFileSync } from "fs";
|
|
24
|
+
import { dirname, join } from "path";
|
|
25
|
+
import { fileURLToPath } from "url";
|
|
26
|
+
import { Command } from "commander";
|
|
27
|
+
import pc from "picocolors";
|
|
28
|
+
var cliDir = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
var pkgRaw = JSON.parse(
|
|
30
|
+
readFileSync(join(cliDir, "..", "package.json"), "utf-8")
|
|
31
|
+
);
|
|
32
|
+
var cliVersion = typeof pkgRaw === "object" && pkgRaw !== null && "version" in pkgRaw && typeof pkgRaw.version === "string" ? pkgRaw.version : "0.0.0";
|
|
33
|
+
function parseAdapter(value) {
|
|
34
|
+
if (value === "drizzle" || value === "prisma" || value === void 0) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
throw new Error("--adapter must be 'drizzle' or 'prisma'");
|
|
38
|
+
}
|
|
39
|
+
function parseDialect(value) {
|
|
40
|
+
if (value === "postgres" || value === "mysql" || value === "sqlite" || value === void 0) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
throw new Error("--dialect must be 'postgres', 'mysql', or 'sqlite'");
|
|
44
|
+
}
|
|
45
|
+
function formatErrorMessage(err) {
|
|
46
|
+
if (err instanceof AggregateError && err.errors.length > 0) {
|
|
47
|
+
const nested = err.errors.map((e) => e instanceof Error ? e.message : String(e)).join("; ");
|
|
48
|
+
return err.message !== "" ? `${err.message}: ${nested}` : nested;
|
|
49
|
+
}
|
|
50
|
+
if (err instanceof Error) {
|
|
51
|
+
return err.message;
|
|
52
|
+
}
|
|
53
|
+
return String(err);
|
|
54
|
+
}
|
|
55
|
+
async function runAction(fn) {
|
|
56
|
+
try {
|
|
57
|
+
await fn();
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err instanceof CheckFailedError) {
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const message = formatErrorMessage(err);
|
|
63
|
+
console.error(pc.red(message));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
var program = new Command().name("@usebetterdev/audit-cli").description("CLI for @usebetterdev/audit \u2014 compliance-ready audit logging").version(cliVersion);
|
|
68
|
+
program.command("migrate").description("Generate the audit_logs table migration").option(
|
|
69
|
+
"--dry-run",
|
|
70
|
+
"Print SQL to stdout without writing a file"
|
|
71
|
+
).option(
|
|
72
|
+
"--adapter <adapter>",
|
|
73
|
+
"ORM adapter: drizzle or prisma (auto-detected from package.json)"
|
|
74
|
+
).option(
|
|
75
|
+
"--dialect <dialect>",
|
|
76
|
+
"Database dialect: postgres, mysql, or sqlite (auto-detected from DATABASE_URL)"
|
|
77
|
+
).option("-o, --output <path>", "Output directory or .sql file path").action(
|
|
78
|
+
async (opts) => {
|
|
79
|
+
await runAction(
|
|
80
|
+
() => migrate({
|
|
81
|
+
dryRun: opts.dryRun,
|
|
82
|
+
adapter: parseAdapter(opts.adapter),
|
|
83
|
+
dialect: parseDialect(opts.dialect),
|
|
84
|
+
output: opts.output
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
program.command("check").description("Verify the audit_logs table and ORM adapter are working").option("--verbose", "Show detailed results for each check").option(
|
|
90
|
+
"--database-url <url>",
|
|
91
|
+
"Database URL (default: DATABASE_URL env)"
|
|
92
|
+
).action(async (opts) => {
|
|
93
|
+
await runAction(
|
|
94
|
+
() => check({ verbose: opts.verbose, databaseUrl: opts.databaseUrl })
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
program.command("stats").description("Show audit log statistics").option("-c, --config <path>", "Config file path", "better-audit.config.ts").option("--since <period>", "Time window (e.g. 30d)", "30d").action(async (opts) => {
|
|
98
|
+
await runAction(() => stats({ config: opts.config, since: opts.since }));
|
|
99
|
+
});
|
|
100
|
+
program.command("purge").description("Delete audit logs older than the retention period").option("-c, --config <path>", "Config file path", "better-audit.config.ts").option("--dry-run", "Preview rows to be deleted without deleting them").option(
|
|
101
|
+
"--since <value>",
|
|
102
|
+
'ISO date (e.g. "2025-01-01") or duration shorthand (e.g. "90d") \u2014 overrides config'
|
|
103
|
+
).option("--batch-size <n>", "Rows per DELETE batch (default: 1000)").option(
|
|
104
|
+
"--database-url <url>",
|
|
105
|
+
"Database URL (default: DATABASE_URL env)"
|
|
106
|
+
).option("-y, --yes", "Skip confirmation prompt (required for live deletion)").action(
|
|
107
|
+
async (opts) => {
|
|
108
|
+
await runAction(async () => {
|
|
109
|
+
const purgeOpts = { config: opts.config };
|
|
110
|
+
if (opts.dryRun !== void 0) {
|
|
111
|
+
purgeOpts.dryRun = opts.dryRun;
|
|
112
|
+
}
|
|
113
|
+
if (opts.since !== void 0) {
|
|
114
|
+
purgeOpts.since = opts.since;
|
|
115
|
+
}
|
|
116
|
+
if (opts.batchSize !== void 0) {
|
|
117
|
+
const n = Number(opts.batchSize);
|
|
118
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Invalid --batch-size "${opts.batchSize}". Expected a positive integer.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
purgeOpts.batchSize = n;
|
|
124
|
+
}
|
|
125
|
+
if (opts.databaseUrl !== void 0) {
|
|
126
|
+
purgeOpts.databaseUrl = opts.databaseUrl;
|
|
127
|
+
}
|
|
128
|
+
if (opts.yes !== void 0) {
|
|
129
|
+
purgeOpts.yes = opts.yes;
|
|
130
|
+
}
|
|
131
|
+
await purge(purgeOpts);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
program.command("export").description("Export audit log entries as CSV or JSON").option("--format <format>", "Output format: csv or json (default: csv)").option("-o, --output <path>", "Output file path (default: stdout)").option(
|
|
136
|
+
"--since <value>",
|
|
137
|
+
'Duration (e.g. "90d") or ISO date (e.g. "2025-01-01")'
|
|
138
|
+
).option(
|
|
139
|
+
"--severity <level>",
|
|
140
|
+
"Filter by severity: low, medium, high, or critical"
|
|
141
|
+
).option(
|
|
142
|
+
"--compliance <tags>",
|
|
143
|
+
'Comma-separated compliance tags (e.g. "soc2:access-control,gdpr")'
|
|
144
|
+
).option("--actor <id>", "Filter by actor ID").option("--limit <n>", "Maximum number of rows to export").option(
|
|
145
|
+
"--database-url <url>",
|
|
146
|
+
"Database URL (default: DATABASE_URL env)"
|
|
147
|
+
).action(
|
|
148
|
+
async (opts) => {
|
|
149
|
+
await runAction(() => exportLogs(opts));
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
153
|
+
console.error(err);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { migrate } from \"./migrate.js\";\nimport { check, CheckFailedError } from \"./check.js\";\nimport { stats } from \"./stats.js\";\nimport { purge } from \"./purge.js\";\nimport { exportLogs } from \"./export.js\";\n\n// Read version from package.json at the package root\nconst cliDir = dirname(fileURLToPath(import.meta.url));\nconst pkgRaw: unknown = JSON.parse(\n readFileSync(join(cliDir, \"..\", \"package.json\"), \"utf-8\"),\n);\nconst cliVersion =\n typeof pkgRaw === \"object\" &&\n pkgRaw !== null &&\n \"version\" in pkgRaw &&\n typeof pkgRaw.version === \"string\"\n ? pkgRaw.version\n : \"0.0.0\";\n\n/** Validate and narrow the --adapter flag value. */\nfunction parseAdapter(\n value: string | undefined,\n): \"drizzle\" | \"prisma\" | undefined {\n if (value === \"drizzle\" || value === \"prisma\" || value === undefined) {\n return value;\n }\n throw new Error(\"--adapter must be 'drizzle' or 'prisma'\");\n}\n\n/** Validate and narrow the --dialect flag value. */\nfunction parseDialect(\n value: string | undefined,\n): \"postgres\" | \"mysql\" | \"sqlite\" | undefined {\n if (\n value === \"postgres\" ||\n value === \"mysql\" ||\n value === \"sqlite\" ||\n value === undefined\n ) {\n return value;\n }\n throw new Error(\"--dialect must be 'postgres', 'mysql', or 'sqlite'\");\n}\n\n/** Extract a human-readable message from an error, including AggregateError nested errors. */\nfunction formatErrorMessage(err: unknown): string {\n if (err instanceof AggregateError && err.errors.length > 0) {\n // AggregateError (e.g. from pg connection failure) has an empty .message\n // but contains nested errors with the actual details\n const nested = err.errors\n .map((e: unknown) => (e instanceof Error ? e.message : String(e)))\n .join(\"; \");\n return err.message !== \"\" ? `${err.message}: ${nested}` : nested;\n }\n if (err instanceof Error) {\n return err.message;\n }\n return String(err);\n}\n\n/** Wrap a subcommand action with consistent error handling. */\nasync function runAction(fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n } catch (err) {\n // CheckFailedError already printed its output — just exit\n if (err instanceof CheckFailedError) {\n process.exit(1);\n }\n const message = formatErrorMessage(err);\n console.error(pc.red(message));\n process.exit(1);\n }\n}\n\nconst program = new Command()\n .name(\"@usebetterdev/audit-cli\")\n .description(\"CLI for @usebetterdev/audit — compliance-ready audit logging\")\n .version(cliVersion);\n\nprogram\n .command(\"migrate\")\n .description(\"Generate the audit_logs table migration\")\n .option(\n \"--dry-run\",\n \"Print SQL to stdout without writing a file\",\n )\n .option(\n \"--adapter <adapter>\",\n \"ORM adapter: drizzle or prisma (auto-detected from package.json)\",\n )\n .option(\n \"--dialect <dialect>\",\n \"Database dialect: postgres, mysql, or sqlite (auto-detected from DATABASE_URL)\",\n )\n .option(\"-o, --output <path>\", \"Output directory or .sql file path\")\n .action(\n async (opts: {\n dryRun?: boolean;\n adapter?: string;\n dialect?: string;\n output?: string;\n }) => {\n await runAction(() =>\n migrate({\n dryRun: opts.dryRun,\n adapter: parseAdapter(opts.adapter),\n dialect: parseDialect(opts.dialect),\n output: opts.output,\n }),\n );\n },\n );\n\nprogram\n .command(\"check\")\n .description(\"Verify the audit_logs table and ORM adapter are working\")\n .option(\"--verbose\", \"Show detailed results for each check\")\n .option(\n \"--database-url <url>\",\n \"Database URL (default: DATABASE_URL env)\",\n )\n .action(async (opts: { verbose?: boolean; databaseUrl?: string }) => {\n await runAction(() =>\n check({ verbose: opts.verbose, databaseUrl: opts.databaseUrl }),\n );\n });\n\nprogram\n .command(\"stats\")\n .description(\"Show audit log statistics\")\n .option(\"-c, --config <path>\", \"Config file path\", \"better-audit.config.ts\")\n .option(\"--since <period>\", \"Time window (e.g. 30d)\", \"30d\")\n .action(async (opts: { config: string; since: string }) => {\n await runAction(() => stats({ config: opts.config, since: opts.since }));\n });\n\nprogram\n .command(\"purge\")\n .description(\"Delete audit logs older than the retention period\")\n .option(\"-c, --config <path>\", \"Config file path\", \"better-audit.config.ts\")\n .option(\"--dry-run\", \"Preview rows to be deleted without deleting them\")\n .option(\n \"--since <value>\",\n 'ISO date (e.g. \"2025-01-01\") or duration shorthand (e.g. \"90d\") — overrides config',\n )\n .option(\"--batch-size <n>\", \"Rows per DELETE batch (default: 1000)\")\n .option(\n \"--database-url <url>\",\n \"Database URL (default: DATABASE_URL env)\",\n )\n .option(\"-y, --yes\", \"Skip confirmation prompt (required for live deletion)\")\n .action(\n async (opts: {\n config: string;\n dryRun?: boolean;\n since?: string;\n batchSize?: string;\n databaseUrl?: string;\n yes?: boolean;\n }) => {\n await runAction(async () => {\n const purgeOpts: Parameters<typeof purge>[0] = { config: opts.config };\n if (opts.dryRun !== undefined) {\n purgeOpts.dryRun = opts.dryRun;\n }\n if (opts.since !== undefined) {\n purgeOpts.since = opts.since;\n }\n if (opts.batchSize !== undefined) {\n const n = Number(opts.batchSize);\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(\n `Invalid --batch-size \"${opts.batchSize}\". Expected a positive integer.`,\n );\n }\n purgeOpts.batchSize = n;\n }\n if (opts.databaseUrl !== undefined) {\n purgeOpts.databaseUrl = opts.databaseUrl;\n }\n if (opts.yes !== undefined) {\n purgeOpts.yes = opts.yes;\n }\n await purge(purgeOpts);\n });\n },\n );\n\nprogram\n .command(\"export\")\n .description(\"Export audit log entries as CSV or JSON\")\n .option(\"--format <format>\", \"Output format: csv or json (default: csv)\")\n .option(\"-o, --output <path>\", \"Output file path (default: stdout)\")\n .option(\n \"--since <value>\",\n 'Duration (e.g. \"90d\") or ISO date (e.g. \"2025-01-01\")',\n )\n .option(\n \"--severity <level>\",\n \"Filter by severity: low, medium, high, or critical\",\n )\n .option(\n \"--compliance <tags>\",\n 'Comma-separated compliance tags (e.g. \"soc2:access-control,gdpr\")',\n )\n .option(\"--actor <id>\", \"Filter by actor ID\")\n .option(\"--limit <n>\", \"Maximum number of rows to export\")\n .option(\n \"--database-url <url>\",\n \"Database URL (default: DATABASE_URL env)\",\n )\n .action(\n async (opts: {\n format?: string;\n output?: string;\n since?: string;\n severity?: string;\n compliance?: string;\n actor?: string;\n limit?: string;\n databaseUrl?: string;\n }) => {\n await runAction(() => exportLogs(opts));\n },\n );\n\nprogram.parseAsync(process.argv).catch((err: unknown) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AuditSeverity } from '@usebetterdev/audit-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `better-audit export` — Export audit log entries as CSV or JSON.
|
|
5
|
+
*
|
|
6
|
+
* Connects to the database via Kysely (multi-dialect), builds query filters
|
|
7
|
+
* from CLI flags, and streams results to a file or stdout.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface ExportCommandOptions {
|
|
11
|
+
format?: string;
|
|
12
|
+
output?: string;
|
|
13
|
+
since?: string;
|
|
14
|
+
severity?: string;
|
|
15
|
+
compliance?: string;
|
|
16
|
+
actor?: string;
|
|
17
|
+
limit?: string;
|
|
18
|
+
databaseUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Parse `--format` flag, validate and default to "csv". */
|
|
21
|
+
declare function parseFormat(value: string | undefined): "csv" | "json";
|
|
22
|
+
/** Parse `--severity` flag, validate against known values. */
|
|
23
|
+
declare function parseSeverity(value: string): AuditSeverity;
|
|
24
|
+
/**
|
|
25
|
+
* Parse `--since` flag. ISO date strings (e.g. "2025-01-01") become Date objects.
|
|
26
|
+
* Duration strings (e.g. "90d") are returned as-is for the query builder.
|
|
27
|
+
*/
|
|
28
|
+
declare function parseSinceValue(value: string): Date | string;
|
|
29
|
+
/**
|
|
30
|
+
* Create a WHATWG WritableStream<string> that writes to a Node.js file.
|
|
31
|
+
* Handles backpressure by awaiting the drain event.
|
|
32
|
+
*/
|
|
33
|
+
declare function createFileWritableStream(path: string): WritableStream<string>;
|
|
34
|
+
/** Create a WHATWG WritableStream<string> that writes to process.stdout. */
|
|
35
|
+
declare function createStdoutWritableStream(): WritableStream<string>;
|
|
36
|
+
declare function exportLogs(options: ExportCommandOptions): Promise<void>;
|
|
37
|
+
|
|
38
|
+
export { type ExportCommandOptions, createFileWritableStream, createStdoutWritableStream, exportLogs, parseFormat, parseSeverity, parseSinceValue };
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createFileWritableStream,
|
|
3
|
+
createStdoutWritableStream,
|
|
4
|
+
exportLogs,
|
|
5
|
+
parseFormat,
|
|
6
|
+
parseSeverity,
|
|
7
|
+
parseSinceValue
|
|
8
|
+
} from "./chunk-M46VJ3FO.js";
|
|
9
|
+
import "./chunk-7GSN73TA.js";
|
|
10
|
+
import "./chunk-HDO5P6X7.js";
|
|
11
|
+
export {
|
|
12
|
+
createFileWritableStream,
|
|
13
|
+
createStdoutWritableStream,
|
|
14
|
+
exportLogs,
|
|
15
|
+
parseFormat,
|
|
16
|
+
parseSeverity,
|
|
17
|
+
parseSinceValue
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { A as AdapterType, D as DatabaseDialect } from './detect-adapter-DNHcPCKz.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `better-audit migrate` — Generate the audit_logs table migration.
|
|
5
|
+
*
|
|
6
|
+
* Generates appropriate DDL for the configured database:
|
|
7
|
+
* - Postgres: JSONB columns, TIMESTAMPTZ, gen_random_uuid()
|
|
8
|
+
* - MySQL: JSON columns, DATETIME(6), UUID() default
|
|
9
|
+
* - SQLite: TEXT columns for JSON, no UUID default
|
|
10
|
+
*
|
|
11
|
+
* Writes the migration file into the appropriate ORM directory,
|
|
12
|
+
* or prints to stdout with --dry-run.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface MigrateOptions {
|
|
16
|
+
/** Output SQL to stdout instead of writing a file */
|
|
17
|
+
dryRun?: boolean | undefined;
|
|
18
|
+
/** Override ORM auto-detection */
|
|
19
|
+
adapter?: AdapterType | undefined;
|
|
20
|
+
/** Override database dialect detection */
|
|
21
|
+
dialect?: DatabaseDialect | undefined;
|
|
22
|
+
/** Override output directory/file */
|
|
23
|
+
output?: string | undefined;
|
|
24
|
+
/** Working directory (defaults to process.cwd()) */
|
|
25
|
+
cwd?: string | undefined;
|
|
26
|
+
/** Database connection URL for dialect detection (defaults to DATABASE_URL env) */
|
|
27
|
+
databaseUrl?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
declare function migrate(options?: MigrateOptions): Promise<void>;
|
|
30
|
+
|
|
31
|
+
export { type MigrateOptions, migrate };
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/purge.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `better-audit purge` — Delete audit logs older than the configured retention period.
|
|
3
|
+
*
|
|
4
|
+
* Connects to the database directly via Kysely (no ORM adapter needed).
|
|
5
|
+
* Executes batched DELETEs to avoid holding long row-level locks on large tables.
|
|
6
|
+
*
|
|
7
|
+
* Always run with --dry-run first to preview what will be deleted.
|
|
8
|
+
*/
|
|
9
|
+
interface PurgeOptions {
|
|
10
|
+
config?: string;
|
|
11
|
+
/** Preview rows to be deleted without deleting them. */
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
/** ISO date string (e.g. "2025-01-01") or duration shorthand (e.g. "90d", "1y"). */
|
|
14
|
+
since?: string;
|
|
15
|
+
/** Rows per DELETE batch. Default: 1000. */
|
|
16
|
+
batchSize?: number;
|
|
17
|
+
/** Database URL (default: DATABASE_URL env). */
|
|
18
|
+
databaseUrl?: string;
|
|
19
|
+
/** Skip confirmation prompt (required for live deletion). */
|
|
20
|
+
yes?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse a `--since` value to an absolute cutoff `Date`.
|
|
24
|
+
*
|
|
25
|
+
* Accepts:
|
|
26
|
+
* - ISO-8601 date strings: "2025-01-01" or "2025-01-01T00:00:00Z"
|
|
27
|
+
* - Duration shorthands: "90d", "4w", "3m", "1y"
|
|
28
|
+
*
|
|
29
|
+
* Exported for testing.
|
|
30
|
+
*/
|
|
31
|
+
declare function parseSinceValue(value: string): Date;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the cutoff date from options.
|
|
34
|
+
*
|
|
35
|
+
* Priority: `--since` flag > config `retention.days`.
|
|
36
|
+
* Throws if neither is available.
|
|
37
|
+
*
|
|
38
|
+
* Exported for testing.
|
|
39
|
+
*/
|
|
40
|
+
declare function resolveCutoffDate(options: {
|
|
41
|
+
since?: string;
|
|
42
|
+
config?: string;
|
|
43
|
+
}): Promise<Date>;
|
|
44
|
+
/**
|
|
45
|
+
* Format elapsed milliseconds to a human-readable string.
|
|
46
|
+
*
|
|
47
|
+
* Exported for testing.
|
|
48
|
+
*/
|
|
49
|
+
declare function formatDuration(ms: number): string;
|
|
50
|
+
declare function purge(options?: PurgeOptions): Promise<void>;
|
|
51
|
+
|
|
52
|
+
export { type PurgeOptions, formatDuration, parseSinceValue, purge, resolveCutoffDate };
|
package/dist/purge.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatDuration,
|
|
3
|
+
parseSinceValue,
|
|
4
|
+
purge,
|
|
5
|
+
resolveCutoffDate
|
|
6
|
+
} from "./chunk-SJSGTCG4.js";
|
|
7
|
+
import "./chunk-7GSN73TA.js";
|
|
8
|
+
import "./chunk-HDO5P6X7.js";
|
|
9
|
+
export {
|
|
10
|
+
formatDuration,
|
|
11
|
+
parseSinceValue,
|
|
12
|
+
purge,
|
|
13
|
+
resolveCutoffDate
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=purge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `better-audit stats` — Show audit log statistics.
|
|
3
|
+
*
|
|
4
|
+
* Outputs:
|
|
5
|
+
* - Total log count
|
|
6
|
+
* - Events per day (last 7 / 30 days)
|
|
7
|
+
* - Top actors (by event count)
|
|
8
|
+
* - Top tables (by event count)
|
|
9
|
+
* - Severity distribution
|
|
10
|
+
*
|
|
11
|
+
* TODO: Connect to DB and run aggregation queries.
|
|
12
|
+
*/
|
|
13
|
+
interface StatsOptions {
|
|
14
|
+
config?: string;
|
|
15
|
+
since?: string;
|
|
16
|
+
}
|
|
17
|
+
declare function stats(options?: StatsOptions): Promise<void>;
|
|
18
|
+
|
|
19
|
+
export { type StatsOptions, stats };
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usebetterdev/audit-cli",
|
|
3
|
-
"version": "0.5.0
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"repository": "github:usebetter-dev/usebetter",
|
|
5
5
|
"bugs": "https://github.com/usebetter-dev/usebetter/issues",
|
|
6
6
|
"homepage": "https://github.com/usebetter-dev/usebetter#readme",
|
|
@@ -20,8 +20,25 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@clack/prompts": "^0.10.0",
|
|
22
22
|
"commander": "^12.1.0",
|
|
23
|
+
"kysely": "^0.28.11",
|
|
23
24
|
"picocolors": "^1.1.0",
|
|
24
|
-
"@usebetterdev/audit-core": "0.5.0
|
|
25
|
+
"@usebetterdev/audit-core": "0.5.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"pg": ">=8.0.0",
|
|
29
|
+
"mysql2": ">=3.0.0",
|
|
30
|
+
"better-sqlite3": ">=11.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"pg": {
|
|
34
|
+
"optional": true
|
|
35
|
+
},
|
|
36
|
+
"mysql2": {
|
|
37
|
+
"optional": true
|
|
38
|
+
},
|
|
39
|
+
"better-sqlite3": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
25
42
|
},
|
|
26
43
|
"devDependencies": {
|
|
27
44
|
"@types/node": "^22.10.0",
|
|
@@ -34,6 +51,7 @@
|
|
|
34
51
|
},
|
|
35
52
|
"scripts": {
|
|
36
53
|
"build": "tsup",
|
|
54
|
+
"build:types": "tsc --build tsconfig.build.json",
|
|
37
55
|
"test": "vitest run",
|
|
38
56
|
"test:integration": "vitest run -c vitest.integration.config.ts",
|
|
39
57
|
"typecheck": "tsc --noEmit"
|