toolcraft 0.0.5 → 0.0.7
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 +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +77 -59
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +15 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +13 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/codex.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/codex.js +14 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/goose.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/goose.js +14 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/index.d.ts +7 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/index.js +7 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/kimi.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +15 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/opencode.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +14 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.js +13 -0
- package/node_modules/@poe-code/agent-defs/dist/index.d.ts +5 -0
- package/node_modules/@poe-code/agent-defs/dist/index.js +3 -0
- package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +3 -0
- package/node_modules/@poe-code/agent-defs/dist/registry.js +26 -0
- package/node_modules/@poe-code/agent-defs/dist/specifier.d.ts +7 -0
- package/node_modules/@poe-code/agent-defs/dist/specifier.js +27 -0
- package/node_modules/@poe-code/agent-defs/dist/types.d.ts +16 -0
- package/node_modules/@poe-code/agent-defs/dist/types.js +1 -0
- package/node_modules/@poe-code/agent-defs/package.json +20 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +5 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +552 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.d.ts +17 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +58 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.d.ts +7 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +46 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/index.d.ts +13 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/index.js +49 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/json.d.ts +31 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/json.js +140 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/toml.d.ts +2 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +72 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/yaml.d.ts +2 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +73 -0
- package/node_modules/@poe-code/config-mutations/dist/fs-utils.d.ts +18 -0
- package/node_modules/@poe-code/config-mutations/dist/fs-utils.js +45 -0
- package/node_modules/@poe-code/config-mutations/dist/index.d.ts +8 -0
- package/node_modules/@poe-code/config-mutations/dist/index.js +8 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.d.ts +47 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.js +34 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +52 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +46 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +40 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.js +32 -0
- package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +7 -0
- package/node_modules/@poe-code/config-mutations/dist/template/render.js +28 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.d.ts +7 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.js +21 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/index.d.ts +3 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/index.js +2 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.d.ts +25 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +170 -0
- package/node_modules/@poe-code/config-mutations/dist/types.d.ts +156 -0
- package/node_modules/@poe-code/config-mutations/dist/types.js +6 -0
- package/node_modules/@poe-code/config-mutations/package.json +33 -0
- package/node_modules/@poe-code/file-lock/README.md +52 -0
- package/node_modules/@poe-code/file-lock/dist/index.d.ts +1 -0
- package/node_modules/@poe-code/file-lock/dist/index.js +1 -0
- package/node_modules/@poe-code/file-lock/dist/lock.d.ts +27 -0
- package/node_modules/@poe-code/file-lock/dist/lock.js +203 -0
- package/node_modules/@poe-code/file-lock/package.json +23 -0
- package/node_modules/auth-store/README.md +47 -0
- package/node_modules/auth-store/dist/create-secret-store.d.ts +2 -0
- package/node_modules/auth-store/dist/create-secret-store.js +35 -0
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +39 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +156 -0
- package/node_modules/auth-store/dist/index.d.ts +7 -0
- package/node_modules/auth-store/dist/index.js +4 -0
- package/node_modules/auth-store/dist/keychain-store.d.ts +22 -0
- package/node_modules/auth-store/dist/keychain-store.js +111 -0
- package/node_modules/auth-store/dist/provider-store.d.ts +10 -0
- package/node_modules/auth-store/dist/provider-store.js +28 -0
- package/node_modules/auth-store/dist/types.d.ts +20 -0
- package/node_modules/auth-store/dist/types.js +1 -0
- package/node_modules/auth-store/package.json +25 -0
- package/node_modules/mcp-oauth/README.md +31 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +14 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +97 -0
- package/node_modules/mcp-oauth/dist/client/authorization-state.d.ts +8 -0
- package/node_modules/mcp-oauth/dist/client/authorization-state.js +34 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.d.ts +3 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +491 -0
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +20 -0
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +169 -0
- package/node_modules/mcp-oauth/dist/client/pkce.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/pkce.js +7 -0
- package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +40 -0
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +143 -0
- package/node_modules/mcp-oauth/dist/client/types.d.ts +113 -0
- package/node_modules/mcp-oauth/dist/client/types.js +1 -0
- package/node_modules/mcp-oauth/dist/index.d.ts +10 -0
- package/node_modules/mcp-oauth/dist/index.js +7 -0
- package/node_modules/mcp-oauth/dist/resource-indicator.d.ts +1 -0
- package/node_modules/mcp-oauth/dist/resource-indicator.js +11 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +27 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +259 -0
- package/node_modules/mcp-oauth/dist/types.compile-check.d.ts +1 -0
- package/node_modules/mcp-oauth/dist/types.compile-check.js +22 -0
- package/node_modules/mcp-oauth/package.json +31 -0
- package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +4 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/index.js +1 -0
- package/node_modules/tiny-mcp-client/dist/internal.d.ts +547 -0
- package/node_modules/tiny-mcp-client/dist/internal.js +2404 -0
- package/node_modules/tiny-mcp-client/dist/jsonrpc-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/jsonrpc-types.compile-check.js +37 -0
- package/node_modules/tiny-mcp-client/dist/mcp-lifecycle-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-lifecycle-types.compile-check.js +50 -0
- package/node_modules/tiny-mcp-client/dist/mcp-prompt-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-prompt-types.compile-check.js +50 -0
- package/node_modules/tiny-mcp-client/dist/mcp-resource-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-resource-types.compile-check.js +51 -0
- package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.js +89 -0
- package/node_modules/tiny-mcp-client/dist/mcp-transport-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-transport-types.compile-check.js +56 -0
- package/node_modules/tiny-mcp-client/dist/mcp-utility-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-utility-types.compile-check.js +145 -0
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +24 -0
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +385 -0
- package/node_modules/tiny-mcp-client/package.json +22 -0
- package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +823 -0
- package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +882 -0
- package/node_modules/tiny-mcp-client/src/index.ts +94 -0
- package/node_modules/tiny-mcp-client/src/internal.ts +3566 -0
- package/node_modules/tiny-mcp-client/src/jsonrpc-types.compile-check.ts +66 -0
- package/node_modules/tiny-mcp-client/src/mcp-client-http-transport.integration.test.ts +222 -0
- package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +1294 -0
- package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +143 -0
- package/node_modules/tiny-mcp-client/src/mcp-lifecycle-types.compile-check.ts +65 -0
- package/node_modules/tiny-mcp-client/src/mcp-prompt-types.compile-check.ts +66 -0
- package/node_modules/tiny-mcp-client/src/mcp-resource-types.compile-check.ts +70 -0
- package/node_modules/tiny-mcp-client/src/mcp-tool-types.compile-check.ts +117 -0
- package/node_modules/tiny-mcp-client/src/mcp-transport-types.compile-check.ts +75 -0
- package/node_modules/tiny-mcp-client/src/mcp-utility-types.compile-check.ts +181 -0
- package/node_modules/tiny-mcp-client/src/mock-servers.test.ts +980 -0
- package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +583 -0
- package/node_modules/tiny-mcp-client/src/transports.test.ts +8139 -0
- package/node_modules/tiny-mcp-client/src/utilities.test.ts +372 -0
- package/node_modules/tiny-mcp-client/tsconfig.json +11 -0
- package/package.json +24 -11
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import Mustache from "mustache";
|
|
2
|
+
import { getConfigFormat, detectFormat } from "../formats/index.js";
|
|
3
|
+
import { resolvePath } from "./path-utils.js";
|
|
4
|
+
import { isNotFound, readFileIfExists, pathExists, createTimestamp } from "../fs-utils.js";
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Helper Functions
|
|
7
|
+
// ============================================================================
|
|
8
|
+
function resolveValue(resolver, options) {
|
|
9
|
+
if (typeof resolver === "function") {
|
|
10
|
+
return resolver(options);
|
|
11
|
+
}
|
|
12
|
+
return resolver;
|
|
13
|
+
}
|
|
14
|
+
function createInvalidDocumentBackupPath(targetPath) {
|
|
15
|
+
const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
|
|
16
|
+
return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
|
|
17
|
+
}
|
|
18
|
+
async function backupInvalidDocument(fs, targetPath, content) {
|
|
19
|
+
const backupPath = createInvalidDocumentBackupPath(targetPath);
|
|
20
|
+
await fs.writeFile(backupPath, content, { encoding: "utf8" });
|
|
21
|
+
}
|
|
22
|
+
function describeMutation(kind, targetPath) {
|
|
23
|
+
const displayPath = targetPath ?? "target";
|
|
24
|
+
switch (kind) {
|
|
25
|
+
case "ensureDirectory":
|
|
26
|
+
return `Create ${displayPath}`;
|
|
27
|
+
case "removeDirectory":
|
|
28
|
+
return `Remove directory ${displayPath}`;
|
|
29
|
+
case "backup":
|
|
30
|
+
return `Backup ${displayPath}`;
|
|
31
|
+
case "templateWrite":
|
|
32
|
+
return `Write ${displayPath}`;
|
|
33
|
+
case "chmod":
|
|
34
|
+
return `Set permissions on ${displayPath}`;
|
|
35
|
+
case "removeFile":
|
|
36
|
+
return `Remove ${displayPath}`;
|
|
37
|
+
case "configMerge":
|
|
38
|
+
case "configPrune":
|
|
39
|
+
case "configTransform":
|
|
40
|
+
case "templateMergeToml":
|
|
41
|
+
case "templateMergeJson":
|
|
42
|
+
return `Update ${displayPath}`;
|
|
43
|
+
default:
|
|
44
|
+
return "Operation";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function pruneKeysByPrefix(table, prefix) {
|
|
48
|
+
const result = {};
|
|
49
|
+
for (const [key, value] of Object.entries(table)) {
|
|
50
|
+
if (!key.startsWith(prefix)) {
|
|
51
|
+
result[key] = value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
function isConfigObject(value) {
|
|
57
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
58
|
+
}
|
|
59
|
+
function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
|
|
60
|
+
const result = { ...base };
|
|
61
|
+
const prefixMap = pruneByPrefix ?? {};
|
|
62
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
63
|
+
const current = result[key];
|
|
64
|
+
const prefix = prefixMap[key];
|
|
65
|
+
if (isConfigObject(current) && isConfigObject(value)) {
|
|
66
|
+
if (prefix) {
|
|
67
|
+
const pruned = pruneKeysByPrefix(current, prefix);
|
|
68
|
+
result[key] = { ...pruned, ...value };
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
result[key] = mergeWithPruneByPrefix(current, value, prefixMap);
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
result[key] = value;
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Apply Mutation
|
|
81
|
+
// ============================================================================
|
|
82
|
+
export async function applyMutation(mutation, context, options) {
|
|
83
|
+
switch (mutation.kind) {
|
|
84
|
+
case "ensureDirectory":
|
|
85
|
+
return applyEnsureDirectory(mutation, context, options);
|
|
86
|
+
case "removeDirectory":
|
|
87
|
+
return applyRemoveDirectory(mutation, context, options);
|
|
88
|
+
case "removeFile":
|
|
89
|
+
return applyRemoveFile(mutation, context, options);
|
|
90
|
+
case "chmod":
|
|
91
|
+
return applyChmod(mutation, context, options);
|
|
92
|
+
case "backup":
|
|
93
|
+
return applyBackup(mutation, context, options);
|
|
94
|
+
case "configMerge":
|
|
95
|
+
return applyConfigMerge(mutation, context, options);
|
|
96
|
+
case "configPrune":
|
|
97
|
+
return applyConfigPrune(mutation, context, options);
|
|
98
|
+
case "configTransform":
|
|
99
|
+
return applyConfigTransform(mutation, context, options);
|
|
100
|
+
case "templateWrite":
|
|
101
|
+
return applyTemplateWrite(mutation, context, options);
|
|
102
|
+
case "templateMergeToml":
|
|
103
|
+
return applyTemplateMerge(mutation, context, options, "toml");
|
|
104
|
+
case "templateMergeJson":
|
|
105
|
+
return applyTemplateMerge(mutation, context, options, "json");
|
|
106
|
+
default: {
|
|
107
|
+
const never = mutation;
|
|
108
|
+
throw new Error(`Unknown mutation kind: ${never.kind}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// File Mutation Handlers
|
|
114
|
+
// ============================================================================
|
|
115
|
+
async function applyEnsureDirectory(mutation, context, options) {
|
|
116
|
+
const rawPath = resolveValue(mutation.path, options);
|
|
117
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
118
|
+
const details = {
|
|
119
|
+
kind: mutation.kind,
|
|
120
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
121
|
+
targetPath
|
|
122
|
+
};
|
|
123
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
124
|
+
if (!context.dryRun) {
|
|
125
|
+
await context.fs.mkdir(targetPath, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
outcome: {
|
|
129
|
+
changed: !existed,
|
|
130
|
+
effect: "mkdir",
|
|
131
|
+
detail: existed ? "noop" : "create"
|
|
132
|
+
},
|
|
133
|
+
details
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async function applyRemoveDirectory(mutation, context, options) {
|
|
137
|
+
const rawPath = resolveValue(mutation.path, options);
|
|
138
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
139
|
+
const details = {
|
|
140
|
+
kind: mutation.kind,
|
|
141
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
142
|
+
targetPath
|
|
143
|
+
};
|
|
144
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
145
|
+
if (!existed) {
|
|
146
|
+
return {
|
|
147
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
148
|
+
details
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (typeof context.fs.rm !== "function") {
|
|
152
|
+
return {
|
|
153
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
154
|
+
details
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (mutation.force) {
|
|
158
|
+
if (!context.dryRun) {
|
|
159
|
+
await context.fs.rm(targetPath, { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
163
|
+
details
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const entries = await context.fs.readdir(targetPath);
|
|
167
|
+
if (entries.length > 0) {
|
|
168
|
+
return {
|
|
169
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
170
|
+
details
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (!context.dryRun) {
|
|
174
|
+
await context.fs.rm(targetPath, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
178
|
+
details
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async function applyRemoveFile(mutation, context, options) {
|
|
182
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
183
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
184
|
+
const details = {
|
|
185
|
+
kind: mutation.kind,
|
|
186
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
187
|
+
targetPath
|
|
188
|
+
};
|
|
189
|
+
try {
|
|
190
|
+
const content = await context.fs.readFile(targetPath, "utf8");
|
|
191
|
+
const trimmed = content.trim();
|
|
192
|
+
// Check whenContentMatches guard
|
|
193
|
+
if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
|
|
194
|
+
return {
|
|
195
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
196
|
+
details
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Check whenEmpty guard
|
|
200
|
+
if (mutation.whenEmpty && trimmed.length > 0) {
|
|
201
|
+
return {
|
|
202
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
203
|
+
details
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (!context.dryRun) {
|
|
207
|
+
await context.fs.unlink(targetPath);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
211
|
+
details
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (isNotFound(error)) {
|
|
216
|
+
return {
|
|
217
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
218
|
+
details
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function applyChmod(mutation, context, options) {
|
|
225
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
226
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
227
|
+
const details = {
|
|
228
|
+
kind: mutation.kind,
|
|
229
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
230
|
+
targetPath
|
|
231
|
+
};
|
|
232
|
+
if (typeof context.fs.chmod !== "function") {
|
|
233
|
+
return {
|
|
234
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
235
|
+
details
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const stat = await context.fs.stat(targetPath);
|
|
240
|
+
const currentMode = typeof stat.mode === "number" ? stat.mode & 0o777 : null;
|
|
241
|
+
if (currentMode === mutation.mode) {
|
|
242
|
+
return {
|
|
243
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
244
|
+
details
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (!context.dryRun) {
|
|
248
|
+
await context.fs.chmod(targetPath, mutation.mode);
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
outcome: { changed: true, effect: "chmod", detail: "update" },
|
|
252
|
+
details
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
if (isNotFound(error)) {
|
|
257
|
+
return {
|
|
258
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
259
|
+
details
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function applyBackup(mutation, context, options) {
|
|
266
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
267
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
268
|
+
const details = {
|
|
269
|
+
kind: mutation.kind,
|
|
270
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
271
|
+
targetPath
|
|
272
|
+
};
|
|
273
|
+
const content = await readFileIfExists(context.fs, targetPath);
|
|
274
|
+
if (content === null) {
|
|
275
|
+
return {
|
|
276
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
277
|
+
details
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (!context.dryRun) {
|
|
281
|
+
const backupPath = `${targetPath}.backup-${createTimestamp()}`;
|
|
282
|
+
await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
outcome: { changed: true, effect: "copy", detail: "backup" },
|
|
286
|
+
details
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
// ============================================================================
|
|
290
|
+
// Config Mutation Handlers
|
|
291
|
+
// ============================================================================
|
|
292
|
+
async function applyConfigMerge(mutation, context, options) {
|
|
293
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
294
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
295
|
+
const details = {
|
|
296
|
+
kind: mutation.kind,
|
|
297
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
298
|
+
targetPath
|
|
299
|
+
};
|
|
300
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
301
|
+
if (!formatName) {
|
|
302
|
+
throw new Error(`Cannot detect config format for "${rawPath}". Provide explicit format option.`);
|
|
303
|
+
}
|
|
304
|
+
const format = getConfigFormat(formatName);
|
|
305
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
306
|
+
let current;
|
|
307
|
+
try {
|
|
308
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// Invalid file - backup and start fresh
|
|
312
|
+
if (rawContent !== null) {
|
|
313
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
314
|
+
}
|
|
315
|
+
current = {};
|
|
316
|
+
}
|
|
317
|
+
const value = resolveValue(mutation.value, options);
|
|
318
|
+
// Use mergeWithPruneByPrefix for TOML files with pruneByPrefix option
|
|
319
|
+
let merged;
|
|
320
|
+
if (mutation.pruneByPrefix) {
|
|
321
|
+
merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
merged = format.merge(current, value);
|
|
325
|
+
}
|
|
326
|
+
const serialized = format.serialize(merged);
|
|
327
|
+
const changed = serialized !== rawContent;
|
|
328
|
+
if (changed && !context.dryRun) {
|
|
329
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
outcome: {
|
|
333
|
+
changed,
|
|
334
|
+
effect: changed ? "write" : "none",
|
|
335
|
+
detail: changed ? (rawContent === null ? "create" : "update") : "noop"
|
|
336
|
+
},
|
|
337
|
+
details
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async function applyConfigPrune(mutation, context, options) {
|
|
341
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
342
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
343
|
+
const details = {
|
|
344
|
+
kind: mutation.kind,
|
|
345
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
346
|
+
targetPath
|
|
347
|
+
};
|
|
348
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
349
|
+
if (rawContent === null) {
|
|
350
|
+
return {
|
|
351
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
352
|
+
details
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
356
|
+
if (!formatName) {
|
|
357
|
+
throw new Error(`Cannot detect config format for "${rawPath}". Provide explicit format option.`);
|
|
358
|
+
}
|
|
359
|
+
const format = getConfigFormat(formatName);
|
|
360
|
+
let current;
|
|
361
|
+
try {
|
|
362
|
+
current = format.parse(rawContent);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// Invalid file - can't prune, leave as-is
|
|
366
|
+
return {
|
|
367
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
368
|
+
details
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
// Check onlyIf guard
|
|
372
|
+
if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
|
|
373
|
+
return {
|
|
374
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
375
|
+
details
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const shape = resolveValue(mutation.shape, options);
|
|
379
|
+
const { changed, result } = format.prune(current, shape);
|
|
380
|
+
if (!changed) {
|
|
381
|
+
return {
|
|
382
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
383
|
+
details
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
// Delete file if empty
|
|
387
|
+
if (Object.keys(result).length === 0) {
|
|
388
|
+
if (!context.dryRun) {
|
|
389
|
+
await context.fs.unlink(targetPath);
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
393
|
+
details
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const serialized = format.serialize(result);
|
|
397
|
+
if (!context.dryRun) {
|
|
398
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
outcome: { changed: true, effect: "write", detail: "update" },
|
|
402
|
+
details
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
async function applyConfigTransform(mutation, context, options) {
|
|
406
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
407
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
408
|
+
const details = {
|
|
409
|
+
kind: mutation.kind,
|
|
410
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
411
|
+
targetPath
|
|
412
|
+
};
|
|
413
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
414
|
+
if (!formatName) {
|
|
415
|
+
throw new Error(`Cannot detect config format for "${rawPath}". Provide explicit format option.`);
|
|
416
|
+
}
|
|
417
|
+
const format = getConfigFormat(formatName);
|
|
418
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
419
|
+
let current;
|
|
420
|
+
try {
|
|
421
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
if (rawContent !== null) {
|
|
425
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
426
|
+
}
|
|
427
|
+
current = {};
|
|
428
|
+
}
|
|
429
|
+
const { content: transformed, changed } = mutation.transform(current, options);
|
|
430
|
+
if (!changed) {
|
|
431
|
+
return {
|
|
432
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
433
|
+
details
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
// Delete file if null
|
|
437
|
+
if (transformed === null) {
|
|
438
|
+
if (rawContent === null) {
|
|
439
|
+
return {
|
|
440
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
441
|
+
details
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (!context.dryRun) {
|
|
445
|
+
await context.fs.unlink(targetPath);
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
449
|
+
details
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
const serialized = format.serialize(transformed);
|
|
453
|
+
if (!context.dryRun) {
|
|
454
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
outcome: {
|
|
458
|
+
changed: true,
|
|
459
|
+
effect: "write",
|
|
460
|
+
detail: rawContent === null ? "create" : "update"
|
|
461
|
+
},
|
|
462
|
+
details
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
// ============================================================================
|
|
466
|
+
// Template Mutation Handlers
|
|
467
|
+
// ============================================================================
|
|
468
|
+
async function applyTemplateWrite(mutation, context, options) {
|
|
469
|
+
if (!context.templates) {
|
|
470
|
+
throw new Error("Template mutations require a templates loader. " +
|
|
471
|
+
"Provide templates function to runMutations context.");
|
|
472
|
+
}
|
|
473
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
474
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
475
|
+
const details = {
|
|
476
|
+
kind: mutation.kind,
|
|
477
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
478
|
+
targetPath
|
|
479
|
+
};
|
|
480
|
+
const template = await context.templates(mutation.templateId);
|
|
481
|
+
const templateContext = mutation.context
|
|
482
|
+
? resolveValue(mutation.context, options)
|
|
483
|
+
: {};
|
|
484
|
+
const rendered = Mustache.render(template, templateContext);
|
|
485
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
486
|
+
if (!context.dryRun) {
|
|
487
|
+
await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
outcome: {
|
|
491
|
+
changed: true,
|
|
492
|
+
effect: "write",
|
|
493
|
+
detail: existed ? "update" : "create"
|
|
494
|
+
},
|
|
495
|
+
details
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
async function applyTemplateMerge(mutation, context, options, formatName) {
|
|
499
|
+
if (!context.templates) {
|
|
500
|
+
throw new Error("Template mutations require a templates loader. " +
|
|
501
|
+
"Provide templates function to runMutations context.");
|
|
502
|
+
}
|
|
503
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
504
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
505
|
+
const details = {
|
|
506
|
+
kind: mutation.kind,
|
|
507
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
508
|
+
targetPath
|
|
509
|
+
};
|
|
510
|
+
const format = getConfigFormat(formatName);
|
|
511
|
+
// Load and render template
|
|
512
|
+
const template = await context.templates(mutation.templateId);
|
|
513
|
+
const templateContext = mutation.context
|
|
514
|
+
? resolveValue(mutation.context, options)
|
|
515
|
+
: {};
|
|
516
|
+
const rendered = Mustache.render(template, templateContext);
|
|
517
|
+
// Parse rendered template
|
|
518
|
+
let templateDoc;
|
|
519
|
+
try {
|
|
520
|
+
templateDoc = format.parse(rendered);
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
throw new Error(`Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error}`, { cause: error });
|
|
524
|
+
}
|
|
525
|
+
// Read and parse existing file
|
|
526
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
527
|
+
let current;
|
|
528
|
+
try {
|
|
529
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
if (rawContent !== null) {
|
|
533
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
534
|
+
}
|
|
535
|
+
current = {};
|
|
536
|
+
}
|
|
537
|
+
// Merge
|
|
538
|
+
const merged = format.merge(current, templateDoc);
|
|
539
|
+
const serialized = format.serialize(merged);
|
|
540
|
+
const changed = serialized !== rawContent;
|
|
541
|
+
if (changed && !context.dryRun) {
|
|
542
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
outcome: {
|
|
546
|
+
changed,
|
|
547
|
+
effect: changed ? "write" : "none",
|
|
548
|
+
detail: changed ? (rawContent === null ? "create" : "update") : "noop"
|
|
549
|
+
},
|
|
550
|
+
details
|
|
551
|
+
};
|
|
552
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PathMapper } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Expand ~ shortcut to the provided home directory.
|
|
4
|
+
*/
|
|
5
|
+
export declare function expandHome(targetPath: string, homeDir: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Validate that a path is home-relative (starts with ~).
|
|
8
|
+
* Throws if the path is not home-relative.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateHomePath(targetPath: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a path with optional path mapping for isolated configurations.
|
|
13
|
+
* 1. Validates the path starts with ~
|
|
14
|
+
* 2. Expands ~ to home directory
|
|
15
|
+
* 3. If pathMapper is provided, maps the directory portion and reconstructs the path
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolvePath(rawPath: string, homeDir: string, pathMapper?: PathMapper): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Expand ~ shortcut to the provided home directory.
|
|
4
|
+
*/
|
|
5
|
+
export function expandHome(targetPath, homeDir) {
|
|
6
|
+
if (!targetPath?.startsWith("~")) {
|
|
7
|
+
return targetPath;
|
|
8
|
+
}
|
|
9
|
+
// Handle ~./ -> ~/.
|
|
10
|
+
if (targetPath.startsWith("~./")) {
|
|
11
|
+
targetPath = `~/.${targetPath.slice(3)}`;
|
|
12
|
+
}
|
|
13
|
+
let remainder = targetPath.slice(1);
|
|
14
|
+
// Remove leading slash or backslash
|
|
15
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) {
|
|
16
|
+
remainder = remainder.slice(1);
|
|
17
|
+
}
|
|
18
|
+
else if (remainder.startsWith(".")) {
|
|
19
|
+
// Handle ~/.
|
|
20
|
+
remainder = remainder.slice(1);
|
|
21
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) {
|
|
22
|
+
remainder = remainder.slice(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return remainder.length === 0 ? homeDir : path.join(homeDir, remainder);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validate that a path is home-relative (starts with ~).
|
|
29
|
+
* Throws if the path is not home-relative.
|
|
30
|
+
*/
|
|
31
|
+
export function validateHomePath(targetPath) {
|
|
32
|
+
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
33
|
+
throw new Error("Target path must be a non-empty string.");
|
|
34
|
+
}
|
|
35
|
+
if (!targetPath.startsWith("~")) {
|
|
36
|
+
throw new Error(`All target paths must be home-relative (start with ~). Received: "${targetPath}"`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a path with optional path mapping for isolated configurations.
|
|
41
|
+
* 1. Validates the path starts with ~
|
|
42
|
+
* 2. Expands ~ to home directory
|
|
43
|
+
* 3. If pathMapper is provided, maps the directory portion and reconstructs the path
|
|
44
|
+
*/
|
|
45
|
+
export function resolvePath(rawPath, homeDir, pathMapper) {
|
|
46
|
+
validateHomePath(rawPath);
|
|
47
|
+
const expanded = expandHome(rawPath, homeDir);
|
|
48
|
+
if (!pathMapper) {
|
|
49
|
+
return expanded;
|
|
50
|
+
}
|
|
51
|
+
// Map the directory portion
|
|
52
|
+
const rawDirectory = path.dirname(expanded);
|
|
53
|
+
const mappedDirectory = pathMapper.mapTargetDirectory({
|
|
54
|
+
targetDirectory: rawDirectory
|
|
55
|
+
});
|
|
56
|
+
const filename = path.basename(expanded);
|
|
57
|
+
return filename.length === 0 ? mappedDirectory : path.join(mappedDirectory, filename);
|
|
58
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Mutation, MutationContext, MutationResult, MutationOptions } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Execute an array of mutations in order.
|
|
4
|
+
*
|
|
5
|
+
* All dependencies must be injected - no defaults, no globals.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runMutations(mutations: Mutation[], context: MutationContext, options?: MutationOptions): Promise<MutationResult>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { applyMutation } from "./apply-mutation.js";
|
|
2
|
+
/**
|
|
3
|
+
* Execute an array of mutations in order.
|
|
4
|
+
*
|
|
5
|
+
* All dependencies must be injected - no defaults, no globals.
|
|
6
|
+
*/
|
|
7
|
+
export async function runMutations(mutations, context, options) {
|
|
8
|
+
const effects = [];
|
|
9
|
+
let anyChanged = false;
|
|
10
|
+
const resolverOptions = options ?? {};
|
|
11
|
+
for (const mutation of mutations) {
|
|
12
|
+
const { outcome } = await executeMutation(mutation, context, resolverOptions);
|
|
13
|
+
effects.push(outcome);
|
|
14
|
+
if (outcome.changed) {
|
|
15
|
+
anyChanged = true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
changed: anyChanged,
|
|
20
|
+
effects
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function executeMutation(mutation, context, options) {
|
|
24
|
+
// Call onStart observer
|
|
25
|
+
context.observers?.onStart?.({
|
|
26
|
+
kind: mutation.kind,
|
|
27
|
+
label: mutation.label ?? mutation.kind,
|
|
28
|
+
targetPath: undefined // Will be resolved during apply
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
const { outcome, details } = await applyMutation(mutation, context, options);
|
|
32
|
+
// Call onComplete observer
|
|
33
|
+
context.observers?.onComplete?.(details, outcome);
|
|
34
|
+
return { outcome, details };
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// Call onError observer
|
|
38
|
+
context.observers?.onError?.({
|
|
39
|
+
kind: mutation.kind,
|
|
40
|
+
label: mutation.label ?? mutation.kind,
|
|
41
|
+
targetPath: undefined
|
|
42
|
+
}, error);
|
|
43
|
+
// Re-throw the error
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ConfigFormat } from "../types.js";
|
|
2
|
+
export type FormatName = "json" | "toml" | "yaml";
|
|
3
|
+
/**
|
|
4
|
+
* Get a format handler by path (auto-detect from extension) or explicit format name.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getConfigFormat(pathOrFormat: string): ConfigFormat;
|
|
7
|
+
/**
|
|
8
|
+
* Detect format name from a file path.
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectFormat(path: string): FormatName | undefined;
|
|
11
|
+
export { jsonFormat } from "./json.js";
|
|
12
|
+
export { tomlFormat } from "./toml.js";
|
|
13
|
+
export { yamlFormat } from "./yaml.js";
|