@crombolo/cli 0.10.0 → 0.11.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/bin/crombolo.ts
CHANGED
|
@@ -2111,7 +2111,10 @@ function help(): void {
|
|
|
2111
2111
|
}
|
|
2112
2112
|
|
|
2113
2113
|
try {
|
|
2114
|
+
// Before dispatching, which rewrites `bundle install` as `content import`.
|
|
2115
|
+
const audited = commandAudit()
|
|
2114
2116
|
await dispatch()
|
|
2117
|
+
if (audited) await ops.recordCommand(await siteConfig(), audited)
|
|
2115
2118
|
} catch (error) {
|
|
2116
2119
|
if (!(error instanceof ops.CommandError)) throw error
|
|
2117
2120
|
for (const problem of error.problems) console.error(` ${problem}`)
|
|
@@ -2119,6 +2122,52 @@ try {
|
|
|
2119
2122
|
process.exit(1)
|
|
2120
2123
|
}
|
|
2121
2124
|
|
|
2125
|
+
// A declaration, not a const: the top-level dispatch above runs before this line.
|
|
2126
|
+
function capital(word: string): string {
|
|
2127
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
/** The audit log entry for a command that changes the environment; undefined for one that only reads. */
|
|
2131
|
+
function commandAudit(): ops.CommandAudit | undefined {
|
|
2132
|
+
if (flags.has('--dry-run') || flags.has('--plan') || flags.has('--help')) return undefined
|
|
2133
|
+
const sub = positional[0]
|
|
2134
|
+
const typed = `crombolo ${command}${sub ? ` ${sub}` : ''}`
|
|
2135
|
+
const entry = (
|
|
2136
|
+
category: ops.CommandAudit['category'],
|
|
2137
|
+
action: string,
|
|
2138
|
+
entityType?: string,
|
|
2139
|
+
entityKey?: string,
|
|
2140
|
+
): ops.CommandAudit => ({ category, action, command: typed, entityType, entityKey })
|
|
2141
|
+
switch (command) {
|
|
2142
|
+
case 'admin':
|
|
2143
|
+
return sub === 'reset-password' ? entry('user', 'ResetPassword', 'User') : undefined
|
|
2144
|
+
case 'schema':
|
|
2145
|
+
if (sub === 'sync') return entry('schema', 'Sync', 'Schema')
|
|
2146
|
+
return sub === 'purge' ? entry('schema', 'Purge', 'Schema') : undefined
|
|
2147
|
+
case 'content':
|
|
2148
|
+
case 'bundle':
|
|
2149
|
+
if (sub === 'purge') return entry('system', 'Purge', 'Bundle', positional[1])
|
|
2150
|
+
if (sub === 'import' || sub === 'install') return entry('content', 'Import', 'Bundle')
|
|
2151
|
+
return sub === 'publish' || sub === 'unpublish' || sub === 'revert'
|
|
2152
|
+
? entry('content', capital(sub), 'Document', positional[1])
|
|
2153
|
+
: undefined
|
|
2154
|
+
case 'dictionary':
|
|
2155
|
+
return sub === 'import' ? entry('settings', 'Import', 'DictionaryItem') : undefined
|
|
2156
|
+
case 'domains':
|
|
2157
|
+
return sub && sub !== 'list'
|
|
2158
|
+
? entry('settings', `${capital(sub)}Domains`, 'Domain')
|
|
2159
|
+
: undefined
|
|
2160
|
+
case 'upgrade':
|
|
2161
|
+
if (sub === undefined) return entry('system', 'Upgrade')
|
|
2162
|
+
return sub === 'check' && flags.has('--fix') ? entry('system', 'UpgradeFix') : undefined
|
|
2163
|
+
case 'maintenance':
|
|
2164
|
+
if (sub === 'pause') return entry('system', 'PauseEditing')
|
|
2165
|
+
return sub === 'resume' ? entry('system', 'ResumeEditing') : undefined
|
|
2166
|
+
default:
|
|
2167
|
+
return undefined
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2122
2171
|
async function dispatch(): Promise<void> {
|
|
2123
2172
|
switch (command) {
|
|
2124
2173
|
case 'start':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crombolo/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "The crombolo command line: scaffolding a site, schema, views, domains, content transfer and upgrades.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Crombolo",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@crombolo/auth": "0.
|
|
38
|
-
"@crombolo/core": "0.
|
|
39
|
-
"@crombolo/data": "0.
|
|
40
|
-
"@crombolo/schema": "0.
|
|
41
|
-
"@crombolo/server": "0.
|
|
42
|
-
"@crombolo/transfer": "0.
|
|
37
|
+
"@crombolo/auth": "0.11.0",
|
|
38
|
+
"@crombolo/core": "0.11.0",
|
|
39
|
+
"@crombolo/data": "0.11.0",
|
|
40
|
+
"@crombolo/schema": "0.11.0",
|
|
41
|
+
"@crombolo/server": "0.11.0",
|
|
42
|
+
"@crombolo/transfer": "0.11.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* a platform's own tooling — gets the same results as data. One implementation,
|
|
6
6
|
* so the two can never disagree about what an import or an upgrade does.
|
|
7
7
|
*/
|
|
8
|
+
import { userInfo } from 'node:os'
|
|
8
9
|
import { ROOT_ACCESS } from '@crombolo/core'
|
|
9
10
|
import {
|
|
11
|
+
type AuditCategory,
|
|
10
12
|
bundleStateKey,
|
|
11
13
|
bundleTablesIn,
|
|
12
14
|
connect,
|
|
@@ -52,6 +54,7 @@ import {
|
|
|
52
54
|
planBundleUpgrades,
|
|
53
55
|
readBlobs,
|
|
54
56
|
readBundle,
|
|
57
|
+
recordAudit,
|
|
55
58
|
syncOptionsFor,
|
|
56
59
|
type TransferInput,
|
|
57
60
|
VERSION,
|
|
@@ -565,6 +568,44 @@ export async function backup(
|
|
|
565
568
|
})
|
|
566
569
|
}
|
|
567
570
|
|
|
571
|
+
export interface CommandAudit {
|
|
572
|
+
category: AuditCategory
|
|
573
|
+
action: string
|
|
574
|
+
/** The command as typed, without its arguments, which can hold a password. */
|
|
575
|
+
command: string
|
|
576
|
+
entityType?: string
|
|
577
|
+
entityKey?: string
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Records a command that changed this environment, in the audit log, as the
|
|
582
|
+
* account that ran it — `CROMBOLO_ACTOR` when a pipeline names who it runs for.
|
|
583
|
+
* Never fails the command: what it records has already happened.
|
|
584
|
+
*/
|
|
585
|
+
export async function recordCommand(config: CromboloConfig, entry: CommandAudit): Promise<void> {
|
|
586
|
+
let actor = Bun.env.CROMBOLO_ACTOR
|
|
587
|
+
try {
|
|
588
|
+
actor ??= userInfo().username
|
|
589
|
+
} catch {
|
|
590
|
+
actor ??= undefined
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
await withDatabase(config, (db) =>
|
|
594
|
+
recordAudit(db, {
|
|
595
|
+
category: entry.category,
|
|
596
|
+
action: entry.action,
|
|
597
|
+
source: 'cli',
|
|
598
|
+
userName: actor ?? null,
|
|
599
|
+
entityType: entry.entityType ?? null,
|
|
600
|
+
entityKey: entry.entityKey ?? null,
|
|
601
|
+
detail: entry.command,
|
|
602
|
+
}),
|
|
603
|
+
)
|
|
604
|
+
} catch {
|
|
605
|
+
// No database to write to, or one without the log yet: the command still happened.
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
568
609
|
async function withDatabase<T>(config: CromboloConfig, use: (db: Db) => Promise<T>): Promise<T> {
|
|
569
610
|
const db = await connect({ file: config.sqliteFile })
|
|
570
611
|
try {
|