@ebay-dt/bonfire-context 0.1.0-alpha.4
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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/discover.d.ts +9 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +103 -0
- package/dist/discover.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +33 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +488 -0
- package/dist/install.js.map +1 -0
- package/dist/internal/filesystem.d.ts +5 -0
- package/dist/internal/filesystem.d.ts.map +1 -0
- package/dist/internal/filesystem.js +82 -0
- package/dist/internal/filesystem.js.map +1 -0
- package/dist/mcp.d.ts +26 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +178 -0
- package/dist/mcp.js.map +1 -0
- package/dist/migration.d.ts +23 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +206 -0
- package/dist/migration.js.map +1 -0
- package/dist/paths.d.ts +58 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +191 -0
- package/dist/paths.js.map +1 -0
- package/dist/prototype-project.d.ts +7 -0
- package/dist/prototype-project.d.ts.map +1 -0
- package/dist/prototype-project.js +38 -0
- package/dist/prototype-project.js.map +1 -0
- package/dist/registry.d.ts +36 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +189 -0
- package/dist/registry.js.map +1 -0
- package/dist/render.d.ts +15 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +64 -0
- package/dist/render.js.map +1 -0
- package/dist/root-project.d.ts +11 -0
- package/dist/root-project.d.ts.map +1 -0
- package/dist/root-project.js +152 -0
- package/dist/root-project.js.map +1 -0
- package/dist/skills.d.ts +13 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +170 -0
- package/dist/skills.js.map +1 -0
- package/dist/sync.d.ts +23 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +232 -0
- package/dist/sync.js.map +1 -0
- package/dist/template.d.ts +18 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +114 -0
- package/dist/template.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
package/dist/sync.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
import { discoverPrototypeDirectoryPaths, discoverPrototypePaths, } from "./discover.js";
|
|
4
|
+
import { listSkillFiles, reconcileSkills } from "./root-project.js";
|
|
5
|
+
import { atomicWrite, assertWritableTarget, removeManagedFile, } from "./internal/filesystem.js";
|
|
6
|
+
import { renderPrototypeMarkdown, renderRootMarkdown } from "./render.js";
|
|
7
|
+
import { assertSafeContextPaths, assertSafeGeneratedPath, assertSafePrototypeContextPaths, resolveContextPaths, resolvePrototypeContextPaths, } from "./paths.js";
|
|
8
|
+
import { renderSkillFiles } from "./skills.js";
|
|
9
|
+
import { renderOptionalMcp, renderOptionalSettings } from "./template.js";
|
|
10
|
+
export class ContextSyncError extends Error {
|
|
11
|
+
path;
|
|
12
|
+
constructor(path, message) {
|
|
13
|
+
super(`Unable to check context at ${path}: ${message}`);
|
|
14
|
+
this.name = "ContextSyncError";
|
|
15
|
+
this.path = path;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function isNotFound(error) {
|
|
19
|
+
return (error !== null &&
|
|
20
|
+
typeof error === "object" &&
|
|
21
|
+
"code" in error &&
|
|
22
|
+
error.code === "ENOENT");
|
|
23
|
+
}
|
|
24
|
+
function relativePath(repositoryRoot, path) {
|
|
25
|
+
return relative(repositoryRoot, path).split(sep).join("/");
|
|
26
|
+
}
|
|
27
|
+
function addExpected(expected, path, content, mode) {
|
|
28
|
+
expected.set(path, { content, mode });
|
|
29
|
+
}
|
|
30
|
+
async function addExistingManagedPath(managed, path, label) {
|
|
31
|
+
assertSafeGeneratedPath(path, label);
|
|
32
|
+
try {
|
|
33
|
+
const stats = await lstat(path);
|
|
34
|
+
if (stats.isSymbolicLink()) {
|
|
35
|
+
throw new ContextSyncError(label, "must not be a symbolic link");
|
|
36
|
+
}
|
|
37
|
+
managed.add(path);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error instanceof ContextSyncError)
|
|
41
|
+
throw error;
|
|
42
|
+
if (!isNotFound(error)) {
|
|
43
|
+
throw new ContextSyncError(label, "could not be inspected safely");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function buildProjectionPlan(options) {
|
|
48
|
+
const paths = resolveContextPaths(options);
|
|
49
|
+
assertSafeContextPaths(paths);
|
|
50
|
+
const [rootMarkdown, rootSettings, rootMcp, rootSkills] = await Promise.all([
|
|
51
|
+
renderRootMarkdown(paths),
|
|
52
|
+
renderOptionalSettings(paths),
|
|
53
|
+
renderOptionalMcp(paths),
|
|
54
|
+
renderSkillFiles(paths),
|
|
55
|
+
]);
|
|
56
|
+
const prototypeDirectories = await discoverPrototypeDirectoryPaths(options);
|
|
57
|
+
const prototypePaths = await discoverPrototypePaths(options);
|
|
58
|
+
const prototypePathSet = new Set(prototypePaths);
|
|
59
|
+
const expected = new Map();
|
|
60
|
+
const rootFiles = new Map();
|
|
61
|
+
addExpected(rootFiles, paths.rootOutputs.agents, rootMarkdown.agents);
|
|
62
|
+
addExpected(rootFiles, paths.rootOutputs.claude, rootMarkdown.claude);
|
|
63
|
+
if (rootSettings !== undefined) {
|
|
64
|
+
addExpected(rootFiles, paths.rootOutputs.settings, rootSettings);
|
|
65
|
+
}
|
|
66
|
+
if (rootMcp !== undefined) {
|
|
67
|
+
addExpected(rootFiles, paths.rootOutputs.mcp, rootMcp);
|
|
68
|
+
}
|
|
69
|
+
for (const [path, file] of rootFiles)
|
|
70
|
+
expected.set(path, file);
|
|
71
|
+
for (const skill of rootSkills) {
|
|
72
|
+
addExpected(expected, join(paths.rootOutputs.skills, skill.relativePath), skill.content, skill.mode);
|
|
73
|
+
}
|
|
74
|
+
const prototypes = [];
|
|
75
|
+
for (const prototypePath of prototypePaths) {
|
|
76
|
+
const prototypeContextPaths = resolvePrototypeContextPaths({
|
|
77
|
+
...options,
|
|
78
|
+
prototypePath,
|
|
79
|
+
});
|
|
80
|
+
assertSafePrototypeContextPaths(prototypeContextPaths);
|
|
81
|
+
const [markdown, settings] = await Promise.all([
|
|
82
|
+
renderPrototypeMarkdown(prototypeContextPaths),
|
|
83
|
+
renderOptionalSettings(prototypeContextPaths),
|
|
84
|
+
]);
|
|
85
|
+
const files = new Map();
|
|
86
|
+
addExpected(files, prototypeContextPaths.prototypeOutputs.agents, markdown.agents);
|
|
87
|
+
addExpected(files, prototypeContextPaths.prototypeOutputs.claude, markdown.claude);
|
|
88
|
+
if (settings !== undefined) {
|
|
89
|
+
addExpected(files, prototypeContextPaths.prototypeOutputs.settings, settings);
|
|
90
|
+
}
|
|
91
|
+
for (const [path, file] of files)
|
|
92
|
+
expected.set(path, file);
|
|
93
|
+
prototypes.push({ paths: prototypeContextPaths, files });
|
|
94
|
+
}
|
|
95
|
+
const managed = new Set([
|
|
96
|
+
paths.rootOutputs.agents,
|
|
97
|
+
paths.rootOutputs.claude,
|
|
98
|
+
paths.rootOutputs.mcp,
|
|
99
|
+
paths.rootOutputs.settings,
|
|
100
|
+
]);
|
|
101
|
+
for (const path of await listSkillFiles(paths.rootOutputs.skills)) {
|
|
102
|
+
managed.add(join(paths.rootOutputs.skills, path));
|
|
103
|
+
}
|
|
104
|
+
for (const prototypePath of prototypeDirectories) {
|
|
105
|
+
const prototypeContextPaths = resolvePrototypeContextPaths({
|
|
106
|
+
...options,
|
|
107
|
+
prototypePath,
|
|
108
|
+
});
|
|
109
|
+
if (prototypePathSet.has(prototypePath)) {
|
|
110
|
+
for (const path of Object.values(prototypeContextPaths.prototypeOutputs)) {
|
|
111
|
+
managed.add(path);
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const [label, path] of Object.entries(prototypeContextPaths.prototypeOutputs)) {
|
|
116
|
+
await addExistingManagedPath(managed, path, `prototype/${relativePath(paths.repositoryRoot, path)}:${label}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
paths,
|
|
121
|
+
rootFiles,
|
|
122
|
+
rootSkills,
|
|
123
|
+
prototypes,
|
|
124
|
+
expected,
|
|
125
|
+
managed,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async function compareFile(path, expected, repositoryRoot) {
|
|
129
|
+
const displayPath = relativePath(repositoryRoot, path);
|
|
130
|
+
let stats;
|
|
131
|
+
try {
|
|
132
|
+
stats = await lstat(path);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (isNotFound(error))
|
|
136
|
+
return { kind: "missing", path: displayPath };
|
|
137
|
+
throw new ContextSyncError(displayPath, "could not be inspected safely");
|
|
138
|
+
}
|
|
139
|
+
if (stats.isSymbolicLink()) {
|
|
140
|
+
throw new ContextSyncError(displayPath, "must not be a symbolic link");
|
|
141
|
+
}
|
|
142
|
+
if (!stats.isFile())
|
|
143
|
+
return { kind: "stale", path: displayPath };
|
|
144
|
+
let actual;
|
|
145
|
+
try {
|
|
146
|
+
actual = await readFile(path);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new ContextSyncError(displayPath, "could not be read safely");
|
|
150
|
+
}
|
|
151
|
+
const expectedBytes = typeof expected.content === "string"
|
|
152
|
+
? Buffer.from(expected.content)
|
|
153
|
+
: Buffer.from(expected.content);
|
|
154
|
+
return actual.equals(expectedBytes)
|
|
155
|
+
? undefined
|
|
156
|
+
: { kind: "stale", path: displayPath };
|
|
157
|
+
}
|
|
158
|
+
async function comparePlan(plan) {
|
|
159
|
+
const issues = [];
|
|
160
|
+
for (const [path, expected] of [...plan.expected.entries()].sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))) {
|
|
161
|
+
const issue = await compareFile(path, expected, plan.paths.repositoryRoot);
|
|
162
|
+
if (issue !== undefined)
|
|
163
|
+
issues.push(issue);
|
|
164
|
+
}
|
|
165
|
+
for (const path of [...plan.managed].sort()) {
|
|
166
|
+
if (plan.expected.has(path))
|
|
167
|
+
continue;
|
|
168
|
+
const displayPath = relativePath(plan.paths.repositoryRoot, path);
|
|
169
|
+
let stats;
|
|
170
|
+
try {
|
|
171
|
+
stats = await lstat(path);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (isNotFound(error))
|
|
175
|
+
continue;
|
|
176
|
+
throw new ContextSyncError(displayPath, "could not be inspected safely");
|
|
177
|
+
}
|
|
178
|
+
if (stats.isSymbolicLink()) {
|
|
179
|
+
throw new ContextSyncError(displayPath, "must not be a symbolic link");
|
|
180
|
+
}
|
|
181
|
+
if (!stats.isFile()) {
|
|
182
|
+
throw new ContextSyncError(displayPath, "must be a regular file");
|
|
183
|
+
}
|
|
184
|
+
issues.push({ kind: "unexpected", path: displayPath });
|
|
185
|
+
}
|
|
186
|
+
issues.sort((left, right) => {
|
|
187
|
+
if (left.path !== right.path)
|
|
188
|
+
return left.path < right.path ? -1 : 1;
|
|
189
|
+
return left.kind < right.kind ? -1 : left.kind > right.kind ? 1 : 0;
|
|
190
|
+
});
|
|
191
|
+
return { ok: issues.length === 0, issues };
|
|
192
|
+
}
|
|
193
|
+
async function preflightWrites(plan, issues) {
|
|
194
|
+
const issuePaths = new Set(issues
|
|
195
|
+
.filter((issue) => issue.kind === "missing" || issue.kind === "stale")
|
|
196
|
+
.map((issue) => join(plan.paths.repositoryRoot, issue.path)));
|
|
197
|
+
await Promise.all([...issuePaths].map((path) => assertWritableTarget(path, relativePath(plan.paths.repositoryRoot, path))));
|
|
198
|
+
return issuePaths;
|
|
199
|
+
}
|
|
200
|
+
async function writeFiles(files, writePaths, repositoryRoot, written) {
|
|
201
|
+
for (const [path, file] of [...files.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
|
|
202
|
+
if (!writePaths.has(path))
|
|
203
|
+
continue;
|
|
204
|
+
await atomicWrite(path, file.content, relativePath(repositoryRoot, path), file.mode);
|
|
205
|
+
written.push(relativePath(repositoryRoot, path));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
export async function checkContext(options) {
|
|
209
|
+
return comparePlan(await buildProjectionPlan(options));
|
|
210
|
+
}
|
|
211
|
+
export async function syncContext(options) {
|
|
212
|
+
const plan = await buildProjectionPlan(options);
|
|
213
|
+
const before = await comparePlan(plan);
|
|
214
|
+
const writePaths = await preflightWrites(plan, before.issues);
|
|
215
|
+
const written = [];
|
|
216
|
+
const removed = [];
|
|
217
|
+
await writeFiles(plan.rootFiles, writePaths, plan.paths.repositoryRoot, written);
|
|
218
|
+
await reconcileSkills(plan.paths.rootOutputs.skills, plan.rootSkills, plan.paths.repositoryRoot, written, removed, writePaths);
|
|
219
|
+
for (const prototype of plan.prototypes) {
|
|
220
|
+
await writeFiles(prototype.files, writePaths, plan.paths.repositoryRoot, written);
|
|
221
|
+
}
|
|
222
|
+
for (const issue of before.issues) {
|
|
223
|
+
if (issue.kind !== "unexpected")
|
|
224
|
+
continue;
|
|
225
|
+
const path = join(plan.paths.repositoryRoot, issue.path);
|
|
226
|
+
if (await removeManagedFile(path, issue.path))
|
|
227
|
+
removed.push(issue.path);
|
|
228
|
+
}
|
|
229
|
+
const after = await checkContext(options);
|
|
230
|
+
return { ...after, written, removed };
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=sync.js.map
|
package/dist/sync.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, readFile } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\nimport {\n discoverPrototypeDirectoryPaths,\n discoverPrototypePaths,\n} from \"./discover.js\";\nimport { listSkillFiles, reconcileSkills } from \"./root-project.js\";\nimport {\n atomicWrite,\n assertWritableTarget,\n removeManagedFile,\n} from \"./internal/filesystem.js\";\nimport { renderPrototypeMarkdown, renderRootMarkdown } from \"./render.js\";\nimport {\n assertSafeContextPaths,\n assertSafeGeneratedPath,\n assertSafePrototypeContextPaths,\n resolveContextPaths,\n resolvePrototypeContextPaths,\n type ContextPathOptions,\n type ResolvedContextPaths,\n type ResolvedPrototypeContextPaths,\n} from \"./paths.js\";\nimport { renderSkillFiles, type RenderedSkillFile } from \"./skills.js\";\nimport { renderOptionalMcp, renderOptionalSettings } from \"./template.js\";\n\nexport type SyncContextOptions = ContextPathOptions;\nexport type CheckContextOptions = ContextPathOptions;\n\nexport type ContextIssueKind = \"missing\" | \"stale\" | \"unexpected\";\n\nexport type ContextCheckIssue = Readonly<{\n kind: ContextIssueKind;\n path: string;\n}>;\n\nexport type ContextCheckResult = Readonly<{\n ok: boolean;\n issues: readonly ContextCheckIssue[];\n}>;\n\nexport type ContextSyncResult = Readonly<\n ContextCheckResult & {\n written: readonly string[];\n removed: readonly string[];\n }\n>;\n\nexport class ContextSyncError extends Error {\n readonly path: string;\n\n constructor(path: string, message: string) {\n super(`Unable to check context at ${path}: ${message}`);\n this.name = \"ContextSyncError\";\n this.path = path;\n }\n}\n\ntype RenderedFile = Readonly<{\n content: string | Uint8Array;\n mode?: number;\n}>;\n\ntype PrototypeProjectionPlan = Readonly<{\n paths: ResolvedPrototypeContextPaths;\n files: ReadonlyMap<string, RenderedFile>;\n}>;\n\ntype ProjectionPlan = Readonly<{\n paths: ResolvedContextPaths;\n rootFiles: ReadonlyMap<string, RenderedFile>;\n rootSkills: readonly RenderedSkillFile[];\n prototypes: readonly PrototypeProjectionPlan[];\n expected: ReadonlyMap<string, RenderedFile>;\n managed: ReadonlySet<string>;\n}>;\n\nfunction isNotFound(error: unknown) {\n return (\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n );\n}\n\nfunction relativePath(repositoryRoot: string, path: string) {\n return relative(repositoryRoot, path).split(sep).join(\"/\");\n}\n\nfunction addExpected(\n expected: Map<string, RenderedFile>,\n path: string,\n content: string | Uint8Array,\n mode?: number,\n) {\n expected.set(path, { content, mode });\n}\n\nasync function addExistingManagedPath(\n managed: Set<string>,\n path: string,\n label: string,\n) {\n assertSafeGeneratedPath(path, label);\n try {\n const stats = await lstat(path);\n if (stats.isSymbolicLink()) {\n throw new ContextSyncError(label, \"must not be a symbolic link\");\n }\n managed.add(path);\n } catch (error) {\n if (error instanceof ContextSyncError) throw error;\n if (!isNotFound(error)) {\n throw new ContextSyncError(label, \"could not be inspected safely\");\n }\n }\n}\n\nasync function buildProjectionPlan(\n options: SyncContextOptions,\n): Promise<ProjectionPlan> {\n const paths = resolveContextPaths(options);\n assertSafeContextPaths(paths);\n\n const [rootMarkdown, rootSettings, rootMcp, rootSkills] = await Promise.all([\n renderRootMarkdown(paths),\n renderOptionalSettings(paths),\n renderOptionalMcp(paths),\n renderSkillFiles(paths),\n ]);\n const prototypeDirectories = await discoverPrototypeDirectoryPaths(options);\n const prototypePaths = await discoverPrototypePaths(options);\n const prototypePathSet = new Set(prototypePaths);\n\n const expected = new Map<string, RenderedFile>();\n const rootFiles = new Map<string, RenderedFile>();\n addExpected(rootFiles, paths.rootOutputs.agents, rootMarkdown.agents);\n addExpected(rootFiles, paths.rootOutputs.claude, rootMarkdown.claude);\n if (rootSettings !== undefined) {\n addExpected(rootFiles, paths.rootOutputs.settings, rootSettings);\n }\n if (rootMcp !== undefined) {\n addExpected(rootFiles, paths.rootOutputs.mcp, rootMcp);\n }\n for (const [path, file] of rootFiles) expected.set(path, file);\n for (const skill of rootSkills) {\n addExpected(\n expected,\n join(paths.rootOutputs.skills, skill.relativePath),\n skill.content,\n skill.mode,\n );\n }\n\n const prototypes: PrototypeProjectionPlan[] = [];\n for (const prototypePath of prototypePaths) {\n const prototypeContextPaths = resolvePrototypeContextPaths({\n ...options,\n prototypePath,\n });\n assertSafePrototypeContextPaths(prototypeContextPaths);\n const [markdown, settings] = await Promise.all([\n renderPrototypeMarkdown(prototypeContextPaths),\n renderOptionalSettings(prototypeContextPaths),\n ]);\n const files = new Map<string, RenderedFile>();\n addExpected(\n files,\n prototypeContextPaths.prototypeOutputs.agents,\n markdown.agents,\n );\n addExpected(\n files,\n prototypeContextPaths.prototypeOutputs.claude,\n markdown.claude,\n );\n if (settings !== undefined) {\n addExpected(\n files,\n prototypeContextPaths.prototypeOutputs.settings,\n settings,\n );\n }\n for (const [path, file] of files) expected.set(path, file);\n prototypes.push({ paths: prototypeContextPaths, files });\n }\n\n const managed = new Set<string>([\n paths.rootOutputs.agents,\n paths.rootOutputs.claude,\n paths.rootOutputs.mcp,\n paths.rootOutputs.settings,\n ]);\n for (const path of await listSkillFiles(paths.rootOutputs.skills)) {\n managed.add(join(paths.rootOutputs.skills, path));\n }\n\n for (const prototypePath of prototypeDirectories) {\n const prototypeContextPaths = resolvePrototypeContextPaths({\n ...options,\n prototypePath,\n });\n if (prototypePathSet.has(prototypePath)) {\n for (const path of Object.values(\n prototypeContextPaths.prototypeOutputs,\n )) {\n managed.add(path);\n }\n continue;\n }\n for (const [label, path] of Object.entries(\n prototypeContextPaths.prototypeOutputs,\n )) {\n await addExistingManagedPath(\n managed,\n path,\n `prototype/${relativePath(paths.repositoryRoot, path)}:${label}`,\n );\n }\n }\n\n return {\n paths,\n rootFiles,\n rootSkills,\n prototypes,\n expected,\n managed,\n };\n}\n\nasync function compareFile(\n path: string,\n expected: RenderedFile,\n repositoryRoot: string,\n): Promise<ContextCheckIssue | undefined> {\n const displayPath = relativePath(repositoryRoot, path);\n let stats;\n try {\n stats = await lstat(path);\n } catch (error) {\n if (isNotFound(error)) return { kind: \"missing\", path: displayPath };\n throw new ContextSyncError(displayPath, \"could not be inspected safely\");\n }\n if (stats.isSymbolicLink()) {\n throw new ContextSyncError(displayPath, \"must not be a symbolic link\");\n }\n if (!stats.isFile()) return { kind: \"stale\", path: displayPath };\n\n let actual;\n try {\n actual = await readFile(path);\n } catch {\n throw new ContextSyncError(displayPath, \"could not be read safely\");\n }\n const expectedBytes =\n typeof expected.content === \"string\"\n ? Buffer.from(expected.content)\n : Buffer.from(expected.content);\n return actual.equals(expectedBytes)\n ? undefined\n : { kind: \"stale\", path: displayPath };\n}\n\nasync function comparePlan(plan: ProjectionPlan): Promise<ContextCheckResult> {\n const issues: ContextCheckIssue[] = [];\n for (const [path, expected] of [...plan.expected.entries()].sort(\n ([left], [right]) => (left < right ? -1 : left > right ? 1 : 0),\n )) {\n const issue = await compareFile(path, expected, plan.paths.repositoryRoot);\n if (issue !== undefined) issues.push(issue);\n }\n for (const path of [...plan.managed].sort()) {\n if (plan.expected.has(path)) continue;\n const displayPath = relativePath(plan.paths.repositoryRoot, path);\n let stats;\n try {\n stats = await lstat(path);\n } catch (error) {\n if (isNotFound(error)) continue;\n throw new ContextSyncError(displayPath, \"could not be inspected safely\");\n }\n if (stats.isSymbolicLink()) {\n throw new ContextSyncError(displayPath, \"must not be a symbolic link\");\n }\n if (!stats.isFile()) {\n throw new ContextSyncError(displayPath, \"must be a regular file\");\n }\n issues.push({ kind: \"unexpected\", path: displayPath });\n }\n\n issues.sort((left, right) => {\n if (left.path !== right.path) return left.path < right.path ? -1 : 1;\n return left.kind < right.kind ? -1 : left.kind > right.kind ? 1 : 0;\n });\n return { ok: issues.length === 0, issues };\n}\n\nasync function preflightWrites(\n plan: ProjectionPlan,\n issues: readonly ContextCheckIssue[],\n) {\n const issuePaths = new Set(\n issues\n .filter((issue) => issue.kind === \"missing\" || issue.kind === \"stale\")\n .map((issue) => join(plan.paths.repositoryRoot, issue.path)),\n );\n await Promise.all(\n [...issuePaths].map((path) =>\n assertWritableTarget(path, relativePath(plan.paths.repositoryRoot, path)),\n ),\n );\n return issuePaths;\n}\n\nasync function writeFiles(\n files: ReadonlyMap<string, RenderedFile>,\n writePaths: ReadonlySet<string>,\n repositoryRoot: string,\n written: string[],\n) {\n for (const [path, file] of [...files.entries()].sort(([left], [right]) =>\n left < right ? -1 : left > right ? 1 : 0,\n )) {\n if (!writePaths.has(path)) continue;\n await atomicWrite(\n path,\n file.content,\n relativePath(repositoryRoot, path),\n file.mode,\n );\n written.push(relativePath(repositoryRoot, path));\n }\n}\n\nexport async function checkContext(\n options: CheckContextOptions,\n): Promise<ContextCheckResult> {\n return comparePlan(await buildProjectionPlan(options));\n}\n\nexport async function syncContext(\n options: SyncContextOptions,\n): Promise<ContextSyncResult> {\n const plan = await buildProjectionPlan(options);\n const before = await comparePlan(plan);\n const writePaths = await preflightWrites(plan, before.issues);\n const written: string[] = [];\n const removed: string[] = [];\n\n await writeFiles(\n plan.rootFiles,\n writePaths,\n plan.paths.repositoryRoot,\n written,\n );\n await reconcileSkills(\n plan.paths.rootOutputs.skills,\n plan.rootSkills,\n plan.paths.repositoryRoot,\n written,\n removed,\n writePaths,\n );\n for (const prototype of plan.prototypes) {\n await writeFiles(\n prototype.files,\n writePaths,\n plan.paths.repositoryRoot,\n written,\n );\n }\n\n for (const issue of before.issues) {\n if (issue.kind !== \"unexpected\") continue;\n const path = join(plan.paths.repositoryRoot, issue.path);\n if (await removeManagedFile(path, issue.path)) removed.push(issue.path);\n }\n\n const after = await checkContext(options);\n return { ...after, written, removed };\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ResolvedContextPaths } from "./paths.js";
|
|
2
|
+
export declare const BONFIRE_REPO_ROOT_TOKEN = "{{BONFIRE_REPO_ROOT}}";
|
|
3
|
+
export declare const BONFIRE_REPO_ROOT_EXPRESSION = "$(git rev-parse --show-toplevel)";
|
|
4
|
+
type JsonPrimitive = null | boolean | number | string;
|
|
5
|
+
export type JsonValue = JsonPrimitive | readonly JsonValue[] | {
|
|
6
|
+
readonly [key: string]: JsonValue;
|
|
7
|
+
};
|
|
8
|
+
export type JsonTemplateKind = "settings" | "mcp";
|
|
9
|
+
export declare class ContextTemplateError extends Error {
|
|
10
|
+
readonly source: string;
|
|
11
|
+
constructor(source: string, message: string);
|
|
12
|
+
}
|
|
13
|
+
export declare function renderTemplateText(source: string, sourceLabel: string): string;
|
|
14
|
+
export declare function renderJsonTemplate(source: string, sourceLabel: string, kind: JsonTemplateKind): string;
|
|
15
|
+
export declare function renderOptionalSettings(paths: ResolvedContextPaths): Promise<string | undefined>;
|
|
16
|
+
export declare function renderOptionalMcp(paths: ResolvedContextPaths): Promise<string | undefined>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE/E,eAAO,MAAM,uBAAuB,0BAA0B,CAAC;AAC/D,eAAO,MAAM,4BAA4B,qCAAqC,CAAC;AAM/E,KAAK,aAAa,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AACtD,MAAM,MAAM,SAAS,GACnB,aAAa,GAAG,SAAS,SAAS,EAAE,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,KAAK,CAAC;AAElD,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,YAAY,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI1C;CACF;AAUD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAYrE;AAqDD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,gBAAgB,UAgBvB;AAsCD,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAS7B;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM7B","sourcesContent":["import { lstat, readFile } from \"node:fs/promises\";\nimport { assertSafeContextPaths, type ResolvedContextPaths } from \"./paths.js\";\n\nexport const BONFIRE_REPO_ROOT_TOKEN = \"{{BONFIRE_REPO_ROOT}}\";\nexport const BONFIRE_REPO_ROOT_EXPRESSION = \"$(git rev-parse --show-toplevel)\";\n\nconst SETTINGS_SOURCE_LABEL = \"context/settings.json\";\nconst MCP_SOURCE_LABEL = \"context/mcp.json\";\nconst UNRESOLVED_TEMPLATE_TOKEN = /\\{\\{[^{}]*\\}\\}/u;\n\ntype JsonPrimitive = null | boolean | number | string;\nexport type JsonValue =\n JsonPrimitive | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n\nexport type JsonTemplateKind = \"settings\" | \"mcp\";\n\nexport class ContextTemplateError extends Error {\n readonly source: string;\n\n constructor(source: string, message: string) {\n super(`Unable to render ${source}: ${message}`);\n this.name = \"ContextTemplateError\";\n this.source = source;\n }\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction unresolvedTemplateToken(value: string) {\n return UNRESOLVED_TEMPLATE_TOKEN.test(value);\n}\n\nexport function renderTemplateText(source: string, sourceLabel: string) {\n const rendered = source.replaceAll(\n BONFIRE_REPO_ROOT_TOKEN,\n BONFIRE_REPO_ROOT_EXPRESSION,\n );\n if (unresolvedTemplateToken(rendered)) {\n throw new ContextTemplateError(\n sourceLabel,\n \"contains an unresolved template token\",\n );\n }\n return rendered;\n}\n\nfunction renderJsonValue(value: unknown, sourceLabel: string): JsonValue {\n if (typeof value === \"string\") {\n return renderTemplateText(value, sourceLabel);\n }\n if (Array.isArray(value)) {\n return value.map((entry) => renderJsonValue(entry, sourceLabel));\n }\n if (isObject(value)) {\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => [\n key,\n renderJsonValue(entry, sourceLabel),\n ]),\n );\n }\n return value as JsonPrimitive;\n}\n\nfunction parseJsonSource(source: string, sourceLabel: string) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(source);\n } catch {\n throw new ContextTemplateError(sourceLabel, \"contains malformed JSON\");\n }\n return parsed;\n}\n\nfunction assertTemplateShape(\n value: unknown,\n kind: JsonTemplateKind,\n sourceLabel: string,\n): asserts value is Record<string, unknown> {\n if (!isObject(value)) {\n throw new ContextTemplateError(\n sourceLabel,\n `${kind} configuration must be a JSON object`,\n );\n }\n if (\n kind === \"mcp\" &&\n (!isObject(value.mcpServers) ||\n Object.values(value.mcpServers).some((server) => !isObject(server)))\n ) {\n throw new ContextTemplateError(\n sourceLabel,\n \"MCP configuration must contain an object-valued mcpServers map\",\n );\n }\n}\n\nexport function renderJsonTemplate(\n source: string,\n sourceLabel: string,\n kind: JsonTemplateKind,\n) {\n const parsed = parseJsonSource(source, sourceLabel);\n assertTemplateShape(parsed, kind, sourceLabel);\n const rendered = renderJsonValue(parsed, sourceLabel);\n\n // This catches tokens in object keys as well as any future value shape that\n // is accidentally omitted from the recursive renderer.\n const serialized = JSON.stringify(rendered, null, 2);\n if (unresolvedTemplateToken(serialized)) {\n throw new ContextTemplateError(\n sourceLabel,\n \"contains an unresolved template token\",\n );\n }\n return `${serialized}\\n`;\n}\n\nasync function readOptionalJson(path: string, sourceLabel: string) {\n let stats;\n try {\n stats = await lstat(path);\n } catch (error) {\n if (\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n return undefined;\n }\n throw new ContextTemplateError(\n sourceLabel,\n \"the optional source is unreadable\",\n );\n }\n\n if (!stats.isFile() || stats.isSymbolicLink()) {\n throw new ContextTemplateError(\n sourceLabel,\n \"the optional source must be a regular file\",\n );\n }\n\n try {\n return await readFile(path, \"utf8\");\n } catch {\n throw new ContextTemplateError(\n sourceLabel,\n \"the optional source is unreadable\",\n );\n }\n}\n\nexport async function renderOptionalSettings(\n paths: ResolvedContextPaths,\n): Promise<string | undefined> {\n assertSafeContextPaths(paths);\n const source = await readOptionalJson(\n paths.sources.settings,\n SETTINGS_SOURCE_LABEL,\n );\n return source === undefined\n ? undefined\n : renderJsonTemplate(source, SETTINGS_SOURCE_LABEL, \"settings\");\n}\n\nexport async function renderOptionalMcp(\n paths: ResolvedContextPaths,\n): Promise<string | undefined> {\n assertSafeContextPaths(paths);\n const source = await readOptionalJson(paths.sources.mcp, MCP_SOURCE_LABEL);\n return source === undefined\n ? undefined\n : renderJsonTemplate(source, MCP_SOURCE_LABEL, \"mcp\");\n}\n"]}
|
package/dist/template.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
+
import { assertSafeContextPaths } from "./paths.js";
|
|
3
|
+
export const BONFIRE_REPO_ROOT_TOKEN = "{{BONFIRE_REPO_ROOT}}";
|
|
4
|
+
export const BONFIRE_REPO_ROOT_EXPRESSION = "$(git rev-parse --show-toplevel)";
|
|
5
|
+
const SETTINGS_SOURCE_LABEL = "context/settings.json";
|
|
6
|
+
const MCP_SOURCE_LABEL = "context/mcp.json";
|
|
7
|
+
const UNRESOLVED_TEMPLATE_TOKEN = /\{\{[^{}]*\}\}/u;
|
|
8
|
+
export class ContextTemplateError extends Error {
|
|
9
|
+
source;
|
|
10
|
+
constructor(source, message) {
|
|
11
|
+
super(`Unable to render ${source}: ${message}`);
|
|
12
|
+
this.name = "ContextTemplateError";
|
|
13
|
+
this.source = source;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function isObject(value) {
|
|
17
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function unresolvedTemplateToken(value) {
|
|
20
|
+
return UNRESOLVED_TEMPLATE_TOKEN.test(value);
|
|
21
|
+
}
|
|
22
|
+
export function renderTemplateText(source, sourceLabel) {
|
|
23
|
+
const rendered = source.replaceAll(BONFIRE_REPO_ROOT_TOKEN, BONFIRE_REPO_ROOT_EXPRESSION);
|
|
24
|
+
if (unresolvedTemplateToken(rendered)) {
|
|
25
|
+
throw new ContextTemplateError(sourceLabel, "contains an unresolved template token");
|
|
26
|
+
}
|
|
27
|
+
return rendered;
|
|
28
|
+
}
|
|
29
|
+
function renderJsonValue(value, sourceLabel) {
|
|
30
|
+
if (typeof value === "string") {
|
|
31
|
+
return renderTemplateText(value, sourceLabel);
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
return value.map((entry) => renderJsonValue(entry, sourceLabel));
|
|
35
|
+
}
|
|
36
|
+
if (isObject(value)) {
|
|
37
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
38
|
+
key,
|
|
39
|
+
renderJsonValue(entry, sourceLabel),
|
|
40
|
+
]));
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function parseJsonSource(source, sourceLabel) {
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(source);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new ContextTemplateError(sourceLabel, "contains malformed JSON");
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
function assertTemplateShape(value, kind, sourceLabel) {
|
|
55
|
+
if (!isObject(value)) {
|
|
56
|
+
throw new ContextTemplateError(sourceLabel, `${kind} configuration must be a JSON object`);
|
|
57
|
+
}
|
|
58
|
+
if (kind === "mcp" &&
|
|
59
|
+
(!isObject(value.mcpServers) ||
|
|
60
|
+
Object.values(value.mcpServers).some((server) => !isObject(server)))) {
|
|
61
|
+
throw new ContextTemplateError(sourceLabel, "MCP configuration must contain an object-valued mcpServers map");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function renderJsonTemplate(source, sourceLabel, kind) {
|
|
65
|
+
const parsed = parseJsonSource(source, sourceLabel);
|
|
66
|
+
assertTemplateShape(parsed, kind, sourceLabel);
|
|
67
|
+
const rendered = renderJsonValue(parsed, sourceLabel);
|
|
68
|
+
// This catches tokens in object keys as well as any future value shape that
|
|
69
|
+
// is accidentally omitted from the recursive renderer.
|
|
70
|
+
const serialized = JSON.stringify(rendered, null, 2);
|
|
71
|
+
if (unresolvedTemplateToken(serialized)) {
|
|
72
|
+
throw new ContextTemplateError(sourceLabel, "contains an unresolved template token");
|
|
73
|
+
}
|
|
74
|
+
return `${serialized}\n`;
|
|
75
|
+
}
|
|
76
|
+
async function readOptionalJson(path, sourceLabel) {
|
|
77
|
+
let stats;
|
|
78
|
+
try {
|
|
79
|
+
stats = await lstat(path);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error !== null &&
|
|
83
|
+
typeof error === "object" &&
|
|
84
|
+
"code" in error &&
|
|
85
|
+
error.code === "ENOENT") {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
throw new ContextTemplateError(sourceLabel, "the optional source is unreadable");
|
|
89
|
+
}
|
|
90
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
91
|
+
throw new ContextTemplateError(sourceLabel, "the optional source must be a regular file");
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return await readFile(path, "utf8");
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
throw new ContextTemplateError(sourceLabel, "the optional source is unreadable");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function renderOptionalSettings(paths) {
|
|
101
|
+
assertSafeContextPaths(paths);
|
|
102
|
+
const source = await readOptionalJson(paths.sources.settings, SETTINGS_SOURCE_LABEL);
|
|
103
|
+
return source === undefined
|
|
104
|
+
? undefined
|
|
105
|
+
: renderJsonTemplate(source, SETTINGS_SOURCE_LABEL, "settings");
|
|
106
|
+
}
|
|
107
|
+
export async function renderOptionalMcp(paths) {
|
|
108
|
+
assertSafeContextPaths(paths);
|
|
109
|
+
const source = await readOptionalJson(paths.sources.mcp, MCP_SOURCE_LABEL);
|
|
110
|
+
return source === undefined
|
|
111
|
+
? undefined
|
|
112
|
+
: renderJsonTemplate(source, MCP_SOURCE_LABEL, "mcp");
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, readFile } from \"node:fs/promises\";\nimport { assertSafeContextPaths, type ResolvedContextPaths } from \"./paths.js\";\n\nexport const BONFIRE_REPO_ROOT_TOKEN = \"{{BONFIRE_REPO_ROOT}}\";\nexport const BONFIRE_REPO_ROOT_EXPRESSION = \"$(git rev-parse --show-toplevel)\";\n\nconst SETTINGS_SOURCE_LABEL = \"context/settings.json\";\nconst MCP_SOURCE_LABEL = \"context/mcp.json\";\nconst UNRESOLVED_TEMPLATE_TOKEN = /\\{\\{[^{}]*\\}\\}/u;\n\ntype JsonPrimitive = null | boolean | number | string;\nexport type JsonValue =\n JsonPrimitive | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n\nexport type JsonTemplateKind = \"settings\" | \"mcp\";\n\nexport class ContextTemplateError extends Error {\n readonly source: string;\n\n constructor(source: string, message: string) {\n super(`Unable to render ${source}: ${message}`);\n this.name = \"ContextTemplateError\";\n this.source = source;\n }\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction unresolvedTemplateToken(value: string) {\n return UNRESOLVED_TEMPLATE_TOKEN.test(value);\n}\n\nexport function renderTemplateText(source: string, sourceLabel: string) {\n const rendered = source.replaceAll(\n BONFIRE_REPO_ROOT_TOKEN,\n BONFIRE_REPO_ROOT_EXPRESSION,\n );\n if (unresolvedTemplateToken(rendered)) {\n throw new ContextTemplateError(\n sourceLabel,\n \"contains an unresolved template token\",\n );\n }\n return rendered;\n}\n\nfunction renderJsonValue(value: unknown, sourceLabel: string): JsonValue {\n if (typeof value === \"string\") {\n return renderTemplateText(value, sourceLabel);\n }\n if (Array.isArray(value)) {\n return value.map((entry) => renderJsonValue(entry, sourceLabel));\n }\n if (isObject(value)) {\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => [\n key,\n renderJsonValue(entry, sourceLabel),\n ]),\n );\n }\n return value as JsonPrimitive;\n}\n\nfunction parseJsonSource(source: string, sourceLabel: string) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(source);\n } catch {\n throw new ContextTemplateError(sourceLabel, \"contains malformed JSON\");\n }\n return parsed;\n}\n\nfunction assertTemplateShape(\n value: unknown,\n kind: JsonTemplateKind,\n sourceLabel: string,\n): asserts value is Record<string, unknown> {\n if (!isObject(value)) {\n throw new ContextTemplateError(\n sourceLabel,\n `${kind} configuration must be a JSON object`,\n );\n }\n if (\n kind === \"mcp\" &&\n (!isObject(value.mcpServers) ||\n Object.values(value.mcpServers).some((server) => !isObject(server)))\n ) {\n throw new ContextTemplateError(\n sourceLabel,\n \"MCP configuration must contain an object-valued mcpServers map\",\n );\n }\n}\n\nexport function renderJsonTemplate(\n source: string,\n sourceLabel: string,\n kind: JsonTemplateKind,\n) {\n const parsed = parseJsonSource(source, sourceLabel);\n assertTemplateShape(parsed, kind, sourceLabel);\n const rendered = renderJsonValue(parsed, sourceLabel);\n\n // This catches tokens in object keys as well as any future value shape that\n // is accidentally omitted from the recursive renderer.\n const serialized = JSON.stringify(rendered, null, 2);\n if (unresolvedTemplateToken(serialized)) {\n throw new ContextTemplateError(\n sourceLabel,\n \"contains an unresolved template token\",\n );\n }\n return `${serialized}\\n`;\n}\n\nasync function readOptionalJson(path: string, sourceLabel: string) {\n let stats;\n try {\n stats = await lstat(path);\n } catch (error) {\n if (\n error !== null &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n return undefined;\n }\n throw new ContextTemplateError(\n sourceLabel,\n \"the optional source is unreadable\",\n );\n }\n\n if (!stats.isFile() || stats.isSymbolicLink()) {\n throw new ContextTemplateError(\n sourceLabel,\n \"the optional source must be a regular file\",\n );\n }\n\n try {\n return await readFile(path, \"utf8\");\n } catch {\n throw new ContextTemplateError(\n sourceLabel,\n \"the optional source is unreadable\",\n );\n }\n}\n\nexport async function renderOptionalSettings(\n paths: ResolvedContextPaths,\n): Promise<string | undefined> {\n assertSafeContextPaths(paths);\n const source = await readOptionalJson(\n paths.sources.settings,\n SETTINGS_SOURCE_LABEL,\n );\n return source === undefined\n ? undefined\n : renderJsonTemplate(source, SETTINGS_SOURCE_LABEL, \"settings\");\n}\n\nexport async function renderOptionalMcp(\n paths: ResolvedContextPaths,\n): Promise<string | undefined> {\n assertSafeContextPaths(paths);\n const source = await readOptionalJson(paths.sources.mcp, MCP_SOURCE_LABEL);\n return source === undefined\n ? undefined\n : renderJsonTemplate(source, MCP_SOURCE_LABEL, \"mcp\");\n}\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC,CAAC","sourcesContent":["export type ContextProjectionResult = Readonly<{\n written: readonly string[];\n removed: readonly string[];\n}>;\n"]}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type ContextProjectionResult = Readonly<{\n written: readonly string[];\n removed: readonly string[];\n}>;\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ebay-dt/bonfire-context",
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
|
+
"description": "Context projection and installation mechanics for Bonfire.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=24"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.corp.ebay.com/Design-Technology/bonfire",
|
|
24
|
+
"directory": "packages/context"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@repo/eslint-config": "0.0.0",
|
|
31
|
+
"@repo/typescript-config": "0.0.0",
|
|
32
|
+
"@types/node": "20.19.43",
|
|
33
|
+
"eslint": "10.9.1",
|
|
34
|
+
"typescript": "7.0.2",
|
|
35
|
+
"vitest": "^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/embed-source-map-sources.mjs dist",
|
|
39
|
+
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
40
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
41
|
+
"lint": "eslint .",
|
|
42
|
+
"test": "vitest run --passWithNoTests --exclude 'dist/**'"
|
|
43
|
+
}
|
|
44
|
+
}
|