@lyupro/codex-bridge 0.6.8 → 0.6.10
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/CHANGELOG.md +160 -0
- package/README.md +6 -4
- package/bin/codex-bridge.mjs +35 -177
- package/cli/command-help.mjs +49 -0
- package/cli/command-registry.mjs +284 -0
- package/cli/copy.mjs +42 -5
- package/cli/dispatcher-contract-record.mjs +3 -2
- package/cli/doctor-host-side.mjs +110 -0
- package/cli/doctor.mjs +17 -1
- package/cli/home-inspection.mjs +122 -0
- package/cli/home-plan-execute.mjs +140 -0
- package/cli/home-removal-plan.mjs +61 -0
- package/cli/hook-recognizer.mjs +78 -0
- package/cli/host-contract.mjs +11 -5
- package/cli/host-inspection.mjs +156 -0
- package/cli/host-removal.mjs +91 -0
- package/cli/image-evidence.mjs +44 -0
- package/cli/install-owners.mjs +244 -0
- package/cli/install-record.mjs +71 -9
- package/cli/install.mjs +88 -75
- package/cli/inventory-removal.mjs +84 -0
- package/cli/inventory-transition.mjs +66 -0
- package/cli/lifecycle-lock-clear.mjs +104 -0
- package/cli/lifecycle-lock-inspect.mjs +92 -0
- package/cli/lifecycle-lock.mjs +222 -0
- package/cli/lifecycle-transaction.mjs +57 -0
- package/cli/link-segments.mjs +45 -0
- package/cli/model-set.mjs +4 -3
- package/cli/model-speed.mjs +9 -2
- package/cli/model.mjs +5 -3
- package/cli/owner-record.mjs +40 -0
- package/cli/owner-sync-apply.mjs +108 -0
- package/cli/owner-sync.mjs +115 -0
- package/cli/projects.mjs +5 -2
- package/cli/prune-args.mjs +9 -4
- package/cli/prune.mjs +23 -72
- package/cli/purge-live-runs.mjs +98 -0
- package/cli/purge-preflight.mjs +103 -0
- package/cli/read.mjs +22 -1
- package/cli/record-removal.mjs +43 -0
- package/cli/removal-outcomes.mjs +115 -0
- package/cli/removal-plan.mjs +187 -0
- package/cli/remove-layout.mjs +40 -1
- package/cli/rules-owners.mjs +4 -2
- package/cli/settings-merge.mjs +3 -29
- package/cli/terminal-question.mjs +41 -0
- package/cli/uninstall-purge.mjs +139 -0
- package/cli/uninstall-rules.mjs +72 -0
- package/cli/uninstall.mjs +208 -127
- package/cli/unlock-lifecycle.mjs +102 -0
- package/cli/unlock.mjs +7 -4
- package/cli/update.mjs +102 -27
- package/package.json +4 -3
- package/scripts/home-write-audit.mjs +286 -0
- package/scripts/home-write-inventory.mjs +59 -0
- package/scripts/home-write-lexer.mjs +182 -0
- package/scripts/isolated-roots.mjs +6 -1
- package/scripts/open-work/check.mjs +180 -0
- package/scripts/open-work/live-thresholds.mjs +155 -0
- package/scripts/open-work/register.mjs +56 -0
- package/scripts/root-guard/root-entries.mjs +15 -0
- package/scripts/run-tests.mjs +26 -1
- package/src/agents/codex-advisor.md +23 -10
- package/src/agents/codex-build.md +24 -14
- package/src/agents/codex-review.md +17 -10
- package/src/agents/codex-scout.md +22 -12
- package/src/home/conventions.md +9 -0
- package/src/home/hooks/guard-tries.mjs +6 -4
- package/src/home/hooks/order-gate.mjs +27 -19
- package/src/home/hooks/reply-guard.mjs +2 -7
- package/src/home/hooks/reply-parser.mjs +1 -1
- package/src/home/hooks/worktree-witness.mjs +6 -6
- package/src/home/lib/agents.mjs +1 -1
- package/src/home/lib/atomic-json.mjs +24 -6
- package/src/home/lib/command-path.mjs +32 -0
- package/src/home/lib/config-edit.mjs +25 -10
- package/src/home/lib/dispatcher-command.mjs +4 -2
- package/src/home/lib/dispatcher-state.mjs +25 -10
- package/src/home/lib/file-lock.mjs +41 -10
- package/src/home/lib/handback-witness.mjs +7 -4
- package/src/home/lib/home-registry.mjs +211 -0
- package/src/home/lib/home-write.mjs +189 -0
- package/src/home/lib/hook-diagnostics.mjs +20 -0
- package/src/home/lib/host-observations.mjs +7 -4
- package/src/home/lib/kernel-lock.mjs +171 -0
- package/src/home/lib/meta/advice-status.mjs +2 -2
- package/src/home/lib/meta/advice-verdict.mjs +30 -1
- package/src/home/lib/meta/chain.mjs +19 -8
- package/src/home/lib/meta/context-only.mjs +43 -0
- package/src/home/lib/meta/events.mjs +16 -3
- package/src/home/lib/meta/launch-rows.mjs +23 -0
- package/src/home/lib/meta/paths.mjs +24 -24
- package/src/home/lib/meta/reply.mjs +37 -3
- package/src/home/lib/meta/run-snapshots.mjs +29 -0
- package/src/home/lib/meta/run-state.mjs +3 -0
- package/src/home/lib/meta/snapshot-format.mjs +96 -0
- package/src/home/lib/meta/verdict.mjs +30 -24
- package/src/home/lib/required-inputs.mjs +48 -8
- package/src/home/lib/run-codex.mjs +6 -3
- package/src/home/lib/run-config.mjs +10 -1
- package/src/home/lib/runner/advise-carry.mjs +2 -1
- package/src/home/lib/runner/args.mjs +16 -5
- package/src/home/lib/runner/attach.mjs +21 -14
- package/src/home/lib/runner/build-evidence.mjs +29 -0
- package/src/home/lib/runner/codex-availability.mjs +50 -0
- package/src/home/lib/runner/codex-cmd.mjs +2 -19
- package/src/home/lib/runner/continuation.mjs +60 -8
- package/src/home/lib/runner/exit-codes.mjs +13 -0
- package/src/home/lib/runner/flag-baseline.mjs +105 -0
- package/src/home/lib/runner/flag-scan.mjs +165 -0
- package/src/home/lib/runner/git-paths.mjs +74 -0
- package/src/home/lib/runner/git-state.mjs +37 -66
- package/src/home/lib/runner/launcher.mjs +233 -273
- package/src/home/lib/runner/order-claim.mjs +84 -0
- package/src/home/lib/runner/order-owner.mjs +18 -0
- package/src/home/lib/runner/pass-gate.mjs +109 -0
- package/src/home/lib/runner/preflight.mjs +22 -18
- package/src/home/lib/runner/prompts.mjs +42 -4
- package/src/home/lib/runner/question-kind.mjs +41 -0
- package/src/home/lib/runner/run-env.mjs +2 -1
- package/src/home/lib/runner/sandbox-probe.mjs +1 -1
- package/src/home/lib/runner/schemas.mjs +4 -1
- package/src/home/lib/runner/scope-check.mjs +55 -28
- package/src/home/lib/runner/task-input.mjs +11 -2
- package/src/home/lib/runner/worker-admission.mjs +43 -0
- package/src/home/lib/runner/worker.mjs +19 -18
- package/src/home/lib/task-header.mjs +127 -0
- package/src/home/lib/write-meta.mjs +17 -5
package/cli/settings-merge.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Merges and removes named codex-bridge hooks without disturbing host settings. */
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import { existsSync, statSync } from 'node:fs';
|
|
5
4
|
import fs from 'node:fs/promises';
|
|
6
5
|
import path from 'node:path';
|
|
7
6
|
import { randomUUID } from 'node:crypto';
|
|
8
7
|
import { readJsonFileWithRaw } from '../src/home/lib/json-file.mjs';
|
|
8
|
+
import { envValue, resolveCommandOnPath } from '../src/home/lib/command-path.mjs';
|
|
9
9
|
|
|
10
10
|
const LEGACY_SPEC = { event: 'SubagentStop', matcher: '*' };
|
|
11
11
|
const settingsRuns = new AsyncLocalStorage();
|
|
@@ -18,34 +18,8 @@ export function shortCommandFor(name) {
|
|
|
18
18
|
return `codex-bridge hook ${name}`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function envValue(env, name) {
|
|
22
|
-
const key = Object.keys(env).find((entry) => entry.toLowerCase() === name);
|
|
23
|
-
return key ? env[key] : '';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function pathValue(env) {
|
|
27
|
-
return envValue(env, 'path');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function resolveCommand(name, env) {
|
|
31
|
-
const extensions = process.platform === 'win32'
|
|
32
|
-
? (env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';')
|
|
33
|
-
: [''];
|
|
34
|
-
for (const directory of pathValue(env).split(path.delimiter).filter(Boolean)) {
|
|
35
|
-
for (const extension of extensions) {
|
|
36
|
-
const candidate = path.join(path.resolve(directory), `${name}${extension}`);
|
|
37
|
-
try {
|
|
38
|
-
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
|
|
39
|
-
} catch {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
21
|
export function commandReachable(name = 'codex-bridge', env = process.env) {
|
|
48
|
-
return Boolean(
|
|
22
|
+
return Boolean(resolveCommandOnPath(name, env));
|
|
49
23
|
}
|
|
50
24
|
|
|
51
25
|
/**
|
|
@@ -63,7 +37,7 @@ export function reachableCommandVersion(name = 'codex-bridge', env = process.env
|
|
|
63
37
|
}
|
|
64
38
|
|
|
65
39
|
export function runReachableCommand(name, args, env = process.env) {
|
|
66
|
-
const shimPath =
|
|
40
|
+
const shimPath = resolveCommandOnPath(name, env);
|
|
67
41
|
if (!shimPath) return null;
|
|
68
42
|
// Node 24 warned with DEP0190 during update (2026-09-07): invoke cmd explicitly for npm shims.
|
|
69
43
|
// Live probes covered plain, bin with space, Program Files (x86), weird & name, and caret^dir.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one terminal yes/no question. Plan_65 D9 needs three outcomes kept apart, because each leads
|
|
3
|
+
* to a different action: 'yes', 'no' (a no, a bare Enter, or input that ended — EOF is no answer,
|
|
4
|
+
* never consent) and 'cancel' (Ctrl+C: the caller exits 130 and changes nothing). prune's private
|
|
5
|
+
* copy folded all of them into "no"; a second copy in install would have drifted from it.
|
|
6
|
+
*/
|
|
7
|
+
import readline from 'node:readline/promises';
|
|
8
|
+
|
|
9
|
+
// One spelling only. A guard that answers to several option names is a guard with several ways
|
|
10
|
+
// to be switched off by accident.
|
|
11
|
+
export function isInteractive(options) {
|
|
12
|
+
if (options.isTTY !== undefined) return Boolean(options.isTTY);
|
|
13
|
+
return Boolean((options.stdin || process.stdin).isTTY);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function outcome(answer) {
|
|
17
|
+
if (answer === 'cancel') return 'cancel';
|
|
18
|
+
return answer === true || (typeof answer === 'string' && /^(y|yes)$/i.test(answer.trim()))
|
|
19
|
+
? 'yes'
|
|
20
|
+
: 'no';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** `options.prompt` is the test seam: it may answer a boolean, a string, or the string 'cancel'. */
|
|
24
|
+
export async function askYesNo(question, options) {
|
|
25
|
+
if (options.prompt !== undefined) return outcome(await options.prompt(question));
|
|
26
|
+
|
|
27
|
+
const input = options.stdin || process.stdin;
|
|
28
|
+
const output = options.promptOutput || process.stderr;
|
|
29
|
+
const interfaceHandle = readline.createInterface({ input, output });
|
|
30
|
+
const ended = new Promise((resolve) => {
|
|
31
|
+
interfaceHandle.once('SIGINT', () => resolve('cancel'));
|
|
32
|
+
interfaceHandle.once('close', () => resolve('no'));
|
|
33
|
+
});
|
|
34
|
+
// A closed interface rejects the pending question; the close itself already decided the answer.
|
|
35
|
+
const answered = interfaceHandle.question(`${question} [y/N] `).then(outcome, () => ended);
|
|
36
|
+
try {
|
|
37
|
+
return await Promise.race([answered, ended]);
|
|
38
|
+
} finally {
|
|
39
|
+
interfaceHandle.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uninstalls one host and purges the package home behind one preflight.
|
|
3
|
+
* Plan_65 D12 items 5-7 and D14 item 1 require both consents and authorization before any
|
|
4
|
+
* host mutation; D10 item 4 forbids removing the home until this host's hooks are gone.
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { uninstall, removePermissions, stillAttachedLine } from './uninstall.mjs';
|
|
8
|
+
import { legacyInstallRecordPath, readInstallRecord } from './manifest.mjs';
|
|
9
|
+
import { normalizeRepoPath } from '../src/home/lib/runner/project-dir.mjs';
|
|
10
|
+
import { inspectHost } from './host-inspection.mjs';
|
|
11
|
+
import { removeHostSide } from './host-removal.mjs';
|
|
12
|
+
import { withSettingsRun } from './settings-merge.mjs';
|
|
13
|
+
import { withLifecycle } from './lifecycle-transaction.mjs';
|
|
14
|
+
import { readRulesRegistry } from './rules-owners.mjs';
|
|
15
|
+
import { rulesDryRunLines, removeRulesForHost } from './uninstall-rules.mjs';
|
|
16
|
+
import { removeEmpty, removeEmptyLayout } from './remove-layout.mjs';
|
|
17
|
+
import { recordHomeWriter, removeOutside } from './record-removal.mjs';
|
|
18
|
+
import { runPurgePreflight, diagnosePurge } from './purge-preflight.mjs';
|
|
19
|
+
import { executeHomePlan } from './home-plan-execute.mjs';
|
|
20
|
+
import { inspectHome } from './home-inspection.mjs';
|
|
21
|
+
import {
|
|
22
|
+
purgeDryRunLines, purgeDryRunExitCode, outcomeLines, outcomeExitCode,
|
|
23
|
+
} from './removal-outcomes.mjs';
|
|
24
|
+
|
|
25
|
+
const ACCOUNTED = new Set(['kept', 'blocked', 'failed']);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Plan_65 D12 item 7: a run started during purge (D14 item 3) or any other writer can recreate a registry
|
|
29
|
+
* file after its removal, and "Purged" would then be a false report. One more pass names every registry
|
|
30
|
+
* file present that the outcomes did not already name as kept. It does not defend against a deliberate
|
|
31
|
+
* swap of a folder between check and removal (D4 item 6); that limit is documented, not compensated.
|
|
32
|
+
*/
|
|
33
|
+
export function appearedLines(host, plan, outcomes) {
|
|
34
|
+
const inspection = inspectHome(host.brandRoot, { imageMembers: plan.imageMembers });
|
|
35
|
+
if (inspection.root === 'missing') return [];
|
|
36
|
+
if (inspection.root === 'error') {
|
|
37
|
+
return [`Could not re-check ${host.brandRoot} after purge (${inspection.rootCode}).`];
|
|
38
|
+
}
|
|
39
|
+
const named = new Set(outcomes.filter((entry) => entry.kind !== 'directory' && ACCOUNTED.has(entry.result))
|
|
40
|
+
.map((entry) => entry.relative));
|
|
41
|
+
return inspection.files.filter((file) => !named.has(file.relative)).map((file) =>
|
|
42
|
+
`Appeared during purge: brand/${file.relative}; run uninstall --purge again once nothing writes to the home.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function purgeInRun(options, ticket) {
|
|
46
|
+
const { host } = options;
|
|
47
|
+
let registry = null;
|
|
48
|
+
let registryError = null;
|
|
49
|
+
try {
|
|
50
|
+
registry = await readRulesRegistry(host);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
registryError = err;
|
|
53
|
+
}
|
|
54
|
+
const preflight = await runPurgePreflight({
|
|
55
|
+
host, ticket, packageRoot: options.packageRoot,
|
|
56
|
+
...(options.liveRunCheck !== undefined ? { liveRunCheck: options.liveRunCheck } : {}),
|
|
57
|
+
options: { ...options, candidates: options.candidates ?? registry?.owners ?? [] },
|
|
58
|
+
});
|
|
59
|
+
if (preflight.verdict === 'refused') {
|
|
60
|
+
return { exitCode: 1, output: ['Purge refused; nothing was changed.', ...preflight.lines].join('\n') };
|
|
61
|
+
}
|
|
62
|
+
if (preflight.verdict === 'cancelled') {
|
|
63
|
+
return { exitCode: 130, output: 'Cancelled; nothing was changed.' };
|
|
64
|
+
}
|
|
65
|
+
const { plan, authorization } = preflight;
|
|
66
|
+
const owner = plan.format2?.owners[normalizeRepoPath(host.root)] ?? null;
|
|
67
|
+
const inspection = await inspectHost(host, { owner });
|
|
68
|
+
const record = await readInstallRecord(host);
|
|
69
|
+
const writer = record ? recordHomeWriter(host, record.files) : null;
|
|
70
|
+
const permissionLine = await removePermissions(host, inspection, false);
|
|
71
|
+
const hostSide = await removeHostSide(host, inspection, { owner });
|
|
72
|
+
if (!hostSide.detached) {
|
|
73
|
+
return {
|
|
74
|
+
exitCode: 1,
|
|
75
|
+
output: [stillAttachedLine(host), permissionLine, ...hostSide.lines,
|
|
76
|
+
`Left ${host.brandRoot} untouched: purge removes the home only after this host's hooks are gone.`]
|
|
77
|
+
.join('\n'),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const rules = await removeRulesForHost({ host, record, registry, registryError, detached: true, writer });
|
|
81
|
+
const { outcomes } = await executeHomePlan(host, plan, {
|
|
82
|
+
detached: true, imageMembers: plan.imageMembers, authorization,
|
|
83
|
+
});
|
|
84
|
+
await removeEmpty(host.commandsDir);
|
|
85
|
+
if (record) await removeOutside(writer, legacyInstallRecordPath(host));
|
|
86
|
+
await removeEmpty(host.agentsDir);
|
|
87
|
+
await removeEmptyLayout(host.legacyAgentsDir);
|
|
88
|
+
await removeEmptyLayout(host.legacyCommandsDir);
|
|
89
|
+
const appeared = appearedLines(host, plan, outcomes);
|
|
90
|
+
return {
|
|
91
|
+
exitCode: appeared.length ? 1 : outcomeExitCode(outcomes),
|
|
92
|
+
output: ['Purged codex-bridge.', permissionLine, ...hostSide.lines, ...rules.lines,
|
|
93
|
+
...outcomeLines(outcomes, { host }), ...appeared].join('\n'),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function purgeDryRun(options) {
|
|
98
|
+
const { host } = options;
|
|
99
|
+
let registry = null;
|
|
100
|
+
let registryError = null;
|
|
101
|
+
try {
|
|
102
|
+
registry = await readRulesRegistry(host);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
registryError = err;
|
|
105
|
+
}
|
|
106
|
+
const diagnosis = await diagnosePurge({
|
|
107
|
+
host, packageRoot: options.packageRoot,
|
|
108
|
+
...(options.liveRunCheck !== undefined ? { liveRunCheck: options.liveRunCheck } : {}),
|
|
109
|
+
});
|
|
110
|
+
// D12: a corrupt inventory is a diagnosis, never a throwing host-record read.
|
|
111
|
+
const corrupt = diagnosis.plan.recordState === 'corrupt';
|
|
112
|
+
const owner = corrupt ? null : diagnosis.plan.format2?.owners[normalizeRepoPath(host.root)] ?? null;
|
|
113
|
+
const inspection = await inspectHost(host, { owner });
|
|
114
|
+
const record = corrupt ? null : await readInstallRecord(host);
|
|
115
|
+
const permissionLine = await removePermissions(host, inspection, true);
|
|
116
|
+
const hostSide = await removeHostSide(host, inspection, { owner, dryRun: true });
|
|
117
|
+
const lines = [permissionLine, ...hostSide.lines];
|
|
118
|
+
if (!hostSide.detached) {
|
|
119
|
+
lines.push(`Would leave ${host.brandRoot} untouched: this host's hooks could not be removed.`);
|
|
120
|
+
}
|
|
121
|
+
lines.push(...await rulesDryRunLines({ host, record, registry, registryError, detached: hostSide.detached }));
|
|
122
|
+
lines.push(...purgeDryRunLines(diagnosis, { host }));
|
|
123
|
+
return { exitCode: hostSide.detached ? purgeDryRunExitCode(diagnosis) : 1, output: lines.join('\n') };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function missingHome(options) {
|
|
127
|
+
const result = await uninstall(options);
|
|
128
|
+
return { ...result, output: `${result.output}\nNothing to purge: ${options.host.brandRoot} does not exist.` };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function purge(options) {
|
|
132
|
+
const { host } = options;
|
|
133
|
+
if (options.dryRun) return withSettingsRun(host.settingsPath, () => purgeDryRun(options));
|
|
134
|
+
if (!fs.existsSync(host.brandRoot)) return missingHome(options);
|
|
135
|
+
return withLifecycle(host, 'purge', (ticket) => {
|
|
136
|
+
if (ticket === undefined) return missingHome(options);
|
|
137
|
+
return withSettingsRun(host.settingsPath, () => purgeInRun(options, ticket));
|
|
138
|
+
}, { waitMs: options.lifecycleWaitMs });
|
|
139
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decides and performs what happens to the Codex rules file when a host leaves the package.
|
|
3
|
+
* Plan_65 B20a: uninstall and purge detach hosts alike, so sharing rules ownership and fingerprint
|
|
4
|
+
* decisions prevents their behavior and output from drifting apart.
|
|
5
|
+
*/
|
|
6
|
+
import { fileFingerprint } from './manifest.mjs';
|
|
7
|
+
import { removeRulesOwner, remainingRulesOwners } from './rules-owners.mjs';
|
|
8
|
+
import { removeOutside } from './record-removal.mjs';
|
|
9
|
+
|
|
10
|
+
export function remainingOwnersText(count) {
|
|
11
|
+
return `${count} other owner${count === 1 ? '' : 's'} ${count === 1 ? 'remains' : 'remain'}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function rulesDryRunLines({ host, record, registry, registryError, detached }) {
|
|
15
|
+
const lines = [];
|
|
16
|
+
if (!record?.rules || !detached) return lines;
|
|
17
|
+
if (registryError) {
|
|
18
|
+
lines.push(`Would leave ${record.rules.path} because the rules ownership registry is invalid; \
|
|
19
|
+
ownership is unknown.`);
|
|
20
|
+
} else {
|
|
21
|
+
const remainingOwners = remainingRulesOwners(registry, host);
|
|
22
|
+
const currentFingerprint = await fileFingerprint(record.rules.path);
|
|
23
|
+
if (remainingOwners?.length) {
|
|
24
|
+
lines.push(`Would leave ${record.rules.path} because ${remainingOwnersText(remainingOwners.length)}.`);
|
|
25
|
+
} else if (currentFingerprint === record.rules.fingerprint) {
|
|
26
|
+
lines.push(`Would remove ${record.rules.path}; no other owners remain and its fingerprint is unchanged.`);
|
|
27
|
+
} else if (currentFingerprint !== null) {
|
|
28
|
+
lines.push(`Would leave ${record.rules.path} because its contents changed after installation.`);
|
|
29
|
+
} else {
|
|
30
|
+
lines.push(`Would leave ${record.rules.path} because it is already absent.`);
|
|
31
|
+
}
|
|
32
|
+
if (!registry) {
|
|
33
|
+
lines.push(`Warning: the rules ownership registry was missing; \
|
|
34
|
+
other installations may use ${record.rules.path}.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function removeRulesForHost({ host, record, registry, registryError, detached, writer }) {
|
|
41
|
+
const detachedRecord = record && detached ? record : null;
|
|
42
|
+
let ownership = null;
|
|
43
|
+
if (detachedRecord && !registryError) {
|
|
44
|
+
try {
|
|
45
|
+
ownership = await removeRulesOwner(host);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
registryError = err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const lines = [];
|
|
51
|
+
if (detachedRecord?.rules) {
|
|
52
|
+
if (registryError) {
|
|
53
|
+
lines.push(`Left ${record.rules.path} because the rules ownership registry is invalid; ownership is unknown.`);
|
|
54
|
+
} else {
|
|
55
|
+
if (ownership?.owners.length) {
|
|
56
|
+
lines.push(`Left ${record.rules.path} because ${remainingOwnersText(ownership.owners.length)}.`);
|
|
57
|
+
} else {
|
|
58
|
+
const currentFingerprint = await fileFingerprint(record.rules.path);
|
|
59
|
+
if (currentFingerprint === record.rules.fingerprint) {
|
|
60
|
+
await removeOutside(writer, record.rules.path);
|
|
61
|
+
} else if (currentFingerprint !== null) {
|
|
62
|
+
lines.push(`Left ${record.rules.path} because its contents changed after installation.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!registry) {
|
|
66
|
+
lines.push(`Warning: the rules ownership registry was missing; \
|
|
67
|
+
other installations may use ${record.rules.path}.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { lines, registryError };
|
|
72
|
+
}
|