@bjoernrochel/sevro 0.1.0-rc.2 → 0.1.0-rc.21
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/CONTRIBUTING.md +170 -0
- package/LICENSE +63 -136
- package/README.md +83 -66
- package/docs/README.md +44 -0
- package/docs/acknowledgements.md +14 -0
- package/docs/architecture.md +146 -0
- package/docs/assets/README.md +27 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +1 -0
- package/docs/assets/sevro-wordmark.svg +1 -0
- package/docs/builtin-graders-v1.md +41 -13
- package/docs/codex-host.md +184 -52
- package/docs/contracts.md +19 -0
- package/docs/creating-cases.md +139 -0
- package/docs/development-cli.md +190 -30
- package/docs/documentation-quality.md +61 -0
- package/docs/evaluation-workflow.md +110 -0
- package/docs/evidence/code-mode-controls-71.md +64 -0
- package/docs/evidence/code-mode-programs-85.md +64 -0
- package/docs/evidence/codex-state-73/README.md +204 -0
- package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
- package/docs/evidence/codex-state-73/results.json +661 -0
- package/docs/evidence/local-http-74.md +104 -0
- package/docs/evidence/runtime-startup-30/README.md +185 -0
- package/docs/evidence/runtime-startup-30/after.json +319 -0
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
- package/docs/evidence/runtime-startup-30/before.json +403 -0
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
- package/docs/evidence/skill-reads-70/results.json +823 -0
- package/docs/evidence.md +21 -0
- package/docs/extension-protocol-v1.md +50 -16
- package/docs/fixture-cleanup-validation.md +3 -0
- package/docs/getting-started.md +102 -0
- package/docs/guide-evaluation.md +89 -0
- package/docs/guide-verification.json +239 -0
- package/docs/identity-v1.md +35 -1
- package/docs/installing.md +88 -0
- package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
- package/docs/licensing.md +42 -0
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +228 -0
- package/docs/reading-results.md +141 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +81 -40
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +180 -0
- package/docs/run-settings-v1.md +147 -0
- package/docs/running-evaluations.md +31 -0
- package/docs/runtime-v1.md +614 -0
- package/docs/specs/repository-guide.md +58 -0
- package/docs/troubleshooting.md +30 -0
- package/docs/typescript-quality.md +234 -0
- package/docs/usability-study.md +114 -0
- package/examples/basic/host.ts +2 -2
- package/examples/recipes/candidate.ts +26 -0
- package/examples/recipes/cases/coding-change.json +21 -0
- package/examples/recipes/cases/host-tools.json +21 -0
- package/examples/recipes/cases/mise.json +21 -0
- package/examples/recipes/cases/node.json +21 -0
- package/examples/recipes/cases/python-uv.json +21 -0
- package/examples/recipes/cases/semantic.json +17 -0
- package/examples/recipes/coding-change/check.ts +23 -0
- package/examples/recipes/extension-artifacts.ts +65 -0
- package/examples/recipes/extension-methods.ts +174 -0
- package/examples/recipes/extension.ts +37 -0
- package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/README.md +3 -0
- package/examples/recipes/run-inputs.ts +188 -0
- package/examples/recipes/run-options.ts +131 -0
- package/examples/recipes/run.ts +63 -0
- package/examples/recipes/runtime/empty.json +1 -0
- package/examples/recipes/runtime/goals.json +1 -0
- package/examples/recipes/runtime/mise.json +6 -0
- package/examples/recipes/runtime/path.json +1 -0
- package/examples/recipes/runtime/plugins.json +4 -0
- package/examples/recipes/runtime/python-uv.json +10 -0
- package/examples/recipes/semantic-judge.ts +29 -0
- package/examples/templates/coding/adapter.ts +17 -0
- package/examples/templates/coding/case.json +15 -0
- package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
- package/examples/templates/repository-skill/extension-data.ts +64 -0
- package/examples/templates/repository-skill/extension.ts +31 -0
- package/examples/templates/response/adapter.ts +12 -0
- package/examples/templates/response/case.json +13 -0
- package/package.json +39 -6
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +193 -0
- package/src/advisory.ts +10 -5
- package/src/candidate-artifacts.ts +208 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +105 -0
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +81 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +59 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +222 -0
- package/src/cli/hosts.ts +59 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/cli/invocation.ts +627 -0
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +163 -0
- package/src/cli/presentation.ts +91 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +133 -0
- package/src/cli/run-options.ts +101 -0
- package/src/cli/run-overrides.ts +83 -0
- package/src/cli/settings.ts +160 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -843
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +19 -2373
- package/src/evaluation/advisory.ts +150 -0
- package/src/evaluation/check-plan.ts +510 -0
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/evaluation/grading-host.ts +39 -0
- package/src/evaluation/grading.ts +258 -0
- package/src/evaluation/invocation-plan.ts +548 -0
- package/src/evaluation/plan.ts +736 -0
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
- package/src/evaluation/semantic.ts +312 -0
- package/src/evaluation/trial.ts +866 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +92 -0
- package/src/evidence/retained-artifacts.ts +18 -0
- package/src/evidence/trial-writer.ts +203 -0
- package/src/evidence/usage.ts +17 -0
- package/src/extension-client.ts +228 -148
- package/src/extension-session.ts +326 -195
- package/src/fixtures/advisory.ts +338 -0
- package/src/fixtures/bin.ts +57 -0
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +187 -0
- package/src/fixtures/generated.ts +300 -0
- package/src/fixtures/git-hooks.ts +24 -0
- package/src/fixtures/preparation.ts +270 -0
- package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
- package/src/fixtures/setup.ts +224 -0
- package/src/fixtures/tools.ts +88 -0
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.cjs +2 -0
- package/src/generated/cli-result.d.cts +3 -0
- package/src/generated/extension.cjs +2 -0
- package/src/generated/extension.d.cts +3 -0
- package/src/generated/report.cjs +2 -0
- package/src/generated/report.d.cts +3 -0
- package/src/generated/run-evidence.cjs +2 -0
- package/src/generated/run-evidence.d.cts +3 -0
- package/src/generated/runtime.cjs +2 -0
- package/src/generated/runtime.d.cts +3 -0
- package/src/graders/artifact.ts +56 -23
- package/src/graders/git-head.ts +79 -75
- package/src/graders/output.ts +222 -140
- package/src/graders/semantic.ts +102 -43
- package/src/graders/shell.ts +379 -207
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +319 -0
- package/src/hosts/claude-continuation.ts +63 -18
- package/src/hosts/claude-credential.ts +43 -62
- package/src/hosts/claude-events.ts +86 -51
- package/src/hosts/claude-keychain.ts +59 -0
- package/src/hosts/claude-native-goal.ts +57 -33
- package/src/hosts/claude-process.ts +111 -0
- package/src/hosts/claude-repository-invocation.ts +208 -117
- package/src/hosts/claude-settings.ts +240 -21
- package/src/hosts/claude-skill-activations.ts +166 -0
- package/src/hosts/claude-state.ts +403 -0
- package/src/hosts/claude-tool-calls.ts +171 -106
- package/src/hosts/claude.ts +561 -451
- package/src/hosts/codex-app-server.ts +326 -178
- package/src/hosts/codex-configuration.ts +44 -18
- package/src/hosts/codex-events.ts +97 -60
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-invocation.ts +70 -0
- package/src/hosts/codex-marketplace.ts +283 -0
- package/src/hosts/codex-native-calls.ts +546 -500
- package/src/hosts/codex-process.ts +202 -0
- package/src/hosts/codex-profile.ts +257 -28
- package/src/hosts/codex-session-files.ts +106 -0
- package/src/hosts/codex-state.ts +492 -0
- package/src/hosts/codex.ts +783 -832
- package/src/hosts/isolation-common.ts +94 -0
- package/src/hosts/isolation-roots.ts +54 -27
- package/src/hosts/isolation.ts +10 -0
- package/src/hosts/linux-sandbox.ts +159 -0
- package/src/hosts/mac-sandbox.ts +187 -61
- package/src/hosts/native-controls.ts +152 -62
- package/src/hosts/process-lifecycle.ts +37 -0
- package/src/hosts/protected-worktrees.ts +51 -19
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +580 -0
- package/src/hosts/workspace-fingerprint.ts +55 -35
- package/src/identity.ts +102 -48
- package/src/instrumentation.ts +109 -48
- package/src/native-transcript-state.ts +131 -0
- package/src/native-transcript-view.ts +90 -0
- package/src/native-transcripts.ts +286 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +161 -81
- package/src/prune.ts +215 -0
- package/src/report.ts +172 -140
- package/src/resolved-case.ts +33 -0
- package/src/results-lock.ts +29 -0
- package/src/results.ts +133 -86
- package/src/run-owner.ts +154 -63
- package/src/runtime/config.ts +456 -0
- package/src/runtime/discovery-apple.ts +90 -0
- package/src/runtime/discovery-providers.ts +63 -0
- package/src/runtime/discovery.ts +70 -0
- package/src/runtime/hook-processes.ts +97 -0
- package/src/runtime/hooks.ts +476 -0
- package/src/runtime/paths.ts +313 -0
- package/src/runtime/query.ts +85 -0
- package/src/runtime/seeds.ts +134 -0
- package/src/runtime/shared-cache.ts +143 -0
- package/src/runtime/state.ts +143 -0
- package/src/schema-types.ts +198 -0
- package/src/schema.ts +15 -19
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +20 -0
- package/src/advisory-fixture.ts +0 -282
- package/src/fixture-bin.ts +0 -87
- package/src/fixture-setup.ts +0 -163
- package/src/generated-fixture.ts +0 -220
- package/src/git-hooks.ts +0 -63
- package/src/hosts/claude-nested-skills.ts +0 -218
- package/src/hosts/codex-command-output.ts +0 -399
- package/src/hosts/codex-executor-command.ts +0 -195
- package/src/hosts/codex-skill-reads.ts +0 -391
- package/src/preparation.ts +0 -211
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
declaredRuntimeRoots,
|
|
5
|
+
optionalRuntimeRoots,
|
|
6
|
+
runtimePathDirectories,
|
|
7
|
+
runtimeInheritedPath,
|
|
8
|
+
requireRuntimeReadRoots,
|
|
9
|
+
} from "./paths";
|
|
10
|
+
import validate from "../generated/runtime.cjs";
|
|
11
|
+
import {
|
|
12
|
+
requireArtifactDirectories,
|
|
13
|
+
type RuntimeArtifactDirectory,
|
|
14
|
+
} from "../candidate-artifacts";
|
|
15
|
+
import { runtimeSeedDigest } from "./seeds";
|
|
16
|
+
import {
|
|
17
|
+
resolveSharedCaches,
|
|
18
|
+
requireSharedCachePolicy,
|
|
19
|
+
type SharedCacheDirectory,
|
|
20
|
+
} from "./shared-cache";
|
|
21
|
+
import {
|
|
22
|
+
discoverRuntimeSupport,
|
|
23
|
+
type RuntimeDiscoverySource,
|
|
24
|
+
} from "./discovery";
|
|
25
|
+
|
|
26
|
+
export type RuntimeRole =
|
|
27
|
+
"candidate" | "checks" | "hooks" | "semantic" | "advisory";
|
|
28
|
+
export interface RuntimeSeed {
|
|
29
|
+
source: string;
|
|
30
|
+
target: string;
|
|
31
|
+
roles?: RuntimeRole[];
|
|
32
|
+
}
|
|
33
|
+
export interface RuntimeConfiguration {
|
|
34
|
+
format: "sevro.runtime.v1";
|
|
35
|
+
codexState?: boolean;
|
|
36
|
+
network?: { localHttp?: boolean };
|
|
37
|
+
nativeTranscripts?: boolean;
|
|
38
|
+
environment?: { inherit?: string[]; set?: Record<string, string> };
|
|
39
|
+
filesystem?: { readOnlyRoots?: string[]; optionalReadOnlyRoots?: string[] };
|
|
40
|
+
runtime?: {
|
|
41
|
+
seedDirectories?: RuntimeSeed[];
|
|
42
|
+
sharedCacheDirectories?: SharedCacheDirectory[];
|
|
43
|
+
artifactDirectories?: RuntimeArtifactDirectory[];
|
|
44
|
+
};
|
|
45
|
+
hooks?: { nativeGoal?: boolean; plugins?: string[] };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface RuntimePolicy {
|
|
49
|
+
format: "sevro.runtime.v1";
|
|
50
|
+
codexState?: boolean;
|
|
51
|
+
network?: { localHttp?: boolean };
|
|
52
|
+
artifactDirectories?: RuntimeArtifactDirectory[];
|
|
53
|
+
nativeTranscripts?: boolean;
|
|
54
|
+
environment: Record<string, string>;
|
|
55
|
+
readOnlyRoots: string[];
|
|
56
|
+
discovery?: RuntimeDiscoverySource[];
|
|
57
|
+
skippedOptionalReadOnlyRoots?: string[];
|
|
58
|
+
/** Original source denials retained when a derived role policy omits seeds. */
|
|
59
|
+
seedReadDenyRoots?: string[];
|
|
60
|
+
seeds?: (RuntimeSeed & { sha256: string })[];
|
|
61
|
+
sharedCacheDirectories?: SharedCacheDirectory[];
|
|
62
|
+
hooks?: { nativeGoal?: boolean; plugins?: string[] };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function runtimeLocalHttpEnabled(
|
|
66
|
+
policy: RuntimePolicy | undefined,
|
|
67
|
+
role: RuntimeRole,
|
|
68
|
+
): boolean {
|
|
69
|
+
const network = policy?.network;
|
|
70
|
+
return network?.localHttp === true && ["candidate", "checks"].includes(role);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function runtimeNativeGoalEnabled(
|
|
74
|
+
policy: RuntimePolicy | undefined,
|
|
75
|
+
): boolean {
|
|
76
|
+
const hooks = policy?.hooks;
|
|
77
|
+
return hooks?.nativeGoal === true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function runtimePolicySnapshot(
|
|
81
|
+
policy: RuntimePolicy | undefined,
|
|
82
|
+
): RuntimePolicy | undefined {
|
|
83
|
+
if (!policy) return undefined;
|
|
84
|
+
requireRuntimeEnvironment(policy.environment);
|
|
85
|
+
requireSharedCachePolicy(policy);
|
|
86
|
+
const snapshot = structuredClone(policy);
|
|
87
|
+
Object.freeze(snapshot.network);
|
|
88
|
+
freezeArtifactDirectories(snapshot);
|
|
89
|
+
Object.freeze(snapshot.environment);
|
|
90
|
+
Object.freeze(snapshot.readOnlyRoots);
|
|
91
|
+
freezeDiscovery(snapshot);
|
|
92
|
+
freezeRuntimeCaches(snapshot);
|
|
93
|
+
Object.freeze(snapshot.seedReadDenyRoots);
|
|
94
|
+
const hooks = snapshot.hooks;
|
|
95
|
+
if (hooks) {
|
|
96
|
+
Object.freeze(hooks.plugins);
|
|
97
|
+
Object.freeze(hooks);
|
|
98
|
+
}
|
|
99
|
+
return Object.freeze(snapshot);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function freezeRuntimeCaches(snapshot: RuntimePolicy): void {
|
|
103
|
+
for (const seed of [
|
|
104
|
+
...(snapshot.seeds ?? []),
|
|
105
|
+
...(snapshot.sharedCacheDirectories ?? []),
|
|
106
|
+
]) {
|
|
107
|
+
Object.freeze(seed.roles);
|
|
108
|
+
Object.freeze(seed);
|
|
109
|
+
}
|
|
110
|
+
Object.freeze(snapshot.seeds);
|
|
111
|
+
Object.freeze(snapshot.sharedCacheDirectories);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function freezeDiscovery(snapshot: RuntimePolicy): void {
|
|
115
|
+
for (const source of snapshot.discovery ?? []) {
|
|
116
|
+
Object.freeze(source.readOnlyRoots);
|
|
117
|
+
Object.freeze(source);
|
|
118
|
+
}
|
|
119
|
+
Object.freeze(snapshot.discovery);
|
|
120
|
+
Object.freeze(snapshot.skippedOptionalReadOnlyRoots);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function freezeArtifactDirectories(snapshot: RuntimePolicy): void {
|
|
124
|
+
const directories = snapshot.artifactDirectories ?? [];
|
|
125
|
+
requireArtifactDirectories(directories);
|
|
126
|
+
for (const directory of directories) Object.freeze(directory);
|
|
127
|
+
Object.freeze(snapshot.artifactDirectories);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function assertRuntimeConfiguration(
|
|
131
|
+
value: unknown,
|
|
132
|
+
): asserts value is RuntimeConfiguration {
|
|
133
|
+
if (!validate(value)) throw new Error("invalid runtime configuration schema");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const RESERVED_ENVIRONMENT =
|
|
137
|
+
/^(HOME|TMPDIR|TMPPREFIX|TMP|TEMP|CODEX_HOME|CODEX_THREAD_ID|CLAUDE_CONFIG_DIR|CLAUDE_EFFORT|BASH_ENV|ENV|ZDOTDIR|NODE_OPTIONS|BUN_OPTIONS|UV_OFFLINE|GIT_CONFIG.*|LD_.*|DYLD_.*|CLAUDE_CODE_.*|SEVRO_CODEX_.*|SEVRO_NATIVE_.*|SEVRO_CANDIDATE_.*)$/i;
|
|
138
|
+
|
|
139
|
+
function requireEnvironmentName(name: string): void {
|
|
140
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(name))
|
|
141
|
+
throw new Error("invalid runtime environment variable name");
|
|
142
|
+
if (
|
|
143
|
+
RESERVED_ENVIRONMENT.test(name) ||
|
|
144
|
+
/KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH/i.test(name)
|
|
145
|
+
)
|
|
146
|
+
throw new Error(`runtime environment variable is protected: ${name}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function snapshotEnvironment(
|
|
150
|
+
names: string[],
|
|
151
|
+
values: Record<string, string>,
|
|
152
|
+
): Record<string, string> {
|
|
153
|
+
const inherited = Object.fromEntries(
|
|
154
|
+
names.map((name) => {
|
|
155
|
+
requireEnvironmentName(name);
|
|
156
|
+
const value = process.env[name];
|
|
157
|
+
if (value === undefined)
|
|
158
|
+
throw new Error(`runtime environment variable is missing: ${name}`);
|
|
159
|
+
return [name, value];
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
const environment = { ...inherited, ...values };
|
|
163
|
+
for (const [name, value] of Object.entries(environment)) {
|
|
164
|
+
requireEnvironmentName(name);
|
|
165
|
+
if (value.includes("\0") || Buffer.byteLength(value) > 8192)
|
|
166
|
+
throw new Error("runtime environment value is invalid or oversized");
|
|
167
|
+
}
|
|
168
|
+
return environment;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function requireRuntimeEnvironment(
|
|
172
|
+
environment: Record<string, string>,
|
|
173
|
+
): void {
|
|
174
|
+
snapshotEnvironment([], environment);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function requireRuntimeSeedTargets(seeds: RuntimeSeed[]): void {
|
|
178
|
+
for (const seed of seeds) requireRuntimeSeedRoles(seed.roles);
|
|
179
|
+
const targets = seeds.map((seed) => seed.target);
|
|
180
|
+
if (
|
|
181
|
+
targets.some(
|
|
182
|
+
(target) =>
|
|
183
|
+
!/^[a-z][a-z0-9-]{0,63}$/.test(target) ||
|
|
184
|
+
["home", "tmp"].includes(target),
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
throw new Error("runtime seed target is invalid or reserved");
|
|
188
|
+
if (new Set(targets).size !== targets.length)
|
|
189
|
+
throw new Error("runtime seed targets overlap");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Read the operator's repository configuration before any candidate starts. */
|
|
193
|
+
export async function loadRuntimeConfiguration(
|
|
194
|
+
projectRoot: string,
|
|
195
|
+
explicitFile?: string,
|
|
196
|
+
protectedRoots: string[] = [],
|
|
197
|
+
): Promise<RuntimePolicy | undefined> {
|
|
198
|
+
const path = await selectedRuntimeFile(projectRoot, explicitFile);
|
|
199
|
+
if (path === undefined) return undefined;
|
|
200
|
+
try {
|
|
201
|
+
const canonical = await realpath(path);
|
|
202
|
+
const source = await readFile(canonical, "utf8");
|
|
203
|
+
if (Buffer.byteLength(source) > 65536)
|
|
204
|
+
throw new Error("runtime configuration exceeds 64 KiB");
|
|
205
|
+
return await resolveRuntimeConfiguration(
|
|
206
|
+
JSON.parse(source),
|
|
207
|
+
dirname(canonical),
|
|
208
|
+
protectedRoots,
|
|
209
|
+
);
|
|
210
|
+
} catch (cause) {
|
|
211
|
+
throw new Error(runtimeConfigurationDiagnostic(cause), { cause });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function runtimeConfigurationDiagnostic(cause: unknown): string {
|
|
216
|
+
if (
|
|
217
|
+
cause instanceof Error &&
|
|
218
|
+
/^(runtime |declared runtime |selected Apple )/.test(cause.message)
|
|
219
|
+
)
|
|
220
|
+
return `invalid runtime configuration: ${cause.message}`;
|
|
221
|
+
const filesystem = runtimeFilesystemDiagnostic(cause);
|
|
222
|
+
if (filesystem) return filesystem;
|
|
223
|
+
return "invalid runtime configuration";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function runtimeFilesystemDiagnostic(cause: unknown): string | undefined {
|
|
227
|
+
if (!(cause instanceof Error)) return undefined;
|
|
228
|
+
const error = cause as NodeJS.ErrnoException;
|
|
229
|
+
if (
|
|
230
|
+
!validFilesystemCode(error.code) ||
|
|
231
|
+
!validFilesystemOperation(error.syscall) ||
|
|
232
|
+
typeof error.path !== "string"
|
|
233
|
+
)
|
|
234
|
+
return undefined;
|
|
235
|
+
const path = safeFilesystemPath(error.path);
|
|
236
|
+
return `invalid runtime configuration: filesystem ${error.syscall} failed (${error.code}) at ${path}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function validFilesystemCode(code: string | undefined): boolean {
|
|
240
|
+
return code !== undefined && /^E[A-Z0-9]+$/.test(code);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function validFilesystemOperation(operation: string | undefined): boolean {
|
|
244
|
+
return operation !== undefined && /^[A-Za-z][A-Za-z0-9_]*$/.test(operation);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function safeFilesystemPath(path: string): string {
|
|
248
|
+
return Array.from(path.slice(0, 256), (character) => {
|
|
249
|
+
const code = character.charCodeAt(0);
|
|
250
|
+
return code < 0x20 || code === 0x7f ? "?" : character;
|
|
251
|
+
}).join("");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function selectedRuntimeFile(
|
|
255
|
+
projectRoot: string,
|
|
256
|
+
explicitFile?: string,
|
|
257
|
+
): Promise<string | undefined> {
|
|
258
|
+
const path = explicitFile ?? join(projectRoot, "sevro.json");
|
|
259
|
+
const info = await selectedRuntimeStat(path, Boolean(explicitFile));
|
|
260
|
+
if (!info) return undefined;
|
|
261
|
+
if (!info.isFile() || info.size > 65536)
|
|
262
|
+
throw new Error(
|
|
263
|
+
"invalid runtime configuration: expected a regular file up to 64 KiB",
|
|
264
|
+
);
|
|
265
|
+
return path;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function selectedRuntimeStat(path: string, explicit: boolean) {
|
|
269
|
+
try {
|
|
270
|
+
return await lstat(path);
|
|
271
|
+
} catch (cause) {
|
|
272
|
+
if (!explicit && (cause as NodeJS.ErrnoException).code === "ENOENT")
|
|
273
|
+
return undefined;
|
|
274
|
+
throw new Error(
|
|
275
|
+
runtimeFilesystemDiagnostic(cause) ?? "invalid runtime configuration",
|
|
276
|
+
{ cause },
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function resolvedSeeds(
|
|
282
|
+
seeds: RuntimeSeed[],
|
|
283
|
+
environment: Record<string, string>,
|
|
284
|
+
base: string,
|
|
285
|
+
) {
|
|
286
|
+
const resolved = await Promise.all(
|
|
287
|
+
seeds.map(async (seed) => {
|
|
288
|
+
const roots = await declaredRuntimeRoots(
|
|
289
|
+
[seed.source],
|
|
290
|
+
environment,
|
|
291
|
+
base,
|
|
292
|
+
);
|
|
293
|
+
requireRuntimeReadRoots(roots, []);
|
|
294
|
+
const [source] = roots;
|
|
295
|
+
if (!source) throw new Error("runtime seed source is missing");
|
|
296
|
+
return {
|
|
297
|
+
source,
|
|
298
|
+
target: seed.target,
|
|
299
|
+
...(seed.roles ? { roles: seed.roles } : {}),
|
|
300
|
+
sha256: await runtimeSeedDigest(source),
|
|
301
|
+
};
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
requireRuntimeSeedTargets(resolved);
|
|
305
|
+
return resolved;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function resolveRuntimeConfiguration(
|
|
309
|
+
value: unknown,
|
|
310
|
+
base: string,
|
|
311
|
+
protectedRoots: string[],
|
|
312
|
+
): Promise<RuntimePolicy> {
|
|
313
|
+
assertRuntimeConfiguration(value);
|
|
314
|
+
const environment = await resolvedRuntimeEnvironment(value);
|
|
315
|
+
const optional = await optionalRuntimeRoots(
|
|
316
|
+
value.filesystem?.optionalReadOnlyRoots ?? [],
|
|
317
|
+
environment,
|
|
318
|
+
base,
|
|
319
|
+
protectedRoots,
|
|
320
|
+
);
|
|
321
|
+
const pathRoots = await runtimePathDirectories(environment.PATH);
|
|
322
|
+
requireRuntimeReadRoots(pathRoots, protectedRoots);
|
|
323
|
+
requireRuntimeReadRoots(optional.roots, protectedRoots);
|
|
324
|
+
const discovery = await discoverRuntimeSupport(environment, protectedRoots);
|
|
325
|
+
const readOnlyRoots = [
|
|
326
|
+
...optional.roots,
|
|
327
|
+
...discovery.sources.flatMap((source) => source.readOnlyRoots),
|
|
328
|
+
...pathRoots.filter((root) => !discovery.excludedPrefixes.includes(root)),
|
|
329
|
+
...(await declaredRuntimeRoots(
|
|
330
|
+
configurationReadRoots(value),
|
|
331
|
+
environment,
|
|
332
|
+
base,
|
|
333
|
+
)),
|
|
334
|
+
];
|
|
335
|
+
requireRuntimeReadRoots(readOnlyRoots, protectedRoots);
|
|
336
|
+
return {
|
|
337
|
+
format: value.format,
|
|
338
|
+
...selectedRuntimeCapabilities(value),
|
|
339
|
+
...artifactConfiguration(value.runtime),
|
|
340
|
+
environment,
|
|
341
|
+
readOnlyRoots: [...new Set(readOnlyRoots)].sort(),
|
|
342
|
+
discovery: discovery.sources,
|
|
343
|
+
skippedOptionalReadOnlyRoots: optional.skipped,
|
|
344
|
+
...(await resolvedRuntimeCaches(value, environment, base, [
|
|
345
|
+
...protectedRoots,
|
|
346
|
+
...readOnlyRoots,
|
|
347
|
+
])),
|
|
348
|
+
hooks: resolvedHooks(value.hooks),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function resolvedRuntimeCaches(
|
|
353
|
+
value: RuntimeConfiguration,
|
|
354
|
+
environment: Record<string, string>,
|
|
355
|
+
base: string,
|
|
356
|
+
protectedRoots: string[],
|
|
357
|
+
) {
|
|
358
|
+
const seeds = await resolvedSeeds(
|
|
359
|
+
configurationSeeds(value),
|
|
360
|
+
environment,
|
|
361
|
+
base,
|
|
362
|
+
);
|
|
363
|
+
const caches = await resolveSharedCaches(
|
|
364
|
+
value.runtime?.sharedCacheDirectories ?? [],
|
|
365
|
+
environment,
|
|
366
|
+
base,
|
|
367
|
+
[...protectedRoots, ...seeds.map((seed) => seed.source)],
|
|
368
|
+
);
|
|
369
|
+
requireRuntimeSeedTargets([...seeds, ...caches]);
|
|
370
|
+
requireSharedCachePolicy({
|
|
371
|
+
format: value.format,
|
|
372
|
+
environment,
|
|
373
|
+
readOnlyRoots: [],
|
|
374
|
+
seeds,
|
|
375
|
+
sharedCacheDirectories: caches,
|
|
376
|
+
});
|
|
377
|
+
return {
|
|
378
|
+
seeds,
|
|
379
|
+
...(caches.length ? { sharedCacheDirectories: caches } : {}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function selectedRuntimeCapabilities(value: RuntimeConfiguration) {
|
|
384
|
+
return {
|
|
385
|
+
...(value.network === undefined ? {} : { network: value.network }),
|
|
386
|
+
...(value.codexState === undefined ? {} : { codexState: value.codexState }),
|
|
387
|
+
...(value.nativeTranscripts === undefined
|
|
388
|
+
? {}
|
|
389
|
+
: { nativeTranscripts: value.nativeTranscripts }),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function resolvedRuntimeEnvironment(value: RuntimeConfiguration) {
|
|
394
|
+
const { inherit, set } = configurationEnvironment(value);
|
|
395
|
+
const environment = snapshotEnvironment(inherit, set);
|
|
396
|
+
for (const [name, value] of Object.entries(environment)) {
|
|
397
|
+
if (name === "PATH" && inherit.includes(name) && !Object.hasOwn(set, name))
|
|
398
|
+
environment[name] = await runtimeInheritedPath(value);
|
|
399
|
+
}
|
|
400
|
+
return environment;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function configurationEnvironment(value: RuntimeConfiguration) {
|
|
404
|
+
const environment = value.environment;
|
|
405
|
+
return { inherit: environment?.inherit ?? [], set: environment?.set ?? {} };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function artifactConfiguration(runtime: RuntimeConfiguration["runtime"]) {
|
|
409
|
+
const directories = runtime?.artifactDirectories;
|
|
410
|
+
if (directories === undefined) return {};
|
|
411
|
+
requireArtifactDirectories(directories);
|
|
412
|
+
return { artifactDirectories: directories };
|
|
413
|
+
}
|
|
414
|
+
function configurationReadRoots(value: RuntimeConfiguration): string[] {
|
|
415
|
+
return value.filesystem?.readOnlyRoots ?? [];
|
|
416
|
+
}
|
|
417
|
+
function configurationSeeds(value: RuntimeConfiguration) {
|
|
418
|
+
return value.runtime?.seedDirectories ?? [];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function resolvedHooks(hooks: RuntimeConfiguration["hooks"]) {
|
|
422
|
+
return {
|
|
423
|
+
nativeGoal: hooks?.nativeGoal ?? false,
|
|
424
|
+
plugins: hooks?.plugins ?? [],
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function requireRuntimeSeedRoles(roles: RuntimeRole[] | undefined): void {
|
|
429
|
+
if (roles === undefined) return;
|
|
430
|
+
const allowed = ["candidate", "checks", "hooks", "semantic", "advisory"];
|
|
431
|
+
if (
|
|
432
|
+
!validRoleArray(roles) ||
|
|
433
|
+
new Set(roles).size !== roles.length ||
|
|
434
|
+
roles.some((role) => !allowed.includes(role))
|
|
435
|
+
)
|
|
436
|
+
throw new Error("runtime seed roles are invalid");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function runtimeRoleSeeds(policy: RuntimePolicy, role: RuntimeRole) {
|
|
440
|
+
return (policy.seeds ?? []).filter(
|
|
441
|
+
(seed) => seed.roles?.includes(role) ?? role !== "checks",
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function validRoleArray(roles: unknown): roles is RuntimeRole[] {
|
|
446
|
+
return Array.isArray(roles) && roles.length > 0;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function runtimeSeedSources(
|
|
450
|
+
policy: RuntimePolicy | undefined,
|
|
451
|
+
): string[] {
|
|
452
|
+
return [
|
|
453
|
+
...(policy?.seedReadDenyRoots ?? []),
|
|
454
|
+
...(policy?.seeds ?? []).map((seed) => seed.source),
|
|
455
|
+
];
|
|
456
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { RuntimeMetadataQueryError } from "./query";
|
|
4
|
+
import { optionalRuntimeRoots, requireRuntimeReadRoots } from "./paths";
|
|
5
|
+
import {
|
|
6
|
+
providerExecutable,
|
|
7
|
+
query,
|
|
8
|
+
requireProvider,
|
|
9
|
+
type RuntimeDiscoverySource,
|
|
10
|
+
} from "./discovery-providers";
|
|
11
|
+
|
|
12
|
+
export async function discoverApple(
|
|
13
|
+
path: string,
|
|
14
|
+
environment: Record<string, string>,
|
|
15
|
+
sources: RuntimeDiscoverySource[],
|
|
16
|
+
protectedRoots: string[],
|
|
17
|
+
) {
|
|
18
|
+
const executable = await providerExecutable("xcode-select", path);
|
|
19
|
+
if (!executable) return;
|
|
20
|
+
if (!(await selectedAppleShim(executable, path))) return;
|
|
21
|
+
await requireProvider(executable, protectedRoots);
|
|
22
|
+
const directory = await selectedAppleDirectory(executable, environment);
|
|
23
|
+
if (directory === undefined) return;
|
|
24
|
+
const support = await selectedDeveloperRoots(directory, protectedRoots);
|
|
25
|
+
requireRuntimeReadRoots(support.roots, []);
|
|
26
|
+
sources.push({
|
|
27
|
+
provider: "apple-developer",
|
|
28
|
+
executable,
|
|
29
|
+
readOnlyRoots: support.roots,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function selectedDeveloperRoots(
|
|
34
|
+
directory: string,
|
|
35
|
+
protectedRoots: string[],
|
|
36
|
+
) {
|
|
37
|
+
const support = await optionalRuntimeRoots(
|
|
38
|
+
[directory],
|
|
39
|
+
{},
|
|
40
|
+
"/",
|
|
41
|
+
protectedRoots,
|
|
42
|
+
);
|
|
43
|
+
if (support.skipped.length)
|
|
44
|
+
throw new Error("selected Apple developer directory is missing");
|
|
45
|
+
return support;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function selectedAppleShim(
|
|
49
|
+
provider: string,
|
|
50
|
+
path: string,
|
|
51
|
+
): Promise<boolean> {
|
|
52
|
+
const providerDirectory = dirname(await realpath(provider));
|
|
53
|
+
for (const name of [
|
|
54
|
+
"git",
|
|
55
|
+
"python3",
|
|
56
|
+
"clang",
|
|
57
|
+
"clang++",
|
|
58
|
+
"cc",
|
|
59
|
+
"c++",
|
|
60
|
+
"xcrun",
|
|
61
|
+
"xcodebuild",
|
|
62
|
+
"swift",
|
|
63
|
+
"make",
|
|
64
|
+
"ar",
|
|
65
|
+
"ranlib",
|
|
66
|
+
]) {
|
|
67
|
+
const executable = await providerExecutable(name, path);
|
|
68
|
+
if (executable && dirname(await realpath(executable)) === providerDirectory)
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function selectedAppleDirectory(
|
|
75
|
+
executable: string,
|
|
76
|
+
environment: Record<string, string>,
|
|
77
|
+
) {
|
|
78
|
+
try {
|
|
79
|
+
return await query(executable, "--print-path", environment);
|
|
80
|
+
} catch (cause) {
|
|
81
|
+
const failure = (cause as Error).cause;
|
|
82
|
+
if (
|
|
83
|
+
environment.DEVELOPER_DIR === undefined &&
|
|
84
|
+
failure instanceof RuntimeMetadataQueryError &&
|
|
85
|
+
failure.noActiveDeveloper
|
|
86
|
+
)
|
|
87
|
+
return undefined;
|
|
88
|
+
throw cause;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { access, realpath } from "node:fs/promises";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
|
+
import { runtimeMetadataQuery } from "./query";
|
|
5
|
+
import { requireRuntimeReadRoots } from "./paths";
|
|
6
|
+
|
|
7
|
+
export interface RuntimeDiscoverySource {
|
|
8
|
+
provider: "homebrew" | "apple-developer";
|
|
9
|
+
executable: string;
|
|
10
|
+
readOnlyRoots: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function providerExecutable(name: string, path: string) {
|
|
14
|
+
for (const entry of path.split(delimiter)) {
|
|
15
|
+
const executable = join(entry, name);
|
|
16
|
+
try {
|
|
17
|
+
await access(executable, constants.X_OK);
|
|
18
|
+
return executable;
|
|
19
|
+
} catch (cause) {
|
|
20
|
+
const code = (cause as NodeJS.ErrnoException).code;
|
|
21
|
+
if (code !== "ENOENT" && code !== "EACCES") throw cause;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function query(
|
|
28
|
+
executable: string,
|
|
29
|
+
argument: string,
|
|
30
|
+
environment: Record<string, string>,
|
|
31
|
+
) {
|
|
32
|
+
try {
|
|
33
|
+
const value = (
|
|
34
|
+
await runtimeMetadataQuery(executable, argument, environment)
|
|
35
|
+
).trim();
|
|
36
|
+
if (!isAbsolute(value) || containsControl(value))
|
|
37
|
+
throw new Error("expected one absolute metadata path");
|
|
38
|
+
return value;
|
|
39
|
+
} catch (cause) {
|
|
40
|
+
const reason = cause instanceof Error ? cause.message : "query unavailable";
|
|
41
|
+
throw new Error(
|
|
42
|
+
`runtime discovery query failed: ${executable} ${argument}: ${reason} (3 second / 8 KiB bound)`,
|
|
43
|
+
{ cause },
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function containsControl(value: string): boolean {
|
|
49
|
+
return Array.from(value).some(
|
|
50
|
+
(character) =>
|
|
51
|
+
character.charCodeAt(0) < 0x20 || character.charCodeAt(0) === 0x7f,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function requireProvider(
|
|
56
|
+
executable: string,
|
|
57
|
+
protectedRoots: string[],
|
|
58
|
+
) {
|
|
59
|
+
requireRuntimeReadRoots(
|
|
60
|
+
[executable, await realpath(executable)],
|
|
61
|
+
protectedRoots,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
insideRuntimeRoot,
|
|
5
|
+
optionalRuntimeRoots,
|
|
6
|
+
requireRuntimeReadRoots,
|
|
7
|
+
} from "./paths";
|
|
8
|
+
import {
|
|
9
|
+
providerExecutable,
|
|
10
|
+
query,
|
|
11
|
+
requireProvider,
|
|
12
|
+
type RuntimeDiscoverySource,
|
|
13
|
+
} from "./discovery-providers";
|
|
14
|
+
export type { RuntimeDiscoverySource } from "./discovery-providers";
|
|
15
|
+
|
|
16
|
+
async function homebrew(
|
|
17
|
+
executable: string,
|
|
18
|
+
environment: Record<string, string>,
|
|
19
|
+
protectedRoots: string[],
|
|
20
|
+
) {
|
|
21
|
+
const prefix = await query(executable, "--prefix", environment);
|
|
22
|
+
const canonicalPrefix = await realpath(prefix);
|
|
23
|
+
requireRuntimeReadRoots([prefix, canonicalPrefix], []);
|
|
24
|
+
const cellar = await query(executable, "--cellar", environment);
|
|
25
|
+
const support = await optionalRuntimeRoots(
|
|
26
|
+
[cellar, join(prefix, "opt"), join(prefix, "etc")],
|
|
27
|
+
{},
|
|
28
|
+
"/",
|
|
29
|
+
protectedRoots,
|
|
30
|
+
);
|
|
31
|
+
requireRuntimeReadRoots(support.roots, []);
|
|
32
|
+
if (
|
|
33
|
+
support.roots.some((root) =>
|
|
34
|
+
[prefix, canonicalPrefix].some((installation) =>
|
|
35
|
+
insideRuntimeRoot(root, installation),
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
throw new Error(
|
|
40
|
+
"runtime Homebrew metadata grants whole installation prefix",
|
|
41
|
+
);
|
|
42
|
+
return { prefix, roots: support.roots };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Query only providers selected by the operator's declared PATH. */
|
|
46
|
+
export async function discoverRuntimeSupport(
|
|
47
|
+
environment: Record<string, string>,
|
|
48
|
+
protectedRoots: string[],
|
|
49
|
+
) {
|
|
50
|
+
const path = environment.PATH;
|
|
51
|
+
const sources: RuntimeDiscoverySource[] = [];
|
|
52
|
+
const excludedPrefixes: string[] = [];
|
|
53
|
+
if (path === undefined) return { sources, excludedPrefixes };
|
|
54
|
+
const brew = await providerExecutable("brew", path);
|
|
55
|
+
if (brew) {
|
|
56
|
+
await requireProvider(brew, protectedRoots);
|
|
57
|
+
const support = await homebrew(brew, environment, protectedRoots);
|
|
58
|
+
excludedPrefixes.push(support.prefix, await realpath(support.prefix));
|
|
59
|
+
sources.push({
|
|
60
|
+
provider: "homebrew",
|
|
61
|
+
executable: brew,
|
|
62
|
+
readOnlyRoots: support.roots,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (process.platform === "darwin") {
|
|
66
|
+
const { discoverApple } = await import("./discovery-apple");
|
|
67
|
+
await discoverApple(path, environment, sources, protectedRoots);
|
|
68
|
+
}
|
|
69
|
+
return { sources, excludedPrefixes };
|
|
70
|
+
}
|