Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/web/src/chat/deployable-package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ describe("deployablePackageFromBody", () => {
expect(result?.pkg.name).toBe("Echo");
});

test("reads a comment label with a path prefix on the fence's first line", () => {
const body =
`Here is the package:\n\n\`\`\`json\n// Scribe/definition.json\n${DEFINITION_JSON}\n\`\`\`\n\n` +
`\`\`\`json\n# Scribe/package.json\n${PACKAGE_JSON}\n\`\`\`\n\nPress Deploy.`;
const result = deployablePackageFromBody(body);
expect(result?.pkg.name).toBe("Echo");
expect(result?.strippedBody).toBe("Here is the package:\n\nPress Deploy.");
});

test("is null when only one of the two files is present", () => {
const body = `package.json\n\`\`\`\n${PACKAGE_JSON}\n\`\`\``;
expect(deployablePackageFromBody(body)).toBeNull();
Expand Down
28 changes: 25 additions & 3 deletions apps/web/src/chat/deployable-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,18 @@ export function deployablePackage(
/** Strips wrapping backticks/colon and returns the canonical file name the
* text names, or null when it names neither of the two contract files. */
function namedFile(text: string): string | null {
const stripped = text.trim().replace(/^`+/, "").replace(/`+$/, "").replace(/:$/, "").trim();
if (/^package\.json$/i.test(stripped)) return PACKAGE_MANIFEST_NAME;
if (/^definition\.json$/i.test(stripped)) return AGENT_DEFINITION_NAME;
// A label may be a comment (`// x`, `# x`) and a path (`Scribe/x`); only
// the final segment names the file.
const stripped = text
.trim()
.replace(/^(\/\/|#)\s*/, "")
.replace(/^`+/, "")
.replace(/`+$/, "")
.replace(/:$/, "")
.trim();
const base = stripped.split("/").pop() ?? "";
if (/^package\.json$/i.test(base)) return PACKAGE_MANIFEST_NAME;
if (/^definition\.json$/i.test(base)) return AGENT_DEFINITION_NAME;
return null;
}

Expand Down Expand Up @@ -110,6 +119,19 @@ function findNamedFencedBlocks(lines: readonly string[]): Map<string, FencedBloc
}
const end = Math.min(index + 1, lines.length);
index = end;
// A comment label on the fence's first line (`// Scribe/definition.json`)
// names the file too; it is not part of the file.
if (name === null) {
const firstContent = contentLines.findIndex((content) => content.trim() !== "");
const first = contentLines[firstContent] ?? "";
if (/^\s*(\/\/|#)/.test(first)) {
const fromComment = namedFile(first);
if (fromComment !== null) {
name = fromComment;
contentLines.splice(0, firstContent + 1);
}
}
}
if (name !== null && !found.has(name)) {
found.set(name, {
content: contentLines.join("\n"),
Expand Down
Loading