@meldscience/meld
Version:
pipeable one-shot prompt scripting toolkit
168 lines (152 loc) • 5.89 kB
text/typescript
import { Plugin } from 'unified';
import { Node, Parent } from 'unist';
import { Text } from 'mdast';
import { visit } from 'unist-util-visit';
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { join } from 'path';
import { importMarkdownSection } from '../markdownImporter';
import { importCodeSymbols } from '../codeImporter';
interface MeldDirectiveNode extends Node {
type: 'meldDirective';
meldType: 'cmd' | 'import';
data?: {
command?: string;
importPath?: string;
heading?: string;
headingLevelOverride?: string;
headingTextOverride?: string;
importSymbolList?: string[];
importWholeFile?: boolean;
};
}
interface ProcessMeldNodesOptions {
currentFileDir: string;
debug?: boolean;
detailedErrors?: boolean;
}
export const remarkProcessMeldNodes: Plugin<[ProcessMeldNodesOptions]> = function (options: ProcessMeldNodesOptions) {
return async function transformer(tree: Node, file: any) {
const errors: string[] = [];
// Initialize meldErrors in file.data
file.data = file.data || {};
file.data.meldErrors = [];
visit(tree, 'meldDirective', (node: MeldDirectiveNode, index, parent: Parent) => {
if (options.debug) {
console.log('Visiting node:', {
type: node.type,
meldType: node.meldType,
index,
parentType: parent?.type,
parentChildren: parent?.children?.length
});
}
if (index === null || index === undefined) {
if (options.debug) console.log('Skipping node due to null/undefined index');
return;
}
try {
if (node.meldType === 'cmd') {
if (options.debug) console.log('Processing command:', node.data?.command);
try {
const output = execSync(node.data?.command || '', {
encoding: 'utf-8',
cwd: options.currentFileDir,
stdio: ['pipe', 'pipe', 'pipe']
}).trim();
if (options.debug) {
console.log('Command output:', output);
console.log('Replacing node at index', index);
}
// Replace with a text node containing the output
parent.children[index] = {
type: 'text',
value: output
} as Text;
if (options.debug) console.log('Node replaced, new node:', parent.children[index]);
} catch (cmdError: any) {
const errorMessage = options.detailedErrors
? 'Command produced error output (included below)\n' +
(cmdError.stderr || cmdError.message || 'Unknown error')
: 'Error: Error processing cmd directive';
parent.children[index] = {
type: 'text',
value: errorMessage
} as Text;
file.data.meldErrors.push(errorMessage);
}
} else {
// It's an import directive
const importPath = node.data?.importPath;
if (options.debug) console.log('Processing import:', importPath, 'heading:', node.data?.heading);
if (!importPath) {
throw new Error('Invalid import directive: missing import path');
}
// Logic to figure out if it's a .md or .ts
if (importPath.endsWith('.md')) {
const heading = node.data?.heading;
// Reuse existing markdownImporter
try {
const resolvedPath = join(options.currentFileDir, importPath);
if (!existsSync(resolvedPath)) {
throw new Error('File not found');
}
const result = importMarkdownSection({
filePath: resolvedPath,
heading,
headingLevelOverride: node.data?.headingLevelOverride,
headingTextOverride: node.data?.headingTextOverride
});
if (!result.headingFound && heading) {
throw new Error(`Heading "${heading}" not found in ${importPath}`);
}
parent.children[index] = {
type: 'text',
value: result.content
} as Text;
} catch (error) {
throw new Error(`Error processing import directive: ${(error as Error).message}`);
}
} else {
// Assume it's a code file
try {
const resolvedPath = join(options.currentFileDir, importPath);
if (!existsSync(resolvedPath)) {
throw new Error('File not found');
}
const result = importCodeSymbols({
filePath: resolvedPath,
symbolList: node.data?.importSymbolList || [],
headingLevelOverride: node.data?.headingLevelOverride,
headingTextOverride: node.data?.headingTextOverride
});
if (result.content.trim() === '') {
const errorMessage = 'Error: Error processing import directive';
parent.children[index] = {
type: 'text',
value: errorMessage
} as Text;
file.data.meldErrors.push(errorMessage);
} else {
parent.children[index] = {
type: 'text',
value: result.content
} as Text;
}
} catch (error) {
throw new Error(`Error processing import directive: ${(error as Error).message}`);
}
}
}
} catch (error) {
const errorMessage = `Error: ${(error as Error).message}\n\n`;
parent.children[index] = {
type: 'text',
value: errorMessage
} as Text;
file.data.meldErrors.push(errorMessage);
}
});
return tree;
};
};