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
1,562 changes: 885 additions & 677 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eldrforge/github-tools",
"version": "0.1.14",
"version": "0.1.15",
"description": "GitHub API utilities for automation - PR management, issue tracking, workflow monitoring",
"main": "dist/index.js",
"type": "module",
Expand Down Expand Up @@ -41,7 +41,7 @@
"author": "Calen Varek <calenvarek@gmail.com>",
"license": "Apache-2.0",
"dependencies": {
"@eldrforge/git-tools": "^0.1.11",
"@eldrforge/git-tools": "^0.1.13",
"@octokit/rest": "^22.0.0"
},
"peerDependencies": {
Expand Down
22 changes: 20 additions & 2 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,12 +779,30 @@ export const createRelease = async (tagName: string, title: string, notes: strin
const logger = getLogger();

logger.info(`Creating release for tag ${tagName}...`);

// Unescape the release notes body and title in case they contain escaped newlines from JSON serialization
// Background: If release notes were generated by agentic AI systems or went through JSON serialization,
// the newlines may be stored as escaped sequences (literal \n characters) rather than actual line breaks.
// This can happen when:
// 1. Release notes are JSON.stringify'd and stored in a file, then re-read as a string
// 2. The release notes pass through multiple serialization layers
// Without unescaping, GitHub will render "some text\nmore text" instead of line-separated content
const unescapedNotes = notes
.replace(/\\n/g, '\n')
.replace(/\\r/g, '\r')
.replace(/\\t/g, '\t');

const unescapedTitle = title
.replace(/\\n/g, '\n')
.replace(/\\r/g, '\r')
.replace(/\\t/g, '\t');

await octokit.repos.createRelease({
owner,
repo,
tag_name: tagName,
name: title,
body: notes,
name: unescapedTitle,
body: unescapedNotes,
});
logger.info(`Release ${tagName} created.`);
};
Expand Down
30 changes: 29 additions & 1 deletion tests/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
import { describe, it, expect } from 'vitest';
import { PullRequestCreationError, PullRequestCheckError } from '../src/errors';
import { PullRequestCreationError, PullRequestCheckError, CommandError, ArgumentError } from '../src/errors';

describe('CommandError', () => {
it('should create an error with correct properties', () => {
const error = new CommandError('Command failed');
expect(error.message).toBe('Command failed');
expect(error.name).toBe('CommandError');
expect(error instanceof Error).toBe(true);
});

it('should set correct error name', () => {
const error = new CommandError('Test message');
expect(error.name).toBe('CommandError');
});
});

describe('ArgumentError', () => {
it('should create an error with correct properties', () => {
const error = new ArgumentError('Invalid argument');
expect(error.message).toBe('Invalid argument');
expect(error.name).toBe('ArgumentError');
expect(error instanceof Error).toBe(true);
});

it('should set correct error name', () => {
const error = new ArgumentError('Test message');
expect(error.name).toBe('ArgumentError');
});
});

describe('PullRequestCreationError', () => {
describe('422 errors', () => {
Expand Down
Loading