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
111 changes: 111 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Release Build

on:
release:
types: [published]

jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Extract version from tag
id: version
shell: bash
run: |
# Remove 'v' prefix if present (e.g., "v1.2.3" -> "1.2.3")
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "Extracted version: ${VERSION}"

- name: Update package.json version
shell: bash
run: |
node apps/ui/scripts/update-version.mjs "${{ steps.version.outputs.version }}"

- name: Setup project
uses: ./.github/actions/setup-project
with:
check-lockfile: 'true'

- name: Build Electron app (macOS)
if: matrix.os == 'macos-latest'
shell: bash
run: npm run build:electron:mac --workspace=apps/ui
env:
CSC_IDENTITY_AUTO_DISCOVERY: false

- name: Build Electron app (Windows)
if: matrix.os == 'windows-latest'
shell: bash
run: npm run build:electron:win --workspace=apps/ui

- name: Build Electron app (Linux)
if: matrix.os == 'ubuntu-latest'
shell: bash
run: npm run build:electron:linux --workspace=apps/ui

- name: Upload macOS artifacts
if: matrix.os == 'macos-latest'
uses: actions/upload-artifact@v4
with:
name: macos-builds
path: apps/ui/release/*.{dmg,zip}
retention-days: 30

- name: Upload Windows artifacts
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: windows-builds
path: apps/ui/release/*.exe
retention-days: 30

- name: Upload Linux artifacts
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: linux-builds
path: apps/ui/release/*.{AppImage,deb}
retention-days: 30

upload:
needs: build
runs-on: ubuntu-latest
if: github.event.release.draft == false

steps:
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos-builds
path: artifacts/macos-builds

- name: Download Windows artifacts
uses: actions/download-artifact@v4
with:
name: windows-builds
path: artifacts/windows-builds

- name: Download Linux artifacts
uses: actions/download-artifact@v4
with:
name: linux-builds
path: artifacts/linux-builds

- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/macos-builds/*
artifacts/windows-builds/*
artifacts/linux-builds/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 changes: 46 additions & 0 deletions apps/ui/scripts/update-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env node
/**
* Updates the version in apps/ui/package.json
* Usage: node scripts/update-version.mjs <version>
* Example: node scripts/update-version.mjs 1.2.3
*/

