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
145 changes: 25 additions & 120 deletions actions/setup/js/render_template.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,12 @@
// Single-function Markdown → Markdown postprocessor for GitHub Actions.
// Processes only {{#if <expr>}} ... {{/if}} blocks after ${{ }} evaluation.

const { getErrorMessage } = require("./error_helpers.cjs");
require("./shim.cjs");

const { getErrorMessage } = require("./error_helpers.cjs");
const fs = require("fs");
const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");

/**
* Determines if a value is truthy according to template logic
* @param {string} expr - The expression to evaluate
* @returns {boolean} - Whether the expression is truthy
*/
function isTruthy(expr) {
const v = expr.trim().toLowerCase();
const result = !(v === "" || v === "false" || v === "0" || v === "null" || v === "undefined");
if (typeof core !== "undefined") {
core.info(`[isTruthy] Evaluating "${expr}" (trimmed: "${v}") -> ${result}`);
}
return result;
}
const { ERR_API, ERR_CONFIG } = require("./error_codes.cjs");
const { isTruthy } = require("./is_truthy.cjs");

/**
* Renders a Markdown template by processing {{#if}} conditional blocks.
Expand All @@ -33,18 +21,14 @@ function isTruthy(expr) {
* @returns {string} - The processed markdown content
*/
function renderMarkdownTemplate(markdown) {
if (typeof core !== "undefined") {
core.info(`[renderMarkdownTemplate] Starting template rendering`);
core.info(`[renderMarkdownTemplate] Input length: ${markdown.length} characters`);
}
core.info(`[renderMarkdownTemplate] Starting template rendering`);
core.info(`[renderMarkdownTemplate] Input length: ${markdown.length} characters`);

// Count conditionals before processing
const blockConditionals = (markdown.match(/(\n?)([ \t]*{{#if\s+(.*?)\s*}}[ \t]*\n)([\s\S]*?)([ \t]*{{\/if}}[ \t]*)(\n?)/g) || []).length;
const inlineConditionals = (markdown.match(/{{#if\s+(.*?)\s*}}([\s\S]*?){{\/if}}/g) || []).length - blockConditionals;

if (typeof core !== "undefined") {
core.info(`[renderMarkdownTemplate] Found ${blockConditionals} block conditional(s) and ${inlineConditionals} inline conditional(s)`);
}
core.info(`[renderMarkdownTemplate] Found ${blockConditionals} block conditional(s) and ${inlineConditionals} inline conditional(s)`);

let blockCount = 0;
let keptBlocks = 0;
Expand All @@ -53,37 +37,24 @@ function renderMarkdownTemplate(markdown) {
// First pass: Handle blocks where tags are on their own lines
// Captures: (leading newline)(opening tag line)(condition)(body)(closing tag line)(trailing newline)
// Uses .*? (non-greedy) with \s* to handle expressions with or without trailing spaces
let result = markdown.replace(/(\n?)([ \t]*{{#if\s+(.*?)\s*}}[ \t]*\n)([\s\S]*?)([ \t]*{{\/if}}[ \t]*)(\n?)/g, (match, leadNL, openLine, cond, body, closeLine, trailNL) => {
let result = markdown.replace(/(\n?)([ \t]*{{#if\s+(.*?)\s*}}[ \t]*\n)([\s\S]*?)([ \t]*{{\/if}}[ \t]*)(\n?)/g, (match, leadNL, openLine, cond, body) => {
blockCount++;
const condTrimmed = cond.trim();
const truthyResult = isTruthy(cond);
const bodyPreview = body.substring(0, 60).replace(/\n/g, "\\n");

if (typeof core !== "undefined") {
core.info(`[renderMarkdownTemplate] Block ${blockCount}: condition="${condTrimmed}" -> ${truthyResult ? "KEEP" : "REMOVE"}`);
core.info(`[renderMarkdownTemplate] Body preview: "${bodyPreview}${body.length > 60 ? "..." : ""}"`);
}
core.info(`[renderMarkdownTemplate] Block ${blockCount}: condition="${cond.trim()}" -> ${truthyResult ? "KEEP" : "REMOVE"}`);

if (truthyResult) {
// Keep body with leading newline if there was one before the opening tag
keptBlocks++;
if (typeof core !== "undefined") {
core.info(`[renderMarkdownTemplate] Action: Keeping body with leading newline=${!!leadNL}`);
}
return leadNL + body;
} else {
// Remove entire block completely - the line containing the template is removed
removedBlocks++;
if (typeof core !== "undefined") {
core.info(`[renderMarkdownTemplate] Action: Removing entire block`);
}
return "";
}
});

if (typeof core !== "undefined") {
core.info(`[renderMarkdownTemplate] First pass complete: ${keptBlocks} kept, ${removedBlocks} removed`);
}
core.info(`[renderMarkdownTemplate] First pass complete: ${keptBlocks} kept, ${removedBlocks} removed`);

let inlineCount = 0;
let keptInline = 0;
Expand All @@ -93,14 +64,9 @@ function renderMarkdownTemplate(markdown) {
// Uses .*? (non-greedy) with \s* to handle expressions with or without trailing spaces
result = result.replace(/{{#if\s+(.*?)\s*}}([\s\S]*?){{\/if}}/g, (_, cond, body) => {
inlineCount++;
const condTrimmed = cond.trim();
const truthyResult = isTruthy(cond);
const bodyPreview = body.substring(0, 40).replace(/\n/g, "\\n");

if (typeof core !== "undefined") {
core.info(`[renderMarkdownTemplate] Inline ${inlineCount}: condition="${condTrimmed}" -> ${truthyResult ? "KEEP" : "REMOVE"}`);
core.info(`[renderMarkdownTemplate] Body preview: "${bodyPreview}${body.length > 40 ? "..." : ""}"`);
}
core.info(`[renderMarkdownTemplate] Inline ${inlineCount}: condition="${cond.trim()}" -> ${truthyResult ? "KEEP" : "REMOVE"}`);

if (truthyResult) {
keptInline++;
Expand All @@ -111,22 +77,12 @@ function renderMarkdownTemplate(markdown) {
}
});

if (typeof core !== "undefined") {
core.info(`[renderMarkdownTemplate] Second pass complete: ${keptInline} kept, ${removedInline} removed`);
}
core.info(`[renderMarkdownTemplate] Second pass complete: ${keptInline} kept, ${removedInline} removed`);

// Clean up excessive blank lines (more than one blank line = 2 newlines)
const beforeCleanup = result.length;
const excessiveLines = (result.match(/\n{3,}/g) || []).length;
result = result.replace(/\n{3,}/g, "\n\n");

if (typeof core !== "undefined") {
if (excessiveLines > 0) {
core.info(`[renderMarkdownTemplate] Cleaned up ${excessiveLines} excessive blank line sequence(s)`);
core.info(`[renderMarkdownTemplate] Length change from cleanup: ${beforeCleanup} -> ${result.length} characters`);
}
core.info(`[renderMarkdownTemplate] Final output length: ${result.length} characters`);
}
core.info(`[renderMarkdownTemplate] Final output length: ${result.length} characters`);

return result;
}
Expand All @@ -136,91 +92,40 @@ function renderMarkdownTemplate(markdown) {
*/
function main() {
try {
if (typeof core !== "undefined") {
core.info("========================================");
core.info("[main] Starting render_template processing");
core.info("========================================");
}
core.info("[render_template] Starting template rendering");

const promptPath = process.env.GH_AW_PROMPT;
if (!promptPath) {
if (typeof core !== "undefined") {
core.setFailed(`${ERR_CONFIG}: GH_AW_PROMPT environment variable is not set`);
}
core.setFailed(`${ERR_CONFIG}: GH_AW_PROMPT environment variable is not set`);
process.exit(1);
}

if (typeof core !== "undefined") {
core.info(`[main] Prompt path: ${promptPath}`);
}
core.info(`[render_template] Prompt path: ${promptPath}`);

// Read the prompt file
if (typeof core !== "undefined") {
core.info(`[main] Reading prompt file...`);
}
const markdown = fs.readFileSync(promptPath, "utf8");
const originalLength = markdown.length;
core.info(`[render_template] Read ${markdown.length} characters`);

if (typeof core !== "undefined") {
core.info(`[main] Original content length: ${originalLength} characters`);
core.info(`[main] First 200 characters: ${markdown.substring(0, 200).replace(/\n/g, "\\n")}`);
}

// Check if there are any conditional blocks
const hasConditionals = /{{#if\s+[^}]+}}/.test(markdown);
if (!hasConditionals) {
if (typeof core !== "undefined") {
core.info("No conditional blocks found in prompt, skipping template rendering");
core.info("========================================");
core.info("[main] Processing complete - SKIPPED");
core.info("========================================");
}
core.info("No conditional blocks found in prompt, skipping template rendering");
process.exit(0);
}

const conditionalMatches = markdown.match(/{{#if\s+[^}]+}}/g) || [];
if (typeof core !== "undefined") {
core.info(`[main] Processing ${conditionalMatches.length} conditional template block(s)`);
}
core.info(`[render_template] Processing ${conditionalMatches.length} conditional template block(s)`);

// Render the template
const beforeRendering = markdown.length;
const rendered = renderMarkdownTemplate(markdown);
const afterRendering = rendered.length;

// Write back to the same file
if (typeof core !== "undefined") {
core.info("\n========================================");
core.info("[main] Writing Output");
core.info("========================================");
core.info(`[main] Writing processed content back to: ${promptPath}`);
core.info(`[main] Final content length: ${afterRendering} characters`);
core.info(`[main] Total length change: ${beforeRendering} -> ${afterRendering} (${afterRendering > beforeRendering ? "+" : ""}${afterRendering - beforeRendering})`);
}

core.info(`[render_template] Writing back to ${promptPath} (${rendered.length} characters)`);
fs.writeFileSync(promptPath, rendered, "utf8");

if (typeof core !== "undefined") {
core.info(`[main] Last 200 characters: ${rendered.substring(Math.max(0, rendered.length - 200)).replace(/\n/g, "\\n")}`);
core.info("========================================");
core.info("[main] Processing complete - SUCCESS");
core.info("========================================");
}
core.info("[render_template] Processing complete");
} catch (error) {
if (typeof core !== "undefined") {
core.info("========================================");
core.info("[main] Processing failed - ERROR");
core.info("========================================");
const err = error instanceof Error ? error : new Error(String(error));
core.info(`[main] Error type: ${err.constructor.name}`);
core.info(`[main] Error message: ${err.message}`);
if (err.stack) {
core.info(`[main] Stack trace:\n${err.stack}`);
}
core.setFailed(`${ERR_API}: ${getErrorMessage(error)}`);
} else {
throw error;
const err = error instanceof Error ? error : new Error(String(error));
if (err.stack) {
core.info(`[render_template] Stack trace:\n${err.stack}`);
}
core.setFailed(`${ERR_API}: ${getErrorMessage(error)}`);
}
}

Expand Down
Loading
Loading