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,49 @@
|
|
|
1
|
+
import { jsonFormat } from "./json.js";
|
|
2
|
+
import { tomlFormat } from "./toml.js";
|
|
3
|
+
import { yamlFormat } from "./yaml.js";
|
|
4
|
+
const formatRegistry = {
|
|
5
|
+
json: jsonFormat,
|
|
6
|
+
toml: tomlFormat,
|
|
7
|
+
yaml: yamlFormat
|
|
8
|
+
};
|
|
9
|
+
const extensionMap = {
|
|
10
|
+
".json": "json",
|
|
11
|
+
".toml": "toml",
|
|
12
|
+
".yaml": "yaml",
|
|
13
|
+
".yml": "yaml"
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Get a format handler by path (auto-detect from extension) or explicit format name.
|
|
17
|
+
*/
|
|
18
|
+
export function getConfigFormat(pathOrFormat) {
|
|
19
|
+
// Check if it's an explicit format name
|
|
20
|
+
if (pathOrFormat in formatRegistry) {
|
|
21
|
+
return formatRegistry[pathOrFormat];
|
|
22
|
+
}
|
|
23
|
+
// Try to detect from extension
|
|
24
|
+
const ext = getExtension(pathOrFormat);
|
|
25
|
+
const formatName = extensionMap[ext];
|
|
26
|
+
if (!formatName) {
|
|
27
|
+
throw new Error(`Unsupported config format. Cannot detect format from "${pathOrFormat}". ` +
|
|
28
|
+
`Supported extensions: ${Object.keys(extensionMap).join(", ")}. ` +
|
|
29
|
+
`Supported format names: ${Object.keys(formatRegistry).join(", ")}.`);
|
|
30
|
+
}
|
|
31
|
+
return formatRegistry[formatName];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detect format name from a file path.
|
|
35
|
+
*/
|
|
36
|
+
export function detectFormat(path) {
|
|
37
|
+
const ext = getExtension(path);
|
|
38
|
+
return extensionMap[ext];
|
|
39
|
+
}
|
|
40
|
+
function getExtension(path) {
|
|
41
|
+
const lastDot = path.lastIndexOf(".");
|
|
42
|
+
if (lastDot === -1) {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
return path.slice(lastDot).toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
export { jsonFormat } from "./json.js";
|
|
48
|
+
export { tomlFormat } from "./toml.js";
|
|
49
|
+
export { yamlFormat } from "./yaml.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ConfigFormat, ConfigObject, ConfigValue } from "../types.js";
|
|
2
|
+
declare function detectIndent(content: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Modify JSON content at a specific path while preserving comments and formatting.
|
|
5
|
+
* Uses jsonc-parser's modify() for targeted updates.
|
|
6
|
+
*
|
|
7
|
+
* @param content - The original JSON content (may include comments)
|
|
8
|
+
* @param path - JSON path array, e.g. ["mcpServers", "my-server"]
|
|
9
|
+
* @param value - The value to set (or undefined to remove)
|
|
10
|
+
* @returns The modified JSON content with comments preserved
|
|
11
|
+
*/
|
|
12
|
+
declare function modifyAtPath(content: string, path: (string | number)[], value: ConfigValue | undefined): string;
|
|
13
|
+
/**
|
|
14
|
+
* Merge a patch into JSON content while preserving comments and formatting.
|
|
15
|
+
* Uses jsonc.modify() for each top-level key to preserve existing comments.
|
|
16
|
+
*
|
|
17
|
+
* @param content - The original JSON content (may include comments)
|
|
18
|
+
* @param patch - Object with values to merge
|
|
19
|
+
* @returns The modified JSON content with comments preserved
|
|
20
|
+
*/
|
|
21
|
+
declare function mergePreservingComments(content: string, patch: ConfigObject): string;
|
|
22
|
+
/**
|
|
23
|
+
* Remove a key from JSON content while preserving comments and formatting.
|
|
24
|
+
*
|
|
25
|
+
* @param content - The original JSON content
|
|
26
|
+
* @param path - JSON path array to the key to remove
|
|
27
|
+
* @returns The modified JSON content with comments preserved
|
|
28
|
+
*/
|
|
29
|
+
declare function removeAtPath(content: string, path: (string | number)[]): string;
|
|
30
|
+
export { detectIndent, modifyAtPath, mergePreservingComments, removeAtPath };
|
|
31
|
+
export declare const jsonFormat: ConfigFormat;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as jsonc from "jsonc-parser";
|
|
2
|
+
function isConfigObject(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function detectIndent(content) {
|
|
6
|
+
const match = content.match(/^[\t ]+/m);
|
|
7
|
+
if (match) {
|
|
8
|
+
return match[0];
|
|
9
|
+
}
|
|
10
|
+
return " ";
|
|
11
|
+
}
|
|
12
|
+
function parse(content) {
|
|
13
|
+
if (!content || content.trim() === "") {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
const errors = [];
|
|
17
|
+
const parsed = jsonc.parse(content, errors, {
|
|
18
|
+
allowTrailingComma: true,
|
|
19
|
+
disallowComments: false
|
|
20
|
+
});
|
|
21
|
+
if (errors.length > 0) {
|
|
22
|
+
throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
|
|
23
|
+
}
|
|
24
|
+
if (parsed === null || parsed === undefined) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
if (!isConfigObject(parsed)) {
|
|
28
|
+
throw new Error("Expected JSON object.");
|
|
29
|
+
}
|
|
30
|
+
return parsed;
|
|
31
|
+
}
|
|
32
|
+
function serialize(obj) {
|
|
33
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
34
|
+
}
|
|
35
|
+
function merge(base, patch) {
|
|
36
|
+
const result = { ...base };
|
|
37
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
38
|
+
if (value === undefined) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const existing = result[key];
|
|
42
|
+
if (isConfigObject(existing) && isConfigObject(value)) {
|
|
43
|
+
result[key] = merge(existing, value);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
result[key] = value;
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function prune(obj, shape) {
|
|
51
|
+
let changed = false;
|
|
52
|
+
const result = { ...obj };
|
|
53
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
54
|
+
if (!(key in result)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const current = result[key];
|
|
58
|
+
// Empty object pattern means "delete this key entirely"
|
|
59
|
+
if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
|
|
60
|
+
delete result[key];
|
|
61
|
+
changed = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Non-empty object pattern with object current: recurse
|
|
65
|
+
if (isConfigObject(pattern) && isConfigObject(current)) {
|
|
66
|
+
const { changed: childChanged, result: childResult } = prune(current, pattern);
|
|
67
|
+
if (childChanged) {
|
|
68
|
+
changed = true;
|
|
69
|
+
}
|
|
70
|
+
if (Object.keys(childResult).length === 0) {
|
|
71
|
+
delete result[key];
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
result[key] = childResult;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
delete result[key];
|
|
79
|
+
changed = true;
|
|
80
|
+
}
|
|
81
|
+
return { changed, result };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Modify JSON content at a specific path while preserving comments and formatting.
|
|
85
|
+
* Uses jsonc-parser's modify() for targeted updates.
|
|
86
|
+
*
|
|
87
|
+
* @param content - The original JSON content (may include comments)
|
|
88
|
+
* @param path - JSON path array, e.g. ["mcpServers", "my-server"]
|
|
89
|
+
* @param value - The value to set (or undefined to remove)
|
|
90
|
+
* @returns The modified JSON content with comments preserved
|
|
91
|
+
*/
|
|
92
|
+
function modifyAtPath(content, path, value) {
|
|
93
|
+
const indent = detectIndent(content);
|
|
94
|
+
const formattingOptions = {
|
|
95
|
+
tabSize: indent === "\t" ? 1 : indent.length,
|
|
96
|
+
insertSpaces: indent !== "\t",
|
|
97
|
+
eol: "\n"
|
|
98
|
+
};
|
|
99
|
+
const edits = jsonc.modify(content, path, value, { formattingOptions });
|
|
100
|
+
let result = jsonc.applyEdits(content, edits);
|
|
101
|
+
if (!result.endsWith("\n")) {
|
|
102
|
+
result += "\n";
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Merge a patch into JSON content while preserving comments and formatting.
|
|
108
|
+
* Uses jsonc.modify() for each top-level key to preserve existing comments.
|
|
109
|
+
*
|
|
110
|
+
* @param content - The original JSON content (may include comments)
|
|
111
|
+
* @param patch - Object with values to merge
|
|
112
|
+
* @returns The modified JSON content with comments preserved
|
|
113
|
+
*/
|
|
114
|
+
function mergePreservingComments(content, patch) {
|
|
115
|
+
let result = content || "{}";
|
|
116
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
117
|
+
if (value === undefined) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
result = modifyAtPath(result, [key], value);
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Remove a key from JSON content while preserving comments and formatting.
|
|
126
|
+
*
|
|
127
|
+
* @param content - The original JSON content
|
|
128
|
+
* @param path - JSON path array to the key to remove
|
|
129
|
+
* @returns The modified JSON content with comments preserved
|
|
130
|
+
*/
|
|
131
|
+
function removeAtPath(content, path) {
|
|
132
|
+
return modifyAtPath(content, path, undefined);
|
|
133
|
+
}
|
|
134
|
+
export { detectIndent, modifyAtPath, mergePreservingComments, removeAtPath };
|
|
135
|
+
export const jsonFormat = {
|
|
136
|
+
parse,
|
|
137
|
+
serialize,
|
|
138
|
+
merge,
|
|
139
|
+
prune
|
|
140
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
2
|
+
function isConfigObject(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function parse(content) {
|
|
6
|
+
if (!content || content.trim() === "") {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
const parsed = parseToml(content);
|
|
10
|
+
if (!isConfigObject(parsed)) {
|
|
11
|
+
throw new Error("Expected TOML document to be a table.");
|
|
12
|
+
}
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
function serialize(obj) {
|
|
16
|
+
const serialized = stringifyToml(obj);
|
|
17
|
+
return serialized.endsWith("\n") ? serialized : `${serialized}\n`;
|
|
18
|
+
}
|
|
19
|
+
function merge(base, patch) {
|
|
20
|
+
const result = { ...base };
|
|
21
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const existing = result[key];
|
|
26
|
+
if (isConfigObject(existing) && isConfigObject(value)) {
|
|
27
|
+
result[key] = merge(existing, value);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
result[key] = value;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function prune(obj, shape) {
|
|
35
|
+
let changed = false;
|
|
36
|
+
const result = { ...obj };
|
|
37
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
38
|
+
if (!(key in result)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const current = result[key];
|
|
42
|
+
// Empty object pattern means "delete this key entirely"
|
|
43
|
+
if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
|
|
44
|
+
delete result[key];
|
|
45
|
+
changed = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// Non-empty object pattern with object current: recurse
|
|
49
|
+
if (isConfigObject(pattern) && isConfigObject(current)) {
|
|
50
|
+
const { changed: childChanged, result: childResult } = prune(current, pattern);
|
|
51
|
+
if (childChanged) {
|
|
52
|
+
changed = true;
|
|
53
|
+
}
|
|
54
|
+
if (Object.keys(childResult).length === 0) {
|
|
55
|
+
delete result[key];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
result[key] = childResult;
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
delete result[key];
|
|
63
|
+
changed = true;
|
|
64
|
+
}
|
|
65
|
+
return { changed, result };
|
|
66
|
+
}
|
|
67
|
+
export const tomlFormat = {
|
|
68
|
+
parse,
|
|
69
|
+
serialize,
|
|
70
|
+
merge,
|
|
71
|
+
prune
|
|
72
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2
|
+
function isConfigObject(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function parse(content) {
|
|
6
|
+
if (!content || content.trim() === "") {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
const parsed = parseYaml(content);
|
|
10
|
+
if (parsed === null || parsed === undefined) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
if (!isConfigObject(parsed)) {
|
|
14
|
+
throw new Error("Expected YAML object.");
|
|
15
|
+
}
|
|
16
|
+
return parsed;
|
|
17
|
+
}
|
|
18
|
+
function serialize(obj) {
|
|
19
|
+
const serialized = stringifyYaml(obj);
|
|
20
|
+
return serialized.endsWith("\n") ? serialized : `${serialized}\n`;
|
|
21
|
+
}
|
|
22
|
+
function merge(base, patch) {
|
|
23
|
+
const result = { ...base };
|
|
24
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
25
|
+
if (value === undefined) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const existing = result[key];
|
|
29
|
+
if (isConfigObject(existing) && isConfigObject(value)) {
|
|
30
|
+
result[key] = merge(existing, value);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
result[key] = value;
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
function prune(obj, shape) {
|
|
38
|
+
let changed = false;
|
|
39
|
+
const result = { ...obj };
|
|
40
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
41
|
+
if (!(key in result)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const current = result[key];
|
|
45
|
+
if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
|
|
46
|
+
delete result[key];
|
|
47
|
+
changed = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (isConfigObject(pattern) && isConfigObject(current)) {
|
|
51
|
+
const { changed: childChanged, result: childResult } = prune(current, pattern);
|
|
52
|
+
if (childChanged) {
|
|
53
|
+
changed = true;
|
|
54
|
+
}
|
|
55
|
+
if (Object.keys(childResult).length === 0) {
|
|
56
|
+
delete result[key];
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
result[key] = childResult;
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
delete result[key];
|
|
64
|
+
changed = true;
|
|
65
|
+
}
|
|
66
|
+
return { changed, result };
|
|
67
|
+
}
|
|
68
|
+
export const yamlFormat = {
|
|
69
|
+
parse,
|
|
70
|
+
serialize,
|
|
71
|
+
merge,
|
|
72
|
+
prune
|
|
73
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FileSystem } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check if an error is a "file not found" (ENOENT) error.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isNotFound(error: unknown): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Read a file if it exists, returning null if not found.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readFileIfExists(fs: FileSystem, target: string): Promise<string | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Check if a path exists (file or directory).
|
|
12
|
+
*/
|
|
13
|
+
export declare function pathExists(fs: FileSystem, target: string): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Create an ISO timestamp safe for use in filenames.
|
|
16
|
+
* Replaces colons and dots with dashes.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createTimestamp(): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if an error is a "file not found" (ENOENT) error.
|
|
3
|
+
*/
|
|
4
|
+
export function isNotFound(error) {
|
|
5
|
+
return (typeof error === "object" &&
|
|
6
|
+
error !== null &&
|
|
7
|
+
"code" in error &&
|
|
8
|
+
error.code === "ENOENT");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Read a file if it exists, returning null if not found.
|
|
12
|
+
*/
|
|
13
|
+
export async function readFileIfExists(fs, target) {
|
|
14
|
+
try {
|
|
15
|
+
return await fs.readFile(target, "utf8");
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (isNotFound(error)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if a path exists (file or directory).
|
|
26
|
+
*/
|
|
27
|
+
export async function pathExists(fs, target) {
|
|
28
|
+
try {
|
|
29
|
+
await fs.stat(target);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (isNotFound(error)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create an ISO timestamp safe for use in filenames.
|
|
41
|
+
* Replaces colons and dots with dashes.
|
|
42
|
+
*/
|
|
43
|
+
export function createTimestamp() {
|
|
44
|
+
return new Date().toISOString().replaceAll(":", "-").replaceAll(".", "-");
|
|
45
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { configMutation } from "./mutations/config-mutation.js";
|
|
2
|
+
export { fileMutation } from "./mutations/file-mutation.js";
|
|
3
|
+
export { templateMutation } from "./mutations/template-mutation.js";
|
|
4
|
+
export { runMutations } from "./execution/run-mutations.js";
|
|
5
|
+
export { renderTemplate, type TemplateVariables } from "./template/render.js";
|
|
6
|
+
export { isNotFound, readFileIfExists, pathExists, createTimestamp } from "./fs-utils.js";
|
|
7
|
+
export type { ConfigObject, ConfigValue, Mutation, MutationContext, MutationResult, MutationObservers, MutationDetails, MutationOutcome, FileSystem, TemplateLoader, ConfigFormat, PathMapper } from "./types.js";
|
|
8
|
+
export { isConfigObject } from "./types.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Main exports
|
|
2
|
+
export { configMutation } from "./mutations/config-mutation.js";
|
|
3
|
+
export { fileMutation } from "./mutations/file-mutation.js";
|
|
4
|
+
export { templateMutation } from "./mutations/template-mutation.js";
|
|
5
|
+
export { runMutations } from "./execution/run-mutations.js";
|
|
6
|
+
export { renderTemplate } from "./template/render.js";
|
|
7
|
+
export { isNotFound, readFileIfExists, pathExists, createTimestamp } from "./fs-utils.js";
|
|
8
|
+
export { isConfigObject } from "./types.js";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ConfigMergeMutation, ConfigPruneMutation, ConfigTransformMutation, ConfigObject, ValueResolver, MutationOptions } from "../types.js";
|
|
2
|
+
export interface MergeOptions {
|
|
3
|
+
/** Target file path (must start with ~) */
|
|
4
|
+
target: ValueResolver<string>;
|
|
5
|
+
/** Value to merge into the config file */
|
|
6
|
+
value: ValueResolver<ConfigObject>;
|
|
7
|
+
/** Optional explicit format override */
|
|
8
|
+
format?: "json" | "toml" | "yaml";
|
|
9
|
+
/** Optional prune by prefix before merging (TOML) */
|
|
10
|
+
pruneByPrefix?: Record<string, string>;
|
|
11
|
+
/** Optional human-readable label for logging */
|
|
12
|
+
label?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PruneOptions {
|
|
15
|
+
/** Target file path (must start with ~) */
|
|
16
|
+
target: ValueResolver<string>;
|
|
17
|
+
/** Shape to prune from the config file */
|
|
18
|
+
shape: ValueResolver<ConfigObject>;
|
|
19
|
+
/** Optional explicit format override */
|
|
20
|
+
format?: "json" | "toml" | "yaml";
|
|
21
|
+
/** Optional guard - only prune if predicate returns true */
|
|
22
|
+
onlyIf?: (doc: ConfigObject, ctx: MutationOptions) => boolean;
|
|
23
|
+
/** Optional human-readable label for logging */
|
|
24
|
+
label?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface TransformOptions {
|
|
27
|
+
/** Target file path (must start with ~) */
|
|
28
|
+
target: ValueResolver<string>;
|
|
29
|
+
/** Optional explicit format override */
|
|
30
|
+
format?: "json" | "toml" | "yaml";
|
|
31
|
+
/** Transform function - receives parsed content, returns transformed content */
|
|
32
|
+
transform: (content: ConfigObject, ctx: MutationOptions) => {
|
|
33
|
+
content: ConfigObject | null;
|
|
34
|
+
changed: boolean;
|
|
35
|
+
};
|
|
36
|
+
/** Optional human-readable label for logging */
|
|
37
|
+
label?: string;
|
|
38
|
+
}
|
|
39
|
+
declare function merge(options: MergeOptions): ConfigMergeMutation;
|
|
40
|
+
declare function prune(options: PruneOptions): ConfigPruneMutation;
|
|
41
|
+
declare function transform(options: TransformOptions): ConfigTransformMutation;
|
|
42
|
+
export declare const configMutation: {
|
|
43
|
+
merge: typeof merge;
|
|
44
|
+
prune: typeof prune;
|
|
45
|
+
transform: typeof transform;
|
|
46
|
+
};
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function merge(options) {
|
|
2
|
+
return {
|
|
3
|
+
kind: "configMerge",
|
|
4
|
+
target: options.target,
|
|
5
|
+
value: options.value,
|
|
6
|
+
format: options.format,
|
|
7
|
+
pruneByPrefix: options.pruneByPrefix,
|
|
8
|
+
label: options.label
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function prune(options) {
|
|
12
|
+
return {
|
|
13
|
+
kind: "configPrune",
|
|
14
|
+
target: options.target,
|
|
15
|
+
shape: options.shape,
|
|
16
|
+
format: options.format,
|
|
17
|
+
onlyIf: options.onlyIf,
|
|
18
|
+
label: options.label
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function transform(options) {
|
|
22
|
+
return {
|
|
23
|
+
kind: "configTransform",
|
|
24
|
+
target: options.target,
|
|
25
|
+
format: options.format,
|
|
26
|
+
transform: options.transform,
|
|
27
|
+
label: options.label
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export const configMutation = {
|
|
31
|
+
merge,
|
|
32
|
+
prune,
|
|
33
|
+
transform
|
|
34
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { EnsureDirectoryMutation, RemoveDirectoryMutation, RemoveFileMutation, ChmodMutation, BackupMutation, ValueResolver } from "../types.js";
|
|
2
|
+
export interface EnsureDirectoryOptions {
|
|
3
|
+
/** Directory path (must start with ~) */
|
|
4
|
+
path: ValueResolver<string>;
|
|
5
|
+
/** Optional human-readable label for logging */
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RemoveOptions {
|
|
9
|
+
/** Target file path (must start with ~) */
|
|
10
|
+
target: ValueResolver<string>;
|
|
11
|
+
/** Only remove if file is empty/whitespace */
|
|
12
|
+
whenEmpty?: boolean;
|
|
13
|
+
/** Only remove if content matches regex */
|
|
14
|
+
whenContentMatches?: RegExp;
|
|
15
|
+
/** Optional human-readable label for logging */
|
|
16
|
+
label?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RemoveDirectoryOptions {
|
|
19
|
+
/** Directory path (must start with ~) */
|
|
20
|
+
path: ValueResolver<string>;
|
|
21
|
+
/** Remove directory even when not empty */
|
|
22
|
+
force?: boolean;
|
|
23
|
+
/** Optional human-readable label for logging */
|
|
24
|
+
label?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ChmodOptions {
|
|
27
|
+
/** Target file path (must start with ~) */
|
|
28
|
+
target: ValueResolver<string>;
|
|
29
|
+
/** File permission mode (e.g., 0o755) */
|
|
30
|
+
mode: number;
|
|
31
|
+
/** Optional human-readable label for logging */
|
|
32
|
+
label?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface BackupOptions {
|
|
35
|
+
/** Target file path to backup (must start with ~) */
|
|
36
|
+
target: ValueResolver<string>;
|
|
37
|
+
/** Optional human-readable label for logging */
|
|
38
|
+
label?: string;
|
|
39
|
+
}
|
|
40
|
+
declare function ensureDirectory(options: EnsureDirectoryOptions): EnsureDirectoryMutation;
|
|
41
|
+
declare function remove(options: RemoveOptions): RemoveFileMutation;
|
|
42
|
+
declare function removeDirectory(options: RemoveDirectoryOptions): RemoveDirectoryMutation;
|
|
43
|
+
declare function chmod(options: ChmodOptions): ChmodMutation;
|
|
44
|
+
declare function backup(options: BackupOptions): BackupMutation;
|
|
45
|
+
export declare const fileMutation: {
|
|
46
|
+
ensureDirectory: typeof ensureDirectory;
|
|
47
|
+
remove: typeof remove;
|
|
48
|
+
removeDirectory: typeof removeDirectory;
|
|
49
|
+
chmod: typeof chmod;
|
|
50
|
+
backup: typeof backup;
|
|
51
|
+
};
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function ensureDirectory(options) {
|
|
2
|
+
return {
|
|
3
|
+
kind: "ensureDirectory",
|
|
4
|
+
path: options.path,
|
|
5
|
+
label: options.label
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function remove(options) {
|
|
9
|
+
return {
|
|
10
|
+
kind: "removeFile",
|
|
11
|
+
target: options.target,
|
|
12
|
+
whenEmpty: options.whenEmpty,
|
|
13
|
+
whenContentMatches: options.whenContentMatches,
|
|
14
|
+
label: options.label
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function removeDirectory(options) {
|
|
18
|
+
return {
|
|
19
|
+
kind: "removeDirectory",
|
|
20
|
+
path: options.path,
|
|
21
|
+
force: options.force,
|
|
22
|
+
label: options.label
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function chmod(options) {
|
|
26
|
+
return {
|
|
27
|
+
kind: "chmod",
|
|
28
|
+
target: options.target,
|
|
29
|
+
mode: options.mode,
|
|
30
|
+
label: options.label
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function backup(options) {
|
|
34
|
+
return {
|
|
35
|
+
kind: "backup",
|
|
36
|
+
target: options.target,
|
|
37
|
+
label: options.label
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export const fileMutation = {
|
|
41
|
+
ensureDirectory,
|
|
42
|
+
remove,
|
|
43
|
+
removeDirectory,
|
|
44
|
+
chmod,
|
|
45
|
+
backup
|
|
46
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { TemplateWriteMutation, TemplateMergeTomlMutation, TemplateMergeJsonMutation, ConfigObject, ValueResolver } from "../types.js";
|
|
2
|
+
export interface WriteOptions {
|
|
3
|
+
/** Target file path (must start with ~) */
|
|
4
|
+
target: ValueResolver<string>;
|
|
5
|
+
/** Template ID to load via template loader */
|
|
6
|
+
templateId: string;
|
|
7
|
+
/** Context to pass to Mustache.render() */
|
|
8
|
+
context?: ValueResolver<ConfigObject>;
|
|
9
|
+
/** Optional human-readable label for logging */
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface MergeTomlOptions {
|
|
13
|
+
/** Target TOML file path (must start with ~) */
|
|
14
|
+
target: ValueResolver<string>;
|
|
15
|
+
/** Template ID to load via template loader */
|
|
16
|
+
templateId: string;
|
|
17
|
+
/** Context to pass to Mustache.render() */
|
|
18
|
+
context?: ValueResolver<ConfigObject>;
|
|
19
|
+
/** Optional human-readable label for logging */
|
|
20
|
+
label?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface MergeJsonOptions {
|
|
23
|
+
/** Target JSON file path (must start with ~) */
|
|
24
|
+
target: ValueResolver<string>;
|
|
25
|
+
/** Template ID to load via template loader */
|
|
26
|
+
templateId: string;
|
|
27
|
+
/** Context to pass to Mustache.render() */
|
|
28
|
+
context?: ValueResolver<ConfigObject>;
|
|
29
|
+
/** Optional human-readable label for logging */
|
|
30
|
+
label?: string;
|
|
31
|
+
}
|
|
32
|
+
declare function write(options: WriteOptions): TemplateWriteMutation;
|
|
33
|
+
declare function mergeToml(options: MergeTomlOptions): TemplateMergeTomlMutation;
|
|
34
|
+
declare function mergeJson(options: MergeJsonOptions): TemplateMergeJsonMutation;
|
|
35
|
+
export declare const templateMutation: {
|
|
36
|
+
write: typeof write;
|
|
37
|
+
mergeToml: typeof mergeToml;
|
|
38
|
+
mergeJson: typeof mergeJson;
|
|
39
|
+
};
|
|
40
|
+
export {};
|