vouchington-tooling 0.28.0 → 0.30.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/README.md +64 -16
- package/dist/api-contract-discovery/header-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/protocol-body-status.d.mts +2 -0
- package/dist/api-contract-discovery/protocol-body-status.mjs +4 -0
- package/dist/api-contract-discovery/protocol-callback-argument-bindings.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-callback-argument-bindings.mjs +33 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.mjs +103 -0
- package/dist/api-contract-discovery/protocol-callback-mutations.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-callback-mutations.mjs +42 -0
- package/dist/api-contract-discovery/protocol-callback-registration.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-callback-registration.mjs +27 -0
- package/dist/api-contract-discovery/protocol-callback-values.d.mts +17 -0
- package/dist/api-contract-discovery/protocol-callback-values.mjs +114 -0
- package/dist/api-contract-discovery/protocol-context-alias.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-context-alias.mjs +20 -0
- package/dist/api-contract-discovery/protocol-contract-extraction.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-contract-extraction.mjs +87 -0
- package/dist/api-contract-discovery/protocol-contract-keys.d.mts +1 -0
- package/dist/api-contract-discovery/protocol-contract-keys.mjs +8 -0
- package/dist/api-contract-discovery/protocol-contract-registry.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-contract-registry.mjs +133 -0
- package/dist/api-contract-discovery/protocol-executable-path.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-executable-path.mjs +64 -0
- package/dist/api-contract-discovery/protocol-execution-path.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-execution-path.mjs +16 -0
- package/dist/api-contract-discovery/protocol-http-association.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-http-association.mjs +97 -0
- package/dist/api-contract-discovery/protocol-http-body-kinds.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-body-kinds.mjs +19 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +6 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.mjs +34 -0
- package/dist/api-contract-discovery/protocol-http-context.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-http-context.mjs +108 -0
- package/dist/api-contract-discovery/protocol-http-emission.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-emission.mjs +76 -0
- package/dist/api-contract-discovery/protocol-http-implicit-taint.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-http-implicit-taint.mjs +26 -0
- package/dist/api-contract-discovery/protocol-http-registration.d.mts +12 -0
- package/dist/api-contract-discovery/protocol-http-registration.mjs +53 -0
- package/dist/api-contract-discovery/protocol-marker-analysis.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-marker-analysis.mjs +50 -0
- package/dist/api-contract-discovery/protocol-opaque-callback.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-opaque-callback.mjs +107 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +72 -0
- package/dist/api-contract-discovery/protocol-platform-mutation-targets.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-platform-mutation-targets.mjs +89 -0
- package/dist/api-contract-discovery/protocol-platform-writes.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-platform-writes.mjs +92 -0
- package/dist/api-contract-discovery/protocol-requested-keys.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-requested-keys.mjs +45 -0
- package/dist/api-contract-discovery/protocol-sse-callers.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-sse-callers.mjs +67 -0
- package/dist/api-contract-discovery/protocol-sse-emission.d.mts +11 -0
- package/dist/api-contract-discovery/protocol-sse-emission.mjs +61 -0
- package/dist/api-contract-discovery/protocol-sse-feasible-status.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-sse-feasible-status.mjs +24 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +70 -0
- package/dist/api-contract-discovery/protocol-sse-status.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-sse-status.mjs +84 -0
- package/dist/api-contract-discovery/protocol-sse-write-access.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-write-access.mjs +42 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +6 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +66 -0
- package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +36 -0
- package/dist/api-contract-discovery/protocol-sse-write-resolution.d.mts +14 -0
- package/dist/api-contract-discovery/protocol-sse-write-resolution.mjs +79 -0
- package/dist/api-contract-discovery/protocol-status-dominance.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-status-dominance.mjs +35 -0
- package/dist/api-contract-discovery/protocol-write-receiver.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-write-receiver.mjs +79 -0
- package/dist/api-contract-discovery/query-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.mjs +30 -0
- package/dist/api-contract-discovery/registered-route-catalog.mjs +11 -7
- package/dist/api-contract-discovery/registered-route-factory-returns.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-factory-returns.mjs +29 -0
- package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +5 -1
- package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +79 -35
- package/dist/api-contract-discovery/registered-route-runtime-parameters.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-runtime-parameters.mjs +5 -0
- package/dist/api-contract-discovery/registered-route-terminal-error.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-terminal-error.mjs +98 -0
- package/dist/api-contract-discovery/request-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/response-contract-implicit.mjs +28 -10
- package/dist/api-contract-discovery/response-contract-registry.mjs +14 -1
- package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-route-analysis.mjs +22 -4
- package/dist/api-contract-discovery/response-contract-status.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-status.mjs +10 -8
- package/dist/api-contract-discovery/response-contract-types.d.mts +7 -0
- package/dist/cli/index.mjs +0 -3
- package/dist/cli/parse-forwarded-commands.d.mts +0 -3
- package/dist/cli/parse-forwarded-commands.mjs +0 -2
- package/dist/cli/parse.d.mts +0 -3
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +0 -5
- package/dist/contract-schema/contract-schema-bounded-array.d.mts +1 -3
- package/dist/contract-schema/contract-schema-bounded-array.mjs +5 -0
- package/dist/contract-schema/contract-schema-object-tuple.mjs +7 -5
- package/dist/contract-schema/contract-schema-type-extractor.mjs +13 -0
- package/dist/contract-schema/contract-schema-validator.mjs +2 -2
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1
- package/dist/openapi-document/build-openapi-catalog-sse.d.mts +4 -0
- package/dist/openapi-document/build-openapi-catalog-sse.mjs +27 -0
- package/dist/openapi-document/build-openapi-document.mjs +5 -2
- package/dist/openapi-document/build-openapi-response.d.mts +2 -2
- package/dist/openapi-document/build-openapi-response.mjs +33 -3
- package/dist/openapi-document/build-openapi-sse-events.d.mts +11 -0
- package/dist/openapi-document/build-openapi-sse-events.mjs +46 -0
- package/dist/openapi-document/contract-schema-intersection-merge.mjs +11 -11
- package/dist/openapi-document/contract-schema-to-openapi.mjs +3 -3
- package/dist/openapi-document/openapi-route-helpers.mjs +1 -3
- package/dist/openapi-document/openapi-types.d.mts +3 -0
- package/dist/openapi-document/operation-types.d.mts +7 -0
- package/package.json +3 -8
- package/skills/bounded-iteration/SKILL.md +51 -0
- package/skills/bounded-iteration/references/sweep-patterns.md +342 -0
- package/skills/manifest.json +9 -3
- package/skills/postgres-node-performance-tuning/SKILL.md +2 -1
- package/skills/postgres-schema-design/SKILL.md +2 -0
- package/dist/agent-harness-config/apply.d.mts +0 -4
- package/dist/agent-harness-config/apply.mjs +0 -72
- package/dist/agent-harness-config/cli.d.mts +0 -10
- package/dist/agent-harness-config/cli.mjs +0 -100
- package/dist/agent-harness-config/index.d.mts +0 -4
- package/dist/agent-harness-config/index.mjs +0 -3
- package/dist/agent-harness-config/merge-json.d.mts +0 -5
- package/dist/agent-harness-config/merge-json.mjs +0 -62
- package/dist/agent-harness-config/merge-toml.d.mts +0 -6
- package/dist/agent-harness-config/merge-toml.mjs +0 -132
- package/dist/agent-harness-config/paths.d.mts +0 -3
- package/dist/agent-harness-config/paths.mjs +0 -117
- package/dist/agent-harness-config/policy.d.mts +0 -14
- package/dist/agent-harness-config/policy.mjs +0 -51
- package/dist/agent-harness-config/prerequisites.d.mts +0 -2
- package/dist/agent-harness-config/prerequisites.mjs +0 -67
- package/dist/agent-harness-config/toml-value.d.mts +0 -6
- package/dist/agent-harness-config/toml-value.mjs +0 -69
- package/dist/agent-harness-config/types.d.mts +0 -90
- package/dist/agent-harness-config/types.mjs +0 -2
- package/docs/agent-harness-config.md +0 -134
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { dumpHarnessPolicy } from './policy.mjs';
|
|
2
|
-
import { applyHarnessConfig, checkHarnessConfig } from './apply.mjs';
|
|
3
|
-
import { HARNESS_IDS } from './types.mjs';
|
|
4
|
-
const AGENT_HARNESS_CONFIG_USAGE = 'agent-harness-config dump|check|apply [--global] [--repo PATH]... [--harness NAME]... [--home PATH]';
|
|
5
|
-
function isHarness(value) {
|
|
6
|
-
return HARNESS_IDS.includes(value);
|
|
7
|
-
}
|
|
8
|
-
export function parseAgentHarnessConfigArguments(argv) {
|
|
9
|
-
const action = argv[0];
|
|
10
|
-
if (action !== 'dump' && action !== 'check' && action !== 'apply') {
|
|
11
|
-
throw new Error(`unknown agent-harness-config action: ${action ?? ''}\n${AGENT_HARNESS_CONFIG_USAGE}`);
|
|
12
|
-
}
|
|
13
|
-
let global = false;
|
|
14
|
-
let home;
|
|
15
|
-
const repos = [];
|
|
16
|
-
const harnesses = [];
|
|
17
|
-
for (let index = 1; index < argv.length; index += 1) {
|
|
18
|
-
const flag = argv[index];
|
|
19
|
-
if (flag === '--global') {
|
|
20
|
-
global = true;
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
if (flag === '--repo' || flag === '--harness' || flag === '--home') {
|
|
24
|
-
const value = argv[index + 1];
|
|
25
|
-
if (value === undefined)
|
|
26
|
-
throw new Error(`${flag} requires a value\n${AGENT_HARNESS_CONFIG_USAGE}`);
|
|
27
|
-
index += 1;
|
|
28
|
-
if (flag === '--repo')
|
|
29
|
-
repos.push(value);
|
|
30
|
-
else if (flag === '--home')
|
|
31
|
-
home = value;
|
|
32
|
-
else if (!isHarness(value))
|
|
33
|
-
throw new Error(`unknown harness: ${value}`);
|
|
34
|
-
else
|
|
35
|
-
harnesses.push(value);
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
throw new Error(`unknown agent-harness-config option: ${flag}\n${AGENT_HARNESS_CONFIG_USAGE}`);
|
|
39
|
-
}
|
|
40
|
-
if (action !== 'dump' && !global && repos.length === 0) {
|
|
41
|
-
throw new Error(`check and apply require --global and/or --repo\n${AGENT_HARNESS_CONFIG_USAGE}`);
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
action,
|
|
45
|
-
global,
|
|
46
|
-
repos,
|
|
47
|
-
...(harnesses.length > 0 ? { harnesses } : {}),
|
|
48
|
-
...(home === undefined ? {} : { home }),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function formatDrift(drift) {
|
|
52
|
-
return ` ${drift.path} ${drift.key}: ${JSON.stringify(drift.current)} -> ${JSON.stringify(drift.desired)}`;
|
|
53
|
-
}
|
|
54
|
-
function targets(parsed) {
|
|
55
|
-
const list = [];
|
|
56
|
-
if (parsed.global)
|
|
57
|
-
list.push({ kind: 'global' });
|
|
58
|
-
for (const root of parsed.repos)
|
|
59
|
-
list.push({ kind: 'repo', root });
|
|
60
|
-
return list;
|
|
61
|
-
}
|
|
62
|
-
export async function runAgentHarnessConfigCli(argv) {
|
|
63
|
-
try {
|
|
64
|
-
const parsed = parseAgentHarnessConfigArguments(argv);
|
|
65
|
-
if (parsed.action === 'dump') {
|
|
66
|
-
process.stdout.write(`${JSON.stringify(dumpHarnessPolicy(), null, 2)}\n`);
|
|
67
|
-
return 0;
|
|
68
|
-
}
|
|
69
|
-
const options = {
|
|
70
|
-
...(parsed.harnesses === undefined ? {} : { harnesses: parsed.harnesses }),
|
|
71
|
-
...(parsed.home === undefined ? {} : { home: parsed.home }),
|
|
72
|
-
};
|
|
73
|
-
let compliant = true;
|
|
74
|
-
for (const target of targets(parsed)) {
|
|
75
|
-
const result = parsed.action === 'apply'
|
|
76
|
-
? await applyHarnessConfig(target, options)
|
|
77
|
-
: await checkHarnessConfig(target, options);
|
|
78
|
-
compliant &&=
|
|
79
|
-
parsed.action === 'apply'
|
|
80
|
-
? result.prerequisites.every((prerequisite) => prerequisite.satisfied)
|
|
81
|
-
: result.compliant;
|
|
82
|
-
for (const file of result.files) {
|
|
83
|
-
if (file.action === 'ok')
|
|
84
|
-
process.stdout.write(`ok ${file.path}\n`);
|
|
85
|
-
else
|
|
86
|
-
process.stdout.write(`${file.action} ${file.path}\n`);
|
|
87
|
-
for (const drift of file.drifts)
|
|
88
|
-
process.stdout.write(`${formatDrift(drift)}\n`);
|
|
89
|
-
}
|
|
90
|
-
for (const prerequisite of result.prerequisites) {
|
|
91
|
-
process.stdout.write(`prerequisite ${prerequisite.harness} ${prerequisite.key} satisfied=${prerequisite.satisfied}: ${prerequisite.message}\n`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return compliant ? 0 : 1;
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
98
|
-
return 2;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { applyHarnessConfig, checkHarnessConfig, planHarnessConfig } from './apply.mts';
|
|
2
|
-
export { dumpHarnessPolicy, DEFAULT_EXTRA_WRITABLE_ROOTS } from './policy.mts';
|
|
3
|
-
export { HARNESS_IDS } from './types.mts';
|
|
4
|
-
export type { ApplyTarget, FilePlan, FileResult, HarnessApplyResult, HarnessCheckResult, HarnessConfigOptions, HarnessId, HarnessPlan, HarnessPolicyDump, JsonPatch, KeyDrift, TomlPatch, TomlValue, } from './types.mts';
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { sameValue } from './policy.mjs';
|
|
2
|
-
function getJsonPath(value, path) {
|
|
3
|
-
let current = value;
|
|
4
|
-
for (const key of path) {
|
|
5
|
-
if (current === null || typeof current !== 'object' || Array.isArray(current))
|
|
6
|
-
return undefined;
|
|
7
|
-
current = current[key];
|
|
8
|
-
}
|
|
9
|
-
return current;
|
|
10
|
-
}
|
|
11
|
-
function setJsonPath(target, path, value) {
|
|
12
|
-
let current = target;
|
|
13
|
-
for (const key of path.slice(0, -1)) {
|
|
14
|
-
const next = current[key];
|
|
15
|
-
if (next === null || typeof next !== 'object' || Array.isArray(next)) {
|
|
16
|
-
current[key] = {};
|
|
17
|
-
}
|
|
18
|
-
current = current[key];
|
|
19
|
-
}
|
|
20
|
-
const last = path.at(-1);
|
|
21
|
-
if (last !== undefined)
|
|
22
|
-
current[last] = value;
|
|
23
|
-
}
|
|
24
|
-
function parseJsonObject(source, path) {
|
|
25
|
-
if (source.trim() === '')
|
|
26
|
-
return {};
|
|
27
|
-
const parsed = JSON.parse(source);
|
|
28
|
-
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
29
|
-
throw new Error(`${path}: expected a JSON object`);
|
|
30
|
-
}
|
|
31
|
-
return parsed;
|
|
32
|
-
}
|
|
33
|
-
function unionMerge(current, values) {
|
|
34
|
-
const base = Array.isArray(current) ? current : [];
|
|
35
|
-
const merged = [...base];
|
|
36
|
-
for (const value of values) {
|
|
37
|
-
if (!merged.some((entry) => sameValue(entry, value)))
|
|
38
|
-
merged.push(value);
|
|
39
|
-
}
|
|
40
|
-
return merged;
|
|
41
|
-
}
|
|
42
|
-
function desiredValue(document, patch) {
|
|
43
|
-
if (patch.merge !== 'union')
|
|
44
|
-
return patch.value;
|
|
45
|
-
return unionMerge(getJsonPath(document, patch.path), patch.value);
|
|
46
|
-
}
|
|
47
|
-
export function applyJsonPatches(source, patches, path) {
|
|
48
|
-
const document = parseJsonObject(source, path);
|
|
49
|
-
const resolved = patches.map((patch) => ({ desired: desiredValue(document, patch), patch }));
|
|
50
|
-
const drifts = [];
|
|
51
|
-
for (const { desired, patch } of resolved) {
|
|
52
|
-
const current = getJsonPath(document, patch.path);
|
|
53
|
-
if (!sameValue(current, desired)) {
|
|
54
|
-
drifts.push({ current, desired, key: patch.path.join('.'), path });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (drifts.length === 0)
|
|
58
|
-
return { drifts, text: source === '' ? '{}\n' : source };
|
|
59
|
-
for (const { desired, patch } of resolved)
|
|
60
|
-
setJsonPath(document, patch.path, desired);
|
|
61
|
-
return { drifts, text: `${JSON.stringify(document, null, 2)}\n` };
|
|
62
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { KeyDrift, TomlPatch } from './types.mts';
|
|
2
|
-
export declare function readTomlKey(text: string, table: string, key: string): unknown;
|
|
3
|
-
export declare function applyTomlPatches(source: string, patches: readonly TomlPatch[], path: string): {
|
|
4
|
-
drifts: KeyDrift[];
|
|
5
|
-
text: string;
|
|
6
|
-
};
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { parse as parseToml } from 'smol-toml';
|
|
2
|
-
import { sameValue } from './policy.mjs';
|
|
3
|
-
import { formatTomlValue, parseTomlValue } from './toml-value.mjs';
|
|
4
|
-
function lineStarts(text) {
|
|
5
|
-
const starts = [0];
|
|
6
|
-
for (let index = 0; index < text.length; index += 1) {
|
|
7
|
-
if (text[index] === '\n')
|
|
8
|
-
starts.push(index + 1);
|
|
9
|
-
}
|
|
10
|
-
return starts;
|
|
11
|
-
}
|
|
12
|
-
function lineAt(text, start) {
|
|
13
|
-
const end = text.indexOf('\n', start);
|
|
14
|
-
return text.slice(start, end === -1 ? text.length : end);
|
|
15
|
-
}
|
|
16
|
-
function isHeaderLine(line) {
|
|
17
|
-
return line.trimStart().startsWith('[');
|
|
18
|
-
}
|
|
19
|
-
function headerName(line) {
|
|
20
|
-
const trimmed = line.trim();
|
|
21
|
-
if (trimmed.startsWith('[[') || !trimmed.startsWith('['))
|
|
22
|
-
return undefined;
|
|
23
|
-
const close = trimmed.indexOf(']');
|
|
24
|
-
if (close <= 1)
|
|
25
|
-
return undefined;
|
|
26
|
-
return trimmed.slice(1, close);
|
|
27
|
-
}
|
|
28
|
-
function tableRange(text, table) {
|
|
29
|
-
const starts = lineStarts(text);
|
|
30
|
-
if (table === '') {
|
|
31
|
-
const first = starts.find((start) => isHeaderLine(lineAt(text, start)));
|
|
32
|
-
return { bodyEnd: first ?? text.length, bodyStart: 0 };
|
|
33
|
-
}
|
|
34
|
-
for (const start of starts) {
|
|
35
|
-
const line = lineAt(text, start);
|
|
36
|
-
if (headerName(line) !== table)
|
|
37
|
-
continue;
|
|
38
|
-
const bodyStart = start + line.length + (text[start + line.length] === '\n' ? 1 : 0);
|
|
39
|
-
const next = starts.find((candidate) => candidate >= bodyStart && isHeaderLine(lineAt(text, candidate)));
|
|
40
|
-
return { bodyEnd: next ?? text.length, bodyStart };
|
|
41
|
-
}
|
|
42
|
-
return { bodyEnd: -1, bodyStart: -1 };
|
|
43
|
-
}
|
|
44
|
-
function keyMatch(text, bodyStart, bodyEnd, key) {
|
|
45
|
-
const pattern = new RegExp(`^[ \\t]*${key.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&')}[ \\t]*=[ \\t]*`, 'm');
|
|
46
|
-
const slice = text.slice(bodyStart, bodyEnd);
|
|
47
|
-
const match = pattern.exec(slice);
|
|
48
|
-
if (!match || match.index === undefined)
|
|
49
|
-
return undefined;
|
|
50
|
-
const equals = bodyStart + match.index + match[0].length;
|
|
51
|
-
return { equals, parsed: parseTomlValue(text, equals) };
|
|
52
|
-
}
|
|
53
|
-
export function readTomlKey(text, table, key) {
|
|
54
|
-
const range = tableRange(text, table);
|
|
55
|
-
if (range.bodyStart < 0)
|
|
56
|
-
return undefined;
|
|
57
|
-
return keyMatch(text, range.bodyStart, range.bodyEnd, key)?.parsed.value;
|
|
58
|
-
}
|
|
59
|
-
function appendTable(text, table, assignment) {
|
|
60
|
-
const prefix = text === '' || text.endsWith('\n') ? text : `${text}\n`;
|
|
61
|
-
const blank = prefix !== '' && !prefix.endsWith('\n\n') ? '\n' : '';
|
|
62
|
-
return `${prefix}${blank}[${table}]\n${assignment}\n`;
|
|
63
|
-
}
|
|
64
|
-
function upsertTomlKey(text, table, key, value) {
|
|
65
|
-
const assignment = `${key} = ${formatTomlValue(value)}`;
|
|
66
|
-
const range = tableRange(text, table);
|
|
67
|
-
if (range.bodyStart < 0)
|
|
68
|
-
return appendTable(text, table, assignment);
|
|
69
|
-
const existing = keyMatch(text, range.bodyStart, range.bodyEnd, key);
|
|
70
|
-
if (existing) {
|
|
71
|
-
return `${text.slice(0, existing.equals)}${formatTomlValue(value)}${text.slice(existing.parsed.end)}`;
|
|
72
|
-
}
|
|
73
|
-
const at = range.bodyEnd;
|
|
74
|
-
const prefix = at > 0 && text[at - 1] !== '\n' ? '\n' : '';
|
|
75
|
-
return `${text.slice(0, at)}${prefix}${assignment}\n${text.slice(at)}`;
|
|
76
|
-
}
|
|
77
|
-
function tomlPatchValue(parsed, table, key) {
|
|
78
|
-
let current = parsed;
|
|
79
|
-
if (table !== '') {
|
|
80
|
-
for (const segment of table.split('.')) {
|
|
81
|
-
/* v8 ignore next 2 -- if `text` parsed, every ancestor of a dotted table header is
|
|
82
|
-
guaranteed to be a table too, so a real TOML parser can never hand back a non-object
|
|
83
|
-
mid-path here. */
|
|
84
|
-
if (current === null || typeof current !== 'object' || Array.isArray(current))
|
|
85
|
-
return undefined;
|
|
86
|
-
current = current[segment];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/* v8 ignore next 2 -- assertValidToml only reaches a patch's table here once readTomlKey has
|
|
90
|
-
already proven (as a match, or by forcing the write that creates it) that the table is a
|
|
91
|
-
real nested object in this parsed document. */
|
|
92
|
-
if (current === null || typeof current !== 'object' || Array.isArray(current))
|
|
93
|
-
return undefined;
|
|
94
|
-
return current[key];
|
|
95
|
-
}
|
|
96
|
-
function assertValidToml(text, patches, path) {
|
|
97
|
-
let parsed;
|
|
98
|
-
try {
|
|
99
|
-
parsed = parseToml(text);
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
/* v8 ignore next -- smol-toml's parse() only ever throws TomlError, which extends Error, so
|
|
103
|
-
the non-Error fallback here is unreachable in practice. */
|
|
104
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
-
throw new Error(`${path}: produced invalid TOML (${message})`);
|
|
106
|
-
}
|
|
107
|
-
for (const patch of patches) {
|
|
108
|
-
const value = tomlPatchValue(parsed, patch.table, patch.key);
|
|
109
|
-
/* v8 ignore next 6 -- backstop for a formatTomlValue/parseToml encoding mismatch; formatTomlValue's
|
|
110
|
-
JSON.stringify-based escaping is a valid TOML basic string in every case this module writes,
|
|
111
|
-
so a real parser always reads back what we just wrote. */
|
|
112
|
-
if (!sameValue(value, patch.value)) {
|
|
113
|
-
const key = patch.table === '' ? patch.key : `${patch.table}.${patch.key}`;
|
|
114
|
-
throw new Error(`${path}: wrote ${key} but a real TOML parser reads back ${JSON.stringify(value)}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
export function applyTomlPatches(source, patches, path) {
|
|
119
|
-
const drifts = [];
|
|
120
|
-
let text = source.replaceAll('\r\n', '\n');
|
|
121
|
-
for (const patch of patches) {
|
|
122
|
-
const current = readTomlKey(text, patch.table, patch.key);
|
|
123
|
-
if (sameValue(current, patch.value))
|
|
124
|
-
continue;
|
|
125
|
-
const key = patch.table === '' ? patch.key : `${patch.table}.${patch.key}`;
|
|
126
|
-
drifts.push({ current, desired: patch.value, key, path });
|
|
127
|
-
text = upsertTomlKey(text, patch.table, patch.key, patch.value);
|
|
128
|
-
}
|
|
129
|
-
if (drifts.length > 0)
|
|
130
|
-
assertValidToml(text, patches, path);
|
|
131
|
-
return { drifts, text };
|
|
132
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join, resolve } from 'node:path';
|
|
3
|
-
import { CLAUDE_DEFAULT_MODE, CODEX_APPROVAL_POLICY, CODEX_APPROVALS_REVIEWER, CODEX_SANDBOX_MODE, CURSOR_APPROVAL_MODE, CURSOR_SANDBOX_MODE, DEFAULT_EXTRA_WRITABLE_ROOTS, GROK_AUTO_MODE_ENABLED, GROK_DEFAULT_AUTO_MODE, GROK_PERMISSION_MODE, } from './policy.mjs';
|
|
4
|
-
import { HARNESS_IDS } from './types.mjs';
|
|
5
|
-
function selected(options) {
|
|
6
|
-
return new Set(options?.harnesses ?? HARNESS_IDS);
|
|
7
|
-
}
|
|
8
|
-
function extraRoots(options) {
|
|
9
|
-
return options?.extraWritableRoots ?? DEFAULT_EXTRA_WRITABLE_ROOTS;
|
|
10
|
-
}
|
|
11
|
-
function wants(set, id) {
|
|
12
|
-
return set.has(id);
|
|
13
|
-
}
|
|
14
|
-
function codexPatches(roots) {
|
|
15
|
-
return {
|
|
16
|
-
format: 'toml',
|
|
17
|
-
patches: [
|
|
18
|
-
{ key: 'approval_policy', table: '', value: CODEX_APPROVAL_POLICY },
|
|
19
|
-
{ key: 'approvals_reviewer', table: '', value: CODEX_APPROVALS_REVIEWER },
|
|
20
|
-
{ key: 'sandbox_mode', table: '', value: CODEX_SANDBOX_MODE },
|
|
21
|
-
{ key: 'network_access', table: 'sandbox_workspace_write', value: true },
|
|
22
|
-
{ key: 'writable_roots', table: 'sandbox_workspace_write', value: roots },
|
|
23
|
-
],
|
|
24
|
-
path: '',
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function grokSandboxPatches(roots) {
|
|
28
|
-
return {
|
|
29
|
-
format: 'toml',
|
|
30
|
-
patches: [
|
|
31
|
-
{ key: 'extends', table: 'profiles.workspace-write', value: 'workspace' },
|
|
32
|
-
{ key: 'read_write', table: 'profiles.workspace-write', value: roots },
|
|
33
|
-
],
|
|
34
|
-
path: '',
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function homeDir(options) {
|
|
38
|
-
return resolve(options?.home ?? homedir());
|
|
39
|
-
}
|
|
40
|
-
export function planGlobalFiles(options) {
|
|
41
|
-
const harnesses = selected(options);
|
|
42
|
-
const roots = extraRoots(options);
|
|
43
|
-
const home = homeDir(options);
|
|
44
|
-
const files = [];
|
|
45
|
-
if (wants(harnesses, 'claude')) {
|
|
46
|
-
files.push({
|
|
47
|
-
format: 'json',
|
|
48
|
-
patches: [
|
|
49
|
-
{ path: ['permissions', 'defaultMode'], value: CLAUDE_DEFAULT_MODE },
|
|
50
|
-
{ path: ['sandbox', 'enabled'], value: true },
|
|
51
|
-
{ path: ['sandbox', 'failIfUnavailable'], value: true },
|
|
52
|
-
{ merge: 'union', path: ['sandbox', 'filesystem', 'allowWrite'], value: roots },
|
|
53
|
-
{ path: ['useAutoModeDuringPlan'], value: true },
|
|
54
|
-
],
|
|
55
|
-
path: join(home, '.claude', 'settings.json'),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
if (wants(harnesses, 'codex'))
|
|
59
|
-
files.push({ ...codexPatches(roots), path: join(home, '.codex', 'config.toml') });
|
|
60
|
-
if (wants(harnesses, 'grok')) {
|
|
61
|
-
files.push({ ...grokSandboxPatches(roots), path: join(home, '.grok', 'sandbox.toml') });
|
|
62
|
-
files.push({
|
|
63
|
-
format: 'toml',
|
|
64
|
-
patches: [
|
|
65
|
-
{ key: 'permission_mode', table: 'ui', value: GROK_PERMISSION_MODE },
|
|
66
|
-
{ key: 'enabled', table: 'auto_mode', value: GROK_AUTO_MODE_ENABLED },
|
|
67
|
-
{ key: 'default_auto_mode', table: '', value: GROK_DEFAULT_AUTO_MODE },
|
|
68
|
-
],
|
|
69
|
-
path: join(home, '.grok', 'config.toml'),
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
if (wants(harnesses, 'cursor')) {
|
|
73
|
-
files.push({
|
|
74
|
-
format: 'json',
|
|
75
|
-
patches: [
|
|
76
|
-
{ path: ['approvalMode'], value: CURSOR_APPROVAL_MODE },
|
|
77
|
-
{ path: ['sandbox', 'mode'], value: CURSOR_SANDBOX_MODE },
|
|
78
|
-
],
|
|
79
|
-
path: join(home, '.cursor', 'cli-config.json'),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return files;
|
|
83
|
-
}
|
|
84
|
-
export function planRepoFiles(root, options) {
|
|
85
|
-
const harnesses = selected(options);
|
|
86
|
-
const roots = extraRoots(options);
|
|
87
|
-
const repo = resolve(root);
|
|
88
|
-
const files = [];
|
|
89
|
-
if (wants(harnesses, 'claude')) {
|
|
90
|
-
files.push({
|
|
91
|
-
format: 'json',
|
|
92
|
-
patches: [
|
|
93
|
-
{ path: ['sandbox', 'enabled'], value: true },
|
|
94
|
-
{ path: ['sandbox', 'failIfUnavailable'], value: true },
|
|
95
|
-
{ merge: 'union', path: ['sandbox', 'filesystem', 'allowWrite'], value: roots },
|
|
96
|
-
],
|
|
97
|
-
path: join(repo, '.claude', 'settings.json'),
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
if (wants(harnesses, 'codex'))
|
|
101
|
-
files.push({ ...codexPatches(roots), path: join(repo, '.codex', 'config.toml') });
|
|
102
|
-
if (wants(harnesses, 'grok'))
|
|
103
|
-
files.push({ ...grokSandboxPatches(roots), path: join(repo, '.grok', 'sandbox.toml') });
|
|
104
|
-
if (wants(harnesses, 'cursor')) {
|
|
105
|
-
files.push({
|
|
106
|
-
format: 'json',
|
|
107
|
-
patches: [
|
|
108
|
-
{ path: ['type'], value: 'workspace_readwrite' },
|
|
109
|
-
{ path: ['enableSharedBuildCache'], value: true },
|
|
110
|
-
{ path: ['networkPolicy', 'default'], value: 'allow' },
|
|
111
|
-
{ path: ['additionalReadwritePaths'], value: roots },
|
|
112
|
-
],
|
|
113
|
-
path: join(repo, '.cursor', 'sandbox.json'),
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
return files;
|
|
117
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { HarnessPolicyDump } from './types.mts';
|
|
2
|
-
export declare const DEFAULT_EXTRA_WRITABLE_ROOTS: readonly ['~/.cargo', '~/Library/pnpm', '~/.local/share/pnpm', '~/.pnpm-store', '~/Library/Caches/no-mistakes', '~/Library/Caches/pnpm', '~/.pnpm-state', '~/.cache/no-mistakes', '~/.cache/pnpm', '~/.local/state/pnpm', '/var/folders', '/private/var/folders'];
|
|
3
|
-
export declare const CLAUDE_DEFAULT_MODE = "auto";
|
|
4
|
-
export declare const CODEX_APPROVAL_POLICY = "on-request";
|
|
5
|
-
export declare const CODEX_APPROVALS_REVIEWER = "auto_review";
|
|
6
|
-
export declare const CODEX_SANDBOX_MODE = "workspace-write";
|
|
7
|
-
export declare const GROK_PERMISSION_MODE = "auto";
|
|
8
|
-
export declare const GROK_SANDBOX_PROFILE = "workspace-write";
|
|
9
|
-
export declare const GROK_AUTO_MODE_ENABLED = true;
|
|
10
|
-
export declare const GROK_DEFAULT_AUTO_MODE = true;
|
|
11
|
-
export declare const CURSOR_APPROVAL_MODE = "auto-review";
|
|
12
|
-
export declare const CURSOR_SANDBOX_MODE = "enabled";
|
|
13
|
-
export declare function dumpHarnessPolicy(extraWritableRoots?: readonly string[]): HarnessPolicyDump;
|
|
14
|
-
export declare function sameValue(left: unknown, right: unknown): boolean;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_EXTRA_WRITABLE_ROOTS = [
|
|
2
|
-
'~/.cargo',
|
|
3
|
-
'~/Library/pnpm',
|
|
4
|
-
'~/.local/share/pnpm',
|
|
5
|
-
'~/.pnpm-store',
|
|
6
|
-
'~/Library/Caches/no-mistakes',
|
|
7
|
-
'~/Library/Caches/pnpm',
|
|
8
|
-
'~/.pnpm-state',
|
|
9
|
-
'~/.cache/no-mistakes',
|
|
10
|
-
'~/.cache/pnpm',
|
|
11
|
-
'~/.local/state/pnpm',
|
|
12
|
-
'/var/folders',
|
|
13
|
-
'/private/var/folders',
|
|
14
|
-
];
|
|
15
|
-
export const CLAUDE_DEFAULT_MODE = 'auto';
|
|
16
|
-
export const CODEX_APPROVAL_POLICY = 'on-request';
|
|
17
|
-
export const CODEX_APPROVALS_REVIEWER = 'auto_review';
|
|
18
|
-
export const CODEX_SANDBOX_MODE = 'workspace-write';
|
|
19
|
-
export const GROK_PERMISSION_MODE = 'auto';
|
|
20
|
-
export const GROK_SANDBOX_PROFILE = 'workspace-write';
|
|
21
|
-
export const GROK_AUTO_MODE_ENABLED = true;
|
|
22
|
-
export const GROK_DEFAULT_AUTO_MODE = true;
|
|
23
|
-
export const CURSOR_APPROVAL_MODE = 'auto-review';
|
|
24
|
-
export const CURSOR_SANDBOX_MODE = 'enabled';
|
|
25
|
-
export function dumpHarnessPolicy(extraWritableRoots = DEFAULT_EXTRA_WRITABLE_ROOTS) {
|
|
26
|
-
return {
|
|
27
|
-
claude: {
|
|
28
|
-
defaultMode: CLAUDE_DEFAULT_MODE,
|
|
29
|
-
sandboxEnabled: true,
|
|
30
|
-
sandboxFailIfUnavailable: true,
|
|
31
|
-
useAutoModeDuringPlan: true,
|
|
32
|
-
},
|
|
33
|
-
codex: {
|
|
34
|
-
approval_policy: CODEX_APPROVAL_POLICY,
|
|
35
|
-
approvals_reviewer: CODEX_APPROVALS_REVIEWER,
|
|
36
|
-
sandbox_mode: CODEX_SANDBOX_MODE,
|
|
37
|
-
},
|
|
38
|
-
cursor: { approvalMode: CURSOR_APPROVAL_MODE, sandboxMode: CURSOR_SANDBOX_MODE },
|
|
39
|
-
extraWritableRoots: [...extraWritableRoots],
|
|
40
|
-
grok: {
|
|
41
|
-
auto_mode_enabled: GROK_AUTO_MODE_ENABLED,
|
|
42
|
-
default_auto_mode: GROK_DEFAULT_AUTO_MODE,
|
|
43
|
-
permission_mode: GROK_PERMISSION_MODE,
|
|
44
|
-
sandbox_profile_defined: GROK_SANDBOX_PROFILE,
|
|
45
|
-
sandbox_profile_requires_cli_selection: true,
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
export function sameValue(left, right) {
|
|
50
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
51
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import { readTomlKey } from './merge-toml.mjs';
|
|
5
|
-
import { CURSOR_APPROVAL_MODE } from './policy.mjs';
|
|
6
|
-
import { HARNESS_IDS } from './types.mjs';
|
|
7
|
-
async function readOptional(path) {
|
|
8
|
-
try {
|
|
9
|
-
return await readFile(path, 'utf8');
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
12
|
-
if (error.code === 'ENOENT')
|
|
13
|
-
return '';
|
|
14
|
-
throw error;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function selected(options, harness) {
|
|
18
|
-
return (options?.harnesses ?? HARNESS_IDS).includes(harness);
|
|
19
|
-
}
|
|
20
|
-
function homeDir(options) {
|
|
21
|
-
return resolve(options?.home ?? homedir());
|
|
22
|
-
}
|
|
23
|
-
async function codexTrust(root, options) {
|
|
24
|
-
const source = await readOptional(join(homeDir(options), '.codex', 'config.toml'));
|
|
25
|
-
const table = `projects.${JSON.stringify(resolve(root))}`;
|
|
26
|
-
return {
|
|
27
|
-
harness: 'codex',
|
|
28
|
-
key: 'trusted-project',
|
|
29
|
-
message: 'trust this project in Codex before relying on its .codex/config.toml',
|
|
30
|
-
satisfied: readTomlKey(source, table, 'trust_level') === 'trusted',
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
async function cursorApproval(options) {
|
|
34
|
-
const source = await readOptional(join(homeDir(options), '.cursor', 'cli-config.json'));
|
|
35
|
-
let satisfied = false;
|
|
36
|
-
try {
|
|
37
|
-
const parsed = JSON.parse(source);
|
|
38
|
-
satisfied = parsed.approvalMode === CURSOR_APPROVAL_MODE;
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// A missing or invalid user config leaves the prerequisite unsatisfied.
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
harness: 'cursor',
|
|
45
|
-
key: 'global-auto-review',
|
|
46
|
-
message: 'set approvalMode=auto-review in ~/.cursor/cli-config.json',
|
|
47
|
-
satisfied,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function grokSandbox() {
|
|
51
|
-
return {
|
|
52
|
-
harness: 'grok',
|
|
53
|
-
key: 'sandbox-profile-selection',
|
|
54
|
-
message: 'start Grok with --sandbox workspace-write to activate the defined profile',
|
|
55
|
-
satisfied: false,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
export async function checkHarnessPrerequisites(root, options) {
|
|
59
|
-
const prerequisites = [];
|
|
60
|
-
if (selected(options, 'grok'))
|
|
61
|
-
prerequisites.push(grokSandbox());
|
|
62
|
-
if (root !== undefined && selected(options, 'codex'))
|
|
63
|
-
prerequisites.push(await codexTrust(root, options));
|
|
64
|
-
if (root !== undefined && selected(options, 'cursor'))
|
|
65
|
-
prerequisites.push(await cursorApproval(options));
|
|
66
|
-
return prerequisites;
|
|
67
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
export function formatTomlValue(value) {
|
|
2
|
-
if (typeof value === 'boolean')
|
|
3
|
-
return value ? 'true' : 'false';
|
|
4
|
-
if (typeof value === 'string')
|
|
5
|
-
return JSON.stringify(value);
|
|
6
|
-
const items = value.map((entry) => ` ${JSON.stringify(entry)},`).join('\n');
|
|
7
|
-
return `[\n${items}\n]`;
|
|
8
|
-
}
|
|
9
|
-
function skipSpaceAndComments(text, start) {
|
|
10
|
-
let index = start;
|
|
11
|
-
while (index < text.length) {
|
|
12
|
-
const char = text[index];
|
|
13
|
-
if (char === ' ' || char === '\t' || char === '\n' || char === '\r' || char === ',') {
|
|
14
|
-
index += 1;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
if (char === '#') {
|
|
18
|
-
while (index < text.length && text[index] !== '\n')
|
|
19
|
-
index += 1;
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
return index;
|
|
25
|
-
}
|
|
26
|
-
function parseTomlString(text, start) {
|
|
27
|
-
const quote = text[start];
|
|
28
|
-
let index = start + 1;
|
|
29
|
-
let value = '';
|
|
30
|
-
while (index < text.length) {
|
|
31
|
-
const char = text[index];
|
|
32
|
-
if (char === quote)
|
|
33
|
-
return { end: index + 1, value };
|
|
34
|
-
if (quote === '"' && char === '\\') {
|
|
35
|
-
const next = text[index + 1];
|
|
36
|
-
if (next === undefined)
|
|
37
|
-
break;
|
|
38
|
-
value += next === 'n' ? '\n' : next === 't' ? '\t' : next;
|
|
39
|
-
index += 2;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
value += char;
|
|
43
|
-
index += 1;
|
|
44
|
-
}
|
|
45
|
-
throw new Error('unterminated TOML string');
|
|
46
|
-
}
|
|
47
|
-
export function parseTomlValue(text, start) {
|
|
48
|
-
const index = skipSpaceAndComments(text, start);
|
|
49
|
-
if (text.startsWith('true', index))
|
|
50
|
-
return { end: index + 4, value: true };
|
|
51
|
-
if (text.startsWith('false', index))
|
|
52
|
-
return { end: index + 5, value: false };
|
|
53
|
-
if (text[index] === '"' || text[index] === "'")
|
|
54
|
-
return parseTomlString(text, index);
|
|
55
|
-
if (text[index] !== '[')
|
|
56
|
-
throw new Error('unsupported TOML value');
|
|
57
|
-
const items = [];
|
|
58
|
-
let cursor = index + 1;
|
|
59
|
-
for (;;) {
|
|
60
|
-
cursor = skipSpaceAndComments(text, cursor);
|
|
61
|
-
if (text[cursor] === ']')
|
|
62
|
-
return { end: cursor + 1, value: items };
|
|
63
|
-
if (text[cursor] !== '"' && text[cursor] !== "'")
|
|
64
|
-
throw new Error('unsupported TOML array value');
|
|
65
|
-
const parsed = parseTomlString(text, cursor);
|
|
66
|
-
items.push(parsed.value);
|
|
67
|
-
cursor = parsed.end;
|
|
68
|
-
}
|
|
69
|
-
}
|