import { readFileSync, writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const version = process.argv[2];

if (!version) {
console.error('Error: Version argument is required');
console.error('Usage: node scripts/update-version.mjs <version>');
process.exit(1);
}

// Remove 'v' prefix if present (e.g., "v1.2.3" -> "1.2.3")
const cleanVersion = version.startsWith('v') ? version.slice(1) : version;

// Validate version format (basic semver check)
if (!/^\d+\.\d+\.\d+/.test(cleanVersion)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current regex /^\d+\.\d+\.\d+/ for version validation is a bit loose as it only checks the beginning of the string. This would incorrectly validate versions like 1.2.3.4 or 1.2.3-and-more. To ensure the version string strictly follows the X.Y.Z format, you should anchor the regex to the end of the string as well.

Suggested change
if (!/^\d+\.\d+\.\d+/.test(cleanVersion)) {
if (!/^\d+\.\d+\.\d+$/.test(cleanVersion)) {

console.error(`Error: Invalid version format: ${cleanVersion}`);
console.error('Expected format: X.Y.Z (e.g., 1.2.3)');
process.exit(1);
}
Comment on lines +26 to +31
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Strengthen semver validation to reject invalid suffixes.

The regex /^\d+\.\d+\.\d+/ only checks if the version starts with the pattern but doesn't anchor to the end of the string. This would incorrectly accept invalid versions like "1.2.3abc" or "1.2.3-".

🔎 Proposed fix
 // Validate version format (basic semver check)
-if (!/^\d+\.\d+\.\d+/.test(cleanVersion)) {
+if (!/^\d+\.\d+\.\d+$/.test(cleanVersion)) {
   console.error(`Error: Invalid version format: ${cleanVersion}`);
   console.error('Expected format: X.Y.Z (e.g., 1.2.3)');
   process.exit(1);
 }
🤖 Prompt for AI Agents
In apps/ui/scripts/update-version.mjs around lines 26 to 31, the current regex
only anchors the start and will accept invalid suffixes like "1.2.3abc"; update
the validation to anchor the end and use a stricter semver pattern — replace the
regex with a full semver-aware one (for example
/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/) so versions with
invalid trailing characters are rejected, keep the existing error messages and
exit behavior.


const packageJsonPath = join(__dirname, '..', 'package.json');

try {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
const oldVersion = packageJson.version;
packageJson.version = cleanVersion;

writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');

console.log(`Updated version from ${oldVersion} to ${cleanVersion}`);
} catch (error) {
console.error(`Error updating version: ${error.message}`);
process.exit(1);
}
89 changes: 89 additions & 0 deletions apps/ui/tests/agent/start-new-chat-session.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Start New Chat Session E2E Test
*
* Happy path: Start a new agent chat session
*/

import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import {
createTempDirPath,
cleanupTempDir,
setupRealProject,
waitForNetworkIdle,
navigateToAgent,
clickNewSessionButton,
waitForNewSession,
countSessionItems,
} from '../utils';

const TEST_TEMP_DIR = createTempDirPath('agent-session-test');

test.describe('Agent Chat Session', () => {
let projectPath: string;
const projectName = `test-project-${Date.now()}`;

test.beforeAll(async () => {
if (!fs.existsSync(TEST_TEMP_DIR)) {
fs.mkdirSync(TEST_TEMP_DIR, { recursive: true });
}

projectPath = path.join(TEST_TEMP_DIR, projectName);
fs.mkdirSync(projectPath, { recursive: true });

fs.writeFileSync(
path.join(projectPath, 'package.json'),
JSON.stringify({ name: projectName, version: '1.0.0' }, null, 2)
);

const automakerDir = path.join(projectPath, '.automaker');
fs.mkdirSync(automakerDir, { recursive: true });
fs.mkdirSync(path.join(automakerDir, 'features'), { recursive: true });
fs.mkdirSync(path.join(automakerDir, 'context'), { recursive: true });
fs.mkdirSync(path.join(automakerDir, 'sessions'), { recursive: true });

fs.writeFileSync(
path.join(automakerDir, 'categories.json'),
JSON.stringify({ categories: [] }, null, 2)
);

fs.writeFileSync(
path.join(automakerDir, 'app_spec.txt'),
`# ${projectName}\n\nA test project for e2e testing.`
);
});
Comment on lines +27 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This beforeAll setup block for creating a test project is duplicated across several new test files (e.g., add-feature-to-backlog.spec.ts, edit-feature.spec.ts, etc.). To improve maintainability and reduce code duplication, consider extracting this logic into a shared helper function in apps/ui/tests/utils/project/setup.ts. The helper could accept options to conditionally create directories like sessions when needed.


test.afterAll(async () => {
cleanupTempDir(TEST_TEMP_DIR);
});

test('should start a new agent chat session', async ({ page }) => {
await setupRealProject(page, projectPath, projectName, { setAsCurrent: true });

await page.goto('/');
await waitForNetworkIdle(page);

// Navigate to agent view
await navigateToAgent(page);

// Verify we're on the agent view
await expect(page.locator('[data-testid="agent-view"]')).toBeVisible({ timeout: 10000 });

// Click new session button
await clickNewSessionButton(page);

// Wait for new session to appear in the list
await waitForNewSession(page, { timeout: 10000 });

// Verify at least one session exists
const sessionCount = await countSessionItems(page);
expect(sessionCount).toBeGreaterThanOrEqual(1);

// Verify the message list is visible (indicates a session is selected)
await expect(page.locator('[data-testid="message-list"]')).toBeVisible({ timeout: 5000 });

// Verify the agent input is visible
await expect(page.locator('[data-testid="agent-input"]')).toBeVisible();
});
});
Loading